summaryrefslogtreecommitdiffstats
path: root/src/global
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 /src/global
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>
Diffstat (limited to '')
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
399 files changed, 64333 insertions, 0 deletions
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