From b7c15c31519dc44c1f691e0466badd556ffe9423 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 18:18:56 +0200 Subject: Adding upstream version 3.7.10. Signed-off-by: Daniel Baumann --- src/global/.indent.pro | 1 + src/global/.printfck | 25 + src/global/Makefile.in | 3086 ++++++++++++++++++++ src/global/abounce.c | 466 +++ src/global/abounce.h | 43 + src/global/addr_match_list.c | 143 + src/global/addr_match_list.h | 41 + src/global/anvil_clnt.c | 527 ++++ src/global/anvil_clnt.h | 83 + src/global/attr_override.c | 190 ++ src/global/attr_override.h | 70 + src/global/been_here.c | 335 +++ src/global/been_here.h | 57 + src/global/bounce.c | 549 ++++ src/global/bounce.h | 99 + src/global/bounce_log.c | 321 +++ src/global/bounce_log.h | 55 + src/global/canon_addr.c | 67 + src/global/canon_addr.h | 36 + src/global/cfg_parser.c | 322 +++ src/global/cfg_parser.h | 56 + src/global/cleanup_strerror.c | 113 + src/global/cleanup_strflags.c | 89 + src/global/cleanup_user.h | 117 + src/global/clnt_stream.c | 278 ++ src/global/clnt_stream.h | 48 + src/global/compat_level.c | 461 +++ src/global/compat_level.h | 43 + src/global/compat_level_convert.in | 22 + src/global/compat_level_convert.ref | 29 + src/global/compat_level_expand.in | 27 + src/global/compat_level_expand.ref | 55 + src/global/config.h | 59 + src/global/config_known_tcp_ports.c | 257 ++ src/global/config_known_tcp_ports.h | 30 + src/global/config_known_tcp_ports.ref | 8 + src/global/conv_time.c | 113 + src/global/conv_time.h | 30 + src/global/data_redirect.c | 247 ++ src/global/data_redirect.h | 31 + src/global/db_common.c | 575 ++++ src/global/db_common.h | 58 + src/global/debug_peer.c | 133 + src/global/debug_peer.h | 31 + src/global/debug_process.c | 62 + src/global/debug_process.h | 30 + src/global/defer.c | 383 +++ src/global/defer.h | 54 + src/global/deliver_completed.c | 60 + src/global/deliver_completed.h | 35 + src/global/deliver_flock.c | 82 + src/global/deliver_flock.h | 36 + src/global/deliver_pass.c | 247 ++ src/global/deliver_pass.h | 37 + src/global/deliver_request.c | 484 ++++ src/global/deliver_request.h | 156 + src/global/delivered_hdr.c | 266 ++ src/global/delivered_hdr.h | 43 + src/global/delivered_hdr.ref | 3 + src/global/dict_ldap.c | 1994 +++++++++++++ src/global/dict_ldap.h | 33 + src/global/dict_memcache.c | 599 ++++ src/global/dict_memcache.h | 38 + src/global/dict_mysql.c | 963 +++++++ src/global/dict_mysql.h | 40 + src/global/dict_pgsql.c | 924 ++++++ src/global/dict_pgsql.h | 41 + src/global/dict_proxy.c | 532 ++++ src/global/dict_proxy.h | 53 + src/global/dict_sqlite.c | 349 +++ src/global/dict_sqlite.h | 32 + src/global/domain_list.c | 132 + src/global/domain_list.h | 40 + src/global/dot_lockfile.c | 173 ++ src/global/dot_lockfile.h | 36 + src/global/dot_lockfile_as.c | 99 + src/global/dot_lockfile_as.h | 36 + src/global/dsb_scan.c | 73 + src/global/dsb_scan.h | 46 + src/global/dsn.c | 189 ++ src/global/dsn.h | 84 + src/global/dsn_buf.c | 342 +++ src/global/dsn_buf.h | 89 + src/global/dsn_filter.c | 194 ++ src/global/dsn_filter.h | 34 + src/global/dsn_mask.c | 123 + src/global/dsn_mask.h | 91 + src/global/dsn_print.c | 73 + src/global/dsn_print.h | 46 + src/global/dsn_util.c | 183 ++ src/global/dsn_util.h | 77 + src/global/dynamicmaps.c | 367 +++ src/global/dynamicmaps.h | 38 + src/global/ehlo_mask.c | 145 + src/global/ehlo_mask.h | 53 + src/global/ehlo_mask.in | 3 + src/global/ehlo_mask.ref | 3 + src/global/ext_prop.c | 78 + src/global/ext_prop.h | 37 + src/global/file_id.c | 101 + src/global/file_id.h | 38 + src/global/flush_clnt.c | 269 ++ src/global/flush_clnt.h | 53 + src/global/fold_addr.c | 172 ++ src/global/fold_addr.h | 35 + src/global/fold_addr_test.in | 19 + src/global/fold_addr_test.ref | 36 + src/global/haproxy_srvr.c | 891 ++++++ src/global/haproxy_srvr.h | 52 + src/global/header_body_checks.c | 655 +++++ src/global/header_body_checks.h | 83 + src/global/header_body_checks_ignore.ref | 18 + src/global/header_body_checks_null.ref | 42 + src/global/header_body_checks_prepend.ref | 88 + src/global/header_body_checks_replace.ref | 64 + src/global/header_body_checks_strip.ref | 41 + src/global/header_body_checks_warn.ref | 65 + src/global/header_opts.c | 179 ++ src/global/header_opts.h | 84 + src/global/header_token.c | 266 ++ src/global/header_token.h | 47 + src/global/hfrom_format.c | 281 ++ src/global/hfrom_format.h | 34 + src/global/hfrom_format.ref | 8 + src/global/info_log_addr_form.c | 124 + src/global/info_log_addr_form.h | 31 + src/global/input_transp.c | 102 + src/global/input_transp.h | 36 + src/global/int_filt.c | 80 + src/global/int_filt.h | 34 + src/global/is_header.c | 92 + src/global/is_header.h | 32 + src/global/lex_822.h | 36 + src/global/log_adhoc.c | 221 ++ src/global/log_adhoc.h | 43 + src/global/login_sender_match.c | 364 +++ src/global/login_sender_match.h | 49 + src/global/login_sender_match.ref | 35 + src/global/mail_addr.c | 96 + src/global/mail_addr.h | 36 + src/global/mail_addr_crunch.c | 231 ++ src/global/mail_addr_crunch.h | 52 + src/global/mail_addr_crunch.in | 51 + src/global/mail_addr_crunch.ref | 22 + src/global/mail_addr_find.c | 671 +++++ src/global/mail_addr_find.h | 82 + src/global/mail_addr_find.in | 77 + src/global/mail_addr_find.ref | 63 + src/global/mail_addr_form.c | 65 + src/global/mail_addr_form.h | 36 + src/global/mail_addr_map.c | 527 ++++ src/global/mail_addr_map.h | 51 + src/global/mail_addr_map.ref | 26 + src/global/mail_command_client.c | 108 + src/global/mail_command_server.c | 67 + src/global/mail_conf.c | 278 ++ src/global/mail_conf.h | 249 ++ src/global/mail_conf_bool.c | 153 + src/global/mail_conf_int.c | 223 ++ src/global/mail_conf_long.c | 213 ++ src/global/mail_conf_nbool.c | 158 ++ src/global/mail_conf_nint.c | 232 ++ src/global/mail_conf_raw.c | 145 + src/global/mail_conf_str.c | 199 ++ src/global/mail_conf_time.c | 258 ++ src/global/mail_conf_time.ref | 5 + src/global/mail_connect.c | 127 + src/global/mail_copy.c | 315 +++ src/global/mail_copy.h | 63 + src/global/mail_date.c | 141 + src/global/mail_date.h | 35 + src/global/mail_dict.c | 121 + src/global/mail_dict.h | 25 + src/global/mail_error.c | 80 + src/global/mail_error.h | 44 + src/global/mail_flush.c | 80 + src/global/mail_flush.h | 30 + src/global/mail_open_ok.c | 127 + src/global/mail_open_ok.h | 33 + src/global/mail_params.c | 1038 +++++++ src/global/mail_params.h | 4396 +++++++++++++++++++++++++++++ src/global/mail_parm_split.c | 128 + src/global/mail_parm_split.h | 38 + src/global/mail_parm_split.in | 6 + src/global/mail_parm_split.ref | 25 + src/global/mail_pathname.c | 44 + src/global/mail_proto.h | 323 +++ src/global/mail_queue.c | 439 +++ src/global/mail_queue.h | 193 ++ src/global/mail_run.c | 150 + src/global/mail_run.h | 31 + src/global/mail_scan_dir.c | 62 + src/global/mail_scan_dir.h | 35 + src/global/mail_stream.c | 627 ++++ src/global/mail_stream.h | 91 + src/global/mail_task.c | 77 + src/global/mail_task.h | 29 + src/global/mail_trigger.c | 98 + src/global/mail_version.c | 258 ++ src/global/mail_version.h | 109 + src/global/mail_version.in | 8 + src/global/mail_version.ref | 16 + src/global/maillog_client.c | 304 ++ src/global/maillog_client.h | 33 + src/global/map_search.c | 397 +++ src/global/map_search.h | 71 + src/global/map_search.ref | 29 + src/global/maps.c | 337 +++ src/global/maps.h | 44 + src/global/maps.in | 4 + src/global/maps.ref | 8 + src/global/mark_corrupt.c | 77 + src/global/mark_corrupt.h | 35 + src/global/match_parent_style.c | 74 + src/global/match_parent_style.h | 35 + src/global/match_service.c | 176 ++ src/global/match_service.h | 32 + src/global/mbox_conf.c | 100 + src/global/mbox_conf.h | 41 + src/global/mbox_open.c | 256 ++ src/global/mbox_open.h | 50 + src/global/memcache_proto.c | 207 ++ src/global/memcache_proto.h | 34 + src/global/midna_adomain.c | 119 + src/global/midna_adomain.h | 36 + src/global/mime_8bit.in | 3 + src/global/mime_8bit.ref | 9 + src/global/mime_cvt.in | 83 + src/global/mime_cvt.in2 | 83 + src/global/mime_cvt.in3 | 83 + src/global/mime_cvt.ref | 93 + src/global/mime_cvt.ref2 | 93 + src/global/mime_cvt.ref3 | 93 + src/global/mime_dom.in | 2 + src/global/mime_dom.ref | 8 + src/global/mime_garb1.in | 27 + src/global/mime_garb1.ref | 40 + src/global/mime_garb2.in | 27 + src/global/mime_garb2.ref | 38 + src/global/mime_garb3.in | 29 + src/global/mime_garb3.ref | 45 + src/global/mime_garb4.in | 34 + src/global/mime_garb4.ref | 50 + src/global/mime_global.in | 96 + src/global/mime_nest.in | 69 + src/global/mime_nest.ref | 163 ++ src/global/mime_state.c | 1300 +++++++++ src/global/mime_state.h | 96 + src/global/mime_test.in | 38 + src/global/mime_test.ref | 52 + src/global/mime_trunc.in | 1790 ++++++++++++ src/global/mime_trunc.ref | 32 + src/global/mkmap.h | 64 + src/global/mkmap_cdb.c | 65 + src/global/mkmap_db.c | 191 ++ src/global/mkmap_dbm.c | 116 + src/global/mkmap_fail.c | 53 + src/global/mkmap_lmdb.c | 84 + src/global/mkmap_open.c | 313 ++ src/global/mkmap_proxy.c | 58 + src/global/mkmap_sdbm.c | 113 + src/global/msg_stats.h | 104 + src/global/msg_stats_print.c | 69 + src/global/msg_stats_scan.c | 91 + src/global/mynetworks.c | 336 +++ src/global/mynetworks.h | 31 + src/global/mypwd.c | 369 +++ src/global/mypwd.h | 45 + src/global/namadr_list.c | 141 + src/global/namadr_list.h | 40 + src/global/namadr_list.in | 42 + src/global/namadr_list.ref | 53 + src/global/normalize_mailhost_addr.c | 259 ++ src/global/normalize_mailhost_addr.h | 30 + src/global/off_cvt.c | 158 ++ src/global/off_cvt.h | 37 + src/global/off_cvt.in | 9 + src/global/off_cvt.ref | 5 + src/global/opened.c | 94 + src/global/opened.h | 38 + src/global/own_inet_addr.c | 315 +++ src/global/own_inet_addr.h | 39 + src/global/pipe_command.c | 683 +++++ src/global/pipe_command.h | 94 + src/global/post_mail.c | 571 ++++ src/global/post_mail.h | 61 + src/global/qmgr_user.h | 54 + src/global/qmqp_proto.h | 27 + src/global/quote_821_local.c | 179 ++ src/global/quote_821_local.h | 37 + src/global/quote_822_local.c | 294 ++ src/global/quote_822_local.h | 48 + src/global/quote_822_local.in | 6 + src/global/quote_822_local.ref | 6 + src/global/quote_flags.c | 89 + src/global/quote_flags.h | 43 + src/global/rcpt_buf.c | 141 + src/global/rcpt_buf.h | 67 + src/global/rcpt_print.c | 76 + src/global/rcpt_print.h | 46 + src/global/rec2stream.c | 47 + src/global/rec_attr_map.c | 54 + src/global/rec_attr_map.h | 30 + src/global/rec_streamlf.c | 109 + src/global/rec_streamlf.h | 45 + src/global/rec_type.c | 87 + src/global/rec_type.h | 198 ++ src/global/recdump.c | 57 + src/global/recipient_list.c | 191 ++ src/global/recipient_list.h | 76 + src/global/record.c | 415 +++ src/global/record.h | 82 + src/global/reject_deliver_request.c | 105 + src/global/remove.c | 72 + src/global/resolve_clnt.c | 408 +++ src/global/resolve_clnt.h | 83 + src/global/resolve_clnt.in | 49 + src/global/resolve_clnt.ref | 343 +++ src/global/resolve_local.c | 192 ++ src/global/resolve_local.h | 36 + src/global/resolve_local.in | 5 + src/global/resolve_local.ref | 6 + src/global/rewrite_clnt.c | 280 ++ src/global/rewrite_clnt.h | 44 + src/global/rewrite_clnt.in | 26 + src/global/rewrite_clnt.ref | 104 + src/global/safe_ultostr.c | 256 ++ src/global/safe_ultostr.h | 36 + src/global/safe_ultostr.in | 4 + src/global/safe_ultostr.ref | 4 + src/global/sasl_mech_filter.c | 113 + src/global/sasl_mech_filter.h | 35 + src/global/scache.c | 407 +++ src/global/scache.h | 165 ++ src/global/scache_clnt.c | 443 +++ src/global/scache_multi.c | 493 ++++ src/global/scache_multi.in | 52 + src/global/scache_multi.ref | 52 + src/global/scache_single.c | 312 ++ src/global/sent.c | 176 ++ src/global/sent.h | 45 + src/global/server_acl.c | 305 ++ src/global/server_acl.h | 49 + src/global/server_acl.in | 10 + src/global/server_acl.ref | 18 + src/global/smtp_reply_footer.c | 288 ++ src/global/smtp_reply_footer.h | 42 + src/global/smtp_reply_footer.ref | 15 + src/global/smtp_stream.c | 562 ++++ src/global/smtp_stream.h | 74 + src/global/smtputf8.c | 95 + src/global/smtputf8.h | 113 + src/global/split_addr.c | 103 + src/global/split_addr.h | 38 + src/global/stream2rec.c | 47 + src/global/string_list.c | 124 + src/global/string_list.h | 40 + src/global/strip_addr.c | 252 ++ src/global/strip_addr.h | 36 + src/global/strip_addr.ref | 12 + src/global/surrogate.ref | 36 + src/global/sys_exits.c | 143 + src/global/sys_exits.h | 60 + src/global/test_main.c | 224 ++ src/global/test_main.h | 64 + src/global/timed_ipc.c | 55 + src/global/timed_ipc.h | 35 + src/global/tok822.h | 123 + src/global/tok822_find.c | 69 + src/global/tok822_limit.in | 1 + src/global/tok822_limit.ref | 91 + src/global/tok822_node.c | 79 + src/global/tok822_parse.c | 726 +++++ src/global/tok822_parse.in | 47 + src/global/tok822_parse.ref | 417 +++ src/global/tok822_resolve.c | 74 + src/global/tok822_rewrite.c | 68 + src/global/tok822_tree.c | 310 ++ src/global/trace.c | 168 ++ src/global/trace.h | 38 + src/global/user_acl.c | 118 + src/global/user_acl.h | 39 + src/global/uxtext.c | 278 ++ src/global/uxtext.h | 40 + src/global/valid_mailhost_addr.c | 152 + src/global/valid_mailhost_addr.h | 38 + src/global/verify.c | 130 + src/global/verify.h | 41 + src/global/verify_clnt.c | 309 ++ src/global/verify_clnt.h | 54 + src/global/verify_sender_addr.c | 341 +++ src/global/verify_sender_addr.h | 31 + src/global/verify_sender_addr.ref | 24 + src/global/verp_sender.c | 113 + src/global/verp_sender.h | 41 + src/global/wildcard_inet_addr.c | 68 + src/global/wildcard_inet_addr.h | 33 + src/global/xtext.c | 196 ++ src/global/xtext.h | 38 + 399 files changed, 64333 insertions(+) create mode 120000 src/global/.indent.pro create mode 100644 src/global/.printfck create mode 100644 src/global/Makefile.in create mode 100644 src/global/abounce.c create mode 100644 src/global/abounce.h create mode 100644 src/global/addr_match_list.c create mode 100644 src/global/addr_match_list.h create mode 100644 src/global/anvil_clnt.c create mode 100644 src/global/anvil_clnt.h create mode 100644 src/global/attr_override.c create mode 100644 src/global/attr_override.h create mode 100644 src/global/been_here.c create mode 100644 src/global/been_here.h create mode 100644 src/global/bounce.c create mode 100644 src/global/bounce.h create mode 100644 src/global/bounce_log.c create mode 100644 src/global/bounce_log.h create mode 100644 src/global/canon_addr.c create mode 100644 src/global/canon_addr.h create mode 100644 src/global/cfg_parser.c create mode 100644 src/global/cfg_parser.h create mode 100644 src/global/cleanup_strerror.c create mode 100644 src/global/cleanup_strflags.c create mode 100644 src/global/cleanup_user.h create mode 100644 src/global/clnt_stream.c create mode 100644 src/global/clnt_stream.h create mode 100644 src/global/compat_level.c create mode 100644 src/global/compat_level.h create mode 100644 src/global/compat_level_convert.in create mode 100644 src/global/compat_level_convert.ref create mode 100644 src/global/compat_level_expand.in create mode 100644 src/global/compat_level_expand.ref create mode 100644 src/global/config.h create mode 100644 src/global/config_known_tcp_ports.c create mode 100644 src/global/config_known_tcp_ports.h create mode 100644 src/global/config_known_tcp_ports.ref create mode 100644 src/global/conv_time.c create mode 100644 src/global/conv_time.h create mode 100644 src/global/data_redirect.c create mode 100644 src/global/data_redirect.h create mode 100644 src/global/db_common.c create mode 100644 src/global/db_common.h create mode 100644 src/global/debug_peer.c create mode 100644 src/global/debug_peer.h create mode 100644 src/global/debug_process.c create mode 100644 src/global/debug_process.h create mode 100644 src/global/defer.c create mode 100644 src/global/defer.h create mode 100644 src/global/deliver_completed.c create mode 100644 src/global/deliver_completed.h create mode 100644 src/global/deliver_flock.c create mode 100644 src/global/deliver_flock.h create mode 100644 src/global/deliver_pass.c create mode 100644 src/global/deliver_pass.h create mode 100644 src/global/deliver_request.c create mode 100644 src/global/deliver_request.h create mode 100644 src/global/delivered_hdr.c create mode 100644 src/global/delivered_hdr.h create mode 100644 src/global/delivered_hdr.ref create mode 100644 src/global/dict_ldap.c create mode 100644 src/global/dict_ldap.h create mode 100644 src/global/dict_memcache.c create mode 100644 src/global/dict_memcache.h create mode 100644 src/global/dict_mysql.c create mode 100644 src/global/dict_mysql.h create mode 100644 src/global/dict_pgsql.c create mode 100644 src/global/dict_pgsql.h create mode 100644 src/global/dict_proxy.c create mode 100644 src/global/dict_proxy.h create mode 100644 src/global/dict_sqlite.c create mode 100644 src/global/dict_sqlite.h create mode 100644 src/global/domain_list.c create mode 100644 src/global/domain_list.h create mode 100644 src/global/dot_lockfile.c create mode 100644 src/global/dot_lockfile.h create mode 100644 src/global/dot_lockfile_as.c create mode 100644 src/global/dot_lockfile_as.h create mode 100644 src/global/dsb_scan.c create mode 100644 src/global/dsb_scan.h create mode 100644 src/global/dsn.c create mode 100644 src/global/dsn.h create mode 100644 src/global/dsn_buf.c create mode 100644 src/global/dsn_buf.h create mode 100644 src/global/dsn_filter.c create mode 100644 src/global/dsn_filter.h create mode 100644 src/global/dsn_mask.c create mode 100644 src/global/dsn_mask.h create mode 100644 src/global/dsn_print.c create mode 100644 src/global/dsn_print.h create mode 100644 src/global/dsn_util.c create mode 100644 src/global/dsn_util.h create mode 100644 src/global/dynamicmaps.c create mode 100644 src/global/dynamicmaps.h create mode 100644 src/global/ehlo_mask.c create mode 100644 src/global/ehlo_mask.h create mode 100644 src/global/ehlo_mask.in create mode 100644 src/global/ehlo_mask.ref create mode 100644 src/global/ext_prop.c create mode 100644 src/global/ext_prop.h create mode 100644 src/global/file_id.c create mode 100644 src/global/file_id.h create mode 100644 src/global/flush_clnt.c create mode 100644 src/global/flush_clnt.h create mode 100644 src/global/fold_addr.c create mode 100644 src/global/fold_addr.h create mode 100644 src/global/fold_addr_test.in create mode 100644 src/global/fold_addr_test.ref create mode 100644 src/global/haproxy_srvr.c create mode 100644 src/global/haproxy_srvr.h create mode 100644 src/global/header_body_checks.c create mode 100644 src/global/header_body_checks.h create mode 100644 src/global/header_body_checks_ignore.ref create mode 100644 src/global/header_body_checks_null.ref create mode 100644 src/global/header_body_checks_prepend.ref create mode 100644 src/global/header_body_checks_replace.ref create mode 100644 src/global/header_body_checks_strip.ref create mode 100644 src/global/header_body_checks_warn.ref create mode 100644 src/global/header_opts.c create mode 100644 src/global/header_opts.h create mode 100644 src/global/header_token.c create mode 100644 src/global/header_token.h create mode 100644 src/global/hfrom_format.c create mode 100644 src/global/hfrom_format.h create mode 100644 src/global/hfrom_format.ref create mode 100644 src/global/info_log_addr_form.c create mode 100644 src/global/info_log_addr_form.h create mode 100644 src/global/input_transp.c create mode 100644 src/global/input_transp.h create mode 100644 src/global/int_filt.c create mode 100644 src/global/int_filt.h create mode 100644 src/global/is_header.c create mode 100644 src/global/is_header.h create mode 100644 src/global/lex_822.h create mode 100644 src/global/log_adhoc.c create mode 100644 src/global/log_adhoc.h create mode 100644 src/global/login_sender_match.c create mode 100644 src/global/login_sender_match.h create mode 100644 src/global/login_sender_match.ref create mode 100644 src/global/mail_addr.c create mode 100644 src/global/mail_addr.h create mode 100644 src/global/mail_addr_crunch.c create mode 100644 src/global/mail_addr_crunch.h create mode 100644 src/global/mail_addr_crunch.in create mode 100644 src/global/mail_addr_crunch.ref create mode 100644 src/global/mail_addr_find.c create mode 100644 src/global/mail_addr_find.h create mode 100644 src/global/mail_addr_find.in create mode 100644 src/global/mail_addr_find.ref create mode 100644 src/global/mail_addr_form.c create mode 100644 src/global/mail_addr_form.h create mode 100644 src/global/mail_addr_map.c create mode 100644 src/global/mail_addr_map.h create mode 100644 src/global/mail_addr_map.ref create mode 100644 src/global/mail_command_client.c create mode 100644 src/global/mail_command_server.c create mode 100644 src/global/mail_conf.c create mode 100644 src/global/mail_conf.h create mode 100644 src/global/mail_conf_bool.c create mode 100644 src/global/mail_conf_int.c create mode 100644 src/global/mail_conf_long.c create mode 100644 src/global/mail_conf_nbool.c create mode 100644 src/global/mail_conf_nint.c create mode 100644 src/global/mail_conf_raw.c create mode 100644 src/global/mail_conf_str.c create mode 100644 src/global/mail_conf_time.c create mode 100644 src/global/mail_conf_time.ref create mode 100644 src/global/mail_connect.c create mode 100644 src/global/mail_copy.c create mode 100644 src/global/mail_copy.h create mode 100644 src/global/mail_date.c create mode 100644 src/global/mail_date.h create mode 100644 src/global/mail_dict.c create mode 100644 src/global/mail_dict.h create mode 100644 src/global/mail_error.c create mode 100644 src/global/mail_error.h create mode 100644 src/global/mail_flush.c create mode 100644 src/global/mail_flush.h create mode 100644 src/global/mail_open_ok.c create mode 100644 src/global/mail_open_ok.h create mode 100644 src/global/mail_params.c create mode 100644 src/global/mail_params.h create mode 100644 src/global/mail_parm_split.c create mode 100644 src/global/mail_parm_split.h create mode 100644 src/global/mail_parm_split.in create mode 100644 src/global/mail_parm_split.ref create mode 100644 src/global/mail_pathname.c create mode 100644 src/global/mail_proto.h create mode 100644 src/global/mail_queue.c create mode 100644 src/global/mail_queue.h create mode 100644 src/global/mail_run.c create mode 100644 src/global/mail_run.h create mode 100644 src/global/mail_scan_dir.c create mode 100644 src/global/mail_scan_dir.h create mode 100644 src/global/mail_stream.c create mode 100644 src/global/mail_stream.h create mode 100644 src/global/mail_task.c create mode 100644 src/global/mail_task.h create mode 100644 src/global/mail_trigger.c create mode 100644 src/global/mail_version.c create mode 100644 src/global/mail_version.h create mode 100644 src/global/mail_version.in create mode 100644 src/global/mail_version.ref create mode 100644 src/global/maillog_client.c create mode 100644 src/global/maillog_client.h create mode 100644 src/global/map_search.c create mode 100644 src/global/map_search.h create mode 100644 src/global/map_search.ref create mode 100644 src/global/maps.c create mode 100644 src/global/maps.h create mode 100644 src/global/maps.in create mode 100644 src/global/maps.ref create mode 100644 src/global/mark_corrupt.c create mode 100644 src/global/mark_corrupt.h create mode 100644 src/global/match_parent_style.c create mode 100644 src/global/match_parent_style.h create mode 100644 src/global/match_service.c create mode 100644 src/global/match_service.h create mode 100644 src/global/mbox_conf.c create mode 100644 src/global/mbox_conf.h create mode 100644 src/global/mbox_open.c create mode 100644 src/global/mbox_open.h create mode 100644 src/global/memcache_proto.c create mode 100644 src/global/memcache_proto.h create mode 100644 src/global/midna_adomain.c create mode 100644 src/global/midna_adomain.h create mode 100644 src/global/mime_8bit.in create mode 100644 src/global/mime_8bit.ref create mode 100644 src/global/mime_cvt.in create mode 100644 src/global/mime_cvt.in2 create mode 100644 src/global/mime_cvt.in3 create mode 100644 src/global/mime_cvt.ref create mode 100644 src/global/mime_cvt.ref2 create mode 100644 src/global/mime_cvt.ref3 create mode 100644 src/global/mime_dom.in create mode 100644 src/global/mime_dom.ref create mode 100644 src/global/mime_garb1.in create mode 100644 src/global/mime_garb1.ref create mode 100644 src/global/mime_garb2.in create mode 100644 src/global/mime_garb2.ref create mode 100644 src/global/mime_garb3.in create mode 100644 src/global/mime_garb3.ref create mode 100644 src/global/mime_garb4.in create mode 100644 src/global/mime_garb4.ref create mode 100644 src/global/mime_global.in create mode 100644 src/global/mime_nest.in create mode 100644 src/global/mime_nest.ref create mode 100644 src/global/mime_state.c create mode 100644 src/global/mime_state.h create mode 100644 src/global/mime_test.in create mode 100644 src/global/mime_test.ref create mode 100644 src/global/mime_trunc.in create mode 100644 src/global/mime_trunc.ref create mode 100644 src/global/mkmap.h create mode 100644 src/global/mkmap_cdb.c create mode 100644 src/global/mkmap_db.c create mode 100644 src/global/mkmap_dbm.c create mode 100644 src/global/mkmap_fail.c create mode 100644 src/global/mkmap_lmdb.c create mode 100644 src/global/mkmap_open.c create mode 100644 src/global/mkmap_proxy.c create mode 100644 src/global/mkmap_sdbm.c create mode 100644 src/global/msg_stats.h create mode 100644 src/global/msg_stats_print.c create mode 100644 src/global/msg_stats_scan.c create mode 100644 src/global/mynetworks.c create mode 100644 src/global/mynetworks.h create mode 100644 src/global/mypwd.c create mode 100644 src/global/mypwd.h create mode 100644 src/global/namadr_list.c create mode 100644 src/global/namadr_list.h create mode 100644 src/global/namadr_list.in create mode 100644 src/global/namadr_list.ref create mode 100644 src/global/normalize_mailhost_addr.c create mode 100644 src/global/normalize_mailhost_addr.h create mode 100644 src/global/off_cvt.c create mode 100644 src/global/off_cvt.h create mode 100644 src/global/off_cvt.in create mode 100644 src/global/off_cvt.ref create mode 100644 src/global/opened.c create mode 100644 src/global/opened.h create mode 100644 src/global/own_inet_addr.c create mode 100644 src/global/own_inet_addr.h create mode 100644 src/global/pipe_command.c create mode 100644 src/global/pipe_command.h create mode 100644 src/global/post_mail.c create mode 100644 src/global/post_mail.h create mode 100644 src/global/qmgr_user.h create mode 100644 src/global/qmqp_proto.h create mode 100644 src/global/quote_821_local.c create mode 100644 src/global/quote_821_local.h create mode 100644 src/global/quote_822_local.c create mode 100644 src/global/quote_822_local.h create mode 100644 src/global/quote_822_local.in create mode 100644 src/global/quote_822_local.ref create mode 100644 src/global/quote_flags.c create mode 100644 src/global/quote_flags.h create mode 100644 src/global/rcpt_buf.c create mode 100644 src/global/rcpt_buf.h create mode 100644 src/global/rcpt_print.c create mode 100644 src/global/rcpt_print.h create mode 100644 src/global/rec2stream.c create mode 100644 src/global/rec_attr_map.c create mode 100644 src/global/rec_attr_map.h create mode 100644 src/global/rec_streamlf.c create mode 100644 src/global/rec_streamlf.h create mode 100644 src/global/rec_type.c create mode 100644 src/global/rec_type.h create mode 100644 src/global/recdump.c create mode 100644 src/global/recipient_list.c create mode 100644 src/global/recipient_list.h create mode 100644 src/global/record.c create mode 100644 src/global/record.h create mode 100644 src/global/reject_deliver_request.c create mode 100644 src/global/remove.c create mode 100644 src/global/resolve_clnt.c create mode 100644 src/global/resolve_clnt.h create mode 100644 src/global/resolve_clnt.in create mode 100644 src/global/resolve_clnt.ref create mode 100644 src/global/resolve_local.c create mode 100644 src/global/resolve_local.h create mode 100644 src/global/resolve_local.in create mode 100644 src/global/resolve_local.ref create mode 100644 src/global/rewrite_clnt.c create mode 100644 src/global/rewrite_clnt.h create mode 100644 src/global/rewrite_clnt.in create mode 100644 src/global/rewrite_clnt.ref create mode 100644 src/global/safe_ultostr.c create mode 100644 src/global/safe_ultostr.h create mode 100644 src/global/safe_ultostr.in create mode 100644 src/global/safe_ultostr.ref create mode 100644 src/global/sasl_mech_filter.c create mode 100644 src/global/sasl_mech_filter.h create mode 100644 src/global/scache.c create mode 100644 src/global/scache.h create mode 100644 src/global/scache_clnt.c create mode 100644 src/global/scache_multi.c create mode 100644 src/global/scache_multi.in create mode 100644 src/global/scache_multi.ref create mode 100644 src/global/scache_single.c create mode 100644 src/global/sent.c create mode 100644 src/global/sent.h create mode 100644 src/global/server_acl.c create mode 100644 src/global/server_acl.h create mode 100644 src/global/server_acl.in create mode 100644 src/global/server_acl.ref create mode 100644 src/global/smtp_reply_footer.c create mode 100644 src/global/smtp_reply_footer.h create mode 100644 src/global/smtp_reply_footer.ref create mode 100644 src/global/smtp_stream.c create mode 100644 src/global/smtp_stream.h create mode 100644 src/global/smtputf8.c create mode 100644 src/global/smtputf8.h create mode 100644 src/global/split_addr.c create mode 100644 src/global/split_addr.h create mode 100644 src/global/stream2rec.c create mode 100644 src/global/string_list.c create mode 100644 src/global/string_list.h create mode 100644 src/global/strip_addr.c create mode 100644 src/global/strip_addr.h create mode 100644 src/global/strip_addr.ref create mode 100644 src/global/surrogate.ref create mode 100644 src/global/sys_exits.c create mode 100644 src/global/sys_exits.h create mode 100644 src/global/test_main.c create mode 100644 src/global/test_main.h create mode 100644 src/global/timed_ipc.c create mode 100644 src/global/timed_ipc.h create mode 100644 src/global/tok822.h create mode 100644 src/global/tok822_find.c create mode 100644 src/global/tok822_limit.in create mode 100644 src/global/tok822_limit.ref create mode 100644 src/global/tok822_node.c create mode 100644 src/global/tok822_parse.c create mode 100644 src/global/tok822_parse.in create mode 100644 src/global/tok822_parse.ref create mode 100644 src/global/tok822_resolve.c create mode 100644 src/global/tok822_rewrite.c create mode 100644 src/global/tok822_tree.c create mode 100644 src/global/trace.c create mode 100644 src/global/trace.h create mode 100644 src/global/user_acl.c create mode 100644 src/global/user_acl.h create mode 100644 src/global/uxtext.c create mode 100644 src/global/uxtext.h create mode 100644 src/global/valid_mailhost_addr.c create mode 100644 src/global/valid_mailhost_addr.h create mode 100644 src/global/verify.c create mode 100644 src/global/verify.h create mode 100644 src/global/verify_clnt.c create mode 100644 src/global/verify_clnt.h create mode 100644 src/global/verify_sender_addr.c create mode 100644 src/global/verify_sender_addr.h create mode 100644 src/global/verify_sender_addr.ref create mode 100644 src/global/verp_sender.c create mode 100644 src/global/verp_sender.h create mode 100644 src/global/wildcard_inet_addr.c create mode 100644 src/global/wildcard_inet_addr.h create mode 100644 src/global/xtext.c create mode 100644 src/global/xtext.h (limited to 'src/global') 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.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.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.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.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.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.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.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.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.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_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_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_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_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.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.tmp + od -cb 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.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.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.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.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.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.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 "" "" "" "" \ + 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 \ + 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 \ + 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 \ + 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 \ + 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 \ + 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 \ + 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 \ + 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.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.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.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.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.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.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 +/* +/* 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 + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include + + /* + * 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 *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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include "addr_match_list.h" + +#ifdef TEST + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 *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 + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 +#include +#include +#include +#include +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include +#include /* strtol() */ + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +#include + +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 +/* +/* 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 /* 44BSD stdarg.h uses abort() */ +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#define DSN_INTERN +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Global library. + */ +#include +#include + + /* + * 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 + +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 +/* +/* 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 +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* 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 (: 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#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 : "")); + 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 + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include + + /* + * 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 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* 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 +/* 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 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 or StatusOr, 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 +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * 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_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 + +#include +#include +#include +#include +#include +#include + +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 +/* 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} ? {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} {3.2} ? {bad} : {good} } +${ {3.10} >level {3.2} ? {good} : {bad} } +${ {3.10} < {3.2} ? {good} : {bad} } +${ {3.10} 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} {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} +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * Application-specific. + */ +#include + +/* 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 +#include +#include + +#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 +/* 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 +/* +/* 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 +#include /* INT_MAX */ +#include +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +#include + +#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 +/* 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 +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global directory. */ + +#include +#include +#include + +/* 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 +#include +#include +#include + +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 +#include + + /* + * Global library. + */ +#include "cfg_parser.h" + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* 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 +/* 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 +/* +/* 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 +#include +#include +#include + +/* Utility library. */ + +#include + +/* 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 +/* #include +/* 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#define DSN_INTERN +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +#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 +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include +#include + + /* + * 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 +/* +/* 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 +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include +#include + + /* + * 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_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 +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + + /* + * 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 +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include + + /* + * 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 *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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#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 +#include +#include +#include +#include +#include +#include + +/* 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 +#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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 *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 +#include +#include +#include +#include /* XXX sscanf() */ + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include + + /* + * 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 *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 '' +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 *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 '' +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 *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 +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 *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 +#include + +#ifdef HAS_SQLITE +#include + +#if !defined(SQLITE_VERSION_NUMBER) || (SQLITE_VERSION_NUMBER < 3005004) +#define sqlite3_prepare_v2 sqlite3_prepare +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 *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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include "domain_list.h" + +#ifdef TEST + +#include +#include +#include +#include +#include +#include +#include /* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* 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 +#include +#include +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include + +/* 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 +/* 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 +/* +/* 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 +#include /* 44BSD stdarg.h uses abort() */ +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include + + /* + * 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 *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 + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include + + /* + * 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 +/* 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* 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 +/* +/* 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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* #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 +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include + +#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 +/* 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 +/* +/* #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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include + + /* + * 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 +#include +#include +#include + +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 +/* 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 +/* +/* 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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include +#include + +/* 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 +/* 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 +/* +/* 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 +#include +#include + +/* Utility library */ + +#include +#include +#include + +/* Global library. */ + +#define MAIL_QUEUE_INTERNAL +#include +#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 +/* DESCRIPTION +/* .nf + + /* +System library. +*/ +#include + + /* 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* 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 +/* 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +#include + +#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 +#include +#include +#include +#include + +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 | user | all \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 +/* 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 +/* +/* 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 +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* 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 +#include +#include +#include +#include +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include +#include + + /* + * 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + + /* + * 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 +/* 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 +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include + +/* 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 + + /* + * 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 +/* +/* 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 + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * Application-specific. + */ +#include + + /* + * 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 +#include +#include + +#include +#include +#include + +#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 +/* 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 +/* +/* 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 + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include + +#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 +/* 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 +/* 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 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 + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* 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 +/* 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 +/* +/* 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 + +/* 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 +/* 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 +/* 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + + /* + * 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Global library. + */ +#include +#include +#include + + /* + * 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 +/* +/* 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 +#include + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include + + /* + * 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include + +/* 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 +/* 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* 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 +#include +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* 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 +#include + +#include +#include +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include +#include + + /* + * 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 +/* +/* 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 + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include + +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 +/* 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* 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 +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include +#include + + /* + * 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 +/* +/* 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 +#include /* 44BSD stdarg.h uses abort() */ +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include + +/* 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 +/* +/* 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 +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include /* 44BSD stdarg.h uses abort() */ +#include +#include + +/* Utility library. */ + +#include + +/* 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 +/* +/* 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 +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 +/* 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 +/* +/* 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 +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include + +/* 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 +/* +/* 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 +#include +#include /* BUFSIZ */ +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* 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 +/* +/* 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 +#include +#include /* BUFSIZ */ +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* 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 +/* +/* 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 +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include + +/* 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 +/* +/* 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 +#include +#include /* BUFSIZ */ +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* 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 +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include + +/* 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 +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* 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 +/* +/* 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 +#include +#include /* BUFSIZ */ +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* 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 + +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 +/* +/* 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 +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* 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 +/* +/* 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 +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include + +/* 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 + +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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#include + +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 +/* 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 +/* +/* 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 + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include + +/* 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 +/* 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 +/* +/* 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 +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* 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 +/* 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 +/* +/* 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 +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * 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 +/* DESCRIPTION +/* .nf + + /* + * This is to make it easier to auto-generate tables. + */ +typedef int bool; + +#ifdef USE_TLS +#include /* OPENSSL_VERSION_NUMBER */ +#include /* 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 . + */ +#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
*/ +#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} =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} =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} " +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 + +#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} +/* +/* 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 + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include +#include + +/* 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 +#include +#include +#include + +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 +/* DESCRIPTION +/* .nf + +#endif + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * 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 +/* +/* 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 +#include /* rename() */ +#include +#include +#include +#include +#include +#include /* gettimeofday, not in POSIX */ +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include + + /* + * 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 + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* 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 +/* 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 . +/* 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 +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include + +/* 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 +/* 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* 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 +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* 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 +#include +#include +#include + +#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 +/* 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 + +#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 +/* +/* 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 +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include + + /* + * 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 +/* 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 +/* +/* 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 +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include + + /* + * 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 + + /* + * 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 *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 +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* 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 +#include +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 < +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +/* Global library. */ + +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* 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 +/* 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include + + /* + * 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include + +#ifndef EDQUOT +#define EDQUOT EFBIG +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include + +#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 +/* 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 +/* +/* 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 +#include + +#ifndef NO_EAI +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include + +#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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +To: Some One +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 +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 +MAIN 32 |To: Some One +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 +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 +To: Some One +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 +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 +MAIN 32 |To: Some One +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 +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 +To: Some One +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 +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 +MAIN 32 |To: Some One +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 +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 +To: Some One +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 +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 +MAIN 32 |To: Some One +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 +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 *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 +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* 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 +#include +#include +#include +#include + + /* + * 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 + + /* + * Global library. + */ +#include + + /* + * 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 *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 based on mkmap_db by +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +#ifdef HAS_CDB + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include "mkmap.h" +#include + +/* 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 *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 +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include "mkmap.h" + +#ifdef HAS_DB +#ifdef PATH_DB_H +#include PATH_DB_H +#else +#include +#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 *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 +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "mkmap.h" + +#ifdef HAS_DBM +#ifdef PATH_NDBM_H +#include PATH_NDBM_H +#else +#include +#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 *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 + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include +#include + + /* + * 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 *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 +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAS_LMDB +#ifdef PATH_LMDB_H +#include PATH_LMDB_H +#else +#include +#endif + +/* Global library. */ + +#include +#include + +/* 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 +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 *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 + +/* Utility library. */ + +#include +#include + +/* 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 *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 +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "mkmap.h" + +#ifdef HAS_SDBM + +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include +#include + +/* 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include + + /* + * 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 +/* +/* 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: +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +#ifndef IN_CLASSD_NET +#define IN_CLASSD_NET 0xf0000000 +#define IN_CLASSD_NSHIFT 28 +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* 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 + +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 +/* 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 +/* +/* 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 +#include +#include +#ifdef USE_PATHS_H +#include +#endif +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* 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 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 , 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 + + /* + * 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 +#include +#include +#include + +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 +/* 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 *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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include "namadr_list.h" + +#ifdef TEST + +#include +#include +#include +#include +#include +#include +#include /* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include + +/* 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 +#include +#include + + /* + * 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 +/* 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_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 +#include +#include + +/* Utility library. */ + +#include +#include + +/* 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 +#include + +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 +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include /* 44BSD stdarg.h uses abort() */ +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* 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 + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 . +/* +/* 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 +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_PATHS_H +#include +#endif +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 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 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include +#include + + /* + * 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 +/* +/* 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\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\fR. +/* .IP utf8_flags +/* Flags defined in . 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\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 +#include +#include /* 44BSD stdarg.h uses abort() */ +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + + /* + * 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include + + /* + * 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 +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include + + /* + * 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 +/* 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 +#include +#include + +/* Utility library. */ + +#include + +/* 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 +#include +#include +#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 + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include + +/* 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 +#include + +#include +#include +#include +#include +#include + +#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 + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include + +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 + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * 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 + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include + +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 +/* +/* 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 +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 +/* 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * 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>KKon" + + /* + * 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 +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +#include +#include +#include + +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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* 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 +/* 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 +/* +/* 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 +#include /* 44BSD stdarg.h uses abort() */ +#include +#include +#include + +#ifndef NBBY +#define NBBY 8 /* XXX should be in sys_defs.h */ +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include + + /* + * 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 + */ +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 +/* +/* 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 +#include + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include + +/* 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 +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +#include + +/* 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 +/* +/* 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 +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* 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 +#include +#include +#include +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* #include +/* +/* 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 +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* 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 +#include +#include +#include +#include +#include +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include /* 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 +/* +/* 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 +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* 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 /* sscanf */ +#include +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +#include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +#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 + + /* + * 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 +/* 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 +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +#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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + +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 +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/*#define msg_verbose 1*/ + +/* Global library. */ + +#include +#include +#include + +/* 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 +/* 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 +#include +#include /* offsetof() */ +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/*#define msg_verbose 1*/ + +/* Global library. */ + +#include + +/* 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 +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/*#define msg_verbose 1*/ + +/* Global library. */ + +#include + +/* 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#define DSN_INTERN +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include + + /* + * 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 +/* +/* 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 +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* 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 +#include +#include +#include +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +/* . 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 , the result text will also end in +/* . +/* .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 +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* 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 +#include +#include +#include +#include +#include +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * 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 +/* +/* 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 +#include +#include +#include +#include +#include +#include +#include /* FD_ZERO() needs bzero() prototype */ +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* 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 +/* 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 +/* +/* 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 +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 +/* 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 + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include + +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 *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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include "string_list.h" + +#ifdef TEST + +#include +#include +#include +#include +#include +#include +#include +#include /* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +#include +#include + +/* 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 +#include + +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 +/* 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include + +/* 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 +/* 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 +/* +/* 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 +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include + + /* + * Test library. + */ +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include + + /* + * 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 *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 + +/* Utility library. */ + +#include + +/* Global library. */ + +#include + +/* 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 *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 +#include + +/* Utility library. */ + +#include +#include + +/* 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 *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 +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* 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
. 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
. + * 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
'' to 256 bytes even when + * this means chopping the
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 . 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 */ + 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 +#include +#include + +/* 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 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 + , + yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + .yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy +"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + .zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + , + 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" <<< + +Parse tree: + quoted string "wietse venema" + OP "<" + address + atom "wietse" + OP "@" + atom "porcupine" + OP "." + atom "org" + OP ">" + +Internalized: +wietse venema + +Externalized, no newlines inserted: +"wietse venema" + +Externalized, newlines inserted: +"wietse venema" + +>>>"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 , 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 , yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + +Externalized, no newlines inserted: +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx , yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy + +Externalized, newlines inserted: +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx , +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 , 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 , 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: +, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + +unknown: warning: stripping too many comments from address: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxx... +unknown: warning: stripping too many comments from address: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy (yyyyyyyyyyyyyyyyyyyy... +Externalized, newlines inserted: +, +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, +xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, + + +>>>(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 +/* +/* 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 + +/* Utility library. */ + +#include +#include + +/* 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 *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 + +/* Utility library. */ + +#include +#include + +/* 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 *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 + +/* Utility library. */ + +#include +#include + +/* 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 +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include + +/* 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 +/* +/* DESCRIPTION +/* .nf + + /* + * System library + */ +#include /* getuid()/geteuid() */ +#include /* uid_t */ + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include + +/* 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 + +#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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include + +/* 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 + +#include +#include +#include +#include + +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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* 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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library */ + +#include +#include +#include +#include + +/* 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 +#include +#include +#include +#include +#include + +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 +/* 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 +/* +/* 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 +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +#include +#include +#include + +/* 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 + + /* + * Global library. + */ +#include + + /* + * 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 +/* +/* 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: +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* 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 +/* DESCRIPTION +/* .nf +/*--*/ + + /* + * Utility library. + */ +#include + + /* + * 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 +/* +/* 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 +#include +#include + +/* 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 + +#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 +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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 -- cgit v1.2.3