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/anvil/.indent.pro | 1 + src/anvil/.printfck | 25 + src/anvil/Makefile.in | 82 + src/anvil/anvil.c | 1047 +++ src/bounce/.indent.pro | 1 + src/bounce/.printfck | 25 + src/bounce/2template_test.in | 136 + src/bounce/Makefile.in | 695 ++ src/bounce/annotate.sh | 120 + src/bounce/bounce.c | 713 ++ src/bounce/bounce_append_service.c | 168 + src/bounce/bounce_cleanup.c | 177 + src/bounce/bounce_notify_service.c | 327 + src/bounce/bounce_notify_util.c | 1010 +++ src/bounce/bounce_notify_util_tester.c | 167 + src/bounce/bounce_notify_verp.c | 267 + src/bounce/bounce_one_service.c | 266 + src/bounce/bounce_service.h | 121 + src/bounce/bounce_template.c | 548 ++ src/bounce/bounce_template.h | 99 + src/bounce/bounce_templates.c | 399 ++ src/bounce/bounce_trace_service.c | 220 + src/bounce/bounce_warn_service.c | 295 + src/bounce/logfile-no-msgid-no-eoh-event | 13 + src/bounce/logfile-no-msgid-with-eoh-event | 13 + src/bounce/logfile-with-msgid-no-eoh-event | 13 + src/bounce/logfile-with-msgid-with-eoh-event | 13 + src/bounce/logfile-with-msgid-with-filter | 13 + src/bounce/logfile-with-msgid-with-long-line | 13 + src/bounce/msgfile-no-msgid-no-eoh-event | Bin 0 -> 522 bytes src/bounce/msgfile-no-msgid-with-eoh-event | Bin 0 -> 536 bytes src/bounce/msgfile-with-msgid-no-eoh-event | Bin 0 -> 606 bytes src/bounce/msgfile-with-msgid-with-eoh-event | Bin 0 -> 621 bytes src/bounce/msgfile-with-msgid-with-filter | Bin 0 -> 581 bytes src/bounce/msgfile-with-msgid-with-long-line | Bin 0 -> 4624 bytes src/bounce/no-msgid-no-eoh-event-no-thread.ref | 47 + src/bounce/no-msgid-no-eoh-event-with-thread.ref | 47 + src/bounce/no-msgid-with-eoh-event-no-thread.ref | 49 + src/bounce/no-msgid-with-eoh-event-with-thread.ref | 49 + src/bounce/obs_template_test.ref | 68 + src/bounce/template_test.ref | 68 + src/bounce/with-msgid-no-eoh-event-no-thread.ref | 49 + src/bounce/with-msgid-no-eoh-event-with-thread.ref | 51 + src/bounce/with-msgid-with-eoh-event-no-thread.ref | 51 + .../with-msgid-with-eoh-event-with-thread.ref | 53 + src/bounce/with-msgid-with-filter-no-thread.ref | 48 + src/bounce/with-msgid-with-filter-with-thread.ref | 50 + src/bounce/with-msgid-with-long-line-no-thread.ref | 50 + .../with-msgid-with-long-line-with-thread.ref | 52 + src/cleanup/.indent.pro | 1 + src/cleanup/.printfck | 25 + src/cleanup/Makefile.in | 1394 ++++ src/cleanup/bug1.file | Bin 0 -> 1258 bytes src/cleanup/bug1.file.ref | Bin 0 -> 1279 bytes src/cleanup/bug1.in | 41 + src/cleanup/bug1.ref | 56 + src/cleanup/bug1.text.ref | 46 + src/cleanup/bug2.file | Bin 0 -> 573 bytes src/cleanup/bug2.in | 37 + src/cleanup/bug2.ref | 30 + src/cleanup/bug2.text.ref | 22 + src/cleanup/bug3.file | Bin 0 -> 565 bytes src/cleanup/bug3.in | 29 + src/cleanup/bug3.ref | 29 + src/cleanup/bug3.text.ref | 21 + src/cleanup/cleanup.c | 656 ++ src/cleanup/cleanup.h | 370 ++ src/cleanup/cleanup_addr.c | 286 + src/cleanup/cleanup_api.c | 395 ++ src/cleanup/cleanup_body_edit.c | 243 + src/cleanup/cleanup_bounce.c | 257 + src/cleanup/cleanup_envelope.c | 502 ++ src/cleanup/cleanup_extracted.c | 328 + src/cleanup/cleanup_final.c | 78 + src/cleanup/cleanup_init.c | 476 ++ src/cleanup/cleanup_map11.c | 183 + src/cleanup/cleanup_map1n.c | 182 + src/cleanup/cleanup_masq.ref | 66 + src/cleanup/cleanup_masquerade.c | 239 + src/cleanup/cleanup_message.c | 1111 ++++ src/cleanup/cleanup_milter.c | 2775 ++++++++ src/cleanup/cleanup_milter.in1 | 42 + src/cleanup/cleanup_milter.in10a | 11 + src/cleanup/cleanup_milter.in10b | 12 + src/cleanup/cleanup_milter.in10c | 14 + src/cleanup/cleanup_milter.in10d | 14 + src/cleanup/cleanup_milter.in10e | 13 + src/cleanup/cleanup_milter.in11 | 10 + src/cleanup/cleanup_milter.in12 | 22 + src/cleanup/cleanup_milter.in13a | 22 + src/cleanup/cleanup_milter.in13b | 8 + src/cleanup/cleanup_milter.in13c | 9 + src/cleanup/cleanup_milter.in13d | 9 + src/cleanup/cleanup_milter.in13e | 10 + src/cleanup/cleanup_milter.in13f | 10 + src/cleanup/cleanup_milter.in13g | 11 + src/cleanup/cleanup_milter.in13h | 8 + src/cleanup/cleanup_milter.in13i | 9 + src/cleanup/cleanup_milter.in14a | 7 + src/cleanup/cleanup_milter.in14b | 7 + src/cleanup/cleanup_milter.in14c | 7 + src/cleanup/cleanup_milter.in14d | 7 + src/cleanup/cleanup_milter.in14e | 7 + src/cleanup/cleanup_milter.in14f | 7 + src/cleanup/cleanup_milter.in14g | 7 + src/cleanup/cleanup_milter.in15a | 7 + src/cleanup/cleanup_milter.in15b | 7 + src/cleanup/cleanup_milter.in15c | 7 + src/cleanup/cleanup_milter.in15d | 7 + src/cleanup/cleanup_milter.in15e | 7 + src/cleanup/cleanup_milter.in15f | 7 + src/cleanup/cleanup_milter.in15g | 7 + src/cleanup/cleanup_milter.in15h | 11 + src/cleanup/cleanup_milter.in15i | 11 + src/cleanup/cleanup_milter.in16a | 10 + src/cleanup/cleanup_milter.in16b | 12 + src/cleanup/cleanup_milter.in17a | 9 + src/cleanup/cleanup_milter.in17b | 10 + src/cleanup/cleanup_milter.in17c | 12 + src/cleanup/cleanup_milter.in17d | 18 + src/cleanup/cleanup_milter.in17e | 12 + src/cleanup/cleanup_milter.in17f | 15 + src/cleanup/cleanup_milter.in17g | 23 + src/cleanup/cleanup_milter.in2 | 28 + src/cleanup/cleanup_milter.in3 | 60 + src/cleanup/cleanup_milter.in4a | 9 + src/cleanup/cleanup_milter.in4b | 9 + src/cleanup/cleanup_milter.in4c | 12 + src/cleanup/cleanup_milter.in5 | 19 + src/cleanup/cleanup_milter.in6a | 5 + src/cleanup/cleanup_milter.in6b | 6 + src/cleanup/cleanup_milter.in6c | 7 + src/cleanup/cleanup_milter.in7 | 7 + src/cleanup/cleanup_milter.in8 | 7 + src/cleanup/cleanup_milter.in9 | 7 + src/cleanup/cleanup_milter.ref1 | 56 + src/cleanup/cleanup_milter.ref10a | 53 + src/cleanup/cleanup_milter.ref10b | 53 + src/cleanup/cleanup_milter.ref10c | 83 + src/cleanup/cleanup_milter.ref10d | 53 + src/cleanup/cleanup_milter.ref10e | 89 + src/cleanup/cleanup_milter.ref11 | 66 + src/cleanup/cleanup_milter.ref12 | 31 + src/cleanup/cleanup_milter.ref13a | 31 + src/cleanup/cleanup_milter.ref13b | 27 + src/cleanup/cleanup_milter.ref13c | 29 + src/cleanup/cleanup_milter.ref13d | 39 + src/cleanup/cleanup_milter.ref13e | 30 + src/cleanup/cleanup_milter.ref13f | 32 + src/cleanup/cleanup_milter.ref13g | 34 + src/cleanup/cleanup_milter.ref13h | 29 + src/cleanup/cleanup_milter.ref13i | 33 + src/cleanup/cleanup_milter.ref14a1 | 2 + src/cleanup/cleanup_milter.ref14a2 | 27 + src/cleanup/cleanup_milter.ref14b1 | 2 + src/cleanup/cleanup_milter.ref14b2 | 29 + src/cleanup/cleanup_milter.ref14c1 | 1 + src/cleanup/cleanup_milter.ref14c2 | 29 + src/cleanup/cleanup_milter.ref14d1 | 2 + src/cleanup/cleanup_milter.ref14d2 | 25 + src/cleanup/cleanup_milter.ref14e1 | 2 + src/cleanup/cleanup_milter.ref14e2 | 27 + src/cleanup/cleanup_milter.ref14f1 | 2 + src/cleanup/cleanup_milter.ref14f2 | 28 + src/cleanup/cleanup_milter.ref14g1 | 2 + src/cleanup/cleanup_milter.ref14g2 | 27 + src/cleanup/cleanup_milter.ref15a1 | 2 + src/cleanup/cleanup_milter.ref15a2 | 27 + src/cleanup/cleanup_milter.ref15b1 | 2 + src/cleanup/cleanup_milter.ref15b2 | 29 + src/cleanup/cleanup_milter.ref15c1 | 1 + src/cleanup/cleanup_milter.ref15c2 | 29 + src/cleanup/cleanup_milter.ref15d1 | 2 + src/cleanup/cleanup_milter.ref15d2 | 25 + src/cleanup/cleanup_milter.ref15e1 | 2 + src/cleanup/cleanup_milter.ref15e2 | 27 + src/cleanup/cleanup_milter.ref15f1 | 1 + src/cleanup/cleanup_milter.ref15f2 | 30 + src/cleanup/cleanup_milter.ref15g1 | 2 + src/cleanup/cleanup_milter.ref15g2 | 30 + src/cleanup/cleanup_milter.ref15h1 | 3 + src/cleanup/cleanup_milter.ref15h2 | 27 + src/cleanup/cleanup_milter.ref15i1 | 3 + src/cleanup/cleanup_milter.ref15i2 | 29 + src/cleanup/cleanup_milter.ref16a1 | 3 + src/cleanup/cleanup_milter.ref16a2 | 37 + src/cleanup/cleanup_milter.ref16b1 | 1 + src/cleanup/cleanup_milter.ref16b2 | 42 + src/cleanup/cleanup_milter.ref17a1 | 1 + src/cleanup/cleanup_milter.ref17a2 | 30 + src/cleanup/cleanup_milter.ref17b1 | 1 + src/cleanup/cleanup_milter.ref17b2 | 31 + src/cleanup/cleanup_milter.ref17c1 | 1 + src/cleanup/cleanup_milter.ref17c2 | 31 + src/cleanup/cleanup_milter.ref17d1 | 1 + src/cleanup/cleanup_milter.ref17d2 | 35 + src/cleanup/cleanup_milter.ref17e1 | 2 + src/cleanup/cleanup_milter.ref17e2 | 30 + src/cleanup/cleanup_milter.ref17f1 | 2 + src/cleanup/cleanup_milter.ref17f2 | 30 + src/cleanup/cleanup_milter.ref17g1 | 2 + src/cleanup/cleanup_milter.ref17g2 | 33 + src/cleanup/cleanup_milter.ref2 | 36 + src/cleanup/cleanup_milter.ref3 | 50 + src/cleanup/cleanup_milter.ref4 | 59 + src/cleanup/cleanup_milter.ref5 | 29 + src/cleanup/cleanup_milter.ref6a | 28 + src/cleanup/cleanup_milter.ref6b | 31 + src/cleanup/cleanup_milter.ref6c | 33 + src/cleanup/cleanup_milter.ref7 | 32 + src/cleanup/cleanup_milter.ref8 | 34 + src/cleanup/cleanup_milter.ref9 | 33 + src/cleanup/cleanup_milter.reg14a | 1 + src/cleanup/cleanup_milter.reg14b | 1 + src/cleanup/cleanup_milter.reg14c | 1 + src/cleanup/cleanup_milter.reg14d | 1 + src/cleanup/cleanup_milter.reg14e | 1 + src/cleanup/cleanup_milter.reg14f | 1 + src/cleanup/cleanup_milter.reg14g | 1 + src/cleanup/cleanup_milter.reg15a | 1 + src/cleanup/cleanup_milter.reg15b | 1 + src/cleanup/cleanup_milter.reg15c | 1 + src/cleanup/cleanup_milter.reg15d | 1 + src/cleanup/cleanup_milter.reg15e | 1 + src/cleanup/cleanup_milter.reg15f | 1 + src/cleanup/cleanup_milter.reg15g | 1 + src/cleanup/cleanup_milter.reg15h | 2 + src/cleanup/cleanup_milter.reg15i | 2 + src/cleanup/cleanup_milter.reg16a | 2 + src/cleanup/cleanup_out.c | 233 + src/cleanup/cleanup_out_recipient.c | 266 + src/cleanup/cleanup_region.c | 232 + src/cleanup/cleanup_rewrite.c | 123 + src/cleanup/cleanup_state.c | 200 + src/cleanup/loremipsum | 28 + src/cleanup/loremipsum2 | 57 + src/cleanup/test-queue-file | Bin 0 -> 1258 bytes src/cleanup/test-queue-file10 | Bin 0 -> 573 bytes src/cleanup/test-queue-file11 | Bin 0 -> 975 bytes src/cleanup/test-queue-file12 | Bin 0 -> 573 bytes src/cleanup/test-queue-file13a | Bin 0 -> 573 bytes src/cleanup/test-queue-file13b | Bin 0 -> 573 bytes src/cleanup/test-queue-file13c | Bin 0 -> 573 bytes src/cleanup/test-queue-file13d | Bin 0 -> 975 bytes src/cleanup/test-queue-file13e | Bin 0 -> 573 bytes src/cleanup/test-queue-file13f | Bin 0 -> 573 bytes src/cleanup/test-queue-file13g | Bin 0 -> 573 bytes src/cleanup/test-queue-file13h | Bin 0 -> 573 bytes src/cleanup/test-queue-file13i | Bin 0 -> 573 bytes src/cleanup/test-queue-file14 | Bin 0 -> 642 bytes src/cleanup/test-queue-file15 | Bin 0 -> 641 bytes src/cleanup/test-queue-file16 | Bin 0 -> 654 bytes src/cleanup/test-queue-file17 | Bin 0 -> 654 bytes src/cleanup/test-queue-file2 | Bin 0 -> 573 bytes src/cleanup/test-queue-file3 | Bin 0 -> 573 bytes src/cleanup/test-queue-file4 | Bin 0 -> 1258 bytes src/cleanup/test-queue-file5 | Bin 0 -> 617 bytes src/cleanup/test-queue-file6 | Bin 0 -> 573 bytes src/cleanup/test-queue-file7 | Bin 0 -> 573 bytes src/cleanup/test-queue-file8 | Bin 0 -> 573 bytes src/cleanup/test-queue-file9 | Bin 0 -> 573 bytes src/discard/.indent.pro | 1 + src/discard/.printfck | 25 + src/discard/Makefile.in | 86 + src/discard/discard.c | 253 + src/dns/.indent.pro | 1 + src/dns/.printfck | 25 + src/dns/Makefile.in | 408 ++ src/dns/dns.h | 357 ++ src/dns/dns_lookup.c | 1272 ++++ src/dns/dns_rr.c | 347 + src/dns/dns_rr_eq_sa.c | 157 + src/dns/dns_rr_eq_sa.in | 4 + src/dns/dns_rr_eq_sa.ref | 24 + src/dns/dns_rr_filter.c | 150 + src/dns/dns_rr_to_pa.c | 113 + src/dns/dns_rr_to_pa.in | 2 + src/dns/dns_rr_to_pa.ref | 2 + src/dns/dns_rr_to_sa.c | 163 + src/dns/dns_rr_to_sa.in | 2 + src/dns/dns_rr_to_sa.ref | 2 + src/dns/dns_sa_to_rr.c | 138 + src/dns/dns_sa_to_rr.in | 1 + src/dns/dns_sa_to_rr.ref | 2 + src/dns/dns_sec.c | 144 + src/dns/dns_str_resflags.c | 128 + src/dns/dns_strerror.c | 69 + src/dns/dns_strrecord.c | 117 + src/dns/dns_strtype.c | 211 + src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref | 12 + src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref | 6 + src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref | 10 + src/dns/error.ref | 13 + src/dns/error.reg | 1 + src/dns/mxonly_test.ref | 11 + src/dns/no-a.ref | 13 + src/dns/no-a.reg | 1 + src/dns/no-aaaa.ref | 13 + src/dns/no-aaaa.reg | 1 + src/dns/no-mx.ref | 15 + src/dns/no-mx.reg | 1 + src/dns/no-txt.reg | 1 + src/dns/nullmx_test.ref | 8 + src/dns/nxdomain_test.ref | 5 + src/dns/test_dns_lookup.c | 131 + src/dnsblog/.indent.pro | 1 + src/dnsblog/Makefile.in | 84 + src/dnsblog/dnsblog.c | 319 + src/error/.indent.pro | 1 + src/error/.printfck | 25 + src/error/Makefile.in | 89 + src/error/error.c | 266 + src/flush/.indent.pro | 1 + src/flush/.printfck | 25 + src/flush/Makefile.in | 96 + src/flush/flush.c | 865 +++ src/fsstone/.indent.pro | 1 + src/fsstone/.printfck | 25 + src/fsstone/Makefile.in | 69 + src/fsstone/fsstone.c | 242 + 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 + src/local/.indent.pro | 1 + src/local/.printfck | 25 + src/local/Makefile.in | 686 ++ src/local/Musings | 39 + src/local/alias.c | 386 ++ src/local/biff_notify.c | 98 + src/local/biff_notify.h | 30 + src/local/bounce_workaround.c | 159 + src/local/command.c | 251 + src/local/deliver_attr.c | 105 + src/local/dotforward.c | 304 + src/local/file.c | 195 + src/local/forward.c | 393 ++ src/local/include.c | 223 + src/local/indirect.c | 94 + src/local/local.c | 988 +++ src/local/local.h | 251 + src/local/local_expand.c | 180 + src/local/mailbox.c | 373 ++ src/local/maildir.c | 257 + src/local/recipient.c | 307 + src/local/resolve.c | 170 + src/local/token.c | 222 + src/local/unknown.c | 187 + src/master/.indent.pro | 1 + src/master/.printfck | 25 + src/master/Makefile.in | 477 ++ src/master/dgram_server.c | 665 ++ src/master/event_server.c | 968 +++ src/master/mail_flow.c | 142 + src/master/mail_flow.h | 32 + src/master/mail_server.h | 155 + src/master/master.c | 598 ++ src/master/master.h | 246 + src/master/master_avail.c | 251 + src/master/master_conf.c | 152 + src/master/master_ent.c | 648 ++ src/master/master_flow.c | 33 + src/master/master_listen.c | 186 + src/master/master_monitor.c | 106 + src/master/master_proto.c | 89 + src/master/master_proto.h | 75 + src/master/master_service.c | 113 + src/master/master_sig.c | 275 + src/master/master_spawn.c | 371 ++ src/master/master_status.c | 198 + src/master/master_vars.c | 98 + src/master/master_wakeup.c | 192 + src/master/master_watch.c | 151 + src/master/multi_server.c | 931 +++ src/master/single_server.c | 822 +++ src/master/trigger_server.c | 809 +++ src/milter/.indent.pro | 1 + src/milter/Makefile.in | 145 + src/milter/milter.c | 1121 ++++ src/milter/milter.h | 223 + src/milter/milter8.c | 2911 +++++++++ src/milter/milter_macros.c | 303 + src/milter/test-list | 49 + src/milter/test-milter.c | 840 +++ src/oqmgr/.indent.pro | 1 + src/oqmgr/.printfck | 25 + src/oqmgr/Makefile.in | 362 ++ src/oqmgr/qmgr.c | 736 +++ src/oqmgr/qmgr.h | 432 ++ src/oqmgr/qmgr_active.c | 607 ++ src/oqmgr/qmgr_bounce.c | 71 + src/oqmgr/qmgr_defer.c | 158 + src/oqmgr/qmgr_deliver.c | 453 ++ src/oqmgr/qmgr_enable.c | 107 + src/oqmgr/qmgr_entry.c | 391 ++ src/oqmgr/qmgr_error.c | 121 + src/oqmgr/qmgr_feedback.c | 182 + src/oqmgr/qmgr_message.c | 1497 +++++ src/oqmgr/qmgr_move.c | 104 + src/oqmgr/qmgr_queue.c | 442 ++ src/oqmgr/qmgr_scan.c | 185 + src/oqmgr/qmgr_transport.c | 472 ++ src/pickup/.indent.pro | 1 + src/pickup/.printfck | 25 + src/pickup/Makefile.in | 94 + src/pickup/pickup.c | 631 ++ src/pipe/.indent.pro | 1 + src/pipe/.printfck | 25 + src/pipe/Makefile.in | 107 + src/pipe/pipe.c | 1397 ++++ src/postalias/.indent.pro | 1 + src/postalias/.printfck | 25 + src/postalias/Makefile.in | 121 + src/postalias/aliases | 1 + src/postalias/fail_test.in | 7 + src/postalias/fail_test.ref | 7 + src/postalias/map-abc1.ref | 1 + src/postalias/map-abc2.ref | 1 + src/postalias/map-ghi1.ref | 1 + src/postalias/map-ghi2.ref | 1 + src/postalias/map-uABC1.ref | 1 + src/postalias/map-uABC2.ref | 1 + src/postalias/map.in | 2 + src/postalias/postalias.c | 906 +++ src/postcat/.indent.pro | 1 + src/postcat/.printfck | 25 + src/postcat/Makefile.in | 128 + src/postcat/b_test.ref | 2 + src/postcat/bh_test.ref | 9 + src/postcat/default_test.ref | 21 + src/postcat/e_test.ref | 12 + src/postcat/eb_test.ref | 14 + src/postcat/eh_test.ref | 19 + src/postcat/h_test.ref | 7 + src/postcat/postcat.c | 598 ++ src/postcat/test-queue-file | Bin 0 -> 573 bytes src/postconf/.indent.pro | 1 + src/postconf/.printfck | 25 + src/postconf/Makefile.in | 1304 ++++ src/postconf/extract.awk | 201 + src/postconf/extract_cfg.sh | 92 + src/postconf/install_table.h | 2 + src/postconf/install_vars.h | 1 + src/postconf/postconf.c | 1113 ++++ src/postconf/postconf.h | 327 + src/postconf/postconf_builtin.c | 469 ++ src/postconf/postconf_dbms.c | 338 + src/postconf/postconf_edit.c | 625 ++ src/postconf/postconf_lookup.c | 194 + src/postconf/postconf_main.c | 220 + src/postconf/postconf_master.c | 1120 ++++ src/postconf/postconf_match.c | 188 + src/postconf/postconf_misc.c | 60 + src/postconf/postconf_node.c | 185 + src/postconf/postconf_other.c | 141 + src/postconf/postconf_print.c | 114 + src/postconf/postconf_service.c | 199 + src/postconf/postconf_unused.c | 129 + src/postconf/postconf_user.c | 422 ++ src/postconf/test1.ref | 4 + src/postconf/test10.ref | 2 + src/postconf/test11.ref | 2 + src/postconf/test12.ref | 2 + src/postconf/test13.ref | 3 + src/postconf/test14.ref | 3 + src/postconf/test15.ref | 3 + src/postconf/test16.ref | 2 + src/postconf/test17.ref | 1 + src/postconf/test18.ref | 3 + src/postconf/test19.ref | 3 + src/postconf/test2.ref | 3 + src/postconf/test20.ref | 4 + src/postconf/test21.ref | 3 + src/postconf/test22.ref | 16 + src/postconf/test23.ref | 2 + src/postconf/test24.ref | 1 + src/postconf/test25.ref | 16 + src/postconf/test26.ref | 3 + src/postconf/test27.ref | 16 + src/postconf/test28.ref | 10 + src/postconf/test29.ref | 16 + src/postconf/test3.ref | 4 + src/postconf/test30.ref | 7 + src/postconf/test31.ref | 3 + src/postconf/test32.ref | 1 + src/postconf/test33.ref | 1 + src/postconf/test34.ref | 4 + src/postconf/test35.ref | 3 + src/postconf/test36.ref | 4 + src/postconf/test37.ref | 4 + src/postconf/test38.ref | 1 + src/postconf/test39.ref | 2 + src/postconf/test4.ref | 3 + src/postconf/test40.ref | 7 + src/postconf/test41.ref | 18 + src/postconf/test42.ref | 14 + src/postconf/test43.ref | 6 + src/postconf/test44.ref | 6 + src/postconf/test45.ref | 1 + src/postconf/test46.ref | 1 + src/postconf/test47.ref | 1 + src/postconf/test48.ref | 1 + src/postconf/test49.ref | 1 + src/postconf/test4b.ref | 5 + src/postconf/test5.ref | 1 + src/postconf/test50.ref | 1 + src/postconf/test51.ref | 1 + src/postconf/test52.ref | 1 + src/postconf/test53.ref | 3 + src/postconf/test54.ref | 3 + src/postconf/test55.ref | 3 + src/postconf/test56.ref | 5 + src/postconf/test57.ref | 10 + src/postconf/test58.ref | 8 + src/postconf/test59.ref | 10 + src/postconf/test6.ref | 17 + src/postconf/test60.ref | 8 + src/postconf/test61.ref | 1 + src/postconf/test62.ref | 8 + src/postconf/test63.ref | 1 + src/postconf/test64.ref | 1 + src/postconf/test65.ref | 1 + src/postconf/test66.ref | 5 + src/postconf/test67.ref | 10 + src/postconf/test68.ref | 5 + src/postconf/test69.ref | 2 + src/postconf/test7.ref | 1 + src/postconf/test70.ref | 4 + src/postconf/test8.ref | 1 + src/postconf/test9.ref | 1 + src/postdrop/.indent.pro | 1 + src/postdrop/.printfck | 25 + src/postdrop/Makefile.in | 96 + src/postdrop/postdrop.c | 628 ++ src/postfix/.indent.pro | 1 + src/postfix/.printfck | 25 + src/postfix/Makefile.in | 83 + src/postfix/postfix.c | 660 ++ src/postkick/.indent.pro | 1 + src/postkick/.printfck | 25 + src/postkick/Makefile.in | 83 + src/postkick/postkick.c | 221 + src/postlock/.indent.pro | 1 + src/postlock/.printfck | 25 + src/postlock/Makefile.in | 86 + src/postlock/postlock.c | 282 + src/postlog/.indent.pro | 1 + src/postlog/.printfck | 25 + src/postlog/Makefile.in | 84 + src/postlog/postlog.c | 372 ++ src/postlogd/Makefile.in | 79 + src/postlogd/postlogd.c | 267 + src/postmap/.indent.pro | 1 + src/postmap/.printfck | 25 + src/postmap/Makefile.in | 162 + src/postmap/aliases | 1 + src/postmap/fail_test.in | 8 + src/postmap/fail_test.ref | 8 + src/postmap/file_test.in | 17 + src/postmap/file_test.ref | 14 + src/postmap/lmdb_abb | 3 + src/postmap/lmdb_abb.ref | 3 + src/postmap/map-abc1.ref | 1 + src/postmap/map-abc2.ref | 1 + src/postmap/map-ghi1.ref | 1 + src/postmap/map-ghi2.ref | 1 + src/postmap/map-uABC1.ref | 1 + src/postmap/map-uABC2.ref | 1 + src/postmap/map.in | 2 + src/postmap/postmap.c | 1155 ++++ src/postmap/quote_test.in | 8 + src/postmap/quote_test.ref | 7 + src/postmulti/.indent.pro | 1 + src/postmulti/Makefile.in | 87 + src/postmulti/postmulti.c | 1843 ++++++ src/postqueue/.indent.pro | 1 + src/postqueue/Makefile.in | 133 + src/postqueue/postqueue.c | 727 +++ src/postqueue/postqueue.h | 35 + src/postqueue/showq_compat.c | 222 + src/postqueue/showq_json.c | 221 + src/postscreen/.indent.pro | 1 + src/postscreen/Makefile.in | 428 ++ src/postscreen/postscreen.c | 1252 ++++ src/postscreen/postscreen.h | 599 ++ src/postscreen/postscreen_dict.c | 182 + src/postscreen/postscreen_dnsbl.c | 624 ++ src/postscreen/postscreen_early.c | 377 ++ src/postscreen/postscreen_endpt.c | 232 + src/postscreen/postscreen_expand.c | 141 + src/postscreen/postscreen_haproxy.c | 137 + src/postscreen/postscreen_haproxy.h | 30 + src/postscreen/postscreen_misc.c | 164 + src/postscreen/postscreen_send.c | 293 + src/postscreen/postscreen_smtpd.c | 1339 ++++ src/postscreen/postscreen_starttls.c | 317 + src/postscreen/postscreen_state.c | 317 + src/postscreen/postscreen_tests.c | 341 + src/postsuper/.indent.pro | 1 + src/postsuper/.printfck | 25 + src/postsuper/Makefile.in | 90 + src/postsuper/postsuper.c | 1604 +++++ src/posttls-finger/.indent.pro | 1 + src/posttls-finger/Makefile.in | 117 + src/posttls-finger/posttls-finger.c | 2165 +++++++ src/posttls-finger/tlsmgrmem.c | 143 + src/posttls-finger/tlsmgrmem.h | 28 + src/proxymap/.indent.pro | 1 + src/proxymap/Makefile.in | 85 + src/proxymap/proxymap.c | 851 +++ src/qmgr/.indent.pro | 1 + src/qmgr/.printfck | 25 + src/qmgr/Makefile.in | 390 ++ src/qmgr/qmgr.c | 827 +++ src/qmgr/qmgr.h | 546 ++ src/qmgr/qmgr_active.c | 607 ++ src/qmgr/qmgr_bounce.c | 71 + src/qmgr/qmgr_defer.c | 163 + src/qmgr/qmgr_deliver.c | 456 ++ src/qmgr/qmgr_enable.c | 107 + src/qmgr/qmgr_entry.c | 452 ++ src/qmgr/qmgr_error.c | 121 + src/qmgr/qmgr_feedback.c | 182 + src/qmgr/qmgr_job.c | 978 +++ src/qmgr/qmgr_message.c | 1622 +++++ src/qmgr/qmgr_move.c | 104 + src/qmgr/qmgr_peer.c | 154 + src/qmgr/qmgr_queue.c | 439 ++ src/qmgr/qmgr_scan.c | 185 + src/qmgr/qmgr_transport.c | 504 ++ src/qmqpd/.indent.pro | 1 + src/qmqpd/.printfck | 25 + src/qmqpd/Makefile.in | 139 + src/qmqpd/qmqpd.c | 865 +++ src/qmqpd/qmqpd.h | 95 + src/qmqpd/qmqpd_peer.c | 309 + src/qmqpd/qmqpd_state.c | 99 + src/scache/.indent.pro | 1 + src/scache/Makefile.in | 81 + src/scache/scache.c | 586 ++ src/sendmail/.indent.pro | 1 + src/sendmail/.printfck | 25 + src/sendmail/Makefile.in | 113 + src/sendmail/sendmail.c | 1510 +++++ src/showq/.indent.pro | 1 + src/showq/.printfck | 25 + src/showq/Makefile.in | 95 + src/showq/showq.c | 438 ++ src/smtp/.indent.pro | 1 + src/smtp/.printfck | 25 + src/smtp/Makefile.in | 878 +++ src/smtp/lmtp_params.c | 136 + src/smtp/smtp-only | 4 + src/smtp/smtp.c | 1652 +++++ src/smtp/smtp.h | 771 +++ src/smtp/smtp_addr.c | 702 ++ src/smtp/smtp_addr.h | 31 + src/smtp/smtp_chat.c | 503 ++ src/smtp/smtp_connect.c | 1231 ++++ src/smtp/smtp_key.c | 215 + src/smtp/smtp_map11.c | 325 + src/smtp/smtp_map11.in | 33 + src/smtp/smtp_map11.ref | 22 + src/smtp/smtp_misc.c | 100 + src/smtp/smtp_params.c | 140 + src/smtp/smtp_proto.c | 2515 ++++++++ src/smtp/smtp_rcpt.c | 226 + src/smtp/smtp_reuse.c | 269 + src/smtp/smtp_reuse.h | 27 + src/smtp/smtp_sasl.h | 43 + src/smtp/smtp_sasl_auth_cache.c | 272 + src/smtp/smtp_sasl_auth_cache.h | 62 + src/smtp/smtp_sasl_glue.c | 512 ++ src/smtp/smtp_sasl_proto.c | 171 + src/smtp/smtp_session.c | 447 ++ src/smtp/smtp_state.c | 124 + src/smtp/smtp_tls_policy.c | 927 +++ src/smtp/smtp_trouble.c | 476 ++ src/smtp/smtp_unalias.c | 142 + src/smtp/tls_policy.in | 64 + src/smtp/tls_policy.ref | 65 + src/smtpd/.indent.pro | 1 + src/smtpd/.printfck | 25 + src/smtpd/Makefile.in | 679 ++ src/smtpd/smtpd.c | 6776 ++++++++++++++++++++ src/smtpd/smtpd.h | 446 ++ src/smtpd/smtpd_acl.in | 120 + src/smtpd/smtpd_acl.ref | 187 + src/smtpd/smtpd_addr_valid.in | 35 + src/smtpd/smtpd_addr_valid.ref | 57 + src/smtpd/smtpd_chat.c | 352 + src/smtpd/smtpd_chat.h | 45 + src/smtpd/smtpd_check.c | 6445 +++++++++++++++++++ src/smtpd/smtpd_check.h | 44 + src/smtpd/smtpd_check.in | 182 + src/smtpd/smtpd_check.in2 | 116 + src/smtpd/smtpd_check.in3 | 27 + src/smtpd/smtpd_check.in4 | 19 + src/smtpd/smtpd_check.ref | 398 ++ src/smtpd/smtpd_check.ref2 | 236 + src/smtpd/smtpd_check.ref4 | 38 + src/smtpd/smtpd_check_access | 91 + src/smtpd/smtpd_check_backup.in | 20 + src/smtpd/smtpd_check_backup.ref | 34 + src/smtpd/smtpd_check_dsn.in | 60 + src/smtpd/smtpd_check_dsn.ref | 163 + src/smtpd/smtpd_dns_filter.in | 83 + src/smtpd/smtpd_dns_filter.ref | 163 + src/smtpd/smtpd_dnswl.in | 60 + src/smtpd/smtpd_dnswl.ref | 94 + src/smtpd/smtpd_dsn_fix.c | 149 + src/smtpd/smtpd_dsn_fix.h | 44 + src/smtpd/smtpd_error.in | 81 + src/smtpd/smtpd_error.ref | 135 + src/smtpd/smtpd_exp.in | 62 + src/smtpd/smtpd_exp.ref | 111 + src/smtpd/smtpd_expand.c | 247 + src/smtpd/smtpd_expand.h | 40 + src/smtpd/smtpd_haproxy.c | 135 + src/smtpd/smtpd_milter.c | 229 + src/smtpd/smtpd_milter.h | 27 + src/smtpd/smtpd_nullmx.in | 58 + src/smtpd/smtpd_nullmx.ref | 100 + src/smtpd/smtpd_peer.c | 658 ++ src/smtpd/smtpd_proxy.c | 1171 ++++ src/smtpd/smtpd_proxy.h | 66 + src/smtpd/smtpd_resolve.c | 190 + src/smtpd/smtpd_resolve.h | 43 + src/smtpd/smtpd_sasl_glue.c | 396 ++ src/smtpd/smtpd_sasl_glue.h | 41 + src/smtpd/smtpd_sasl_proto.c | 274 + src/smtpd/smtpd_sasl_proto.h | 37 + src/smtpd/smtpd_server.in | 56 + src/smtpd/smtpd_server.ref | 118 + src/smtpd/smtpd_state.c | 248 + src/smtpd/smtpd_token.c | 233 + src/smtpd/smtpd_token.h | 40 + src/smtpd/smtpd_token.in | 12 + src/smtpd/smtpd_token.ref | 84 + src/smtpd/smtpd_xforward.c | 114 + src/smtpstone/.indent.pro | 1 + src/smtpstone/.printfck | 25 + src/smtpstone/Makefile.in | 172 + src/smtpstone/hashed-deferred | 37 + src/smtpstone/hashed-incoming | 48 + src/smtpstone/mx-deliver | 20 + src/smtpstone/mx-explode | 33 + src/smtpstone/mx-relay | 20 + src/smtpstone/performance | 28 + src/smtpstone/qmail-deliver | 20 + src/smtpstone/qmail-explode | 20 + src/smtpstone/qmail-relay | 20 + src/smtpstone/qmqp-sink.c | 325 + src/smtpstone/qmqp-source.c | 673 ++ src/smtpstone/smtp-sink.c | 1643 +++++ src/smtpstone/smtp-source.c | 1188 ++++ src/smtpstone/throughput | 28 + src/smtpstone/vmail-local | 43 + src/smtpstone/vmail-relay | 57 + src/spawn/.indent.pro | 1 + src/spawn/.printfck | 25 + src/spawn/Makefile.in | 82 + src/spawn/spawn.c | 373 ++ src/tls/.indent.pro | 1 + src/tls/Makefile.in | 601 ++ src/tls/bad-back-to-back-keys.pem | 64 + src/tls/bad-back-to-back-keys.pem.ref | 2 + src/tls/bad-ec-cert-before-key.pem | 36 + src/tls/bad-ec-cert-before-key.pem.ref | 2 + src/tls/bad-key-cert-mismatch.pem | 36 + src/tls/bad-key-cert-mismatch.pem.ref | 2 + src/tls/bad-rsa-key-last.pem | 64 + src/tls/bad-rsa-key-last.pem.ref | 2 + src/tls/ecca-cert.pem | 10 + src/tls/ecca-pkey.pem | 5 + src/tls/ecee-cert.pem | 11 + src/tls/ecee-pkey.pem | 5 + src/tls/ecroot-cert.pem | 10 + src/tls/ecroot-pkey.pem | 5 + src/tls/good-mixed-keyfirst.pem | 45 + src/tls/good-mixed-keyfirst.pem.ref | 12 + src/tls/good-mixed-keylast.pem | 45 + src/tls/good-mixed-keylast.pem.ref | 12 + src/tls/good-mixed-keymiddle.pem | 45 + src/tls/good-mixed-keymiddle.pem.ref | 12 + src/tls/goodchains.pem | 121 + src/tls/goodchains.pem.ref | 24 + src/tls/mkcert.sh | 264 + src/tls/rsaca-cert.pem | 19 + src/tls/rsaca-pkey.pem | 28 + src/tls/rsaee-cert.pem | 20 + src/tls/rsaee-pkey.pem | 28 + src/tls/rsaroot-cert.pem | 18 + src/tls/rsaroot-pkey.pem | 28 + src/tls/tls.h | 728 +++ src/tls/tls_bio_ops.c | 296 + src/tls/tls_certkey.c | 721 +++ src/tls/tls_client.c | 1250 ++++ src/tls/tls_dane.c | 1357 ++++ src/tls/tls_dane.sh | 211 + src/tls/tls_dh.c | 384 ++ src/tls/tls_fprint.c | 435 ++ src/tls/tls_level.c | 95 + src/tls/tls_mgr.c | 486 ++ src/tls/tls_mgr.h | 71 + src/tls/tls_misc.c | 1712 +++++ src/tls/tls_prng.h | 50 + src/tls/tls_prng_dev.c | 155 + src/tls/tls_prng_egd.c | 166 + src/tls/tls_prng_exch.c | 142 + src/tls/tls_prng_file.c | 155 + src/tls/tls_proxy.h | 287 + src/tls/tls_proxy_client_misc.c | 130 + src/tls/tls_proxy_client_print.c | 294 + src/tls/tls_proxy_client_scan.c | 496 ++ src/tls/tls_proxy_clnt.c | 300 + src/tls/tls_proxy_context_print.c | 114 + src/tls/tls_proxy_context_scan.c | 190 + src/tls/tls_proxy_server_print.c | 143 + src/tls/tls_proxy_server_scan.c | 245 + src/tls/tls_rsa.c | 0 src/tls/tls_scache.c | 591 ++ src/tls/tls_scache.h | 73 + src/tls/tls_seed.c | 88 + src/tls/tls_server.c | 1051 +++ src/tls/tls_session.c | 185 + src/tls/tls_stream.c | 160 + src/tls/tls_verify.c | 425 ++ src/tls/warn-mixed-multi-key.pem | 51 + src/tls/warn-mixed-multi-key.pem.ref | 13 + src/tlsmgr/.indent.pro | 1 + src/tlsmgr/Makefile.in | 100 + src/tlsmgr/tlsmgr.c | 1115 ++++ src/tlsproxy/.indent.pro | 1 + src/tlsproxy/Makefile.in | 117 + src/tlsproxy/tlsproxy.c | 1968 ++++++ src/tlsproxy/tlsproxy.h | 69 + src/tlsproxy/tlsproxy_state.c | 169 + src/trivial-rewrite/.indent.pro | 1 + src/trivial-rewrite/.printfck | 25 + src/trivial-rewrite/Makefile.in | 199 + src/trivial-rewrite/resolve.c | 828 +++ src/trivial-rewrite/rewrite.c | 303 + src/trivial-rewrite/transport.c | 438 ++ src/trivial-rewrite/transport.h | 51 + src/trivial-rewrite/transport.in | 45 + src/trivial-rewrite/transport.ref | 22 + src/trivial-rewrite/trivial-rewrite.c | 667 ++ src/trivial-rewrite/trivial-rewrite.h | 92 + src/util/.indent.pro | 1 + src/util/.printfck | 25 + src/util/Makefile.in | 2756 ++++++++ src/util/allascii.c | 65 + src/util/alldig.c | 73 + src/util/allprint.c | 50 + src/util/allspace.c | 50 + src/util/argv.c | 326 + src/util/argv.h | 69 + src/util/argv_attr.h | 43 + src/util/argv_attr_print.c | 73 + src/util/argv_attr_scan.c | 93 + src/util/argv_split.c | 112 + src/util/argv_split_at.c | 124 + src/util/argv_splitq.c | 118 + src/util/attr.h | 184 + src/util/attr_clnt.c | 300 + src/util/attr_clnt.h | 60 + src/util/attr_print0.c | 256 + src/util/attr_print64.c | 297 + src/util/attr_print_plain.c | 252 + src/util/attr_scan.ref | 36 + src/util/attr_scan0.c | 596 ++ src/util/attr_scan0.ref | 79 + src/util/attr_scan64.c | 665 ++ src/util/attr_scan64.ref | 79 + src/util/attr_scan_plain.c | 643 ++ src/util/attr_scan_plain.ref | 79 + src/util/auto_clnt.c | 372 ++ src/util/auto_clnt.h | 51 + src/util/balpar.c | 56 + src/util/base32_code.c | 266 + src/util/base32_code.h | 41 + src/util/base64_code.c | 228 + src/util/base64_code.h | 49 + src/util/basename.c | 49 + src/util/binhash.c | 447 ++ src/util/binhash.h | 65 + src/util/byte_mask.c | 306 + src/util/byte_mask.h | 64 + src/util/byte_mask.in | 7 + src/util/byte_mask.ref0 | 14 + src/util/byte_mask.ref1 | 22 + src/util/byte_mask.ref2 | 10 + src/util/cache.in | 26 + src/util/casefold.c | 359 ++ src/util/casefold_test.in | 24 + src/util/casefold_test.ref | 47 + src/util/check_arg.h | 157 + src/util/chroot_uid.c | 88 + src/util/chroot_uid.h | 29 + src/util/cidr_match.c | 316 + src/util/cidr_match.h | 82 + src/util/clean_env.c | 146 + src/util/clean_env.h | 36 + src/util/close_on_exec.c | 60 + src/util/compat_va_copy.h | 44 + src/util/concatenate.c | 73 + src/util/connect.h | 43 + src/util/ctable.c | 321 + src/util/ctable.h | 41 + src/util/ctable.in | 39 + src/util/ctable.ref | 99 + src/util/dict.c | 664 ++ src/util/dict.h | 340 + src/util/dict_alloc.c | 196 + src/util/dict_cache.c | 1121 ++++ src/util/dict_cache.h | 67 + src/util/dict_cdb.c | 446 ++ src/util/dict_cdb.h | 37 + src/util/dict_cidr.c | 361 ++ src/util/dict_cidr.h | 43 + src/util/dict_cidr.in | 23 + src/util/dict_cidr.map | 50 + src/util/dict_cidr.ref | 63 + src/util/dict_cidr_file.in | 3 + src/util/dict_cidr_file.map | 3 + src/util/dict_cidr_file.ref | 8 + src/util/dict_db.c | 880 +++ src/util/dict_db.h | 55 + src/util/dict_dbm.c | 503 ++ src/util/dict_dbm.h | 37 + src/util/dict_debug.c | 150 + src/util/dict_env.c | 112 + src/util/dict_env.h | 37 + src/util/dict_fail.c | 115 + src/util/dict_fail.h | 35 + src/util/dict_file.c | 231 + src/util/dict_ht.c | 171 + src/util/dict_ht.h | 38 + src/util/dict_inline.c | 150 + src/util/dict_inline.h | 37 + src/util/dict_inline.ref | 24 + src/util/dict_inline_cidr.ref | 4 + src/util/dict_inline_file.ref | 12 + src/util/dict_inline_pcre.ref | 4 + src/util/dict_inline_regexp.ref | 4 + src/util/dict_lmdb.c | 706 ++ src/util/dict_lmdb.h | 43 + src/util/dict_ni.c | 194 + src/util/dict_ni.h | 34 + src/util/dict_nis.c | 247 + src/util/dict_nis.h | 37 + src/util/dict_nisplus.c | 304 + src/util/dict_nisplus.h | 37 + src/util/dict_open.c | 600 ++ src/util/dict_pcre.c | 1120 ++++ src/util/dict_pcre.h | 43 + src/util/dict_pcre.in | 15 + src/util/dict_pcre.map | 28 + src/util/dict_pcre.ref | 44 + src/util/dict_pcre_file.in | 4 + src/util/dict_pcre_file.map | 6 + src/util/dict_pcre_file.ref | 12 + src/util/dict_pipe.c | 189 + src/util/dict_pipe.h | 37 + src/util/dict_pipe_test.in | 9 + src/util/dict_pipe_test.ref | 14 + src/util/dict_random.c | 179 + src/util/dict_random.h | 37 + src/util/dict_random.ref | 20 + src/util/dict_random_file.ref | 10 + src/util/dict_regexp.c | 874 +++ src/util/dict_regexp.h | 42 + src/util/dict_regexp.in | 17 + src/util/dict_regexp.map | 27 + src/util/dict_regexp.ref | 46 + src/util/dict_regexp_file.in | 3 + src/util/dict_regexp_file.map | 3 + src/util/dict_regexp_file.ref | 8 + src/util/dict_sdbm.c | 483 ++ src/util/dict_sdbm.h | 37 + src/util/dict_seq.in | 11 + src/util/dict_seq.ref | 22 + src/util/dict_sockmap.c | 384 ++ src/util/dict_sockmap.h | 37 + src/util/dict_static.c | 151 + src/util/dict_static.h | 35 + src/util/dict_static.ref | 14 + src/util/dict_static_file.ref | 10 + src/util/dict_stream.c | 274 + src/util/dict_stream.ref | 13 + src/util/dict_surrogate.c | 180 + src/util/dict_tcp.c | 315 + src/util/dict_tcp.h | 37 + src/util/dict_test.c | 166 + src/util/dict_test.in | 10 + src/util/dict_test.ref | 20 + src/util/dict_thash.c | 255 + src/util/dict_thash.h | 37 + src/util/dict_thash.in | 5 + src/util/dict_thash.map | 17 + src/util/dict_thash.ref | 6 + src/util/dict_union.c | 202 + src/util/dict_union.h | 37 + src/util/dict_union_test.in | 7 + src/util/dict_union_test.ref | 10 + src/util/dict_unix.c | 204 + src/util/dict_unix.h | 37 + src/util/dict_utf8.c | 300 + src/util/dict_utf8_test.in | 14 + src/util/dict_utf8_test.ref | 18 + src/util/dir_forest.c | 110 + src/util/dir_forest.h | 35 + src/util/doze.c | 71 + src/util/dummy_read.c | 61 + src/util/dummy_write.c | 61 + src/util/dup2_pass_on_exec.c | 64 + src/util/duplex_pipe.c | 49 + src/util/edit_file.c | 353 + src/util/edit_file.h | 53 + src/util/environ.c | 156 + src/util/events.c | 1261 ++++ src/util/events.h | 67 + src/util/exec_command.c | 112 + src/util/exec_command.h | 30 + src/util/extpar.c | 109 + src/util/fifo_listen.c | 111 + src/util/fifo_open.c | 67 + src/util/fifo_rdonly_bug.c | 127 + src/util/fifo_rdwr_bug.c | 89 + src/util/fifo_trigger.c | 168 + src/util/file_limit.c | 96 + src/util/find_inet.c | 253 + src/util/find_inet.h | 33 + src/util/find_inet.ref | 5 + src/util/format_tv.c | 160 + src/util/format_tv.h | 35 + src/util/format_tv.in | 68 + src/util/format_tv.ref | 120 + src/util/fsspace.c | 127 + src/util/fsspace.h | 33 + src/util/fullname.c | 111 + src/util/fullname.h | 29 + src/util/gccw.c | 58 + src/util/gccw.ref | 18 + src/util/get_domainname.c | 66 + src/util/get_domainname.h | 29 + src/util/get_hostname.c | 84 + src/util/get_hostname.h | 29 + src/util/hash_fnv.c | 107 + src/util/hash_fnv.h | 39 + src/util/hex_code.c | 243 + src/util/hex_code.h | 54 + src/util/hex_quote.c | 153 + src/util/hex_quote.h | 36 + src/util/host_port.c | 203 + src/util/host_port.h | 35 + src/util/host_port.in | 16 + src/util/host_port.ref | 30 + src/util/htable.c | 439 ++ src/util/htable.h | 70 + src/util/inet_addr_host.c | 173 + src/util/inet_addr_host.h | 35 + src/util/inet_addr_list.c | 186 + src/util/inet_addr_list.h | 44 + src/util/inet_addr_list.in | 9 + src/util/inet_addr_list.ref | 15 + src/util/inet_addr_local.c | 621 ++ src/util/inet_addr_local.h | 35 + src/util/inet_connect.c | 189 + src/util/inet_listen.c | 189 + src/util/inet_proto.c | 328 + src/util/inet_proto.h | 56 + src/util/inet_trigger.c | 130 + src/util/inet_windowsize.c | 82 + src/util/iostuff.h | 73 + src/util/ip_match.c | 676 ++ src/util/ip_match.h | 38 + src/util/ip_match.in | 26 + src/util/ip_match.ref | 69 + src/util/killme_after.c | 69 + src/util/killme_after.h | 30 + src/util/known_tcp_ports.c | 253 + src/util/known_tcp_ports.h | 38 + src/util/known_tcp_ports.ref | 6 + src/util/ldseed.c | 138 + src/util/ldseed.h | 30 + src/util/line_number.c | 71 + src/util/line_number.h | 35 + src/util/line_wrap.c | 122 + src/util/line_wrap.h | 31 + src/util/listen.h | 53 + src/util/lmdb_cache_test_1.sh | 55 + src/util/lmdb_cache_test_2.sh | 42 + src/util/load_file.c | 80 + src/util/load_file.h | 32 + src/util/load_lib.c | 146 + src/util/load_lib.h | 46 + src/util/logwriter.c | 124 + src/util/logwriter.h | 38 + src/util/lowercase.c | 43 + src/util/lstat_as.c | 73 + src/util/lstat_as.h | 35 + src/util/mac_expand.c | 848 +++ src/util/mac_expand.h | 81 + src/util/mac_expand.in | 105 + src/util/mac_expand.ref | 222 + src/util/mac_parse.c | 199 + src/util/mac_parse.h | 51 + src/util/make_dirs.c | 173 + src/util/make_dirs.h | 30 + src/util/mask_addr.c | 68 + src/util/mask_addr.h | 30 + src/util/match_list.c | 281 + src/util/match_list.h | 66 + src/util/match_ops.c | 312 + src/util/midna_domain.c | 419 ++ src/util/midna_domain.h | 43 + src/util/midna_domain_test.in | 21 + src/util/midna_domain_test.ref | 89 + src/util/miss_endif_cidr.map | 1 + src/util/miss_endif_cidr.ref | 4 + src/util/miss_endif_pcre.ref | 4 + src/util/miss_endif_re.map | 1 + src/util/miss_endif_regexp.ref | 4 + src/util/msg.c | 340 + src/util/msg.h | 60 + src/util/msg_logger.c | 371 ++ src/util/msg_logger.h | 62 + src/util/msg_output.c | 174 + src/util/msg_output.h | 51 + src/util/msg_rate_delay.c | 138 + src/util/msg_syslog.c | 266 + src/util/msg_syslog.h | 41 + src/util/msg_vstream.c | 87 + src/util/msg_vstream.h | 34 + src/util/mvect.c | 117 + src/util/mvect.h | 42 + src/util/myaddrinfo.c | 905 +++ src/util/myaddrinfo.h | 229 + src/util/myaddrinfo.ref | 8 + src/util/myaddrinfo.ref2 | 5 + src/util/myaddrinfo4.ref | 6 + src/util/myaddrinfo4.ref2 | 5 + src/util/myflock.c | 152 + src/util/myflock.h | 52 + src/util/mymalloc.c | 269 + src/util/mymalloc.h | 40 + src/util/myrand.c | 63 + src/util/myrand.h | 35 + src/util/mystrtok.c | 258 + src/util/mystrtok.ref | 30 + src/util/name_code.c | 91 + src/util/name_code.h | 39 + src/util/name_mask.c | 511 ++ src/util/name_mask.h | 86 + src/util/name_mask.in | 9 + src/util/name_mask.ref0 | 9 + src/util/name_mask.ref1 | 12 + src/util/name_mask.ref2 | 12 + src/util/name_mask.ref3 | 14 + src/util/name_mask.ref4 | 14 + src/util/name_mask.ref5 | 14 + src/util/name_mask.ref6 | 14 + src/util/name_mask.ref7 | 12 + src/util/name_mask.ref8 | 12 + src/util/name_mask.ref9 | 12 + src/util/nbbio.c | 382 ++ src/util/nbbio.h | 85 + src/util/netstring.c | 498 ++ src/util/netstring.h | 55 + src/util/neuter.c | 56 + src/util/non_blocking.c | 66 + src/util/nvtable.c | 122 + src/util/nvtable.h | 44 + src/util/open_as.c | 70 + src/util/open_as.h | 30 + src/util/open_limit.c | 100 + src/util/open_lock.c | 76 + src/util/open_lock.h | 41 + src/util/pass_accept.c | 106 + src/util/pass_trigger.c | 151 + src/util/peekfd.c | 89 + src/util/poll_fd.c | 269 + src/util/posix_signals.c | 126 + src/util/posix_signals.h | 59 + src/util/printable.c | 100 + src/util/rand_sleep.c | 89 + src/util/readlline.c | 138 + src/util/readlline.h | 38 + src/util/recv_pass_attr.c | 98 + src/util/ring.c | 121 + src/util/ring.h | 59 + src/util/safe.h | 30 + src/util/safe_getenv.c | 41 + src/util/safe_open.c | 283 + src/util/safe_open.h | 42 + src/util/sane_accept.c | 125 + src/util/sane_accept.h | 29 + src/util/sane_basename.c | 181 + src/util/sane_basename.in | 18 + src/util/sane_basename.ref | 18 + src/util/sane_connect.c | 65 + src/util/sane_connect.h | 29 + src/util/sane_fsops.h | 35 + src/util/sane_link.c | 72 + src/util/sane_rename.c | 69 + src/util/sane_socketpair.c | 71 + src/util/sane_socketpair.h | 34 + src/util/sane_strtol.c | 59 + src/util/sane_strtol.h | 26 + src/util/sane_time.c | 127 + src/util/sane_time.h | 34 + src/util/scan_dir.c | 216 + src/util/scan_dir.h | 37 + src/util/select_bug.c | 95 + src/util/set_eugid.c | 70 + src/util/set_eugid.h | 43 + src/util/set_ugid.c | 61 + src/util/set_ugid.h | 29 + src/util/sigdelay.c | 118 + src/util/sigdelay.h | 31 + src/util/skipblanks.c | 43 + src/util/slmdb.c | 938 +++ src/util/slmdb.h | 117 + src/util/sock_addr.c | 173 + src/util/sock_addr.h | 105 + src/util/spawn_command.c | 313 + src/util/spawn_command.h | 66 + src/util/split_at.c | 71 + src/util/split_at.h | 35 + src/util/split_nameval.c | 97 + src/util/split_qnameval.c | 168 + src/util/stat_as.c | 73 + src/util/stat_as.h | 35 + src/util/strcasecmp.c | 66 + src/util/strcasecmp_utf8.c | 216 + src/util/strcasecmp_utf8_test.in | 10 + src/util/strcasecmp_utf8_test.ref | 20 + src/util/stream_connect.c | 109 + src/util/stream_listen.c | 102 + src/util/stream_recv_fd.c | 120 + src/util/stream_send_fd.c | 115 + src/util/stream_test.c | 111 + src/util/stream_trigger.c | 130 + src/util/stringops.h | 107 + src/util/surrogate.ref | 55 + src/util/sys_compat.c | 389 ++ src/util/sys_defs.h | 1797 ++++++ src/util/testdb | 2 + src/util/timecmp.c | 93 + src/util/timecmp.h | 37 + src/util/timed_connect.c | 111 + src/util/timed_connect.h | 31 + src/util/timed_read.c | 86 + src/util/timed_wait.c | 122 + src/util/timed_wait.h | 35 + src/util/timed_write.c | 92 + src/util/translit.c | 86 + src/util/trigger.h | 34 + src/util/trimblanks.c | 50 + src/util/unescape.c | 208 + src/util/unescape.in | 4 + src/util/unescape.ref | 11 + src/util/unix_connect.c | 110 + src/util/unix_dgram_connect.c | 91 + src/util/unix_dgram_listen.c | 93 + src/util/unix_listen.c | 113 + src/util/unix_pass_fd_fix.c | 67 + src/util/unix_recv_fd.c | 178 + src/util/unix_send_fd.c | 193 + src/util/unix_trigger.c | 131 + src/util/unsafe.c | 77 + src/util/uppercase.c | 43 + src/util/username.c | 47 + src/util/username.h | 29 + src/util/valid_hostname.c | 412 ++ src/util/valid_hostname.h | 41 + src/util/valid_hostname.in | 55 + src/util/valid_hostname.ref | 143 + src/util/valid_utf8_hostname.c | 87 + src/util/valid_utf8_hostname.h | 35 + src/util/valid_utf8_string.c | 139 + src/util/vbuf.c | 238 + src/util/vbuf.h | 110 + src/util/vbuf_print.c | 385 ++ src/util/vbuf_print.h | 40 + src/util/vbuf_print_test.in | 33 + src/util/vbuf_print_test.ref | 27 + src/util/vstream.c | 2046 ++++++ src/util/vstream.h | 293 + src/util/vstream_popen.c | 363 ++ src/util/vstream_test.in | 3 + src/util/vstream_test.ref | 41 + src/util/vstream_tweak.c | 168 + src/util/vstring.c | 719 +++ src/util/vstring.h | 125 + src/util/vstring_test.ref | 6 + src/util/vstring_vstream.c | 267 + src/util/vstring_vstream.h | 82 + src/util/warn_stat.c | 101 + src/util/warn_stat.h | 38 + src/util/watchdog.c | 314 + src/util/watchdog.h | 36 + src/util/write_buf.c | 89 + src/verify/.indent.pro | 1 + src/verify/Makefile.in | 98 + src/verify/verify.c | 776 +++ src/virtual/.indent.pro | 1 + src/virtual/.printfck | 25 + src/virtual/Makefile.in | 234 + src/virtual/deliver_attr.c | 90 + src/virtual/mailbox.c | 283 + src/virtual/maildir.c | 254 + src/virtual/recipient.c | 94 + src/virtual/unknown.c | 65 + src/virtual/virtual.c | 573 ++ src/virtual/virtual.h | 150 + src/xsasl/.indent.pro | 1 + src/xsasl/Makefile.in | 165 + src/xsasl/README | 109 + src/xsasl/xsasl.h | 146 + src/xsasl/xsasl_client.c | 240 + src/xsasl/xsasl_cyrus.h | 47 + src/xsasl/xsasl_cyrus_client.c | 585 ++ src/xsasl/xsasl_cyrus_common.h | 39 + src/xsasl/xsasl_cyrus_log.c | 104 + src/xsasl/xsasl_cyrus_security.c | 87 + src/xsasl/xsasl_cyrus_server.c | 640 ++ src/xsasl/xsasl_dovecot.h | 41 + src/xsasl/xsasl_dovecot_server.c | 747 +++ src/xsasl/xsasl_server.c | 270 + 1727 files changed, 293499 insertions(+) create mode 120000 src/anvil/.indent.pro create mode 100644 src/anvil/.printfck create mode 100644 src/anvil/Makefile.in create mode 100644 src/anvil/anvil.c create mode 120000 src/bounce/.indent.pro create mode 100644 src/bounce/.printfck create mode 100644 src/bounce/2template_test.in create mode 100644 src/bounce/Makefile.in create mode 100755 src/bounce/annotate.sh create mode 100644 src/bounce/bounce.c create mode 100644 src/bounce/bounce_append_service.c create mode 100644 src/bounce/bounce_cleanup.c create mode 100644 src/bounce/bounce_notify_service.c create mode 100644 src/bounce/bounce_notify_util.c create mode 100644 src/bounce/bounce_notify_util_tester.c create mode 100644 src/bounce/bounce_notify_verp.c create mode 100644 src/bounce/bounce_one_service.c create mode 100644 src/bounce/bounce_service.h create mode 100644 src/bounce/bounce_template.c create mode 100644 src/bounce/bounce_template.h create mode 100644 src/bounce/bounce_templates.c create mode 100644 src/bounce/bounce_trace_service.c create mode 100644 src/bounce/bounce_warn_service.c create mode 100644 src/bounce/logfile-no-msgid-no-eoh-event create mode 100644 src/bounce/logfile-no-msgid-with-eoh-event create mode 100644 src/bounce/logfile-with-msgid-no-eoh-event create mode 100644 src/bounce/logfile-with-msgid-with-eoh-event create mode 100644 src/bounce/logfile-with-msgid-with-filter create mode 100644 src/bounce/logfile-with-msgid-with-long-line create mode 100755 src/bounce/msgfile-no-msgid-no-eoh-event create mode 100755 src/bounce/msgfile-no-msgid-with-eoh-event create mode 100755 src/bounce/msgfile-with-msgid-no-eoh-event create mode 100755 src/bounce/msgfile-with-msgid-with-eoh-event create mode 100755 src/bounce/msgfile-with-msgid-with-filter create mode 100755 src/bounce/msgfile-with-msgid-with-long-line create mode 100644 src/bounce/no-msgid-no-eoh-event-no-thread.ref create mode 100644 src/bounce/no-msgid-no-eoh-event-with-thread.ref create mode 100644 src/bounce/no-msgid-with-eoh-event-no-thread.ref create mode 100644 src/bounce/no-msgid-with-eoh-event-with-thread.ref create mode 100644 src/bounce/obs_template_test.ref create mode 100644 src/bounce/template_test.ref create mode 100644 src/bounce/with-msgid-no-eoh-event-no-thread.ref create mode 100644 src/bounce/with-msgid-no-eoh-event-with-thread.ref create mode 100644 src/bounce/with-msgid-with-eoh-event-no-thread.ref create mode 100644 src/bounce/with-msgid-with-eoh-event-with-thread.ref create mode 100644 src/bounce/with-msgid-with-filter-no-thread.ref create mode 100644 src/bounce/with-msgid-with-filter-with-thread.ref create mode 100644 src/bounce/with-msgid-with-long-line-no-thread.ref create mode 100644 src/bounce/with-msgid-with-long-line-with-thread.ref create mode 120000 src/cleanup/.indent.pro create mode 100644 src/cleanup/.printfck create mode 100644 src/cleanup/Makefile.in create mode 100644 src/cleanup/bug1.file create mode 100755 src/cleanup/bug1.file.ref create mode 100644 src/cleanup/bug1.in create mode 100644 src/cleanup/bug1.ref create mode 100644 src/cleanup/bug1.text.ref create mode 100644 src/cleanup/bug2.file create mode 100644 src/cleanup/bug2.in create mode 100644 src/cleanup/bug2.ref create mode 100644 src/cleanup/bug2.text.ref create mode 100644 src/cleanup/bug3.file create mode 100644 src/cleanup/bug3.in create mode 100644 src/cleanup/bug3.ref create mode 100644 src/cleanup/bug3.text.ref create mode 100644 src/cleanup/cleanup.c create mode 100644 src/cleanup/cleanup.h create mode 100644 src/cleanup/cleanup_addr.c create mode 100644 src/cleanup/cleanup_api.c create mode 100644 src/cleanup/cleanup_body_edit.c create mode 100644 src/cleanup/cleanup_bounce.c create mode 100644 src/cleanup/cleanup_envelope.c create mode 100644 src/cleanup/cleanup_extracted.c create mode 100644 src/cleanup/cleanup_final.c create mode 100644 src/cleanup/cleanup_init.c create mode 100644 src/cleanup/cleanup_map11.c create mode 100644 src/cleanup/cleanup_map1n.c create mode 100644 src/cleanup/cleanup_masq.ref create mode 100644 src/cleanup/cleanup_masquerade.c create mode 100644 src/cleanup/cleanup_message.c create mode 100644 src/cleanup/cleanup_milter.c create mode 100644 src/cleanup/cleanup_milter.in1 create mode 100644 src/cleanup/cleanup_milter.in10a create mode 100644 src/cleanup/cleanup_milter.in10b create mode 100644 src/cleanup/cleanup_milter.in10c create mode 100644 src/cleanup/cleanup_milter.in10d create mode 100644 src/cleanup/cleanup_milter.in10e create mode 100644 src/cleanup/cleanup_milter.in11 create mode 100644 src/cleanup/cleanup_milter.in12 create mode 100644 src/cleanup/cleanup_milter.in13a create mode 100644 src/cleanup/cleanup_milter.in13b create mode 100644 src/cleanup/cleanup_milter.in13c create mode 100644 src/cleanup/cleanup_milter.in13d create mode 100644 src/cleanup/cleanup_milter.in13e create mode 100644 src/cleanup/cleanup_milter.in13f create mode 100644 src/cleanup/cleanup_milter.in13g create mode 100644 src/cleanup/cleanup_milter.in13h create mode 100644 src/cleanup/cleanup_milter.in13i create mode 100644 src/cleanup/cleanup_milter.in14a create mode 100644 src/cleanup/cleanup_milter.in14b create mode 100644 src/cleanup/cleanup_milter.in14c create mode 100644 src/cleanup/cleanup_milter.in14d create mode 100644 src/cleanup/cleanup_milter.in14e create mode 100644 src/cleanup/cleanup_milter.in14f create mode 100644 src/cleanup/cleanup_milter.in14g create mode 100644 src/cleanup/cleanup_milter.in15a create mode 100644 src/cleanup/cleanup_milter.in15b create mode 100644 src/cleanup/cleanup_milter.in15c create mode 100644 src/cleanup/cleanup_milter.in15d create mode 100644 src/cleanup/cleanup_milter.in15e create mode 100644 src/cleanup/cleanup_milter.in15f create mode 100644 src/cleanup/cleanup_milter.in15g create mode 100644 src/cleanup/cleanup_milter.in15h create mode 100644 src/cleanup/cleanup_milter.in15i create mode 100644 src/cleanup/cleanup_milter.in16a create mode 100644 src/cleanup/cleanup_milter.in16b create mode 100644 src/cleanup/cleanup_milter.in17a create mode 100644 src/cleanup/cleanup_milter.in17b create mode 100644 src/cleanup/cleanup_milter.in17c create mode 100644 src/cleanup/cleanup_milter.in17d create mode 100644 src/cleanup/cleanup_milter.in17e create mode 100644 src/cleanup/cleanup_milter.in17f create mode 100644 src/cleanup/cleanup_milter.in17g create mode 100644 src/cleanup/cleanup_milter.in2 create mode 100644 src/cleanup/cleanup_milter.in3 create mode 100644 src/cleanup/cleanup_milter.in4a create mode 100644 src/cleanup/cleanup_milter.in4b create mode 100644 src/cleanup/cleanup_milter.in4c create mode 100644 src/cleanup/cleanup_milter.in5 create mode 100644 src/cleanup/cleanup_milter.in6a create mode 100644 src/cleanup/cleanup_milter.in6b create mode 100644 src/cleanup/cleanup_milter.in6c create mode 100644 src/cleanup/cleanup_milter.in7 create mode 100644 src/cleanup/cleanup_milter.in8 create mode 100644 src/cleanup/cleanup_milter.in9 create mode 100644 src/cleanup/cleanup_milter.ref1 create mode 100644 src/cleanup/cleanup_milter.ref10a create mode 100644 src/cleanup/cleanup_milter.ref10b create mode 100644 src/cleanup/cleanup_milter.ref10c create mode 100644 src/cleanup/cleanup_milter.ref10d create mode 100644 src/cleanup/cleanup_milter.ref10e create mode 100644 src/cleanup/cleanup_milter.ref11 create mode 100644 src/cleanup/cleanup_milter.ref12 create mode 100644 src/cleanup/cleanup_milter.ref13a create mode 100644 src/cleanup/cleanup_milter.ref13b create mode 100644 src/cleanup/cleanup_milter.ref13c create mode 100644 src/cleanup/cleanup_milter.ref13d create mode 100644 src/cleanup/cleanup_milter.ref13e create mode 100644 src/cleanup/cleanup_milter.ref13f create mode 100644 src/cleanup/cleanup_milter.ref13g create mode 100644 src/cleanup/cleanup_milter.ref13h create mode 100644 src/cleanup/cleanup_milter.ref13i create mode 100644 src/cleanup/cleanup_milter.ref14a1 create mode 100644 src/cleanup/cleanup_milter.ref14a2 create mode 100644 src/cleanup/cleanup_milter.ref14b1 create mode 100644 src/cleanup/cleanup_milter.ref14b2 create mode 100644 src/cleanup/cleanup_milter.ref14c1 create mode 100644 src/cleanup/cleanup_milter.ref14c2 create mode 100644 src/cleanup/cleanup_milter.ref14d1 create mode 100644 src/cleanup/cleanup_milter.ref14d2 create mode 100644 src/cleanup/cleanup_milter.ref14e1 create mode 100644 src/cleanup/cleanup_milter.ref14e2 create mode 100644 src/cleanup/cleanup_milter.ref14f1 create mode 100644 src/cleanup/cleanup_milter.ref14f2 create mode 100644 src/cleanup/cleanup_milter.ref14g1 create mode 100644 src/cleanup/cleanup_milter.ref14g2 create mode 100644 src/cleanup/cleanup_milter.ref15a1 create mode 100644 src/cleanup/cleanup_milter.ref15a2 create mode 100644 src/cleanup/cleanup_milter.ref15b1 create mode 100644 src/cleanup/cleanup_milter.ref15b2 create mode 100644 src/cleanup/cleanup_milter.ref15c1 create mode 100644 src/cleanup/cleanup_milter.ref15c2 create mode 100644 src/cleanup/cleanup_milter.ref15d1 create mode 100644 src/cleanup/cleanup_milter.ref15d2 create mode 100644 src/cleanup/cleanup_milter.ref15e1 create mode 100644 src/cleanup/cleanup_milter.ref15e2 create mode 100644 src/cleanup/cleanup_milter.ref15f1 create mode 100644 src/cleanup/cleanup_milter.ref15f2 create mode 100644 src/cleanup/cleanup_milter.ref15g1 create mode 100644 src/cleanup/cleanup_milter.ref15g2 create mode 100644 src/cleanup/cleanup_milter.ref15h1 create mode 100644 src/cleanup/cleanup_milter.ref15h2 create mode 100644 src/cleanup/cleanup_milter.ref15i1 create mode 100644 src/cleanup/cleanup_milter.ref15i2 create mode 100644 src/cleanup/cleanup_milter.ref16a1 create mode 100644 src/cleanup/cleanup_milter.ref16a2 create mode 100644 src/cleanup/cleanup_milter.ref16b1 create mode 100644 src/cleanup/cleanup_milter.ref16b2 create mode 100644 src/cleanup/cleanup_milter.ref17a1 create mode 100644 src/cleanup/cleanup_milter.ref17a2 create mode 100644 src/cleanup/cleanup_milter.ref17b1 create mode 100644 src/cleanup/cleanup_milter.ref17b2 create mode 100644 src/cleanup/cleanup_milter.ref17c1 create mode 100644 src/cleanup/cleanup_milter.ref17c2 create mode 100644 src/cleanup/cleanup_milter.ref17d1 create mode 100644 src/cleanup/cleanup_milter.ref17d2 create mode 100644 src/cleanup/cleanup_milter.ref17e1 create mode 100644 src/cleanup/cleanup_milter.ref17e2 create mode 100644 src/cleanup/cleanup_milter.ref17f1 create mode 100644 src/cleanup/cleanup_milter.ref17f2 create mode 100644 src/cleanup/cleanup_milter.ref17g1 create mode 100644 src/cleanup/cleanup_milter.ref17g2 create mode 100644 src/cleanup/cleanup_milter.ref2 create mode 100644 src/cleanup/cleanup_milter.ref3 create mode 100644 src/cleanup/cleanup_milter.ref4 create mode 100644 src/cleanup/cleanup_milter.ref5 create mode 100644 src/cleanup/cleanup_milter.ref6a create mode 100644 src/cleanup/cleanup_milter.ref6b create mode 100644 src/cleanup/cleanup_milter.ref6c create mode 100644 src/cleanup/cleanup_milter.ref7 create mode 100644 src/cleanup/cleanup_milter.ref8 create mode 100644 src/cleanup/cleanup_milter.ref9 create mode 100644 src/cleanup/cleanup_milter.reg14a create mode 100644 src/cleanup/cleanup_milter.reg14b create mode 100644 src/cleanup/cleanup_milter.reg14c create mode 100644 src/cleanup/cleanup_milter.reg14d create mode 100644 src/cleanup/cleanup_milter.reg14e create mode 100644 src/cleanup/cleanup_milter.reg14f create mode 100644 src/cleanup/cleanup_milter.reg14g create mode 100644 src/cleanup/cleanup_milter.reg15a create mode 100644 src/cleanup/cleanup_milter.reg15b create mode 100644 src/cleanup/cleanup_milter.reg15c create mode 100644 src/cleanup/cleanup_milter.reg15d create mode 100644 src/cleanup/cleanup_milter.reg15e create mode 100644 src/cleanup/cleanup_milter.reg15f create mode 100644 src/cleanup/cleanup_milter.reg15g create mode 100644 src/cleanup/cleanup_milter.reg15h create mode 100644 src/cleanup/cleanup_milter.reg15i create mode 100644 src/cleanup/cleanup_milter.reg16a create mode 100644 src/cleanup/cleanup_out.c create mode 100644 src/cleanup/cleanup_out_recipient.c create mode 100644 src/cleanup/cleanup_region.c create mode 100644 src/cleanup/cleanup_rewrite.c create mode 100644 src/cleanup/cleanup_state.c create mode 100644 src/cleanup/loremipsum create mode 100644 src/cleanup/loremipsum2 create mode 100644 src/cleanup/test-queue-file create mode 100644 src/cleanup/test-queue-file10 create mode 100644 src/cleanup/test-queue-file11 create mode 100644 src/cleanup/test-queue-file12 create mode 100644 src/cleanup/test-queue-file13a create mode 100644 src/cleanup/test-queue-file13b create mode 100644 src/cleanup/test-queue-file13c create mode 100644 src/cleanup/test-queue-file13d create mode 100644 src/cleanup/test-queue-file13e create mode 100644 src/cleanup/test-queue-file13f create mode 100644 src/cleanup/test-queue-file13g create mode 100644 src/cleanup/test-queue-file13h create mode 100644 src/cleanup/test-queue-file13i create mode 100644 src/cleanup/test-queue-file14 create mode 100644 src/cleanup/test-queue-file15 create mode 100644 src/cleanup/test-queue-file16 create mode 100644 src/cleanup/test-queue-file17 create mode 100644 src/cleanup/test-queue-file2 create mode 100644 src/cleanup/test-queue-file3 create mode 100644 src/cleanup/test-queue-file4 create mode 100644 src/cleanup/test-queue-file5 create mode 100644 src/cleanup/test-queue-file6 create mode 100644 src/cleanup/test-queue-file7 create mode 100644 src/cleanup/test-queue-file8 create mode 100644 src/cleanup/test-queue-file9 create mode 120000 src/discard/.indent.pro create mode 100644 src/discard/.printfck create mode 100644 src/discard/Makefile.in create mode 100644 src/discard/discard.c create mode 120000 src/dns/.indent.pro create mode 100644 src/dns/.printfck create mode 100644 src/dns/Makefile.in create mode 100644 src/dns/dns.h create mode 100644 src/dns/dns_lookup.c create mode 100644 src/dns/dns_rr.c create mode 100644 src/dns/dns_rr_eq_sa.c create mode 100644 src/dns/dns_rr_eq_sa.in create mode 100644 src/dns/dns_rr_eq_sa.ref create mode 100644 src/dns/dns_rr_filter.c create mode 100644 src/dns/dns_rr_to_pa.c create mode 100644 src/dns/dns_rr_to_pa.in create mode 100644 src/dns/dns_rr_to_pa.ref create mode 100644 src/dns/dns_rr_to_sa.c create mode 100644 src/dns/dns_rr_to_sa.in create mode 100644 src/dns/dns_rr_to_sa.ref create mode 100644 src/dns/dns_sa_to_rr.c create mode 100644 src/dns/dns_sa_to_rr.in create mode 100644 src/dns/dns_sa_to_rr.ref create mode 100644 src/dns/dns_sec.c create mode 100644 src/dns/dns_str_resflags.c create mode 100644 src/dns/dns_strerror.c create mode 100644 src/dns/dns_strrecord.c create mode 100644 src/dns/dns_strtype.c create mode 100644 src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref create mode 100644 src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref create mode 100644 src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref create mode 100644 src/dns/error.ref create mode 100644 src/dns/error.reg create mode 100644 src/dns/mxonly_test.ref create mode 100644 src/dns/no-a.ref create mode 100644 src/dns/no-a.reg create mode 100644 src/dns/no-aaaa.ref create mode 100644 src/dns/no-aaaa.reg create mode 100644 src/dns/no-mx.ref create mode 100644 src/dns/no-mx.reg create mode 100644 src/dns/no-txt.reg create mode 100644 src/dns/nullmx_test.ref create mode 100644 src/dns/nxdomain_test.ref create mode 100644 src/dns/test_dns_lookup.c create mode 120000 src/dnsblog/.indent.pro create mode 100644 src/dnsblog/Makefile.in create mode 100644 src/dnsblog/dnsblog.c create mode 120000 src/error/.indent.pro create mode 100644 src/error/.printfck create mode 100644 src/error/Makefile.in create mode 100644 src/error/error.c create mode 120000 src/flush/.indent.pro create mode 100644 src/flush/.printfck create mode 100644 src/flush/Makefile.in create mode 100644 src/flush/flush.c create mode 120000 src/fsstone/.indent.pro create mode 100644 src/fsstone/.printfck create mode 100644 src/fsstone/Makefile.in create mode 100644 src/fsstone/fsstone.c 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 create mode 120000 src/local/.indent.pro create mode 100644 src/local/.printfck create mode 100644 src/local/Makefile.in create mode 100644 src/local/Musings create mode 100644 src/local/alias.c create mode 100644 src/local/biff_notify.c create mode 100644 src/local/biff_notify.h create mode 100644 src/local/bounce_workaround.c create mode 100644 src/local/command.c create mode 100644 src/local/deliver_attr.c create mode 100644 src/local/dotforward.c create mode 100644 src/local/file.c create mode 100644 src/local/forward.c create mode 100644 src/local/include.c create mode 100644 src/local/indirect.c create mode 100644 src/local/local.c create mode 100644 src/local/local.h create mode 100644 src/local/local_expand.c create mode 100644 src/local/mailbox.c create mode 100644 src/local/maildir.c create mode 100644 src/local/recipient.c create mode 100644 src/local/resolve.c create mode 100644 src/local/token.c create mode 100644 src/local/unknown.c create mode 120000 src/master/.indent.pro create mode 100644 src/master/.printfck create mode 100644 src/master/Makefile.in create mode 100644 src/master/dgram_server.c create mode 100644 src/master/event_server.c create mode 100644 src/master/mail_flow.c create mode 100644 src/master/mail_flow.h create mode 100644 src/master/mail_server.h create mode 100644 src/master/master.c create mode 100644 src/master/master.h create mode 100644 src/master/master_avail.c create mode 100644 src/master/master_conf.c create mode 100644 src/master/master_ent.c create mode 100644 src/master/master_flow.c create mode 100644 src/master/master_listen.c create mode 100644 src/master/master_monitor.c create mode 100644 src/master/master_proto.c create mode 100644 src/master/master_proto.h create mode 100644 src/master/master_service.c create mode 100644 src/master/master_sig.c create mode 100644 src/master/master_spawn.c create mode 100644 src/master/master_status.c create mode 100644 src/master/master_vars.c create mode 100644 src/master/master_wakeup.c create mode 100644 src/master/master_watch.c create mode 100644 src/master/multi_server.c create mode 100644 src/master/single_server.c create mode 100644 src/master/trigger_server.c create mode 120000 src/milter/.indent.pro create mode 100644 src/milter/Makefile.in create mode 100644 src/milter/milter.c create mode 100644 src/milter/milter.h create mode 100644 src/milter/milter8.c create mode 100644 src/milter/milter_macros.c create mode 100644 src/milter/test-list create mode 100644 src/milter/test-milter.c create mode 120000 src/oqmgr/.indent.pro create mode 100644 src/oqmgr/.printfck create mode 100644 src/oqmgr/Makefile.in create mode 100644 src/oqmgr/qmgr.c create mode 100644 src/oqmgr/qmgr.h create mode 100644 src/oqmgr/qmgr_active.c create mode 100644 src/oqmgr/qmgr_bounce.c create mode 100644 src/oqmgr/qmgr_defer.c create mode 100644 src/oqmgr/qmgr_deliver.c create mode 100644 src/oqmgr/qmgr_enable.c create mode 100644 src/oqmgr/qmgr_entry.c create mode 100644 src/oqmgr/qmgr_error.c create mode 100644 src/oqmgr/qmgr_feedback.c create mode 100644 src/oqmgr/qmgr_message.c create mode 100644 src/oqmgr/qmgr_move.c create mode 100644 src/oqmgr/qmgr_queue.c create mode 100644 src/oqmgr/qmgr_scan.c create mode 100644 src/oqmgr/qmgr_transport.c create mode 120000 src/pickup/.indent.pro create mode 100644 src/pickup/.printfck create mode 100644 src/pickup/Makefile.in create mode 100644 src/pickup/pickup.c create mode 120000 src/pipe/.indent.pro create mode 100644 src/pipe/.printfck create mode 100644 src/pipe/Makefile.in create mode 100644 src/pipe/pipe.c create mode 120000 src/postalias/.indent.pro create mode 100644 src/postalias/.printfck create mode 100644 src/postalias/Makefile.in create mode 100644 src/postalias/aliases create mode 100644 src/postalias/fail_test.in create mode 100644 src/postalias/fail_test.ref create mode 100644 src/postalias/map-abc1.ref create mode 100644 src/postalias/map-abc2.ref create mode 100644 src/postalias/map-ghi1.ref create mode 100644 src/postalias/map-ghi2.ref create mode 100644 src/postalias/map-uABC1.ref create mode 100644 src/postalias/map-uABC2.ref create mode 100644 src/postalias/map.in create mode 100644 src/postalias/postalias.c create mode 120000 src/postcat/.indent.pro create mode 100644 src/postcat/.printfck create mode 100644 src/postcat/Makefile.in create mode 100644 src/postcat/b_test.ref create mode 100644 src/postcat/bh_test.ref create mode 100644 src/postcat/default_test.ref create mode 100644 src/postcat/e_test.ref create mode 100644 src/postcat/eb_test.ref create mode 100644 src/postcat/eh_test.ref create mode 100644 src/postcat/h_test.ref create mode 100644 src/postcat/postcat.c create mode 100644 src/postcat/test-queue-file create mode 120000 src/postconf/.indent.pro create mode 100644 src/postconf/.printfck create mode 100644 src/postconf/Makefile.in create mode 100644 src/postconf/extract.awk create mode 100644 src/postconf/extract_cfg.sh create mode 100644 src/postconf/install_table.h create mode 100644 src/postconf/install_vars.h create mode 100644 src/postconf/postconf.c create mode 100644 src/postconf/postconf.h create mode 100644 src/postconf/postconf_builtin.c create mode 100644 src/postconf/postconf_dbms.c create mode 100644 src/postconf/postconf_edit.c create mode 100644 src/postconf/postconf_lookup.c create mode 100644 src/postconf/postconf_main.c create mode 100644 src/postconf/postconf_master.c create mode 100644 src/postconf/postconf_match.c create mode 100644 src/postconf/postconf_misc.c create mode 100644 src/postconf/postconf_node.c create mode 100644 src/postconf/postconf_other.c create mode 100644 src/postconf/postconf_print.c create mode 100644 src/postconf/postconf_service.c create mode 100644 src/postconf/postconf_unused.c create mode 100644 src/postconf/postconf_user.c create mode 100644 src/postconf/test1.ref create mode 100644 src/postconf/test10.ref create mode 100644 src/postconf/test11.ref create mode 100644 src/postconf/test12.ref create mode 100644 src/postconf/test13.ref create mode 100644 src/postconf/test14.ref create mode 100644 src/postconf/test15.ref create mode 100644 src/postconf/test16.ref create mode 100644 src/postconf/test17.ref create mode 100644 src/postconf/test18.ref create mode 100644 src/postconf/test19.ref create mode 100644 src/postconf/test2.ref create mode 100644 src/postconf/test20.ref create mode 100644 src/postconf/test21.ref create mode 100644 src/postconf/test22.ref create mode 100644 src/postconf/test23.ref create mode 100644 src/postconf/test24.ref create mode 100644 src/postconf/test25.ref create mode 100644 src/postconf/test26.ref create mode 100644 src/postconf/test27.ref create mode 100644 src/postconf/test28.ref create mode 100644 src/postconf/test29.ref create mode 100644 src/postconf/test3.ref create mode 100644 src/postconf/test30.ref create mode 100644 src/postconf/test31.ref create mode 100644 src/postconf/test32.ref create mode 100644 src/postconf/test33.ref create mode 100644 src/postconf/test34.ref create mode 100644 src/postconf/test35.ref create mode 100644 src/postconf/test36.ref create mode 100644 src/postconf/test37.ref create mode 100644 src/postconf/test38.ref create mode 100644 src/postconf/test39.ref create mode 100644 src/postconf/test4.ref create mode 100644 src/postconf/test40.ref create mode 100644 src/postconf/test41.ref create mode 100644 src/postconf/test42.ref create mode 100644 src/postconf/test43.ref create mode 100644 src/postconf/test44.ref create mode 100644 src/postconf/test45.ref create mode 100644 src/postconf/test46.ref create mode 100644 src/postconf/test47.ref create mode 100644 src/postconf/test48.ref create mode 100644 src/postconf/test49.ref create mode 100644 src/postconf/test4b.ref create mode 100644 src/postconf/test5.ref create mode 100644 src/postconf/test50.ref create mode 100644 src/postconf/test51.ref create mode 100644 src/postconf/test52.ref create mode 100644 src/postconf/test53.ref create mode 100644 src/postconf/test54.ref create mode 100644 src/postconf/test55.ref create mode 100644 src/postconf/test56.ref create mode 100644 src/postconf/test57.ref create mode 100644 src/postconf/test58.ref create mode 100644 src/postconf/test59.ref create mode 100644 src/postconf/test6.ref create mode 100644 src/postconf/test60.ref create mode 100644 src/postconf/test61.ref create mode 100644 src/postconf/test62.ref create mode 100644 src/postconf/test63.ref create mode 100644 src/postconf/test64.ref create mode 100644 src/postconf/test65.ref create mode 100644 src/postconf/test66.ref create mode 100644 src/postconf/test67.ref create mode 100644 src/postconf/test68.ref create mode 100644 src/postconf/test69.ref create mode 100644 src/postconf/test7.ref create mode 100644 src/postconf/test70.ref create mode 100644 src/postconf/test8.ref create mode 100644 src/postconf/test9.ref create mode 120000 src/postdrop/.indent.pro create mode 100644 src/postdrop/.printfck create mode 100644 src/postdrop/Makefile.in create mode 100644 src/postdrop/postdrop.c create mode 120000 src/postfix/.indent.pro create mode 100644 src/postfix/.printfck create mode 100644 src/postfix/Makefile.in create mode 100644 src/postfix/postfix.c create mode 120000 src/postkick/.indent.pro create mode 100644 src/postkick/.printfck create mode 100644 src/postkick/Makefile.in create mode 100644 src/postkick/postkick.c create mode 120000 src/postlock/.indent.pro create mode 100644 src/postlock/.printfck create mode 100644 src/postlock/Makefile.in create mode 100644 src/postlock/postlock.c create mode 120000 src/postlog/.indent.pro create mode 100644 src/postlog/.printfck create mode 100644 src/postlog/Makefile.in create mode 100644 src/postlog/postlog.c create mode 100644 src/postlogd/Makefile.in create mode 100644 src/postlogd/postlogd.c create mode 120000 src/postmap/.indent.pro create mode 100644 src/postmap/.printfck create mode 100644 src/postmap/Makefile.in create mode 100644 src/postmap/aliases create mode 100644 src/postmap/fail_test.in create mode 100644 src/postmap/fail_test.ref create mode 100644 src/postmap/file_test.in create mode 100644 src/postmap/file_test.ref create mode 100644 src/postmap/lmdb_abb create mode 100644 src/postmap/lmdb_abb.ref create mode 100644 src/postmap/map-abc1.ref create mode 100644 src/postmap/map-abc2.ref create mode 100644 src/postmap/map-ghi1.ref create mode 100644 src/postmap/map-ghi2.ref create mode 100644 src/postmap/map-uABC1.ref create mode 100644 src/postmap/map-uABC2.ref create mode 100644 src/postmap/map.in create mode 100644 src/postmap/postmap.c create mode 100644 src/postmap/quote_test.in create mode 100644 src/postmap/quote_test.ref create mode 120000 src/postmulti/.indent.pro create mode 100644 src/postmulti/Makefile.in create mode 100644 src/postmulti/postmulti.c create mode 120000 src/postqueue/.indent.pro create mode 100644 src/postqueue/Makefile.in create mode 100644 src/postqueue/postqueue.c create mode 100644 src/postqueue/postqueue.h create mode 100644 src/postqueue/showq_compat.c create mode 100644 src/postqueue/showq_json.c create mode 120000 src/postscreen/.indent.pro create mode 100644 src/postscreen/Makefile.in create mode 100644 src/postscreen/postscreen.c create mode 100644 src/postscreen/postscreen.h create mode 100644 src/postscreen/postscreen_dict.c create mode 100644 src/postscreen/postscreen_dnsbl.c create mode 100644 src/postscreen/postscreen_early.c create mode 100644 src/postscreen/postscreen_endpt.c create mode 100644 src/postscreen/postscreen_expand.c create mode 100644 src/postscreen/postscreen_haproxy.c create mode 100644 src/postscreen/postscreen_haproxy.h create mode 100644 src/postscreen/postscreen_misc.c create mode 100644 src/postscreen/postscreen_send.c create mode 100644 src/postscreen/postscreen_smtpd.c create mode 100644 src/postscreen/postscreen_starttls.c create mode 100644 src/postscreen/postscreen_state.c create mode 100644 src/postscreen/postscreen_tests.c create mode 120000 src/postsuper/.indent.pro create mode 100644 src/postsuper/.printfck create mode 100644 src/postsuper/Makefile.in create mode 100644 src/postsuper/postsuper.c create mode 120000 src/posttls-finger/.indent.pro create mode 100644 src/posttls-finger/Makefile.in create mode 100644 src/posttls-finger/posttls-finger.c create mode 100644 src/posttls-finger/tlsmgrmem.c create mode 100644 src/posttls-finger/tlsmgrmem.h create mode 120000 src/proxymap/.indent.pro create mode 100644 src/proxymap/Makefile.in create mode 100644 src/proxymap/proxymap.c create mode 120000 src/qmgr/.indent.pro create mode 100644 src/qmgr/.printfck create mode 100644 src/qmgr/Makefile.in create mode 100644 src/qmgr/qmgr.c create mode 100644 src/qmgr/qmgr.h create mode 100644 src/qmgr/qmgr_active.c create mode 100644 src/qmgr/qmgr_bounce.c create mode 100644 src/qmgr/qmgr_defer.c create mode 100644 src/qmgr/qmgr_deliver.c create mode 100644 src/qmgr/qmgr_enable.c create mode 100644 src/qmgr/qmgr_entry.c create mode 100644 src/qmgr/qmgr_error.c create mode 100644 src/qmgr/qmgr_feedback.c create mode 100644 src/qmgr/qmgr_job.c create mode 100644 src/qmgr/qmgr_message.c create mode 100644 src/qmgr/qmgr_move.c create mode 100644 src/qmgr/qmgr_peer.c create mode 100644 src/qmgr/qmgr_queue.c create mode 100644 src/qmgr/qmgr_scan.c create mode 100644 src/qmgr/qmgr_transport.c create mode 120000 src/qmqpd/.indent.pro create mode 100644 src/qmqpd/.printfck create mode 100644 src/qmqpd/Makefile.in create mode 100644 src/qmqpd/qmqpd.c create mode 100644 src/qmqpd/qmqpd.h create mode 100644 src/qmqpd/qmqpd_peer.c create mode 100644 src/qmqpd/qmqpd_state.c create mode 120000 src/scache/.indent.pro create mode 100644 src/scache/Makefile.in create mode 100644 src/scache/scache.c create mode 120000 src/sendmail/.indent.pro create mode 100644 src/sendmail/.printfck create mode 100644 src/sendmail/Makefile.in create mode 100644 src/sendmail/sendmail.c create mode 120000 src/showq/.indent.pro create mode 100644 src/showq/.printfck create mode 100644 src/showq/Makefile.in create mode 100644 src/showq/showq.c create mode 120000 src/smtp/.indent.pro create mode 100644 src/smtp/.printfck create mode 100644 src/smtp/Makefile.in create mode 100644 src/smtp/lmtp_params.c create mode 100644 src/smtp/smtp-only create mode 100644 src/smtp/smtp.c create mode 100644 src/smtp/smtp.h create mode 100644 src/smtp/smtp_addr.c create mode 100644 src/smtp/smtp_addr.h create mode 100644 src/smtp/smtp_chat.c create mode 100644 src/smtp/smtp_connect.c create mode 100644 src/smtp/smtp_key.c create mode 100644 src/smtp/smtp_map11.c create mode 100644 src/smtp/smtp_map11.in create mode 100644 src/smtp/smtp_map11.ref create mode 100644 src/smtp/smtp_misc.c create mode 100644 src/smtp/smtp_params.c create mode 100644 src/smtp/smtp_proto.c create mode 100644 src/smtp/smtp_rcpt.c create mode 100644 src/smtp/smtp_reuse.c create mode 100644 src/smtp/smtp_reuse.h create mode 100644 src/smtp/smtp_sasl.h create mode 100644 src/smtp/smtp_sasl_auth_cache.c create mode 100644 src/smtp/smtp_sasl_auth_cache.h create mode 100644 src/smtp/smtp_sasl_glue.c create mode 100644 src/smtp/smtp_sasl_proto.c create mode 100644 src/smtp/smtp_session.c create mode 100644 src/smtp/smtp_state.c create mode 100644 src/smtp/smtp_tls_policy.c create mode 100644 src/smtp/smtp_trouble.c create mode 100644 src/smtp/smtp_unalias.c create mode 100644 src/smtp/tls_policy.in create mode 100644 src/smtp/tls_policy.ref create mode 120000 src/smtpd/.indent.pro create mode 100644 src/smtpd/.printfck create mode 100644 src/smtpd/Makefile.in create mode 100644 src/smtpd/smtpd.c create mode 100644 src/smtpd/smtpd.h create mode 100644 src/smtpd/smtpd_acl.in create mode 100644 src/smtpd/smtpd_acl.ref create mode 100644 src/smtpd/smtpd_addr_valid.in create mode 100644 src/smtpd/smtpd_addr_valid.ref create mode 100644 src/smtpd/smtpd_chat.c create mode 100644 src/smtpd/smtpd_chat.h create mode 100644 src/smtpd/smtpd_check.c create mode 100644 src/smtpd/smtpd_check.h create mode 100644 src/smtpd/smtpd_check.in create mode 100644 src/smtpd/smtpd_check.in2 create mode 100644 src/smtpd/smtpd_check.in3 create mode 100644 src/smtpd/smtpd_check.in4 create mode 100644 src/smtpd/smtpd_check.ref create mode 100644 src/smtpd/smtpd_check.ref2 create mode 100644 src/smtpd/smtpd_check.ref4 create mode 100644 src/smtpd/smtpd_check_access create mode 100644 src/smtpd/smtpd_check_backup.in create mode 100644 src/smtpd/smtpd_check_backup.ref create mode 100644 src/smtpd/smtpd_check_dsn.in create mode 100644 src/smtpd/smtpd_check_dsn.ref create mode 100644 src/smtpd/smtpd_dns_filter.in create mode 100644 src/smtpd/smtpd_dns_filter.ref create mode 100644 src/smtpd/smtpd_dnswl.in create mode 100644 src/smtpd/smtpd_dnswl.ref create mode 100644 src/smtpd/smtpd_dsn_fix.c create mode 100644 src/smtpd/smtpd_dsn_fix.h create mode 100644 src/smtpd/smtpd_error.in create mode 100644 src/smtpd/smtpd_error.ref create mode 100644 src/smtpd/smtpd_exp.in create mode 100644 src/smtpd/smtpd_exp.ref create mode 100644 src/smtpd/smtpd_expand.c create mode 100644 src/smtpd/smtpd_expand.h create mode 100644 src/smtpd/smtpd_haproxy.c create mode 100644 src/smtpd/smtpd_milter.c create mode 100644 src/smtpd/smtpd_milter.h create mode 100644 src/smtpd/smtpd_nullmx.in create mode 100644 src/smtpd/smtpd_nullmx.ref create mode 100644 src/smtpd/smtpd_peer.c create mode 100644 src/smtpd/smtpd_proxy.c create mode 100644 src/smtpd/smtpd_proxy.h create mode 100644 src/smtpd/smtpd_resolve.c create mode 100644 src/smtpd/smtpd_resolve.h create mode 100644 src/smtpd/smtpd_sasl_glue.c create mode 100644 src/smtpd/smtpd_sasl_glue.h create mode 100644 src/smtpd/smtpd_sasl_proto.c create mode 100644 src/smtpd/smtpd_sasl_proto.h create mode 100644 src/smtpd/smtpd_server.in create mode 100644 src/smtpd/smtpd_server.ref create mode 100644 src/smtpd/smtpd_state.c create mode 100644 src/smtpd/smtpd_token.c create mode 100644 src/smtpd/smtpd_token.h create mode 100644 src/smtpd/smtpd_token.in create mode 100644 src/smtpd/smtpd_token.ref create mode 100644 src/smtpd/smtpd_xforward.c create mode 120000 src/smtpstone/.indent.pro create mode 100644 src/smtpstone/.printfck create mode 100644 src/smtpstone/Makefile.in create mode 100644 src/smtpstone/hashed-deferred create mode 100644 src/smtpstone/hashed-incoming create mode 100644 src/smtpstone/mx-deliver create mode 100644 src/smtpstone/mx-explode create mode 100644 src/smtpstone/mx-relay create mode 100644 src/smtpstone/performance create mode 100644 src/smtpstone/qmail-deliver create mode 100644 src/smtpstone/qmail-explode create mode 100644 src/smtpstone/qmail-relay create mode 100644 src/smtpstone/qmqp-sink.c create mode 100644 src/smtpstone/qmqp-source.c create mode 100644 src/smtpstone/smtp-sink.c create mode 100644 src/smtpstone/smtp-source.c create mode 100644 src/smtpstone/throughput create mode 100644 src/smtpstone/vmail-local create mode 100644 src/smtpstone/vmail-relay create mode 120000 src/spawn/.indent.pro create mode 100644 src/spawn/.printfck create mode 100644 src/spawn/Makefile.in create mode 100644 src/spawn/spawn.c create mode 120000 src/tls/.indent.pro create mode 100644 src/tls/Makefile.in create mode 100644 src/tls/bad-back-to-back-keys.pem create mode 100644 src/tls/bad-back-to-back-keys.pem.ref create mode 100644 src/tls/bad-ec-cert-before-key.pem create mode 100644 src/tls/bad-ec-cert-before-key.pem.ref create mode 100644 src/tls/bad-key-cert-mismatch.pem create mode 100644 src/tls/bad-key-cert-mismatch.pem.ref create mode 100644 src/tls/bad-rsa-key-last.pem create mode 100644 src/tls/bad-rsa-key-last.pem.ref create mode 100644 src/tls/ecca-cert.pem create mode 100644 src/tls/ecca-pkey.pem create mode 100644 src/tls/ecee-cert.pem create mode 100644 src/tls/ecee-pkey.pem create mode 100644 src/tls/ecroot-cert.pem create mode 100644 src/tls/ecroot-pkey.pem create mode 100644 src/tls/good-mixed-keyfirst.pem create mode 100644 src/tls/good-mixed-keyfirst.pem.ref create mode 100644 src/tls/good-mixed-keylast.pem create mode 100644 src/tls/good-mixed-keylast.pem.ref create mode 100644 src/tls/good-mixed-keymiddle.pem create mode 100644 src/tls/good-mixed-keymiddle.pem.ref create mode 100644 src/tls/goodchains.pem create mode 100644 src/tls/goodchains.pem.ref create mode 100644 src/tls/mkcert.sh create mode 100644 src/tls/rsaca-cert.pem create mode 100644 src/tls/rsaca-pkey.pem create mode 100644 src/tls/rsaee-cert.pem create mode 100644 src/tls/rsaee-pkey.pem create mode 100644 src/tls/rsaroot-cert.pem create mode 100644 src/tls/rsaroot-pkey.pem create mode 100644 src/tls/tls.h create mode 100644 src/tls/tls_bio_ops.c create mode 100644 src/tls/tls_certkey.c create mode 100644 src/tls/tls_client.c create mode 100644 src/tls/tls_dane.c create mode 100644 src/tls/tls_dane.sh create mode 100644 src/tls/tls_dh.c create mode 100644 src/tls/tls_fprint.c create mode 100644 src/tls/tls_level.c create mode 100644 src/tls/tls_mgr.c create mode 100644 src/tls/tls_mgr.h create mode 100644 src/tls/tls_misc.c create mode 100644 src/tls/tls_prng.h create mode 100644 src/tls/tls_prng_dev.c create mode 100644 src/tls/tls_prng_egd.c create mode 100644 src/tls/tls_prng_exch.c create mode 100644 src/tls/tls_prng_file.c create mode 100644 src/tls/tls_proxy.h create mode 100644 src/tls/tls_proxy_client_misc.c create mode 100644 src/tls/tls_proxy_client_print.c create mode 100644 src/tls/tls_proxy_client_scan.c create mode 100644 src/tls/tls_proxy_clnt.c create mode 100644 src/tls/tls_proxy_context_print.c create mode 100644 src/tls/tls_proxy_context_scan.c create mode 100644 src/tls/tls_proxy_server_print.c create mode 100644 src/tls/tls_proxy_server_scan.c create mode 100644 src/tls/tls_rsa.c create mode 100644 src/tls/tls_scache.c create mode 100644 src/tls/tls_scache.h create mode 100644 src/tls/tls_seed.c create mode 100644 src/tls/tls_server.c create mode 100644 src/tls/tls_session.c create mode 100644 src/tls/tls_stream.c create mode 100644 src/tls/tls_verify.c create mode 100644 src/tls/warn-mixed-multi-key.pem create mode 100644 src/tls/warn-mixed-multi-key.pem.ref create mode 120000 src/tlsmgr/.indent.pro create mode 100644 src/tlsmgr/Makefile.in create mode 100644 src/tlsmgr/tlsmgr.c create mode 120000 src/tlsproxy/.indent.pro create mode 100644 src/tlsproxy/Makefile.in create mode 100644 src/tlsproxy/tlsproxy.c create mode 100644 src/tlsproxy/tlsproxy.h create mode 100644 src/tlsproxy/tlsproxy_state.c create mode 120000 src/trivial-rewrite/.indent.pro create mode 100644 src/trivial-rewrite/.printfck create mode 100644 src/trivial-rewrite/Makefile.in create mode 100644 src/trivial-rewrite/resolve.c create mode 100644 src/trivial-rewrite/rewrite.c create mode 100644 src/trivial-rewrite/transport.c create mode 100644 src/trivial-rewrite/transport.h create mode 100644 src/trivial-rewrite/transport.in create mode 100644 src/trivial-rewrite/transport.ref create mode 100644 src/trivial-rewrite/trivial-rewrite.c create mode 100644 src/trivial-rewrite/trivial-rewrite.h create mode 120000 src/util/.indent.pro create mode 100644 src/util/.printfck create mode 100644 src/util/Makefile.in create mode 100644 src/util/allascii.c create mode 100644 src/util/alldig.c create mode 100644 src/util/allprint.c create mode 100644 src/util/allspace.c create mode 100644 src/util/argv.c create mode 100644 src/util/argv.h create mode 100644 src/util/argv_attr.h create mode 100644 src/util/argv_attr_print.c create mode 100644 src/util/argv_attr_scan.c create mode 100644 src/util/argv_split.c create mode 100644 src/util/argv_split_at.c create mode 100644 src/util/argv_splitq.c create mode 100644 src/util/attr.h create mode 100644 src/util/attr_clnt.c create mode 100644 src/util/attr_clnt.h create mode 100644 src/util/attr_print0.c create mode 100644 src/util/attr_print64.c create mode 100644 src/util/attr_print_plain.c create mode 100644 src/util/attr_scan.ref create mode 100644 src/util/attr_scan0.c create mode 100644 src/util/attr_scan0.ref create mode 100644 src/util/attr_scan64.c create mode 100644 src/util/attr_scan64.ref create mode 100644 src/util/attr_scan_plain.c create mode 100644 src/util/attr_scan_plain.ref create mode 100644 src/util/auto_clnt.c create mode 100644 src/util/auto_clnt.h create mode 100644 src/util/balpar.c create mode 100644 src/util/base32_code.c create mode 100644 src/util/base32_code.h create mode 100644 src/util/base64_code.c create mode 100644 src/util/base64_code.h create mode 100644 src/util/basename.c create mode 100644 src/util/binhash.c create mode 100644 src/util/binhash.h create mode 100644 src/util/byte_mask.c create mode 100644 src/util/byte_mask.h create mode 100644 src/util/byte_mask.in create mode 100644 src/util/byte_mask.ref0 create mode 100644 src/util/byte_mask.ref1 create mode 100644 src/util/byte_mask.ref2 create mode 100644 src/util/cache.in create mode 100644 src/util/casefold.c create mode 100644 src/util/casefold_test.in create mode 100644 src/util/casefold_test.ref create mode 100644 src/util/check_arg.h create mode 100644 src/util/chroot_uid.c create mode 100644 src/util/chroot_uid.h create mode 100644 src/util/cidr_match.c create mode 100644 src/util/cidr_match.h create mode 100644 src/util/clean_env.c create mode 100644 src/util/clean_env.h create mode 100644 src/util/close_on_exec.c create mode 100644 src/util/compat_va_copy.h create mode 100644 src/util/concatenate.c create mode 100644 src/util/connect.h create mode 100644 src/util/ctable.c create mode 100644 src/util/ctable.h create mode 100644 src/util/ctable.in create mode 100644 src/util/ctable.ref create mode 100644 src/util/dict.c create mode 100644 src/util/dict.h create mode 100644 src/util/dict_alloc.c create mode 100644 src/util/dict_cache.c create mode 100644 src/util/dict_cache.h create mode 100644 src/util/dict_cdb.c create mode 100644 src/util/dict_cdb.h create mode 100644 src/util/dict_cidr.c create mode 100644 src/util/dict_cidr.h create mode 100644 src/util/dict_cidr.in create mode 100644 src/util/dict_cidr.map create mode 100644 src/util/dict_cidr.ref create mode 100644 src/util/dict_cidr_file.in create mode 100644 src/util/dict_cidr_file.map create mode 100644 src/util/dict_cidr_file.ref create mode 100644 src/util/dict_db.c create mode 100644 src/util/dict_db.h create mode 100644 src/util/dict_dbm.c create mode 100644 src/util/dict_dbm.h create mode 100644 src/util/dict_debug.c create mode 100644 src/util/dict_env.c create mode 100644 src/util/dict_env.h create mode 100644 src/util/dict_fail.c create mode 100644 src/util/dict_fail.h create mode 100644 src/util/dict_file.c create mode 100644 src/util/dict_ht.c create mode 100644 src/util/dict_ht.h create mode 100644 src/util/dict_inline.c create mode 100644 src/util/dict_inline.h create mode 100644 src/util/dict_inline.ref create mode 100644 src/util/dict_inline_cidr.ref create mode 100644 src/util/dict_inline_file.ref create mode 100644 src/util/dict_inline_pcre.ref create mode 100644 src/util/dict_inline_regexp.ref create mode 100644 src/util/dict_lmdb.c create mode 100644 src/util/dict_lmdb.h create mode 100644 src/util/dict_ni.c create mode 100644 src/util/dict_ni.h create mode 100644 src/util/dict_nis.c create mode 100644 src/util/dict_nis.h create mode 100644 src/util/dict_nisplus.c create mode 100644 src/util/dict_nisplus.h create mode 100644 src/util/dict_open.c create mode 100644 src/util/dict_pcre.c create mode 100644 src/util/dict_pcre.h create mode 100644 src/util/dict_pcre.in create mode 100644 src/util/dict_pcre.map create mode 100644 src/util/dict_pcre.ref create mode 100644 src/util/dict_pcre_file.in create mode 100644 src/util/dict_pcre_file.map create mode 100644 src/util/dict_pcre_file.ref create mode 100644 src/util/dict_pipe.c create mode 100644 src/util/dict_pipe.h create mode 100644 src/util/dict_pipe_test.in create mode 100644 src/util/dict_pipe_test.ref create mode 100644 src/util/dict_random.c create mode 100644 src/util/dict_random.h create mode 100644 src/util/dict_random.ref create mode 100644 src/util/dict_random_file.ref create mode 100644 src/util/dict_regexp.c create mode 100644 src/util/dict_regexp.h create mode 100644 src/util/dict_regexp.in create mode 100644 src/util/dict_regexp.map create mode 100644 src/util/dict_regexp.ref create mode 100644 src/util/dict_regexp_file.in create mode 100644 src/util/dict_regexp_file.map create mode 100644 src/util/dict_regexp_file.ref create mode 100644 src/util/dict_sdbm.c create mode 100644 src/util/dict_sdbm.h create mode 100644 src/util/dict_seq.in create mode 100644 src/util/dict_seq.ref create mode 100644 src/util/dict_sockmap.c create mode 100644 src/util/dict_sockmap.h create mode 100644 src/util/dict_static.c create mode 100644 src/util/dict_static.h create mode 100644 src/util/dict_static.ref create mode 100644 src/util/dict_static_file.ref create mode 100644 src/util/dict_stream.c create mode 100644 src/util/dict_stream.ref create mode 100644 src/util/dict_surrogate.c create mode 100644 src/util/dict_tcp.c create mode 100644 src/util/dict_tcp.h create mode 100644 src/util/dict_test.c create mode 100644 src/util/dict_test.in create mode 100644 src/util/dict_test.ref create mode 100644 src/util/dict_thash.c create mode 100644 src/util/dict_thash.h create mode 100644 src/util/dict_thash.in create mode 100644 src/util/dict_thash.map create mode 100644 src/util/dict_thash.ref create mode 100644 src/util/dict_union.c create mode 100644 src/util/dict_union.h create mode 100644 src/util/dict_union_test.in create mode 100644 src/util/dict_union_test.ref create mode 100644 src/util/dict_unix.c create mode 100644 src/util/dict_unix.h create mode 100644 src/util/dict_utf8.c create mode 100644 src/util/dict_utf8_test.in create mode 100644 src/util/dict_utf8_test.ref create mode 100644 src/util/dir_forest.c create mode 100644 src/util/dir_forest.h create mode 100644 src/util/doze.c create mode 100644 src/util/dummy_read.c create mode 100644 src/util/dummy_write.c create mode 100644 src/util/dup2_pass_on_exec.c create mode 100644 src/util/duplex_pipe.c create mode 100644 src/util/edit_file.c create mode 100644 src/util/edit_file.h create mode 100644 src/util/environ.c create mode 100644 src/util/events.c create mode 100644 src/util/events.h create mode 100644 src/util/exec_command.c create mode 100644 src/util/exec_command.h create mode 100644 src/util/extpar.c create mode 100644 src/util/fifo_listen.c create mode 100644 src/util/fifo_open.c create mode 100644 src/util/fifo_rdonly_bug.c create mode 100644 src/util/fifo_rdwr_bug.c create mode 100644 src/util/fifo_trigger.c create mode 100644 src/util/file_limit.c create mode 100644 src/util/find_inet.c create mode 100644 src/util/find_inet.h create mode 100644 src/util/find_inet.ref create mode 100644 src/util/format_tv.c create mode 100644 src/util/format_tv.h create mode 100644 src/util/format_tv.in create mode 100644 src/util/format_tv.ref create mode 100644 src/util/fsspace.c create mode 100644 src/util/fsspace.h create mode 100644 src/util/fullname.c create mode 100644 src/util/fullname.h create mode 100644 src/util/gccw.c create mode 100644 src/util/gccw.ref create mode 100644 src/util/get_domainname.c create mode 100644 src/util/get_domainname.h create mode 100644 src/util/get_hostname.c create mode 100644 src/util/get_hostname.h create mode 100644 src/util/hash_fnv.c create mode 100644 src/util/hash_fnv.h create mode 100644 src/util/hex_code.c create mode 100644 src/util/hex_code.h create mode 100644 src/util/hex_quote.c create mode 100644 src/util/hex_quote.h create mode 100644 src/util/host_port.c create mode 100644 src/util/host_port.h create mode 100644 src/util/host_port.in create mode 100644 src/util/host_port.ref create mode 100644 src/util/htable.c create mode 100644 src/util/htable.h create mode 100644 src/util/inet_addr_host.c create mode 100644 src/util/inet_addr_host.h create mode 100644 src/util/inet_addr_list.c create mode 100644 src/util/inet_addr_list.h create mode 100644 src/util/inet_addr_list.in create mode 100644 src/util/inet_addr_list.ref create mode 100644 src/util/inet_addr_local.c create mode 100644 src/util/inet_addr_local.h create mode 100644 src/util/inet_connect.c create mode 100644 src/util/inet_listen.c create mode 100644 src/util/inet_proto.c create mode 100644 src/util/inet_proto.h create mode 100644 src/util/inet_trigger.c create mode 100644 src/util/inet_windowsize.c create mode 100644 src/util/iostuff.h create mode 100644 src/util/ip_match.c create mode 100644 src/util/ip_match.h create mode 100644 src/util/ip_match.in create mode 100644 src/util/ip_match.ref create mode 100644 src/util/killme_after.c create mode 100644 src/util/killme_after.h create mode 100644 src/util/known_tcp_ports.c create mode 100644 src/util/known_tcp_ports.h create mode 100644 src/util/known_tcp_ports.ref create mode 100644 src/util/ldseed.c create mode 100644 src/util/ldseed.h create mode 100644 src/util/line_number.c create mode 100644 src/util/line_number.h create mode 100644 src/util/line_wrap.c create mode 100644 src/util/line_wrap.h create mode 100644 src/util/listen.h create mode 100644 src/util/lmdb_cache_test_1.sh create mode 100644 src/util/lmdb_cache_test_2.sh create mode 100644 src/util/load_file.c create mode 100644 src/util/load_file.h create mode 100644 src/util/load_lib.c create mode 100644 src/util/load_lib.h create mode 100644 src/util/logwriter.c create mode 100644 src/util/logwriter.h create mode 100644 src/util/lowercase.c create mode 100644 src/util/lstat_as.c create mode 100644 src/util/lstat_as.h create mode 100644 src/util/mac_expand.c create mode 100644 src/util/mac_expand.h create mode 100644 src/util/mac_expand.in create mode 100644 src/util/mac_expand.ref create mode 100644 src/util/mac_parse.c create mode 100644 src/util/mac_parse.h create mode 100644 src/util/make_dirs.c create mode 100644 src/util/make_dirs.h create mode 100644 src/util/mask_addr.c create mode 100644 src/util/mask_addr.h create mode 100644 src/util/match_list.c create mode 100644 src/util/match_list.h create mode 100644 src/util/match_ops.c create mode 100644 src/util/midna_domain.c create mode 100644 src/util/midna_domain.h create mode 100644 src/util/midna_domain_test.in create mode 100644 src/util/midna_domain_test.ref create mode 100644 src/util/miss_endif_cidr.map create mode 100644 src/util/miss_endif_cidr.ref create mode 100644 src/util/miss_endif_pcre.ref create mode 100644 src/util/miss_endif_re.map create mode 100644 src/util/miss_endif_regexp.ref create mode 100644 src/util/msg.c create mode 100644 src/util/msg.h create mode 100644 src/util/msg_logger.c create mode 100644 src/util/msg_logger.h create mode 100644 src/util/msg_output.c create mode 100644 src/util/msg_output.h create mode 100644 src/util/msg_rate_delay.c create mode 100644 src/util/msg_syslog.c create mode 100644 src/util/msg_syslog.h create mode 100644 src/util/msg_vstream.c create mode 100644 src/util/msg_vstream.h create mode 100644 src/util/mvect.c create mode 100644 src/util/mvect.h create mode 100644 src/util/myaddrinfo.c create mode 100644 src/util/myaddrinfo.h create mode 100644 src/util/myaddrinfo.ref create mode 100644 src/util/myaddrinfo.ref2 create mode 100644 src/util/myaddrinfo4.ref create mode 100644 src/util/myaddrinfo4.ref2 create mode 100644 src/util/myflock.c create mode 100644 src/util/myflock.h create mode 100644 src/util/mymalloc.c create mode 100644 src/util/mymalloc.h create mode 100644 src/util/myrand.c create mode 100644 src/util/myrand.h create mode 100644 src/util/mystrtok.c create mode 100644 src/util/mystrtok.ref create mode 100644 src/util/name_code.c create mode 100644 src/util/name_code.h create mode 100644 src/util/name_mask.c create mode 100644 src/util/name_mask.h create mode 100644 src/util/name_mask.in create mode 100644 src/util/name_mask.ref0 create mode 100644 src/util/name_mask.ref1 create mode 100644 src/util/name_mask.ref2 create mode 100644 src/util/name_mask.ref3 create mode 100644 src/util/name_mask.ref4 create mode 100644 src/util/name_mask.ref5 create mode 100644 src/util/name_mask.ref6 create mode 100644 src/util/name_mask.ref7 create mode 100644 src/util/name_mask.ref8 create mode 100644 src/util/name_mask.ref9 create mode 100644 src/util/nbbio.c create mode 100644 src/util/nbbio.h create mode 100644 src/util/netstring.c create mode 100644 src/util/netstring.h create mode 100644 src/util/neuter.c create mode 100644 src/util/non_blocking.c create mode 100644 src/util/nvtable.c create mode 100644 src/util/nvtable.h create mode 100644 src/util/open_as.c create mode 100644 src/util/open_as.h create mode 100644 src/util/open_limit.c create mode 100644 src/util/open_lock.c create mode 100644 src/util/open_lock.h create mode 100644 src/util/pass_accept.c create mode 100644 src/util/pass_trigger.c create mode 100644 src/util/peekfd.c create mode 100644 src/util/poll_fd.c create mode 100644 src/util/posix_signals.c create mode 100644 src/util/posix_signals.h create mode 100644 src/util/printable.c create mode 100644 src/util/rand_sleep.c create mode 100644 src/util/readlline.c create mode 100644 src/util/readlline.h create mode 100644 src/util/recv_pass_attr.c create mode 100644 src/util/ring.c create mode 100644 src/util/ring.h create mode 100644 src/util/safe.h create mode 100644 src/util/safe_getenv.c create mode 100644 src/util/safe_open.c create mode 100644 src/util/safe_open.h create mode 100644 src/util/sane_accept.c create mode 100644 src/util/sane_accept.h create mode 100644 src/util/sane_basename.c create mode 100644 src/util/sane_basename.in create mode 100644 src/util/sane_basename.ref create mode 100644 src/util/sane_connect.c create mode 100644 src/util/sane_connect.h create mode 100644 src/util/sane_fsops.h create mode 100644 src/util/sane_link.c create mode 100644 src/util/sane_rename.c create mode 100644 src/util/sane_socketpair.c create mode 100644 src/util/sane_socketpair.h create mode 100644 src/util/sane_strtol.c create mode 100644 src/util/sane_strtol.h create mode 100644 src/util/sane_time.c create mode 100644 src/util/sane_time.h create mode 100644 src/util/scan_dir.c create mode 100644 src/util/scan_dir.h create mode 100644 src/util/select_bug.c create mode 100644 src/util/set_eugid.c create mode 100644 src/util/set_eugid.h create mode 100644 src/util/set_ugid.c create mode 100644 src/util/set_ugid.h create mode 100644 src/util/sigdelay.c create mode 100644 src/util/sigdelay.h create mode 100644 src/util/skipblanks.c create mode 100644 src/util/slmdb.c create mode 100644 src/util/slmdb.h create mode 100644 src/util/sock_addr.c create mode 100644 src/util/sock_addr.h create mode 100644 src/util/spawn_command.c create mode 100644 src/util/spawn_command.h create mode 100644 src/util/split_at.c create mode 100644 src/util/split_at.h create mode 100644 src/util/split_nameval.c create mode 100644 src/util/split_qnameval.c create mode 100644 src/util/stat_as.c create mode 100644 src/util/stat_as.h create mode 100644 src/util/strcasecmp.c create mode 100644 src/util/strcasecmp_utf8.c create mode 100644 src/util/strcasecmp_utf8_test.in create mode 100644 src/util/strcasecmp_utf8_test.ref create mode 100644 src/util/stream_connect.c create mode 100644 src/util/stream_listen.c create mode 100644 src/util/stream_recv_fd.c create mode 100644 src/util/stream_send_fd.c create mode 100644 src/util/stream_test.c create mode 100644 src/util/stream_trigger.c create mode 100644 src/util/stringops.h create mode 100644 src/util/surrogate.ref create mode 100644 src/util/sys_compat.c create mode 100644 src/util/sys_defs.h create mode 100644 src/util/testdb create mode 100644 src/util/timecmp.c create mode 100644 src/util/timecmp.h create mode 100644 src/util/timed_connect.c create mode 100644 src/util/timed_connect.h create mode 100644 src/util/timed_read.c create mode 100644 src/util/timed_wait.c create mode 100644 src/util/timed_wait.h create mode 100644 src/util/timed_write.c create mode 100644 src/util/translit.c create mode 100644 src/util/trigger.h create mode 100644 src/util/trimblanks.c create mode 100644 src/util/unescape.c create mode 100644 src/util/unescape.in create mode 100644 src/util/unescape.ref create mode 100644 src/util/unix_connect.c create mode 100644 src/util/unix_dgram_connect.c create mode 100644 src/util/unix_dgram_listen.c create mode 100644 src/util/unix_listen.c create mode 100644 src/util/unix_pass_fd_fix.c create mode 100644 src/util/unix_recv_fd.c create mode 100644 src/util/unix_send_fd.c create mode 100644 src/util/unix_trigger.c create mode 100644 src/util/unsafe.c create mode 100644 src/util/uppercase.c create mode 100644 src/util/username.c create mode 100644 src/util/username.h create mode 100644 src/util/valid_hostname.c create mode 100644 src/util/valid_hostname.h create mode 100644 src/util/valid_hostname.in create mode 100644 src/util/valid_hostname.ref create mode 100644 src/util/valid_utf8_hostname.c create mode 100644 src/util/valid_utf8_hostname.h create mode 100644 src/util/valid_utf8_string.c create mode 100644 src/util/vbuf.c create mode 100644 src/util/vbuf.h create mode 100644 src/util/vbuf_print.c create mode 100644 src/util/vbuf_print.h create mode 100644 src/util/vbuf_print_test.in create mode 100644 src/util/vbuf_print_test.ref create mode 100644 src/util/vstream.c create mode 100644 src/util/vstream.h create mode 100644 src/util/vstream_popen.c create mode 100644 src/util/vstream_test.in create mode 100644 src/util/vstream_test.ref create mode 100644 src/util/vstream_tweak.c create mode 100644 src/util/vstring.c create mode 100644 src/util/vstring.h create mode 100644 src/util/vstring_test.ref create mode 100644 src/util/vstring_vstream.c create mode 100644 src/util/vstring_vstream.h create mode 100644 src/util/warn_stat.c create mode 100644 src/util/warn_stat.h create mode 100644 src/util/watchdog.c create mode 100644 src/util/watchdog.h create mode 100644 src/util/write_buf.c create mode 120000 src/verify/.indent.pro create mode 100644 src/verify/Makefile.in create mode 100644 src/verify/verify.c create mode 120000 src/virtual/.indent.pro create mode 100644 src/virtual/.printfck create mode 100644 src/virtual/Makefile.in create mode 100644 src/virtual/deliver_attr.c create mode 100644 src/virtual/mailbox.c create mode 100644 src/virtual/maildir.c create mode 100644 src/virtual/recipient.c create mode 100644 src/virtual/unknown.c create mode 100644 src/virtual/virtual.c create mode 100644 src/virtual/virtual.h create mode 120000 src/xsasl/.indent.pro create mode 100644 src/xsasl/Makefile.in create mode 100644 src/xsasl/README create mode 100644 src/xsasl/xsasl.h create mode 100644 src/xsasl/xsasl_client.c create mode 100644 src/xsasl/xsasl_cyrus.h create mode 100644 src/xsasl/xsasl_cyrus_client.c create mode 100644 src/xsasl/xsasl_cyrus_common.h create mode 100644 src/xsasl/xsasl_cyrus_log.c create mode 100644 src/xsasl/xsasl_cyrus_security.c create mode 100644 src/xsasl/xsasl_cyrus_server.c create mode 100644 src/xsasl/xsasl_dovecot.h create mode 100644 src/xsasl/xsasl_dovecot_server.c create mode 100644 src/xsasl/xsasl_server.c (limited to 'src') diff --git a/src/anvil/.indent.pro b/src/anvil/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/anvil/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/anvil/.printfck b/src/anvil/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/anvil/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/anvil/Makefile.in b/src/anvil/Makefile.in new file mode 100644 index 0000000..2caa0a9 --- /dev/null +++ b/src/anvil/Makefile.in @@ -0,0 +1,82 @@ +SHELL = /bin/sh +SRCS = anvil.c +OBJS = anvil.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = anvil +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +anvil.o: ../../include/anvil_clnt.h +anvil.o: ../../include/attr.h +anvil.o: ../../include/attr_clnt.h +anvil.o: ../../include/check_arg.h +anvil.o: ../../include/events.h +anvil.o: ../../include/htable.h +anvil.o: ../../include/iostuff.h +anvil.o: ../../include/mail_conf.h +anvil.o: ../../include/mail_params.h +anvil.o: ../../include/mail_proto.h +anvil.o: ../../include/mail_server.h +anvil.o: ../../include/mail_version.h +anvil.o: ../../include/msg.h +anvil.o: ../../include/mymalloc.h +anvil.o: ../../include/nvtable.h +anvil.o: ../../include/stringops.h +anvil.o: ../../include/sys_defs.h +anvil.o: ../../include/vbuf.h +anvil.o: ../../include/vstream.h +anvil.o: ../../include/vstring.h +anvil.o: anvil.c diff --git a/src/anvil/anvil.c b/src/anvil/anvil.c new file mode 100644 index 0000000..884be28 --- /dev/null +++ b/src/anvil/anvil.c @@ -0,0 +1,1047 @@ +/*++ +/* NAME +/* anvil 8 +/* SUMMARY +/* Postfix session count and request rate control +/* SYNOPSIS +/* \fBanvil\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The Postfix \fBanvil\fR(8) server maintains statistics about +/* client connection counts or client request rates. This +/* information can be used to defend against clients that +/* hammer a server with either too many simultaneous sessions, +/* or with too many successive requests within a configurable +/* time interval. This server is designed to run under control +/* by the Postfix \fBmaster\fR(8) server. +/* +/* In the following text, \fBident\fR specifies a (service, +/* client) combination. The exact syntax of that information +/* is application-dependent; the \fBanvil\fR(8) server does +/* not care. +/* CONNECTION COUNT/RATE CONTROL +/* .ad +/* .fi +/* To register a new connection send the following request to +/* the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=connect\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of +/* simultaneous connections and the number of connections per +/* unit time for the (service, client) combination specified +/* with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBcount=\fInumber\fR +/* \fBrate=\fInumber\fR +/* .fi +/* +/* To register a disconnect event send the following request +/* to the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=disconnect\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server replies with: +/* +/* .nf +/* \fBstatus=0\fR +/* .fi +/* MESSAGE RATE CONTROL +/* .ad +/* .fi +/* To register a message delivery request send the following +/* request to the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=message\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of message +/* delivery requests per unit time for the (service, client) +/* combination specified with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBrate=\fInumber\fR +/* .fi +/* RECIPIENT RATE CONTROL +/* .ad +/* .fi +/* To register a recipient request send the following request +/* to the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=recipient\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of recipient +/* addresses per unit time for the (service, client) combination +/* specified with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBrate=\fInumber\fR +/* .fi +/* TLS SESSION NEGOTIATION RATE CONTROL +/* .ad +/* .fi +/* The features described in this section are available with +/* Postfix 2.3 and later. +/* +/* To register a request for a new (i.e. not cached) TLS session +/* send the following request to the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=newtls\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of new +/* TLS session requests per unit time for the (service, client) +/* combination specified with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBrate=\fInumber\fR +/* .fi +/* +/* To retrieve new TLS session request rate information without +/* updating the counter information, send: +/* +/* .nf +/* \fBrequest=newtls_report\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of new +/* TLS session requests per unit time for the (service, client) +/* combination specified with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBrate=\fInumber\fR +/* .fi +/* AUTH RATE CONTROL +/* .ad +/* .fi +/* To register an AUTH request send the following request +/* to the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=auth\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of auth +/* requests per unit time for the (service, client) combination +/* specified with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBrate=\fInumber\fR +/* .fi +/* SECURITY +/* .ad +/* .fi +/* The \fBanvil\fR(8) server does not talk to the network or to local +/* users, and can run chrooted at fixed low privilege. +/* +/* The \fBanvil\fR(8) server maintains an in-memory table with +/* information about recent clients requests. No persistent +/* state is kept because standard system library routines are +/* not sufficiently robust for update-intensive applications. +/* +/* Although the in-memory state is kept only temporarily, this +/* may require a lot of memory on systems that handle connections +/* from many remote clients. To reduce memory usage, reduce +/* the time unit over which state is kept. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* +/* Upon exit, and every \fBanvil_status_update_time\fR +/* seconds, the server logs the maximal count and rate values measured, +/* together with (service, client) information and the time of day +/* associated with those events. +/* In order to avoid unnecessary overhead, no measurements +/* are done for activity that isn't concurrency limited or +/* rate limited. +/* BUGS +/* Systems behind network address translating routers or proxies +/* appear to have the same client address and can run into connection +/* count and/or rate limits falsely. +/* +/* In this preliminary implementation, a count (or rate) limited server +/* process can have only one remote client at a time. If a +/* server process reports +/* multiple simultaneous clients, state is kept only for the last +/* reported client. +/* +/* The \fBanvil\fR(8) server automatically discards client +/* request information after it expires. To prevent the +/* \fBanvil\fR(8) server from discarding client request rate +/* information too early or too late, a rate limited service +/* should always register connect/disconnect events even when +/* it does not explicitly limit them. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* On low-traffic mail systems, changes to \fBmain.cf\fR are +/* picked up automatically as \fBanvil\fR(8) processes run for +/* only a limited amount of time. On other mail systems, use +/* the command "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBanvil_rate_time_unit (60s)\fR" +/* The time unit over which client connection rates and other rates +/* are calculated. +/* .IP "\fBanvil_status_update_time (600s)\fR" +/* How frequently the \fBanvil\fR(8) connection and rate limiting server +/* logs peak usage information. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* smtpd(8), Postfix SMTP server +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* TUNING_README, performance tuning +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* The anvil service is available in Postfix 2.2 and later. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Server skeleton. */ + +#include + +/* Application-specific. */ + + /* + * Configuration parameters. + */ +int var_anvil_time_unit; +int var_anvil_stat_time; + + /* + * Global dynamic state. + */ +static HTABLE *anvil_remote_map; /* indexed by service+ remote client */ + + /* + * Remote connection state, one instance for each (service, client) pair. + */ +typedef struct { + char *ident; /* lookup key */ + int count; /* connection count */ + int rate; /* connection rate */ + int mail; /* message rate */ + int rcpt; /* recipient rate */ + int ntls; /* new TLS session rate */ + int auth; /* AUTH request rate */ + time_t start; /* time of first rate sample */ +} ANVIL_REMOTE; + + /* + * Local server state, one instance per anvil client connection. This allows + * us to clean up remote connection state when a local server goes away + * without cleaning up. + */ +typedef struct { + ANVIL_REMOTE *anvil_remote; /* XXX should be list */ +} ANVIL_LOCAL; + + /* + * The following operations are implemented as macros with recognizable + * names so that we don't lose sight of what the code is trying to do. + * + * Related operations are defined side by side so that the code implementing + * them isn't pages apart. + */ + +/* Create new (service, client) state. */ + +#define ANVIL_REMOTE_FIRST_CONN(remote, id) \ + do { \ + (remote)->ident = mystrdup(id); \ + (remote)->count = 1; \ + (remote)->rate = 1; \ + (remote)->mail = 0; \ + (remote)->rcpt = 0; \ + (remote)->ntls = 0; \ + (remote)->auth = 0; \ + (remote)->start = event_time(); \ + } while(0) + +/* Destroy unused (service, client) state. */ + +#define ANVIL_REMOTE_FREE(remote) \ + do { \ + myfree((remote)->ident); \ + myfree((void *) (remote)); \ + } while(0) + +/* Reset or update rate information for existing (service, client) state. */ + +#define ANVIL_REMOTE_RSET_RATE(remote, _start) \ + do { \ + (remote)->rate = 0; \ + (remote)->mail = 0; \ + (remote)->rcpt = 0; \ + (remote)->ntls = 0; \ + (remote)->auth = 0; \ + (remote)->start = _start; \ + } while(0) + +#define ANVIL_REMOTE_INCR_RATE(remote, _what) \ + do { \ + time_t _now = event_time(); \ + if ((remote)->start + var_anvil_time_unit < _now) \ + ANVIL_REMOTE_RSET_RATE((remote), _now); \ + if ((remote)->_what < INT_MAX) \ + (remote)->_what += 1; \ + } while(0) + +/* Update existing (service, client) state. */ + +#define ANVIL_REMOTE_NEXT_CONN(remote) \ + do { \ + ANVIL_REMOTE_INCR_RATE((remote), rate); \ + if ((remote)->count == 0) \ + event_cancel_timer(anvil_remote_expire, (void *) remote); \ + (remote)->count++; \ + } while(0) + +#define ANVIL_REMOTE_INCR_MAIL(remote) ANVIL_REMOTE_INCR_RATE((remote), mail) + +#define ANVIL_REMOTE_INCR_RCPT(remote) ANVIL_REMOTE_INCR_RATE((remote), rcpt) + +#define ANVIL_REMOTE_INCR_NTLS(remote) ANVIL_REMOTE_INCR_RATE((remote), ntls) + +#define ANVIL_REMOTE_INCR_AUTH(remote) ANVIL_REMOTE_INCR_RATE((remote), auth) + +/* Drop connection from (service, client) state. */ + +#define ANVIL_REMOTE_DROP_ONE(remote) \ + do { \ + if ((remote) && (remote)->count > 0) { \ + if (--(remote)->count == 0) \ + event_request_timer(anvil_remote_expire, (void *) remote, \ + var_anvil_time_unit); \ + } \ + } while(0) + +/* Create local server state. */ + +#define ANVIL_LOCAL_INIT(local) \ + do { \ + (local)->anvil_remote = 0; \ + } while(0) + +/* Add remote connection to local server. */ + +#define ANVIL_LOCAL_ADD_ONE(local, remote) \ + do { \ + /* XXX allow multiple remote clients per local server. */ \ + if ((local)->anvil_remote) \ + ANVIL_REMOTE_DROP_ONE((local)->anvil_remote); \ + (local)->anvil_remote = (remote); \ + } while(0) + +/* Test if this remote connection is listed for this local server. */ + +#define ANVIL_LOCAL_REMOTE_LINKED(local, remote) \ + ((local)->anvil_remote == (remote)) + +/* Drop specific remote connection from local server. */ + +#define ANVIL_LOCAL_DROP_ONE(local, remote) \ + do { \ + /* XXX allow multiple remote clients per local server. */ \ + if ((local)->anvil_remote == (remote)) \ + (local)->anvil_remote = 0; \ + } while(0) + +/* Drop all remote connections from local server. */ + +#define ANVIL_LOCAL_DROP_ALL(stream, local) \ + do { \ + /* XXX allow multiple remote clients per local server. */ \ + if ((local)->anvil_remote) \ + anvil_remote_disconnect((stream), (local)->anvil_remote->ident); \ + } while (0) + + /* + * Lookup table to map request names to action routines. + */ +typedef struct { + const char *name; + void (*action) (VSTREAM *, const char *); +} ANVIL_REQ_TABLE; + + /* + * Run-time statistics for maximal connection counts and event rates. These + * store the peak resource usage, remote connection, and time. Absent a + * query interface, this information is logged at process exit time and at + * configurable intervals. + */ +typedef struct { + int value; /* peak value */ + char *ident; /* lookup key */ + time_t when; /* time of peak value */ +} ANVIL_MAX; + +static ANVIL_MAX max_conn_count; /* peak connection count */ +static ANVIL_MAX max_conn_rate; /* peak connection rate */ +static ANVIL_MAX max_mail_rate; /* peak message rate */ +static ANVIL_MAX max_rcpt_rate; /* peak recipient rate */ +static ANVIL_MAX max_ntls_rate; /* peak new TLS session rate */ +static ANVIL_MAX max_auth_rate; /* peak AUTH request rate */ + +static int max_cache_size; /* peak cache size */ +static time_t max_cache_time; /* time of peak size */ + +/* Update/report peak usage. */ + +#define ANVIL_MAX_UPDATE(_max, _value, _ident) \ + do { \ + _max.value = _value; \ + if (_max.ident == 0) { \ + _max.ident = mystrdup(_ident); \ + } else if (!STREQ(_max.ident, _ident)) { \ + myfree(_max.ident); \ + _max.ident = mystrdup(_ident); \ + } \ + _max.when = event_time(); \ + } while (0) + +#define ANVIL_MAX_RATE_REPORT(_max, _name) \ + do { \ + if (_max.value > 0) { \ + msg_info("statistics: max " _name " rate %d/%ds for (%s) at %.15s", \ + _max.value, var_anvil_time_unit, \ + _max.ident, ctime(&_max.when) + 4); \ + _max.value = 0; \ + } \ + } while (0); + +#define ANVIL_MAX_COUNT_REPORT(_max, _name) \ + do { \ + if (_max.value > 0) { \ + msg_info("statistics: max " _name " count %d for (%s) at %.15s", \ + _max.value, _max.ident, ctime(&_max.when) + 4); \ + _max.value = 0; \ + } \ + } while (0); + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define STREQ(x,y) (strcmp((x), (y)) == 0) + +/* anvil_remote_expire - purge expired connection state */ + +static void anvil_remote_expire(int unused_event, void *context) +{ + ANVIL_REMOTE *anvil_remote = (ANVIL_REMOTE *) context; + const char *myname = "anvil_remote_expire"; + + if (msg_verbose) + msg_info("%s %s", myname, anvil_remote->ident); + + if (anvil_remote->count != 0) + msg_panic("%s: bad connection count: %d", + myname, anvil_remote->count); + + htable_delete(anvil_remote_map, anvil_remote->ident, + (void (*) (void *)) 0); + ANVIL_REMOTE_FREE(anvil_remote); + + if (msg_verbose) + msg_info("%s: anvil_remote_map used=%ld", + myname, (long) anvil_remote_map->used); +} + +/* anvil_remote_lookup - dump address status */ + +static void anvil_remote_lookup(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + const char *myname = "anvil_remote_lookup"; + + if (msg_verbose) + msg_info("%s fd=%d stream=0x%lx ident=%s", + myname, vstream_fileno(client_stream), + (unsigned long) client_stream, ident); + + /* + * Look up remote client information. + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) { + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_COUNT, 0), + SEND_ATTR_INT(ANVIL_ATTR_RATE, 0), + SEND_ATTR_INT(ANVIL_ATTR_MAIL, 0), + SEND_ATTR_INT(ANVIL_ATTR_RCPT, 0), + SEND_ATTR_INT(ANVIL_ATTR_NTLS, 0), + SEND_ATTR_INT(ANVIL_ATTR_AUTH, 0), + ATTR_TYPE_END); + } else { + + /* + * Do not report stale information. + */ + if (anvil_remote->start != 0 + && anvil_remote->start + var_anvil_time_unit < event_time()) + ANVIL_REMOTE_RSET_RATE(anvil_remote, 0); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_COUNT, anvil_remote->count), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->rate), + SEND_ATTR_INT(ANVIL_ATTR_MAIL, anvil_remote->mail), + SEND_ATTR_INT(ANVIL_ATTR_RCPT, anvil_remote->rcpt), + SEND_ATTR_INT(ANVIL_ATTR_NTLS, anvil_remote->ntls), + SEND_ATTR_INT(ANVIL_ATTR_AUTH, anvil_remote->auth), + ATTR_TYPE_END); + } +} + +/* anvil_remote_conn_update - instantiate or update connection info */ + +static ANVIL_REMOTE *anvil_remote_conn_update(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + ANVIL_LOCAL *anvil_local; + const char *myname = "anvil_remote_conn_update"; + + if (msg_verbose) + msg_info("%s fd=%d stream=0x%lx ident=%s", + myname, vstream_fileno(client_stream), + (unsigned long) client_stream, ident); + + /* + * Look up remote connection count information. Update remote connection + * rate information. Simply reset the counter every var_anvil_time_unit + * seconds. This is easier than maintaining a moving average and it gives + * a quicker response to tresspassers. + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) { + anvil_remote = (ANVIL_REMOTE *) mymalloc(sizeof(*anvil_remote)); + ANVIL_REMOTE_FIRST_CONN(anvil_remote, ident); + htable_enter(anvil_remote_map, ident, (void *) anvil_remote); + if (max_cache_size < anvil_remote_map->used) { + max_cache_size = anvil_remote_map->used; + max_cache_time = event_time(); + } + } else { + ANVIL_REMOTE_NEXT_CONN(anvil_remote); + } + + /* + * Record this connection under the local server information, so that we + * can clean up all its connection state when the local server goes away. + */ + if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) == 0) { + anvil_local = (ANVIL_LOCAL *) mymalloc(sizeof(*anvil_local)); + ANVIL_LOCAL_INIT(anvil_local); + vstream_control(client_stream, + CA_VSTREAM_CTL_CONTEXT((void *) anvil_local), + CA_VSTREAM_CTL_END); + } + ANVIL_LOCAL_ADD_ONE(anvil_local, anvil_remote); + if (msg_verbose) + msg_info("%s: anvil_local 0x%lx", + myname, (unsigned long) anvil_local); + + return (anvil_remote); +} + +/* anvil_remote_connect - report connection event, query address status */ + +static void anvil_remote_connect(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + + /* + * Update or instantiate connection info. + */ + anvil_remote = anvil_remote_conn_update(client_stream, ident); + + /* + * Respond to the local server. + */ + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_COUNT, anvil_remote->count), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->rate), + ATTR_TYPE_END); + + /* + * Update peak statistics. + */ + if (anvil_remote->rate > max_conn_rate.value) + ANVIL_MAX_UPDATE(max_conn_rate, anvil_remote->rate, anvil_remote->ident); + if (anvil_remote->count > max_conn_count.value) + ANVIL_MAX_UPDATE(max_conn_count, anvil_remote->count, anvil_remote->ident); +} + +/* anvil_remote_mail - register message delivery request */ + +static void anvil_remote_mail(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + + /* + * Be prepared for "postfix reload" after "connect". + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) + anvil_remote = anvil_remote_conn_update(client_stream, ident); + + /* + * Update message delivery request rate and respond to local server. + */ + ANVIL_REMOTE_INCR_MAIL(anvil_remote); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->mail), + ATTR_TYPE_END); + + /* + * Update peak statistics. + */ + if (anvil_remote->mail > max_mail_rate.value) + ANVIL_MAX_UPDATE(max_mail_rate, anvil_remote->mail, anvil_remote->ident); +} + +/* anvil_remote_rcpt - register recipient address event */ + +static void anvil_remote_rcpt(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + + /* + * Be prepared for "postfix reload" after "connect". + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) + anvil_remote = anvil_remote_conn_update(client_stream, ident); + + /* + * Update recipient address rate and respond to local server. + */ + ANVIL_REMOTE_INCR_RCPT(anvil_remote); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->rcpt), + ATTR_TYPE_END); + + /* + * Update peak statistics. + */ + if (anvil_remote->rcpt > max_rcpt_rate.value) + ANVIL_MAX_UPDATE(max_rcpt_rate, anvil_remote->rcpt, anvil_remote->ident); +} + +/* anvil_remote_auth - register auth request event */ + +static void anvil_remote_auth(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + + /* + * Be prepared for "postfix reload" after "connect". + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) + anvil_remote = anvil_remote_conn_update(client_stream, ident); + + /* + * Update recipient address rate and respond to local server. + */ + ANVIL_REMOTE_INCR_AUTH(anvil_remote); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->auth), + ATTR_TYPE_END); + + /* + * Update peak statistics. + */ + if (anvil_remote->auth > max_auth_rate.value) + ANVIL_MAX_UPDATE(max_auth_rate, anvil_remote->auth, anvil_remote->ident); +} + +/* anvil_remote_newtls - register newtls event */ + +static void anvil_remote_newtls(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + + /* + * Be prepared for "postfix reload" after "connect". + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) + anvil_remote = anvil_remote_conn_update(client_stream, ident); + + /* + * Update newtls rate and respond to local server. + */ + ANVIL_REMOTE_INCR_NTLS(anvil_remote); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->ntls), + ATTR_TYPE_END); + + /* + * Update peak statistics. + */ + if (anvil_remote->ntls > max_ntls_rate.value) + ANVIL_MAX_UPDATE(max_ntls_rate, anvil_remote->ntls, anvil_remote->ident); +} + +/* anvil_remote_newtls_stat - report newtls stats */ + +static void anvil_remote_newtls_stat(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + int rate; + + /* + * Be prepared for "postfix reload" after "connect". + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) { + rate = 0; + } + + /* + * Do not report stale information. + */ + else { + if (anvil_remote->start != 0 + && anvil_remote->start + var_anvil_time_unit < event_time()) + ANVIL_REMOTE_RSET_RATE(anvil_remote, 0); + rate = anvil_remote->ntls; + } + + /* + * Respond to local server. + */ + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_RATE, rate), + ATTR_TYPE_END); +} + +/* anvil_remote_disconnect - report disconnect event */ + +static void anvil_remote_disconnect(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + ANVIL_LOCAL *anvil_local; + const char *myname = "anvil_remote_disconnect"; + + if (msg_verbose) + msg_info("%s fd=%d stream=0x%lx ident=%s", + myname, vstream_fileno(client_stream), + (unsigned long) client_stream, ident); + + /* + * Update local and remote info if this remote connection is listed for + * this local server. + */ + if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0 + && (anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) != 0 + && ANVIL_LOCAL_REMOTE_LINKED(anvil_local, anvil_remote)) { + ANVIL_REMOTE_DROP_ONE(anvil_remote); + ANVIL_LOCAL_DROP_ONE(anvil_local, anvil_remote); + } + if (msg_verbose) + msg_info("%s: anvil_local 0x%lx", + myname, (unsigned long) anvil_local); + + /* + * Respond to the local server. + */ + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + ATTR_TYPE_END); +} + +/* anvil_service_done - clean up */ + +static void anvil_service_done(VSTREAM *client_stream, char *unused_service, + char **unused_argv) +{ + ANVIL_LOCAL *anvil_local; + const char *myname = "anvil_service_done"; + + if (msg_verbose) + msg_info("%s fd=%d stream=0x%lx", + myname, vstream_fileno(client_stream), + (unsigned long) client_stream); + + /* + * Look up the local server, and get rid of any remote connection state + * that we still have for this local server. Do not destroy remote client + * status information before it expires. + */ + if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0) { + if (msg_verbose) + msg_info("%s: anvil_local 0x%lx", + myname, (unsigned long) anvil_local); + ANVIL_LOCAL_DROP_ALL(client_stream, anvil_local); + myfree((void *) anvil_local); + } else if (msg_verbose) + msg_info("client socket not found for fd=%d", + vstream_fileno(client_stream)); +} + +/* anvil_status_dump - log and reset extreme usage */ + +static void anvil_status_dump(char *unused_name, char **unused_argv) +{ + ANVIL_MAX_RATE_REPORT(max_conn_rate, "connection"); + ANVIL_MAX_COUNT_REPORT(max_conn_count, "connection"); + ANVIL_MAX_RATE_REPORT(max_mail_rate, "message"); + ANVIL_MAX_RATE_REPORT(max_rcpt_rate, "recipient"); + ANVIL_MAX_RATE_REPORT(max_ntls_rate, "newtls"); + ANVIL_MAX_RATE_REPORT(max_auth_rate, "auth"); + + if (max_cache_size > 0) { + msg_info("statistics: max cache size %d at %.15s", + max_cache_size, ctime(&max_cache_time) + 4); + max_cache_size = 0; + } +} + +/* anvil_status_update - log and reset extreme usage periodically */ + +static void anvil_status_update(int unused_event, void *context) +{ + anvil_status_dump((char *) 0, (char **) 0); + event_request_timer(anvil_status_update, context, var_anvil_stat_time); +} + +/* anvil_service - perform service for client */ + +static void anvil_service(VSTREAM *client_stream, char *unused_service, char **argv) +{ + static VSTRING *request; + static VSTRING *ident; + static const ANVIL_REQ_TABLE request_table[] = { + ANVIL_REQ_CONN, anvil_remote_connect, + ANVIL_REQ_MAIL, anvil_remote_mail, + ANVIL_REQ_RCPT, anvil_remote_rcpt, + ANVIL_REQ_NTLS, anvil_remote_newtls, + ANVIL_REQ_DISC, anvil_remote_disconnect, + ANVIL_REQ_NTLS_STAT, anvil_remote_newtls_stat, + ANVIL_REQ_AUTH, anvil_remote_auth, + ANVIL_REQ_LOOKUP, anvil_remote_lookup, + 0, 0, + }; + const ANVIL_REQ_TABLE *rp; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Initialize. + */ + if (request == 0) { + request = vstring_alloc(10); + ident = vstring_alloc(10); + } + + /* + * This routine runs whenever a client connects to the socket dedicated + * to the client connection rate management service. All + * connection-management stuff is handled by the common code in + * multi_server.c. + */ + if (msg_verbose) + msg_info("--- start request ---"); + if (attr_scan_plain(client_stream, + ATTR_FLAG_MISSING | ATTR_FLAG_STRICT, + RECV_ATTR_STR(ANVIL_ATTR_REQ, request), + RECV_ATTR_STR(ANVIL_ATTR_IDENT, ident), + ATTR_TYPE_END) == 2) { + for (rp = request_table; /* see below */ ; rp++) { + if (rp->name == 0) { + msg_warn("unrecognized request: \"%s\", ignored", STR(request)); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_FAIL), + ATTR_TYPE_END); + break; + } + if (STREQ(rp->name, STR(request))) { + rp->action(client_stream, STR(ident)); + break; + } + } + vstream_fflush(client_stream); + } else { + /* Note: invokes anvil_service_done() */ + multi_server_disconnect(client_stream); + } + if (msg_verbose) + msg_info("--- end request ---"); +} + +/* post_jail_init - post-jail initialization */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Dump and reset extreme usage every so often. + */ + event_request_timer(anvil_status_update, (void *) 0, var_anvil_stat_time); + + /* + * Initial client state tables. + */ + anvil_remote_map = htable_create(1000); + + /* + * Do not limit the number of client requests. + */ + var_use_limit = 0; + + /* + * Don't exit before the sampling interval ends. + */ + if (var_idle_limit < var_anvil_time_unit) + var_idle_limit = var_anvil_time_unit; +} + +MAIL_VERSION_STAMP_DECLARE; + +/* post_accept - announce our protocol */ + +static void post_accept(VSTREAM *stream, char *unused_name, + char **unused_argv, HTABLE *unused_table) +{ + + /* + * Announce the protocol. + */ + attr_print_plain(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_ANVIL), + ATTR_TYPE_END); + (void) vstream_fflush(stream); +} + +/* main - pass control to the multi-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_ANVIL_TIME_UNIT, DEF_ANVIL_TIME_UNIT, &var_anvil_time_unit, 1, 0, + VAR_ANVIL_STAT_TIME, DEF_ANVIL_STAT_TIME, &var_anvil_stat_time, 1, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + multi_server_main(argc, argv, anvil_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_POST_ACCEPT(post_accept), + CA_MAIL_SERVER_SOLITARY, + CA_MAIL_SERVER_PRE_DISCONN(anvil_service_done), + CA_MAIL_SERVER_EXIT(anvil_status_dump), + 0); +} diff --git a/src/bounce/.indent.pro b/src/bounce/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/bounce/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/bounce/.printfck b/src/bounce/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/bounce/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/bounce/2template_test.in b/src/bounce/2template_test.in new file mode 100644 index 0000000..e45fd32 --- /dev/null +++ b/src/bounce/2template_test.in @@ -0,0 +1,136 @@ +failure_template = < +Subject: Undelivered Mail Returned to Sender +Postmaster-Subject: Postmaster Copy: Undelivered Mail + +This is the mail system at host $myhostname. + +I'm sorry to have to inform you that your message could not +be delivered to one or more recipients. It's attached below. + +For further assistance, please send mail to postmaster. + +If you do so, please include this problem report. You can +delete your own text from the attached returned message. + + The mail system +EOF + +delay_template = < +Subject: Delayed Mail (still being retried) +Postmaster-Subject: Postmaster Warning: Delayed Mail + +This is the mail system at host $myhostname. + +#################################################################### +# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. # +#################################################################### + +Your message could not be delivered for more than $delay_warning_time_hours hour(s). +It will be retried until it is $maximal_queue_lifetime_days day(s) old. + +For further assistance, please send mail to postmaster. + +If you do so, please include this problem report. You can +delete your own text from the attached returned message. + + The mail system +EOF + +success_template = < +Subject: Successful Mail Delivery Report + +This is the mail system at host $myhostname. + +Your message was successfully delivered to the destination(s) +listed below. If the message was delivered to mailbox you will +receive no further notifications. Otherwise you may still receive +notifications of mail delivery errors from other systems. + + The mail system +EOF + +verify_template = < +Subject: Mail Delivery Status Report + +This is the mail system at host $myhostname. + +Enclosed is the mail delivery report that you requested. + + The mail system +EOF +failure_template = < +Subject: Undelivered Mail Returned to Sender +Postmaster-Subject: Postmaster Copy: Undelivered Mail + +This is the mail system at host $myhostname. + +I'm sorry to have to inform you that your message could not +be delivered to one or more recipients. It's attached below. + +For further assistance, please send mail to postmaster. + +If you do so, please include this problem report. You can +delete your own text from the attached returned message. + + The mail system +EOF + +delay_template = < +Subject: Delayed Mail (still being retried) +Postmaster-Subject: Postmaster Warning: Delayed Mail + +This is the mail system at host $myhostname. + +#################################################################### +# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. # +#################################################################### + +Your message could not be delivered for more than $delay_warning_time_hours hour(s). +It will be retried until it is $maximal_queue_lifetime_days day(s) old. + +For further assistance, please send mail to postmaster. + +If you do so, please include this problem report. You can +delete your own text from the attached returned message. + + The mail system +EOF + +success_template = < +Subject: Successful Mail Delivery Report + +This is the mail system at host $myhostname. + +Your message was successfully delivered to the destination(s) +listed below. If the message was delivered to mailbox you will +receive no further notifications. Otherwise you may still receive +notifications of mail delivery errors from other systems. + + The mail system +EOF + +verify_template = < +Subject: Mail Delivery Status Report + +This is the mail system at host $myhostname. + +Enclosed is the mail delivery report that you requested. + + The mail system +EOF diff --git a/src/bounce/Makefile.in b/src/bounce/Makefile.in new file mode 100644 index 0000000..969413a --- /dev/null +++ b/src/bounce/Makefile.in @@ -0,0 +1,695 @@ +SHELL = /bin/sh +SRCS = bounce.c bounce_append_service.c bounce_notify_service.c \ + bounce_cleanup.c bounce_notify_util.c bounce_notify_verp.c \ + bounce_one_service.c bounce_warn_service.c bounce_trace_service.c \ + bounce_template.c bounce_templates.c +OBJS = bounce.o bounce_append_service.o bounce_notify_service.o \ + bounce_cleanup.o bounce_notify_util.o bounce_notify_verp.o \ + bounce_one_service.o bounce_warn_service.o bounce_trace_service.o \ + bounce_template.o bounce_templates.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= bounce_notify_util_tester +PROG = bounce +SAMPLES = ../../conf/bounce.cf.default +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +all: $(PROG) ../../conf/bounce.cf.default + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +# Eliminate dependency on installed Postfix. +../../conf/bounce.cf.default: template_test.ref annotate.sh + rm -f $@ + ./annotate.sh $@ + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: update template_test obs_template_test 2template_test \ + with-msgid-with-long-line_test \ + with-msgid-with-eoh-event_test \ + with-msgid-no-eoh-event_test \ + no-msgid-with-eoh-event_test \ + no-msgid-no-eoh-event_test \ + with-msgid-with-filter_test + +root_tests: + +update: ../../libexec/$(PROG) $(SAMPLES) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile > printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk main.cf + rm -rf printfck + +tidy: clean + +BOUNCE_NOTIFY_UTIL_TESTER_OBJS = bounce_notify_util_tester.o \ + bounce_notify_util.o bounce_template.o bounce_templates.o + +bounce_notify_util_tester: $(BOUNCE_NOTIFY_UTIL_TESTER_OBJS) $(LIBS) + $(CC) -DTEST $(CFLAGS) -o $@ $(BOUNCE_NOTIFY_UTIL_TESTER_OBJS) \ + $(LIBS) $(SYSLIBS) + +# Avoid dependency on installed Postfix. +# XXX This still requires that default_privs, mail_owner etc. accounts exist. +template_test: $(PROG) template_test.ref + echo queue_directory=. >main.cf + echo myhostname=example.com >>main.cf + echo header_from_format=standard >>main.cf + touch -t 197101010000 main.cf + MAIL_CONFIG=. ./$(PROG) -SVzndump_templates >template_test.tmp + diff template_test.ref template_test.tmp + MAIL_CONFIG=. ./$(PROG) -SVzndump_templates \ + -o bounce_template_file=template_test.ref > template_test.tmp + diff template_test.ref template_test.tmp + rm -f template_test.tmp main.cf + +obs_template_test: $(PROG) obs_template_test.ref + echo queue_directory=. >main.cf + echo myhostname=example.com >>main.cf + echo header_from_format=obsolete >>main.cf + touch -t 197101010000 main.cf + MAIL_CONFIG=. ./$(PROG) -SVzndump_templates >template_test.tmp + diff obs_template_test.ref template_test.tmp + rm -f template_test.tmp main.cf + +2template_test: $(PROG) template_test.ref 2template_test.in + echo queue_directory=. >main.cf + echo myhostname=example.com >>main.cf + touch -t 197101010000 main.cf + MAIL_CONFIG=. ./$(PROG) -SVzndump_templates \ + -o bounce_template_file=2template_test.in > template_test.tmp + diff template_test.ref template_test.tmp + rm -f template_test.tmp main.cf + +with-msgid-with-long-line_test: bounce_notify_util_tester \ + msgfile-with-msgid-with-long-line logfile-with-msgid-with-long-line \ + with-msgid-with-long-line-no-thread.ref \ + with-msgid-with-long-line-with-thread.ref + rm -rf queue main.cf + echo 'enable_threaded_bounces = no' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-with-msgid-with-long-line queue/bounce/msgid + cp msgfile-with-msgid-with-long-line queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > with-msgid-with-long-line-no-thread.tmp + diff with-msgid-with-long-line-no-thread.ref with-msgid-with-long-line-no-thread.tmp + rm -f with-msgid-with-long-line-no-thread.tmp + : + rm -rf queue main.cf + echo 'enable_threaded_bounces = yes' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-with-msgid-with-long-line queue/bounce/msgid + cp msgfile-with-msgid-with-long-line queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > with-msgid-with-long-line-with-thread.tmp + diff with-msgid-with-long-line-with-thread.ref with-msgid-with-long-line-with-thread.tmp + rm -f with-msgid-with-long-line-with-thread.tmp + rm -rf queue main.cf + +with-msgid-with-eoh-event_test: bounce_notify_util_tester \ + msgfile-with-msgid-with-eoh-event logfile-with-msgid-with-eoh-event \ + with-msgid-with-eoh-event-no-thread.ref \ + with-msgid-with-eoh-event-with-thread.ref + rm -rf queue main.cf + echo 'enable_threaded_bounces = no' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-with-msgid-with-eoh-event queue/bounce/msgid + cp msgfile-with-msgid-with-eoh-event queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > with-msgid-with-eoh-event-no-thread.tmp + diff with-msgid-with-eoh-event-no-thread.ref with-msgid-with-eoh-event-no-thread.tmp + rm -f with-msgid-with-eoh-event-no-thread.tmp + : + rm -rf queue main.cf + echo 'enable_threaded_bounces = yes' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-with-msgid-with-eoh-event queue/bounce/msgid + cp msgfile-with-msgid-with-eoh-event queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > with-msgid-with-eoh-event-with-thread.tmp + diff with-msgid-with-eoh-event-with-thread.ref with-msgid-with-eoh-event-with-thread.tmp + rm -f with-msgid-with-eoh-event-with-thread.tmp + rm -rf queue main.cf + +with-msgid-no-eoh-event_test: bounce_notify_util_tester \ + msgfile-with-msgid-no-eoh-event logfile-with-msgid-no-eoh-event \ + with-msgid-no-eoh-event-no-thread.ref \ + with-msgid-no-eoh-event-with-thread.ref + rm -rf queue main.cf + echo 'enable_threaded_bounces = no' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-with-msgid-no-eoh-event queue/bounce/msgid + cp msgfile-with-msgid-no-eoh-event queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > with-msgid-no-eoh-event-no-thread.tmp + diff with-msgid-no-eoh-event-no-thread.ref with-msgid-no-eoh-event-no-thread.tmp + rm -f with-msgid-no-eoh-event-no-thread.tmp + : + rm -rf queue main.cf + echo 'enable_threaded_bounces = yes' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-with-msgid-no-eoh-event queue/bounce/msgid + cp msgfile-with-msgid-no-eoh-event queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > with-msgid-no-eoh-event-with-thread.tmp + diff with-msgid-no-eoh-event-with-thread.ref with-msgid-no-eoh-event-with-thread.tmp + rm -f with-msgid-no-eoh-event-with-thread.tmp + rm -rf queue main.cf + +no-msgid-with-eoh-event_test: bounce_notify_util_tester \ + msgfile-no-msgid-with-eoh-event logfile-no-msgid-with-eoh-event \ + no-msgid-with-eoh-event-no-thread.ref \ + no-msgid-with-eoh-event-with-thread.ref + rm -rf queue main.cf + echo 'enable_threaded_bounces = no' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-no-msgid-with-eoh-event queue/bounce/msgid + cp msgfile-no-msgid-with-eoh-event queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > no-msgid-with-eoh-event-no-thread.tmp + diff no-msgid-with-eoh-event-no-thread.ref no-msgid-with-eoh-event-no-thread.tmp + rm -f no-msgid-with-eoh-event-no-thread.tmp + : + rm -rf queue main.cf + echo 'enable_threaded_bounces = yes' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-no-msgid-with-eoh-event queue/bounce/msgid + cp msgfile-no-msgid-with-eoh-event queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > no-msgid-with-eoh-event-with-thread.tmp + diff no-msgid-with-eoh-event-with-thread.ref no-msgid-with-eoh-event-with-thread.tmp + rm -f no-msgid-with-eoh-event-with-thread.tmp + rm -rf queue main.cf + +no-msgid-no-eoh-event_test: bounce_notify_util_tester \ + msgfile-no-msgid-no-eoh-event logfile-no-msgid-no-eoh-event \ + no-msgid-no-eoh-event-no-thread.ref \ + no-msgid-no-eoh-event-with-thread.ref + rm -rf queue main.cf + echo 'enable_threaded_bounces = no' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-no-msgid-no-eoh-event queue/bounce/msgid + cp msgfile-no-msgid-no-eoh-event queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > no-msgid-no-eoh-event-no-thread.tmp + diff no-msgid-no-eoh-event-no-thread.ref no-msgid-no-eoh-event-no-thread.tmp + rm -f no-msgid-no-eoh-event-no-thread.tmp + : + rm -rf queue main.cf + echo 'enable_threaded_bounces = yes' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-no-msgid-no-eoh-event queue/bounce/msgid + cp msgfile-no-msgid-no-eoh-event queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > no-msgid-no-eoh-event-with-thread.tmp + diff no-msgid-no-eoh-event-with-thread.ref no-msgid-no-eoh-event-with-thread.tmp + rm -f no-msgid-no-eoh-event-with-thread.tmp + rm -rf queue main.cf + +with-msgid-with-filter_test: bounce_notify_util_tester \ + msgfile-with-msgid-with-filter logfile-with-msgid-with-filter \ + with-msgid-with-filter-no-thread.ref \ + with-msgid-with-filter-with-thread.ref + rm -rf queue main.cf + echo 'enable_threaded_bounces = no' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-with-msgid-with-filter queue/bounce/msgid + cp msgfile-with-msgid-with-filter queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > with-msgid-with-filter-no-thread.tmp + diff with-msgid-with-filter-no-thread.ref with-msgid-with-filter-no-thread.tmp + rm -f with-msgid-with-filter-no-thread.tmp + : + rm -rf queue main.cf + echo 'enable_threaded_bounces = yes' >main.cf + echo 'queue_directory = queue' >>main.cf + echo 'myhostname = mail.example' >>main.cf + touch -t 197101010000 main.cf + mkdir -p queue/active queue/bounce + cp logfile-with-msgid-with-filter queue/bounce/msgid + cp msgfile-with-msgid-with-filter queue/active/msgid + $(SHLIB_ENV) $(VALGRIND) ./bounce_notify_util_tester \ + -c. bounce active msgid 2>&1 | \ + sed 's;msgid.[0-9]*/mail.example;msgid.unix-time/mail.example;' \ + > with-msgid-with-filter-with-thread.tmp + diff with-msgid-with-filter-with-thread.ref with-msgid-with-filter-with-thread.tmp + rm -f with-msgid-with-filter-with-thread.tmp + rm -rf queue main.cf + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +bounce.o: ../../include/attr.h +bounce.o: ../../include/bounce.h +bounce.o: ../../include/bounce_log.h +bounce.o: ../../include/check_arg.h +bounce.o: ../../include/deliver_request.h +bounce.o: ../../include/dsb_scan.h +bounce.o: ../../include/dsn.h +bounce.o: ../../include/dsn_buf.h +bounce.o: ../../include/hfrom_format.h +bounce.o: ../../include/htable.h +bounce.o: ../../include/iostuff.h +bounce.o: ../../include/load_file.h +bounce.o: ../../include/mail_addr.h +bounce.o: ../../include/mail_conf.h +bounce.o: ../../include/mail_params.h +bounce.o: ../../include/mail_proto.h +bounce.o: ../../include/mail_queue.h +bounce.o: ../../include/mail_server.h +bounce.o: ../../include/mail_version.h +bounce.o: ../../include/msg.h +bounce.o: ../../include/msg_stats.h +bounce.o: ../../include/mymalloc.h +bounce.o: ../../include/nvtable.h +bounce.o: ../../include/rcpt_buf.h +bounce.o: ../../include/recipient_list.h +bounce.o: ../../include/stringops.h +bounce.o: ../../include/sys_defs.h +bounce.o: ../../include/vbuf.h +bounce.o: ../../include/vstream.h +bounce.o: ../../include/vstring.h +bounce.o: bounce.c +bounce.o: bounce_service.h +bounce.o: bounce_template.h +bounce_append_service.o: ../../include/attr.h +bounce_append_service.o: ../../include/bounce_log.h +bounce_append_service.o: ../../include/check_arg.h +bounce_append_service.o: ../../include/deliver_flock.h +bounce_append_service.o: ../../include/dsn.h +bounce_append_service.o: ../../include/dsn_buf.h +bounce_append_service.o: ../../include/htable.h +bounce_append_service.o: ../../include/iostuff.h +bounce_append_service.o: ../../include/mail_params.h +bounce_append_service.o: ../../include/mail_proto.h +bounce_append_service.o: ../../include/mail_queue.h +bounce_append_service.o: ../../include/msg.h +bounce_append_service.o: ../../include/myflock.h +bounce_append_service.o: ../../include/mymalloc.h +bounce_append_service.o: ../../include/nvtable.h +bounce_append_service.o: ../../include/quote_822_local.h +bounce_append_service.o: ../../include/quote_flags.h +bounce_append_service.o: ../../include/rcpt_buf.h +bounce_append_service.o: ../../include/recipient_list.h +bounce_append_service.o: ../../include/stringops.h +bounce_append_service.o: ../../include/sys_defs.h +bounce_append_service.o: ../../include/vbuf.h +bounce_append_service.o: ../../include/vstream.h +bounce_append_service.o: ../../include/vstring.h +bounce_append_service.o: bounce_append_service.c +bounce_append_service.o: bounce_service.h +bounce_append_service.o: bounce_template.h +bounce_cleanup.o: ../../include/attr.h +bounce_cleanup.o: ../../include/bounce_log.h +bounce_cleanup.o: ../../include/check_arg.h +bounce_cleanup.o: ../../include/dsn.h +bounce_cleanup.o: ../../include/dsn_buf.h +bounce_cleanup.o: ../../include/htable.h +bounce_cleanup.o: ../../include/mail_queue.h +bounce_cleanup.o: ../../include/msg.h +bounce_cleanup.o: ../../include/mymalloc.h +bounce_cleanup.o: ../../include/nvtable.h +bounce_cleanup.o: ../../include/rcpt_buf.h +bounce_cleanup.o: ../../include/recipient_list.h +bounce_cleanup.o: ../../include/sys_defs.h +bounce_cleanup.o: ../../include/vbuf.h +bounce_cleanup.o: ../../include/vstream.h +bounce_cleanup.o: ../../include/vstring.h +bounce_cleanup.o: bounce_cleanup.c +bounce_cleanup.o: bounce_service.h +bounce_cleanup.o: bounce_template.h +bounce_notify_service.o: ../../include/attr.h +bounce_notify_service.o: ../../include/bounce.h +bounce_notify_service.o: ../../include/bounce_log.h +bounce_notify_service.o: ../../include/check_arg.h +bounce_notify_service.o: ../../include/cleanup_user.h +bounce_notify_service.o: ../../include/deliver_request.h +bounce_notify_service.o: ../../include/dsn.h +bounce_notify_service.o: ../../include/dsn_buf.h +bounce_notify_service.o: ../../include/dsn_mask.h +bounce_notify_service.o: ../../include/htable.h +bounce_notify_service.o: ../../include/int_filt.h +bounce_notify_service.o: ../../include/iostuff.h +bounce_notify_service.o: ../../include/mail_addr.h +bounce_notify_service.o: ../../include/mail_error.h +bounce_notify_service.o: ../../include/mail_params.h +bounce_notify_service.o: ../../include/mail_proto.h +bounce_notify_service.o: ../../include/mail_queue.h +bounce_notify_service.o: ../../include/msg.h +bounce_notify_service.o: ../../include/msg_stats.h +bounce_notify_service.o: ../../include/mymalloc.h +bounce_notify_service.o: ../../include/name_mask.h +bounce_notify_service.o: ../../include/nvtable.h +bounce_notify_service.o: ../../include/post_mail.h +bounce_notify_service.o: ../../include/rcpt_buf.h +bounce_notify_service.o: ../../include/rec_type.h +bounce_notify_service.o: ../../include/recipient_list.h +bounce_notify_service.o: ../../include/smtputf8.h +bounce_notify_service.o: ../../include/stringops.h +bounce_notify_service.o: ../../include/sys_defs.h +bounce_notify_service.o: ../../include/vbuf.h +bounce_notify_service.o: ../../include/vstream.h +bounce_notify_service.o: ../../include/vstring.h +bounce_notify_service.o: bounce_notify_service.c +bounce_notify_service.o: bounce_service.h +bounce_notify_service.o: bounce_template.h +bounce_notify_util.o: ../../include/attr.h +bounce_notify_util.o: ../../include/bounce_log.h +bounce_notify_util.o: ../../include/check_arg.h +bounce_notify_util.o: ../../include/cleanup_user.h +bounce_notify_util.o: ../../include/deliver_completed.h +bounce_notify_util.o: ../../include/dsn.h +bounce_notify_util.o: ../../include/dsn_buf.h +bounce_notify_util.o: ../../include/dsn_mask.h +bounce_notify_util.o: ../../include/events.h +bounce_notify_util.o: ../../include/header_opts.h +bounce_notify_util.o: ../../include/htable.h +bounce_notify_util.o: ../../include/int_filt.h +bounce_notify_util.o: ../../include/iostuff.h +bounce_notify_util.o: ../../include/is_header.h +bounce_notify_util.o: ../../include/lex_822.h +bounce_notify_util.o: ../../include/line_wrap.h +bounce_notify_util.o: ../../include/mail_addr.h +bounce_notify_util.o: ../../include/mail_date.h +bounce_notify_util.o: ../../include/mail_error.h +bounce_notify_util.o: ../../include/mail_params.h +bounce_notify_util.o: ../../include/mail_proto.h +bounce_notify_util.o: ../../include/mail_queue.h +bounce_notify_util.o: ../../include/msg.h +bounce_notify_util.o: ../../include/myflock.h +bounce_notify_util.o: ../../include/mymalloc.h +bounce_notify_util.o: ../../include/name_mask.h +bounce_notify_util.o: ../../include/nvtable.h +bounce_notify_util.o: ../../include/post_mail.h +bounce_notify_util.o: ../../include/quote_822_local.h +bounce_notify_util.o: ../../include/quote_flags.h +bounce_notify_util.o: ../../include/rcpt_buf.h +bounce_notify_util.o: ../../include/rec_type.h +bounce_notify_util.o: ../../include/recipient_list.h +bounce_notify_util.o: ../../include/record.h +bounce_notify_util.o: ../../include/smtputf8.h +bounce_notify_util.o: ../../include/stringops.h +bounce_notify_util.o: ../../include/sys_defs.h +bounce_notify_util.o: ../../include/vbuf.h +bounce_notify_util.o: ../../include/vstream.h +bounce_notify_util.o: ../../include/vstring.h +bounce_notify_util.o: bounce_notify_util.c +bounce_notify_util.o: bounce_service.h +bounce_notify_util.o: bounce_template.h +bounce_notify_util_tester.o: ../../include/attr.h +bounce_notify_util_tester.o: ../../include/bounce_log.h +bounce_notify_util_tester.o: ../../include/check_arg.h +bounce_notify_util_tester.o: ../../include/dsn.h +bounce_notify_util_tester.o: ../../include/dsn_buf.h +bounce_notify_util_tester.o: ../../include/dsn_mask.h +bounce_notify_util_tester.o: ../../include/hfrom_format.h +bounce_notify_util_tester.o: ../../include/htable.h +bounce_notify_util_tester.o: ../../include/mail_conf.h +bounce_notify_util_tester.o: ../../include/mail_params.h +bounce_notify_util_tester.o: ../../include/msg.h +bounce_notify_util_tester.o: ../../include/mymalloc.h +bounce_notify_util_tester.o: ../../include/nvtable.h +bounce_notify_util_tester.o: ../../include/rcpt_buf.h +bounce_notify_util_tester.o: ../../include/rec_type.h +bounce_notify_util_tester.o: ../../include/recipient_list.h +bounce_notify_util_tester.o: ../../include/record.h +bounce_notify_util_tester.o: ../../include/sys_defs.h +bounce_notify_util_tester.o: ../../include/test_main.h +bounce_notify_util_tester.o: ../../include/vbuf.h +bounce_notify_util_tester.o: ../../include/vstream.h +bounce_notify_util_tester.o: ../../include/vstring.h +bounce_notify_util_tester.o: bounce_notify_util_tester.c +bounce_notify_util_tester.o: bounce_service.h +bounce_notify_util_tester.o: bounce_template.h +bounce_notify_verp.o: ../../include/attr.h +bounce_notify_verp.o: ../../include/bounce.h +bounce_notify_verp.o: ../../include/bounce_log.h +bounce_notify_verp.o: ../../include/check_arg.h +bounce_notify_verp.o: ../../include/cleanup_user.h +bounce_notify_verp.o: ../../include/deliver_request.h +bounce_notify_verp.o: ../../include/dsn.h +bounce_notify_verp.o: ../../include/dsn_buf.h +bounce_notify_verp.o: ../../include/dsn_mask.h +bounce_notify_verp.o: ../../include/htable.h +bounce_notify_verp.o: ../../include/int_filt.h +bounce_notify_verp.o: ../../include/iostuff.h +bounce_notify_verp.o: ../../include/mail_addr.h +bounce_notify_verp.o: ../../include/mail_error.h +bounce_notify_verp.o: ../../include/mail_params.h +bounce_notify_verp.o: ../../include/mail_proto.h +bounce_notify_verp.o: ../../include/mail_queue.h +bounce_notify_verp.o: ../../include/msg.h +bounce_notify_verp.o: ../../include/msg_stats.h +bounce_notify_verp.o: ../../include/mymalloc.h +bounce_notify_verp.o: ../../include/name_mask.h +bounce_notify_verp.o: ../../include/nvtable.h +bounce_notify_verp.o: ../../include/post_mail.h +bounce_notify_verp.o: ../../include/rcpt_buf.h +bounce_notify_verp.o: ../../include/rec_type.h +bounce_notify_verp.o: ../../include/recipient_list.h +bounce_notify_verp.o: ../../include/smtputf8.h +bounce_notify_verp.o: ../../include/stringops.h +bounce_notify_verp.o: ../../include/sys_defs.h +bounce_notify_verp.o: ../../include/vbuf.h +bounce_notify_verp.o: ../../include/verp_sender.h +bounce_notify_verp.o: ../../include/vstream.h +bounce_notify_verp.o: ../../include/vstring.h +bounce_notify_verp.o: bounce_notify_verp.c +bounce_notify_verp.o: bounce_service.h +bounce_notify_verp.o: bounce_template.h +bounce_one_service.o: ../../include/attr.h +bounce_one_service.o: ../../include/bounce.h +bounce_one_service.o: ../../include/bounce_log.h +bounce_one_service.o: ../../include/check_arg.h +bounce_one_service.o: ../../include/cleanup_user.h +bounce_one_service.o: ../../include/deliver_request.h +bounce_one_service.o: ../../include/dsn.h +bounce_one_service.o: ../../include/dsn_buf.h +bounce_one_service.o: ../../include/dsn_mask.h +bounce_one_service.o: ../../include/htable.h +bounce_one_service.o: ../../include/int_filt.h +bounce_one_service.o: ../../include/iostuff.h +bounce_one_service.o: ../../include/mail_addr.h +bounce_one_service.o: ../../include/mail_error.h +bounce_one_service.o: ../../include/mail_params.h +bounce_one_service.o: ../../include/mail_proto.h +bounce_one_service.o: ../../include/msg.h +bounce_one_service.o: ../../include/msg_stats.h +bounce_one_service.o: ../../include/mymalloc.h +bounce_one_service.o: ../../include/name_mask.h +bounce_one_service.o: ../../include/nvtable.h +bounce_one_service.o: ../../include/post_mail.h +bounce_one_service.o: ../../include/rcpt_buf.h +bounce_one_service.o: ../../include/rec_type.h +bounce_one_service.o: ../../include/recipient_list.h +bounce_one_service.o: ../../include/smtputf8.h +bounce_one_service.o: ../../include/stringops.h +bounce_one_service.o: ../../include/sys_defs.h +bounce_one_service.o: ../../include/vbuf.h +bounce_one_service.o: ../../include/vstream.h +bounce_one_service.o: ../../include/vstring.h +bounce_one_service.o: bounce_one_service.c +bounce_one_service.o: bounce_service.h +bounce_one_service.o: bounce_template.h +bounce_template.o: ../../include/attr.h +bounce_template.o: ../../include/bounce_log.h +bounce_template.o: ../../include/check_arg.h +bounce_template.o: ../../include/dsn.h +bounce_template.o: ../../include/dsn_buf.h +bounce_template.o: ../../include/hfrom_format.h +bounce_template.o: ../../include/htable.h +bounce_template.o: ../../include/iostuff.h +bounce_template.o: ../../include/is_header.h +bounce_template.o: ../../include/mac_expand.h +bounce_template.o: ../../include/mac_parse.h +bounce_template.o: ../../include/mail_conf.h +bounce_template.o: ../../include/mail_params.h +bounce_template.o: ../../include/mail_proto.h +bounce_template.o: ../../include/midna_domain.h +bounce_template.o: ../../include/msg.h +bounce_template.o: ../../include/mymalloc.h +bounce_template.o: ../../include/nvtable.h +bounce_template.o: ../../include/rcpt_buf.h +bounce_template.o: ../../include/recipient_list.h +bounce_template.o: ../../include/split_at.h +bounce_template.o: ../../include/stringops.h +bounce_template.o: ../../include/sys_defs.h +bounce_template.o: ../../include/vbuf.h +bounce_template.o: ../../include/vstream.h +bounce_template.o: ../../include/vstring.h +bounce_template.o: bounce_service.h +bounce_template.o: bounce_template.c +bounce_template.o: bounce_template.h +bounce_templates.o: ../../include/attr.h +bounce_templates.o: ../../include/check_arg.h +bounce_templates.o: ../../include/htable.h +bounce_templates.o: ../../include/iostuff.h +bounce_templates.o: ../../include/mail_addr.h +bounce_templates.o: ../../include/mail_proto.h +bounce_templates.o: ../../include/msg.h +bounce_templates.o: ../../include/mymalloc.h +bounce_templates.o: ../../include/nvtable.h +bounce_templates.o: ../../include/stringops.h +bounce_templates.o: ../../include/sys_defs.h +bounce_templates.o: ../../include/vbuf.h +bounce_templates.o: ../../include/vstream.h +bounce_templates.o: ../../include/vstring.h +bounce_templates.o: ../../include/vstring_vstream.h +bounce_templates.o: bounce_template.h +bounce_templates.o: bounce_templates.c +bounce_trace_service.o: ../../include/attr.h +bounce_trace_service.o: ../../include/bounce_log.h +bounce_trace_service.o: ../../include/check_arg.h +bounce_trace_service.o: ../../include/cleanup_user.h +bounce_trace_service.o: ../../include/deliver_request.h +bounce_trace_service.o: ../../include/dsn.h +bounce_trace_service.o: ../../include/dsn_buf.h +bounce_trace_service.o: ../../include/dsn_mask.h +bounce_trace_service.o: ../../include/htable.h +bounce_trace_service.o: ../../include/int_filt.h +bounce_trace_service.o: ../../include/iostuff.h +bounce_trace_service.o: ../../include/mail_addr.h +bounce_trace_service.o: ../../include/mail_error.h +bounce_trace_service.o: ../../include/mail_params.h +bounce_trace_service.o: ../../include/mail_proto.h +bounce_trace_service.o: ../../include/mail_queue.h +bounce_trace_service.o: ../../include/msg.h +bounce_trace_service.o: ../../include/msg_stats.h +bounce_trace_service.o: ../../include/mymalloc.h +bounce_trace_service.o: ../../include/name_mask.h +bounce_trace_service.o: ../../include/nvtable.h +bounce_trace_service.o: ../../include/post_mail.h +bounce_trace_service.o: ../../include/rcpt_buf.h +bounce_trace_service.o: ../../include/rec_type.h +bounce_trace_service.o: ../../include/recipient_list.h +bounce_trace_service.o: ../../include/smtputf8.h +bounce_trace_service.o: ../../include/stringops.h +bounce_trace_service.o: ../../include/sys_defs.h +bounce_trace_service.o: ../../include/vbuf.h +bounce_trace_service.o: ../../include/vstream.h +bounce_trace_service.o: ../../include/vstring.h +bounce_trace_service.o: bounce_service.h +bounce_trace_service.o: bounce_template.h +bounce_trace_service.o: bounce_trace_service.c +bounce_warn_service.o: ../../include/attr.h +bounce_warn_service.o: ../../include/bounce_log.h +bounce_warn_service.o: ../../include/check_arg.h +bounce_warn_service.o: ../../include/cleanup_user.h +bounce_warn_service.o: ../../include/dsn.h +bounce_warn_service.o: ../../include/dsn_buf.h +bounce_warn_service.o: ../../include/dsn_mask.h +bounce_warn_service.o: ../../include/htable.h +bounce_warn_service.o: ../../include/int_filt.h +bounce_warn_service.o: ../../include/iostuff.h +bounce_warn_service.o: ../../include/mail_addr.h +bounce_warn_service.o: ../../include/mail_error.h +bounce_warn_service.o: ../../include/mail_params.h +bounce_warn_service.o: ../../include/mail_proto.h +bounce_warn_service.o: ../../include/mail_queue.h +bounce_warn_service.o: ../../include/msg.h +bounce_warn_service.o: ../../include/mymalloc.h +bounce_warn_service.o: ../../include/name_mask.h +bounce_warn_service.o: ../../include/nvtable.h +bounce_warn_service.o: ../../include/post_mail.h +bounce_warn_service.o: ../../include/rcpt_buf.h +bounce_warn_service.o: ../../include/rec_type.h +bounce_warn_service.o: ../../include/recipient_list.h +bounce_warn_service.o: ../../include/smtputf8.h +bounce_warn_service.o: ../../include/stringops.h +bounce_warn_service.o: ../../include/sys_defs.h +bounce_warn_service.o: ../../include/vbuf.h +bounce_warn_service.o: ../../include/vstream.h +bounce_warn_service.o: ../../include/vstring.h +bounce_warn_service.o: bounce_service.h +bounce_warn_service.o: bounce_template.h +bounce_warn_service.o: bounce_warn_service.c diff --git a/src/bounce/annotate.sh b/src/bounce/annotate.sh new file mode 100755 index 0000000..c2acaa8 --- /dev/null +++ b/src/bounce/annotate.sh @@ -0,0 +1,120 @@ +#!/bin/sh + +cat <<'EOF' +# +# Do not edit this file. This file shows the default delivery status +# notification (DSN) messages that are built into Postfix. +# +# To change Postfix DSN messages, perhaps to add non-English text, +# follow instructions in the bounce(5) manual page. +# +EOF + +# QUICK INSTRUCTIONS: +# +#-Edit a temporary copy of this file, and preview the result of $name +# expansions with "postconf -b temporary_file". If there are any +# problems, Postfix will log "warning" or "fatal" messages to the +# maillog file. +# +#-The template file can specify bounce message templates for +# failed mail, for delayed mail, for successful delivery, or for +# verbose delivery. You don't have to specify all templates. +# +#-Each template starts with "template_name = < +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single-threaded server skeleton. */ + +#include + +/* Application-specific. */ + +#include + + /* + * Tunables. + */ +int var_bounce_limit; +int var_max_queue_time; +int var_delay_warn_time; +char *var_notify_classes; +char *var_bounce_rcpt; +char *var_2bounce_rcpt; +char *var_delay_rcpt; +char *var_bounce_tmpl; +bool var_threaded_bounce; +char *var_hfrom_format; /* header_from_format */ + + /* + * We're single threaded, so we can avoid some memory allocation overhead. + */ +static VSTRING *queue_id; +static VSTRING *queue_name; +static RCPT_BUF *rcpt_buf; +static VSTRING *encoding; +static VSTRING *sender; +static VSTRING *dsn_envid; +static VSTRING *verp_delims; +static DSN_BUF *dsn_buf; + + /* + * Templates. + */ +BOUNCE_TEMPLATES *bounce_templates; + + /* + * From: header format. + */ +int bounce_hfrom_format; + +#define STR vstring_str + +#define VS_NEUTER(s) printable(vstring_str(s), '?') + +/* bounce_append_proto - bounce_append server protocol */ + +static int bounce_append_proto(char *service_name, VSTREAM *client) +{ + const char *myname = "bounce_append_proto"; + int flags; + + /* + * Read and validate the client request. + */ + if (mail_command_server(client, + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags), + RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), + RECV_ATTR_FUNC(rcpb_scan, (void *) rcpt_buf), + RECV_ATTR_FUNC(dsb_scan, (void *) dsn_buf), + ATTR_TYPE_END) != 4) { + msg_warn("malformed request"); + return (-1); + } + + /* + * Sanitize input. + */ + if (mail_queue_id_ok(STR(queue_id)) == 0) { + msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); + return (-1); + } + VS_NEUTER(rcpt_buf->address); + VS_NEUTER(rcpt_buf->orig_addr); + VS_NEUTER(rcpt_buf->dsn_orcpt); + VS_NEUTER(dsn_buf->status); + VS_NEUTER(dsn_buf->action); + VS_NEUTER(dsn_buf->reason); + VS_NEUTER(dsn_buf->dtype); + VS_NEUTER(dsn_buf->dtext); + VS_NEUTER(dsn_buf->mtype); + VS_NEUTER(dsn_buf->mname); + (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf); + (void) DSN_FROM_DSN_BUF(dsn_buf); + + /* + * Beware: some DSN or RECIPIENT fields may be null; access dsn_buf and + * rcpt_buf buffers instead. See DSN_FROM_DSN_BUF() and + * RECIPIENT_FROM_RCPT_BUF(). + */ + if (msg_verbose) + msg_info("%s: flags=0x%x service=%s id=%s org_to=%s to=%s off=%ld dsn_org=%s, notif=0x%x stat=%s act=%s why=%s", + myname, flags, service_name, STR(queue_id), + STR(rcpt_buf->orig_addr), STR(rcpt_buf->address), + rcpt_buf->offset, STR(rcpt_buf->dsn_orcpt), + rcpt_buf->dsn_notify, STR(dsn_buf->status), + STR(dsn_buf->action), STR(dsn_buf->reason)); + + /* + * On request by the client, set up a trap to delete the log file in case + * of errors. + */ + if (flags & BOUNCE_FLAG_CLEAN) + bounce_cleanup_register(service_name, STR(queue_id)); + + /* + * Execute the request. + */ + return (bounce_append_service(flags, service_name, STR(queue_id), + &rcpt_buf->rcpt, &dsn_buf->dsn)); +} + +/* bounce_notify_proto - bounce_notify server protocol */ + +static int bounce_notify_proto(char *service_name, VSTREAM *client, + int (*service) (int, char *, char *, char *, + char *, int, char *, char *, int, + BOUNCE_TEMPLATES *)) +{ + const char *myname = "bounce_notify_proto"; + int flags; + int smtputf8; + int dsn_ret; + + /* + * Read and validate the client request. + */ + if (mail_command_server(client, + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags), + RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name), + RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), + RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding), + RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8), + RECV_ATTR_STR(MAIL_ATTR_SENDER, sender), + RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid), + RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret), + ATTR_TYPE_END) != 8) { + msg_warn("malformed request"); + return (-1); + } + + /* + * Sanitize input. + */ + if (mail_queue_name_ok(STR(queue_name)) == 0) { + msg_warn("malformed queue name: %s", printable(STR(queue_name), '?')); + return (-1); + } + if (mail_queue_id_ok(STR(queue_id)) == 0) { + msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); + return (-1); + } + VS_NEUTER(encoding); + VS_NEUTER(sender); + VS_NEUTER(dsn_envid); + if (msg_verbose) + msg_info("%s: flags=0x%x service=%s queue=%s id=%s encoding=%s smtputf8=%d sender=%s envid=%s ret=0x%x", + myname, flags, service_name, STR(queue_name), STR(queue_id), + STR(encoding), smtputf8, STR(sender), STR(dsn_envid), + dsn_ret); + + /* + * On request by the client, set up a trap to delete the log file in case + * of errors. + */ + if (flags & BOUNCE_FLAG_CLEAN) + bounce_cleanup_register(service_name, STR(queue_id)); + + /* + * Execute the request. + */ + return (service(flags, service_name, STR(queue_name), + STR(queue_id), STR(encoding), smtputf8, + STR(sender), STR(dsn_envid), dsn_ret, + bounce_templates)); +} + +/* bounce_verp_proto - bounce_notify server protocol, VERP style */ + +static int bounce_verp_proto(char *service_name, VSTREAM *client) +{ + const char *myname = "bounce_verp_proto"; + int flags; + int smtputf8; + int dsn_ret; + + /* + * Read and validate the client request. + */ + if (mail_command_server(client, + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags), + RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name), + RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), + RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding), + RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8), + RECV_ATTR_STR(MAIL_ATTR_SENDER, sender), + RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid), + RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret), + RECV_ATTR_STR(MAIL_ATTR_VERPDL, verp_delims), + ATTR_TYPE_END) != 9) { + msg_warn("malformed request"); + return (-1); + } + + /* + * Sanitize input. + */ + if (mail_queue_name_ok(STR(queue_name)) == 0) { + msg_warn("malformed queue name: %s", printable(STR(queue_name), '?')); + return (-1); + } + if (mail_queue_id_ok(STR(queue_id)) == 0) { + msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); + return (-1); + } + VS_NEUTER(encoding); + VS_NEUTER(sender); + VS_NEUTER(dsn_envid); + VS_NEUTER(verp_delims); + if (strlen(STR(verp_delims)) != 2) { + msg_warn("malformed verp delimiter string: %s", STR(verp_delims)); + return (-1); + } + if (msg_verbose) + msg_info("%s: flags=0x%x service=%s queue=%s id=%s encoding=%s smtputf8=%d sender=%s envid=%s ret=0x%x delim=%s", + myname, flags, service_name, STR(queue_name), + STR(queue_id), STR(encoding), smtputf8, STR(sender), + STR(dsn_envid), dsn_ret, STR(verp_delims)); + + /* + * On request by the client, set up a trap to delete the log file in case + * of errors. + */ + if (flags & BOUNCE_FLAG_CLEAN) + bounce_cleanup_register(service_name, STR(queue_id)); + + /* + * Execute the request. Fall back to traditional notification if a bounce + * was returned as undeliverable, because we don't want to VERPify those. + */ + if (!*STR(sender) || !strcasecmp_utf8(STR(sender), + mail_addr_double_bounce())) { + msg_warn("request to send VERP-style notification of bounced mail"); + return (bounce_notify_service(flags, service_name, STR(queue_name), + STR(queue_id), STR(encoding), smtputf8, + STR(sender), STR(dsn_envid), dsn_ret, + bounce_templates)); + } else + return (bounce_notify_verp(flags, service_name, STR(queue_name), + STR(queue_id), STR(encoding), smtputf8, + STR(sender), STR(dsn_envid), dsn_ret, + STR(verp_delims), bounce_templates)); +} + +/* bounce_one_proto - bounce_one server protocol */ + +static int bounce_one_proto(char *service_name, VSTREAM *client) +{ + const char *myname = "bounce_one_proto"; + int flags; + int smtputf8; + int dsn_ret; + + /* + * Read and validate the client request. + */ + if (mail_command_server(client, + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags), + RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name), + RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), + RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding), + RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8), + RECV_ATTR_STR(MAIL_ATTR_SENDER, sender), + RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid), + RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret), + RECV_ATTR_FUNC(rcpb_scan, (void *) rcpt_buf), + RECV_ATTR_FUNC(dsb_scan, (void *) dsn_buf), + ATTR_TYPE_END) != 10) { + msg_warn("malformed request"); + return (-1); + } + + /* + * Sanitize input. + */ + if (strcmp(service_name, MAIL_SERVICE_BOUNCE) != 0) { + msg_warn("wrong service name \"%s\" for one-recipient bouncing", + service_name); + return (-1); + } + if (mail_queue_name_ok(STR(queue_name)) == 0) { + msg_warn("malformed queue name: %s", printable(STR(queue_name), '?')); + return (-1); + } + if (mail_queue_id_ok(STR(queue_id)) == 0) { + msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); + return (-1); + } + VS_NEUTER(encoding); + VS_NEUTER(sender); + VS_NEUTER(dsn_envid); + VS_NEUTER(rcpt_buf->address); + VS_NEUTER(rcpt_buf->orig_addr); + VS_NEUTER(rcpt_buf->dsn_orcpt); + VS_NEUTER(dsn_buf->status); + VS_NEUTER(dsn_buf->action); + VS_NEUTER(dsn_buf->reason); + VS_NEUTER(dsn_buf->dtype); + VS_NEUTER(dsn_buf->dtext); + VS_NEUTER(dsn_buf->mtype); + VS_NEUTER(dsn_buf->mname); + (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf); + (void) DSN_FROM_DSN_BUF(dsn_buf); + + /* + * Beware: some DSN or RECIPIENT fields may be null; access dsn_buf and + * rcpt_buf buffers instead. See DSN_FROM_DSN_BUF() and + * RECIPIENT_FROM_RCPT_BUF(). + */ + if (msg_verbose) + msg_info("%s: flags=0x%x queue=%s id=%s encoding=%s smtputf8=%d sender=%s envid=%s dsn_ret=0x%x orig_to=%s to=%s off=%ld dsn_orig=%s notif=0x%x stat=%s act=%s why=%s", + myname, flags, STR(queue_name), STR(queue_id), + STR(encoding), smtputf8, STR(sender), STR(dsn_envid), + dsn_ret, STR(rcpt_buf->orig_addr), STR(rcpt_buf->address), + rcpt_buf->offset, STR(rcpt_buf->dsn_orcpt), + rcpt_buf->dsn_notify, STR(dsn_buf->status), + STR(dsn_buf->action), STR(dsn_buf->reason)); + + /* + * Execute the request. + */ + return (bounce_one_service(flags, STR(queue_name), STR(queue_id), + STR(encoding), smtputf8, STR(sender), + STR(dsn_envid), dsn_ret, rcpt_buf, + dsn_buf, bounce_templates)); +} + +/* bounce_service - parse bounce command type and delegate */ + +static void bounce_service(VSTREAM *client, char *service_name, char **argv) +{ + int command; + int status; + + /* + * Sanity check. This service takes no command-line arguments. The + * service name should be usable as a subdirectory name. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + if (mail_queue_name_ok(service_name) == 0) + msg_fatal("malformed service name: %s", service_name); + + /* + * Announce the protocol. + */ + attr_print(client, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_BOUNCE), + ATTR_TYPE_END); + (void) vstream_fflush(client); + + /* + * Read and validate the first parameter of the client request. Let the + * request-specific protocol routines take care of the remainder. + */ + if (attr_scan(client, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, + RECV_ATTR_INT(MAIL_ATTR_NREQ, &command), 0) != 1) { + msg_warn("malformed request"); + status = -1; + } else if (command == BOUNCE_CMD_VERP) { + status = bounce_verp_proto(service_name, client); + } else if (command == BOUNCE_CMD_FLUSH) { + status = bounce_notify_proto(service_name, client, + bounce_notify_service); + } else if (command == BOUNCE_CMD_WARN) { + status = bounce_notify_proto(service_name, client, + bounce_warn_service); + } else if (command == BOUNCE_CMD_TRACE) { + status = bounce_notify_proto(service_name, client, + bounce_trace_service); + } else if (command == BOUNCE_CMD_APPEND) { + status = bounce_append_proto(service_name, client); + } else if (command == BOUNCE_CMD_ONE) { + status = bounce_one_proto(service_name, client); + } else { + msg_warn("unknown command: %d", command); + status = -1; + } + + /* + * When the request has completed, send the completion status to the + * client. + */ + attr_print(client, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + ATTR_TYPE_END); + vstream_fflush(client); + + /* + * When a cleanup trap was set, delete the log file in case of error. + * This includes errors while sending the completion status to the + * client. + */ + if (bounce_cleanup_path) { + if (status || vstream_ferror(client)) + bounce_cleanup_log(); + bounce_cleanup_unregister(); + } +} + +static void load_helper(VSTREAM *stream, void *context) +{ + BOUNCE_TEMPLATES *templates = (BOUNCE_TEMPLATES *) context; + + bounce_templates_load(stream, templates); +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Bundle up a bunch of bounce template information. + */ + bounce_templates = bounce_templates_create(); + + /* + * Load the alternate message files (if specified) before entering the + * chroot jail. + */ + if (*var_bounce_tmpl) + load_file(var_bounce_tmpl, load_helper, (void *) bounce_templates); +} + +/* post_jail_init - initialize after entering chroot jail */ + +static void post_jail_init(char *service_name, char **unused_argv) +{ + bounce_hfrom_format = hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format); + + /* + * Special case: dump bounce templates. This is not part of the master(5) + * public interface. This internal interface is used by the postconf + * command. It was implemented before bounce templates were isolated into + * modules that could have been called directly. + */ + if (strcmp(service_name, "dump_templates") == 0) { + bounce_templates_dump(VSTREAM_OUT, bounce_templates); + vstream_fflush(VSTREAM_OUT); + exit(0); + } + if (strcmp(service_name, "expand_templates") == 0) { + bounce_templates_expand(VSTREAM_OUT, bounce_templates); + vstream_fflush(VSTREAM_OUT); + exit(0); + } + + /* + * Initialize. We're single threaded so we can reuse some memory upon + * successive requests. + */ + queue_id = vstring_alloc(10); + queue_name = vstring_alloc(10); + rcpt_buf = rcpb_create(); + encoding = vstring_alloc(10); + sender = vstring_alloc(10); + dsn_envid = vstring_alloc(10); + verp_delims = vstring_alloc(10); + dsn_buf = dsb_create(); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static const CONFIG_INT_TABLE int_table[] = { + VAR_BOUNCE_LIMIT, DEF_BOUNCE_LIMIT, &var_bounce_limit, 1, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 0, 8640000, + VAR_DELAY_WARN_TIME, DEF_DELAY_WARN_TIME, &var_delay_warn_time, 0, 0, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, + VAR_BOUNCE_RCPT, DEF_BOUNCE_RCPT, &var_bounce_rcpt, 1, 0, + VAR_2BOUNCE_RCPT, DEF_2BOUNCE_RCPT, &var_2bounce_rcpt, 1, 0, + VAR_DELAY_RCPT, DEF_DELAY_RCPT, &var_delay_rcpt, 1, 0, + VAR_BOUNCE_TMPL, DEF_BOUNCE_TMPL, &var_bounce_tmpl, 0, 0, + VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0, + 0, + }; + static const CONFIG_NBOOL_TABLE nbool_table[] = { + VAR_THREADED_BOUNCE, DEF_THREADED_BOUNCE, &var_threaded_bounce, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Pass control to the single-threaded service skeleton. + */ + single_server_main(argc, argv, bounce_service, + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_NBOOL_TABLE(nbool_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_UNLIMITED, + 0); +} diff --git a/src/bounce/bounce_append_service.c b/src/bounce/bounce_append_service.c new file mode 100644 index 0000000..c3cea0b --- /dev/null +++ b/src/bounce/bounce_append_service.c @@ -0,0 +1,168 @@ +/*++ +/* NAME +/* bounce_append_service 3 +/* SUMMARY +/* append record to bounce log, server side +/* SYNOPSIS +/* #include "bounce_service.h" +/* +/* int bounce_append_service(flags, service, queue_id, rcpt, dsn), +/* int flags; +/* char *service; +/* char *queue_id; +/* RECIPIENT *rcpt; +/* DSN *dsn; +/* DESCRIPTION +/* This module implements the server side of the bounce_append() +/* (append bounce log) request. This routine either succeeds or +/* it raises a fatal error. +/* DIAGNOSTICS +/* Fatal errors: all file access errors; memory allocation errors. +/* BUGS +/* SEE ALSO +/* bounce(3) basic bounce service client interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "bounce_service.h" + +/* bounce_append_service - append bounce log */ + +int bounce_append_service(int unused_flags, char *service, char *queue_id, + RECIPIENT *rcpt, DSN *dsn) +{ + VSTRING *in_buf = vstring_alloc(100); + VSTREAM *log; + long orig_length; + + /* + * This code is paranoid for a good reason. Once the bounce service takes + * responsibility, the mail system will make no further attempts to + * deliver this recipient. Whenever file access fails, assume that the + * system is under stress or that something has been mis-configured, and + * force a backoff by raising a fatal run-time error. + */ + log = mail_queue_open(service, queue_id, + O_WRONLY | O_APPEND | O_CREAT, 0600); + if (log == 0) + msg_fatal("open file %s %s: %m", service, queue_id); + + /* + * Lock out other processes to avoid truncating someone else's data in + * case of trouble. + */ + if (deliver_flock(vstream_fileno(log), INTERNAL_LOCK, (VSTRING *) 0) < 0) + msg_fatal("lock file %s %s: %m", service, queue_id); + + /* + * Now, go for it. Append a record. Truncate the log to the original + * length when the append operation fails. We use the plain stream-lf + * file format because we do not need anything more complicated. As a + * benefit, we can still recover some data when the file is a little + * garbled. + * + * XXX addresses in defer logfiles are in printable quoted form, while + * addresses in message envelope records are in raw unquoted form. This + * may change once we replace the present ad-hoc bounce/defer logfile + * format by one that is transparent for control etc. characters. See + * also: showq/showq.c. + * + * While migrating from old format to new format, allow backwards + * compatibility by writing an old-style record before the new-style + * records. + */ + if ((orig_length = vstream_fseek(log, 0L, SEEK_END)) < 0) + msg_fatal("seek file %s %s: %m", service, queue_id); + +#define NOT_NULL_EMPTY(s) ((s) != 0 && *(s) != 0) +#define STR(x) vstring_str(x) + + vstream_fputs("\n", log); + if (var_oldlog_compat) { + vstream_fprintf(log, "<%s>: %s\n", *rcpt->address == 0 ? "" : + STR(quote_822_local(in_buf, rcpt->address)), + dsn->reason); + } + vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_RECIP, *rcpt->address ? + STR(quote_822_local(in_buf, rcpt->address)) : "<>"); + if (NOT_NULL_EMPTY(rcpt->orig_addr) + && strcasecmp_utf8(rcpt->address, rcpt->orig_addr) != 0) + vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_ORCPT, + STR(quote_822_local(in_buf, rcpt->orig_addr))); + if (rcpt->offset > 0) + vstream_fprintf(log, "%s=%ld\n", MAIL_ATTR_OFFSET, rcpt->offset); + if (NOT_NULL_EMPTY(rcpt->dsn_orcpt)) + vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_ORCPT, rcpt->dsn_orcpt); + if (rcpt->dsn_notify != 0) + vstream_fprintf(log, "%s=%d\n", MAIL_ATTR_DSN_NOTIFY, rcpt->dsn_notify); + + if (NOT_NULL_EMPTY(dsn->status)) + vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_STATUS, dsn->status); + if (NOT_NULL_EMPTY(dsn->action)) + vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_ACTION, dsn->action); + if (NOT_NULL_EMPTY(dsn->dtype) && NOT_NULL_EMPTY(dsn->dtext)) { + vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_DTYPE, dsn->dtype); + vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_DTEXT, dsn->dtext); + } + if (NOT_NULL_EMPTY(dsn->mtype) && NOT_NULL_EMPTY(dsn->mname)) { + vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_MTYPE, dsn->mtype); + vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_DSN_MNAME, dsn->mname); + } + if (NOT_NULL_EMPTY(dsn->reason)) + vstream_fprintf(log, "%s=%s\n", MAIL_ATTR_WHY, dsn->reason); + vstream_fputs("\n", log); + + if (vstream_fflush(log) != 0 || fsync(vstream_fileno(log)) < 0) { +#ifndef NO_TRUNCATE + if (ftruncate(vstream_fileno(log), (off_t) orig_length) < 0) + msg_fatal("truncate file %s %s: %m", service, queue_id); +#endif + msg_fatal("append file %s %s: %m", service, queue_id); + } + + /* + * Darn. If closing the log detects a problem, the only way to undo the + * damage is to open the log once more, and to truncate the log to the + * original length. But, this could happen only when the log is kept on a + * remote file system, and that is not recommended practice anyway. + */ + if (vstream_fclose(log) != 0) + msg_warn("append file %s %s: %m", service, queue_id); + + vstring_free(in_buf); + return (0); +} diff --git a/src/bounce/bounce_cleanup.c b/src/bounce/bounce_cleanup.c new file mode 100644 index 0000000..9fb900b --- /dev/null +++ b/src/bounce/bounce_cleanup.c @@ -0,0 +1,177 @@ +/*++ +/* NAME +/* bounce_cleanup 3 +/* SUMMARY +/* cleanup logfile upon error +/* SYNOPSIS +/* #include "bounce_service.h" +/* +/* int bounce_cleanup_registered() +/* +/* void bounce_cleanup_register(queue_id) +/* char *queue_id; +/* +/* void bounce_cleanup_log(void) +/* +/* void bounce_cleanup_unregister(void) +/* DESCRIPTION +/* This module implements support for deleting the current +/* bounce logfile in case of errors, and upon the arrival +/* of a SIGTERM signal (shutdown). +/* +/* bounce_cleanup_register() registers a callback routine with the +/* run-time error handler, for automatic logfile removal in case +/* of a fatal run-time error. +/* +/* bounce_cleanup_unregister() cleans up storage used by +/* bounce_cleanup_register(). +/* +/* In-between bounce_cleanup_register() and bounce_cleanup_unregister() +/* calls, a call of bounce_cleanup_log() will delete the registered +/* bounce logfile. +/* +/* bounce_cleanup_registered() returns non-zero when a cleanup +/* trap has been set. +/* DIAGNOSTICS +/* Fatal error: all file access errors. Panic: nested calls of +/* bounce_cleanup_register(); any calls of bounce_cleanup_unregister() +/* or bounce_cleanup_log() without preceding bounce_cleanup_register() +/* call. +/* BUGS +/* SEE ALSO +/* master(8) process manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include "bounce_service.h" + + /* + * Support for removing a logfile when an update fails. In order to do this, + * we save a copy of the currently-open logfile name, and register a + * callback function pointer with the run-time error handler. The saved + * pathname is made global so that the application can see whether or not a + * trap was set up. + */ +static MSG_CLEANUP_FN bounce_cleanup_func; /* saved callback */ +VSTRING *bounce_cleanup_path; /* saved path name */ + +/* bounce_cleanup_callback - run-time callback to cleanup logfile */ + +static void bounce_cleanup_callback(void) +{ + + /* + * Remove the logfile. + */ + if (bounce_cleanup_path) + bounce_cleanup_log(); + + /* + * Execute the saved cleanup action. + */ + if (bounce_cleanup_func) + bounce_cleanup_func(); +} + +/* bounce_cleanup_log - clean up the logfile */ + +void bounce_cleanup_log(void) +{ + const char *myname = "bounce_cleanup_log"; + + /* + * Sanity checks. + */ + if (bounce_cleanup_path == 0) + msg_panic("%s: no cleanup context", myname); + + /* + * This function may be called before a logfile is created or after it + * has been deleted, so do not complain. + */ + (void) unlink(vstring_str(bounce_cleanup_path)); +} + +/* bounce_cleanup_sig - signal handler */ + +static void bounce_cleanup_sig(int sig) +{ + + /* + * Running as a signal handler - don't do complicated stuff. + */ + if (bounce_cleanup_path) + (void) unlink(vstring_str(bounce_cleanup_path)); + _exit(sig); +} + +/* bounce_cleanup_register - register logfile to clean up */ + +void bounce_cleanup_register(char *service, char *queue_id) +{ + const char *myname = "bounce_cleanup_register"; + + /* + * Sanity checks. + */ + if (bounce_cleanup_path) + msg_panic("%s: nested call", myname); + + /* + * Save a copy of the logfile path, and of the last callback function + * pointer registered with the run-time error handler. + */ + bounce_cleanup_path = vstring_alloc(10); + (void) mail_queue_path(bounce_cleanup_path, service, queue_id); + bounce_cleanup_func = msg_cleanup(bounce_cleanup_callback); + signal(SIGTERM, bounce_cleanup_sig); +} + +/* bounce_cleanup_unregister - unregister logfile to clean up */ + +void bounce_cleanup_unregister(void) +{ + const char *myname = "bounce_cleanup_unregister"; + + /* + * Sanity checks. + */ + if (bounce_cleanup_path == 0) + msg_panic("%s: no cleanup context", myname); + + /* + * Restore the saved callback function pointer, and release storage for + * the saved logfile pathname. + */ + signal(SIGTERM, SIG_DFL); + (void) msg_cleanup(bounce_cleanup_func); + vstring_free(bounce_cleanup_path); + bounce_cleanup_path = 0; +} diff --git a/src/bounce/bounce_notify_service.c b/src/bounce/bounce_notify_service.c new file mode 100644 index 0000000..d6c751f --- /dev/null +++ b/src/bounce/bounce_notify_service.c @@ -0,0 +1,327 @@ +/*++ +/* NAME +/* bounce_notify_service 3 +/* SUMMARY +/* send non-delivery report to sender, server side +/* SYNOPSIS +/* #include "bounce_service.h" +/* +/* int bounce_notify_service(flags, service, queue_name, queue_id, +/* encoding, smtputf8, sender, dsn_envid, +/* dsn_ret, templates) +/* int flags; +/* char *service; +/* char *queue_name; +/* char *queue_id; +/* char *encoding; +/* int smtputf8; +/* char *sender; +/* char *dsn_envid; +/* int dsn_ret; +/* BOUNCE_TEMPLATES *templates; +/* DESCRIPTION +/* This module implements the server side of the bounce_flush() +/* (send bounce message) request. +/* +/* When a message bounces, a full copy is sent to the originator, +/* and an optional copy of the diagnostics with message headers is +/* sent to the postmaster. The result is non-zero when the operation +/* should be tried again. Otherwise, the logfile is removed. +/* +/* When a bounce is sent, the sender address is the empty +/* address. When a bounce bounces, an optional double bounce +/* with the entire undeliverable mail is sent to the postmaster, +/* with as sender address the double bounce address. +/* DIAGNOSTICS +/* Fatal error: error opening existing file. +/* BUGS +/* SEE ALSO +/* bounce(3) basic bounce service client interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "bounce_service.h" + +#define STR vstring_str + +/* bounce_notify_service - send a bounce */ + +int bounce_notify_service(int flags, char *service, char *queue_name, + char *queue_id, char *encoding, + int smtputf8, char *recipient, + char *dsn_envid, int dsn_ret, + BOUNCE_TEMPLATES *ts) +{ + BOUNCE_INFO *bounce_info; + int bounce_status = 1; + int postmaster_status = 1; + VSTREAM *bounce; + int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks, + var_notify_classes); + VSTRING *new_id = vstring_alloc(10); + char *postmaster; + int count; + + /* + * Initialize. Open queue file, bounce log, etc. + * + * XXX DSN The bounce service produces RFC 3464-style "failed mail" reports + * from information in two following types of logfile: + * + * 1 - bounce: this file is used for RFC 3464-style reports of permanent + * delivery errors by the bounce(8) service. This reports to the sender + * all recipients that have no DSN NOTIFY information (compatibility) and + * all recipients that have DSN NOTIFY=FAILURE; this reports to + * postmaster all recipients, if postmaster notification is enabled. + * + * 2 - defer: this file is used for three types of report: + * + * 2a) RFC 3464-style "mail is too old" reports by the bounce(8) service. + * This reports to the sender all recipients that have no DSN NOTIFY + * information (compatibility) and all recipients that have DSN + * NOTIFY=FAILURE; this reports to postmaster all recipients, if + * postmaster notification is enabled. + * + * Other reports that other servers produce from the defer logfile: + * + * 2b) On-demand reports of all delayed deliveries by the showq(8) service + * and mailq(1) command. This reports all recipients that have a + * transient delivery error. + * + * 2c) RFC 3464-style "delayed mail" notifications by the defer(8) service. + * This reports to the sender all recipients that have no DSN NOTIFY + * information (compatibility) and all recipients that have DSN + * NOTIFY=DELAY; this reports to postmaster all recipients, if postmaster + * notification is enabled. + */ + bounce_info = bounce_mail_init(service, queue_name, queue_id, + encoding, smtputf8, dsn_envid, + ts->failure); + +#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */ +#define NULL_TRACE_FLAGS 0 + + /* + * The choice of sender address depends on the recipient address. For a + * single bounce (a non-delivery notification to the message originator), + * the sender address is the empty string. For a double bounce (typically + * a failed single bounce, or a postmaster notification that was produced + * by any of the mail processes) the sender address is defined by the + * var_double_bounce_sender configuration variable. When a double bounce + * cannot be delivered, the queue manager blackholes the resulting triple + * bounce message. + */ + + /* + * Double bounce failed. Never send a triple bounce. + * + * However, this does not prevent double bounces from bouncing on other + * systems. In order to cope with this, either the queue manager must + * recognize the double-bounce recipient address and discard mail, or + * every delivery agent must recognize the double-bounce sender address + * and substitute something else so mail does not come back at us. + */ + if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0) { + msg_warn("%s: undeliverable postmaster notification discarded", + queue_id); + bounce_status = 0; + } + + /* + * Single bounce failed. Optionally send a double bounce to postmaster, + * subject to notify_classes restrictions. + */ +#define ANY_BOUNCE (MAIL_ERROR_2BOUNCE | MAIL_ERROR_BOUNCE) +#define SEND_POSTMASTER_ANY_BOUNCE_NOTICE (notify_mask & ANY_BOUNCE) + + else if (*recipient == 0) { + if (!SEND_POSTMASTER_ANY_BOUNCE_NOTICE) { + bounce_status = 0; + } else { + postmaster = var_2bounce_rcpt; + if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), + postmaster, + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + + /* + * Double bounce to Postmaster. This is the last opportunity + * for this message to be delivered. Send the text with + * reason for the bounce, and the headers of the original + * message. Don't bother sending the boiler-plate text. + */ + count = -1; + if (bounce_header(bounce, bounce_info, postmaster, + POSTMASTER_COPY) == 0 + && (count = bounce_diagnostic_log(bounce, bounce_info, + DSN_NOTIFY_OVERRIDE)) > 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_diagnostic_dsn(bounce, bounce_info, + DSN_NOTIFY_OVERRIDE) > 0) { + bounce_original(bounce, bounce_info, DSN_RET_FULL); + bounce_status = post_mail_fclose(bounce); + if (bounce_status == 0) + msg_info("%s: postmaster non-delivery notification: %s", + queue_id, STR(new_id)); + } else { + /* No applicable recipients found - cancel this notice. */ + (void) vstream_fclose(bounce); + if (count == 0) + bounce_status = 0; + } + } + } + } + + /* + * Non-bounce failed. Send a single bounce to the sender, subject to DSN + * NOTIFY restrictions. + */ + else { + if ((bounce = post_mail_fopen_nowait(NULL_SENDER, recipient, + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + + /* + * Send the bounce message header, some boilerplate text that + * pretends that we are a polite mail system, the text with + * reason for the bounce, and a copy of the original message. + */ + count = -1; + if (bounce_header(bounce, bounce_info, recipient, + NO_POSTMASTER_COPY) == 0 + && bounce_boilerplate(bounce, bounce_info) == 0 + && (count = bounce_diagnostic_log(bounce, bounce_info, + DSN_NOTIFY_FAILURE)) > 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_diagnostic_dsn(bounce, bounce_info, + DSN_NOTIFY_FAILURE) > 0) { + bounce_original(bounce, bounce_info, dsn_ret ? + dsn_ret : DSN_RET_FULL); + bounce_status = post_mail_fclose(bounce); + if (bounce_status == 0) + msg_info("%s: sender non-delivery notification: %s", + queue_id, STR(new_id)); + } else { + /* No applicable recipients found - cancel this notice. */ + (void) vstream_fclose(bounce); + if (count == 0) + bounce_status = 0; + } + } + + /* + * Optionally, send a postmaster notice, subject to notify_classes + * restrictions. + * + * This postmaster notice is not critical, so if it fails don't + * retransmit the bounce that we just generated, just log a warning. + */ +#define SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE (notify_mask & MAIL_ERROR_BOUNCE) + + if (bounce_status == 0 && SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE + && strcasecmp_utf8(recipient, mail_addr_double_bounce()) != 0) { + + /* + * Send the text with reason for the bounce, and the headers of + * the original message. Don't bother sending the boiler-plate + * text. This postmaster notice is not critical, so if it fails + * don't retransmit the bounce that we just generated, just log a + * warning. + */ + postmaster = var_bounce_rcpt; + if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), + postmaster, + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + count = -1; + if (bounce_header(bounce, bounce_info, postmaster, + POSTMASTER_COPY) == 0 + && (count = bounce_diagnostic_log(bounce, bounce_info, + DSN_NOTIFY_OVERRIDE)) > 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_diagnostic_dsn(bounce, bounce_info, + DSN_NOTIFY_OVERRIDE) > 0) { + bounce_original(bounce, bounce_info, DSN_RET_HDRS); + postmaster_status = post_mail_fclose(bounce); + if (postmaster_status == 0) + msg_info("%s: postmaster non-delivery notification: %s", + queue_id, STR(new_id)); + } else { + /* No applicable recipients found - cancel this notice. */ + (void) vstream_fclose(bounce); + if (count == 0) + postmaster_status = 0; + } + } + if (postmaster_status) + msg_warn("%s: postmaster notice failed while bouncing to %s", + queue_id, recipient); + } + } + + /* + * Optionally, delete the recipients from the queue file. + */ + if (bounce_status == 0 && (flags & BOUNCE_FLAG_DELRCPT)) + bounce_delrcpt(bounce_info); + + /* + * Examine the completion status. Delete the bounce log file only when + * the bounce was posted successfully, and only if we are bouncing for + * real, not just warning. + */ + if (bounce_status == 0 && mail_queue_remove(service, queue_id) + && errno != ENOENT) + msg_fatal("remove %s %s: %m", service, queue_id); + + /* + * Cleanup. + */ + bounce_mail_free(bounce_info); + vstring_free(new_id); + + return (bounce_status); +} diff --git a/src/bounce/bounce_notify_util.c b/src/bounce/bounce_notify_util.c new file mode 100644 index 0000000..781a525 --- /dev/null +++ b/src/bounce/bounce_notify_util.c @@ -0,0 +1,1010 @@ +/*++ +/* NAME +/* bounce_notify_util 3 +/* SUMMARY +/* send non-delivery report to sender, server side +/* SYNOPSIS +/* #include "bounce_service.h" +/* +/* typedef struct { +/* .in +4 +/* /* All private members... */ +/* .in -4 +/* } BOUNCE_INFO; +/* +/* BOUNCE_INFO *bounce_mail_init(service, queue_name, queue_id, encoding, +/* smtputf8, dsn_envid, template) +/* const char *service; +/* const char *queue_name; +/* const char *queue_id; +/* const char *encoding; +/* int smtputf8; +/* const char *dsn_envid; +/* const BOUNCE_TEMPLATE *template; +/* +/* BOUNCE_INFO *bounce_mail_one_init(queue_name, queue_id, encoding, +/* smtputf8, dsn_envid, dsn_notify, +/* rcpt_buf, dsn_buf, template) +/* const char *queue_name; +/* const char *queue_id; +/* const char *encoding; +/* int smtputf8; +/* int dsn_notify; +/* const char *dsn_envid; +/* RCPT_BUF *rcpt_buf; +/* DSN_BUF *dsn_buf; +/* const BOUNCE_TEMPLATE *template; +/* +/* void bounce_mail_free(bounce_info) +/* BOUNCE_INFO *bounce_info; +/* +/* int bounce_header(fp, bounce_info, recipient, postmaster_copy) +/* VSTREAM *fp; +/* BOUNCE_INFO *bounce_info; +/* const char *recipient; +/* int postmaster_copy; +/* +/* int bounce_boilerplate(fp, bounce_info) +/* VSTREAM *fp; +/* BOUNCE_INFO *bounce_info; +/* +/* int bounce_recipient_log(fp, bounce_info) +/* VSTREAM *fp; +/* BOUNCE_INFO *bounce_info; +/* +/* int bounce_diagnostic_log(fp, bounce_info, notify_filter) +/* VSTREAM *fp; +/* BOUNCE_INFO *bounce_info; +/* int notify_filter; +/* +/* int bounce_header_dsn(fp, bounce_info) +/* VSTREAM *fp; +/* BOUNCE_INFO *bounce_info; +/* +/* int bounce_recipient_dsn(fp, bounce_info) +/* VSTREAM *fp; +/* BOUNCE_INFO *bounce_info; +/* +/* int bounce_diagnostic_dsn(fp, bounce_info, notify_filter) +/* VSTREAM *fp; +/* BOUNCE_INFO *bounce_info; +/* int notify_filter; +/* +/* int bounce_original(fp, bounce_info, headers_only) +/* VSTREAM *fp; +/* BOUNCE_INFO *bounce_info; +/* int headers_only; +/* +/* void bounce_delrcpt(bounce_info) +/* BOUNCE_INFO *bounce_info; +/* +/* void bounce_delrcpt_one(bounce_info) +/* BOUNCE_INFO *bounce_info; +/* DESCRIPTION +/* This module implements the grunt work of sending a non-delivery +/* notification. A bounce is sent in a form that satisfies RFC 1894 +/* (delivery status notifications). +/* +/* bounce_mail_init() bundles up its argument and attempts to +/* open the corresponding logfile and message file. A BOUNCE_INFO +/* structure contains all the necessary information about an +/* undeliverable message. +/* +/* bounce_mail_one_init() provides the same function for only +/* one recipient that is not read from bounce logfile. +/* +/* bounce_mail_free() releases memory allocated by bounce_mail_init() +/* and closes any files opened by bounce_mail_init(). +/* +/* bounce_header() produces a standard mail header with the specified +/* recipient and starts a text/plain message segment for the +/* human-readable problem description. postmaster_copy is either +/* POSTMASTER_COPY or NO_POSTMASTER_COPY. +/* +/* bounce_boilerplate() produces the standard "sorry" text that +/* creates the illusion that mail systems are civilized. +/* +/* bounce_recipient_log() sends a human-readable representation of +/* logfile information for one recipient, with the recipient address +/* and with the text why the recipient was undeliverable. +/* +/* bounce_diagnostic_log() sends a human-readable representation of +/* logfile information for all undeliverable recipients. The +/* notify_filter specifies what recipient status records should be +/* reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY. +/* In the absence of DSN NOTIFY information all records are reported. +/* The result value is -1 in case of error, the number of reported +/* recipients in case of success. +/* +/* bounce_header_dsn() starts a message/delivery-status message +/* segment and sends the machine-readable information that identifies +/* the reporting MTA. +/* +/* bounce_recipient_dsn() sends a machine-readable representation of +/* logfile information for one recipient, with the recipient address +/* and with the text why the recipient was undeliverable. +/* +/* bounce_diagnostic_dsn() sends a machine-readable representation of +/* logfile information for all undeliverable recipients. The +/* notify_filter specifies what recipient status records should be +/* reported: DSN_NOTIFY_SUCCESS, DSN_NOTIFY_FAILURE, DSN_NOTIFY_DELAY. +/* In the absence of DSN NOTIFY information all records are reported. +/* The result value is -1 in case of error, the number of reported +/* recipients in case of success. +/* +/* bounce_original() starts a message/rfc822 or text/rfc822-headers +/* message segment and sends the original message, either full +/* (DSN_RET_FULL) or message headers only (DSN_RET_HDRS). +/* +/* bounce_delrcpt() deletes recipients in the logfile from the original +/* queue file. +/* +/* bounce_delrcpt_one() deletes one recipient from the original +/* queue file. +/* DIAGNOSTICS +/* Fatal error: error opening existing file. +/* BUGS +/* SEE ALSO +/* bounce(3) basic bounce service client interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include /* sscanf() */ +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "bounce_service.h" + +#define STR vstring_str +#define LEN VSTRING_LEN + +/* bounce_mail_alloc - initialize */ + +static BOUNCE_INFO *bounce_mail_alloc(const char *service, + const char *queue_name, + const char *queue_id, + const char *encoding, + int smtputf8, + const char *dsn_envid, + RCPT_BUF *rcpt_buf, + DSN_BUF *dsn_buf, + BOUNCE_TEMPLATE *template, + BOUNCE_LOG *log_handle) +{ + BOUNCE_INFO *bounce_info; + int rec_type; + int prev_type; + int all_headers_seen = 0; + int skip_message_segment = 0; + int in_envelope = 1; + + /* + * Bundle up a bunch of parameters and initialize information that will + * be discovered on the fly. + * + * XXX Instead of overriding the returned-message MIME encoding, separate + * the returned-message MIME encoding from the (boiler plate, delivery + * status) MIME encoding. + */ + bounce_info = (BOUNCE_INFO *) mymalloc(sizeof(*bounce_info)); + bounce_info->service = service; + bounce_info->queue_name = queue_name; + bounce_info->queue_id = queue_id; + bounce_info->smtputf8 = smtputf8; + /* Fix 20140708: override MIME encoding: addresses may be 8bit. */ + /* Fix 20140718: override MIME encoding: 8bit $myhostname expansion. */ + if (var_smtputf8_enable /* was: bounce_info->smtputf8 */ ) { + bounce_info->mime_encoding = "8bit"; + } else if (strcmp(encoding, MAIL_ATTR_ENC_8BIT) == 0) { + bounce_info->mime_encoding = "8bit"; + } else if (strcmp(encoding, MAIL_ATTR_ENC_7BIT) == 0) { + bounce_info->mime_encoding = "7bit"; + } else { + if (strcmp(encoding, MAIL_ATTR_ENC_NONE) != 0) + msg_warn("%s: unknown encoding: %.200s", + bounce_info->queue_id, encoding); + bounce_info->mime_encoding = 0; + } + if (dsn_envid && *dsn_envid) + bounce_info->dsn_envid = dsn_envid; + else + bounce_info->dsn_envid = 0; + bounce_info->template = template; + bounce_info->buf = vstring_alloc(100); + bounce_info->sender = vstring_alloc(100); + bounce_info->arrival_time = 0; + bounce_info->orig_offs = 0; + bounce_info->message_size = 0; + bounce_info->orig_msgid = vstring_alloc(100); + bounce_info->rcpt_buf = rcpt_buf; + bounce_info->dsn_buf = dsn_buf; + bounce_info->log_handle = log_handle; + + /* + * RFC 1894: diagnostic-type is an RFC 822 atom. We use X-$mail_name and + * must ensure it is valid. + */ + bounce_info->mail_name = mystrdup(var_mail_name); + translit(bounce_info->mail_name, " \t\r\n()<>@,;:\\\".[]", + "-----------------"); + + /* + * Compute a supposedly unique boundary string. This assumes that a queue + * ID and a hostname contain acceptable characters for a boundary string, + * but the assumption is not verified. + */ + vstring_sprintf(bounce_info->buf, "%s.%lu/%s", + queue_id, (unsigned long) event_time(), var_myhostname); + bounce_info->mime_boundary = mystrdup(STR(bounce_info->buf)); + + /* + * If the original message cannot be found, do not raise a run-time + * error. There is nothing we can do about the error, and all we are + * doing is to inform the sender of a delivery problem. Bouncing a + * message does not have to be a perfect job. But if the system IS + * running out of resources, raise a fatal run-time error and force a + * backoff. + */ + if ((bounce_info->orig_fp = mail_queue_open(queue_name, queue_id, + O_RDWR, 0)) == 0 + && errno != ENOENT) + msg_fatal("open %s %s: %m", service, queue_id); + + /* + * Get time/size/sender information from the original message envelope + * records. If the envelope is corrupted just send whatever we can + * (remember this is a best effort, it does not have to be perfect). + * + * Lock the file for shared use, so that queue manager leaves it alone after + * restarting. + */ +#define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT) + + if (bounce_info->orig_fp != 0) { + if (myflock(vstream_fileno(bounce_info->orig_fp), INTERNAL_LOCK, + DELIVER_LOCK_MODE) < 0) + msg_fatal("cannot get shared lock on %s: %m", + VSTREAM_PATH(bounce_info->orig_fp)); + for (prev_type = 0; + (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0; + prev_type = rec_type) { + + /* + * Postfix version dependent: data offset in SIZE record. + */ + if (rec_type == REC_TYPE_SIZE) { + if (bounce_info->message_size == 0) + sscanf(STR(bounce_info->buf), "%ld %ld", + &bounce_info->message_size, + &bounce_info->orig_offs); + if (bounce_info->message_size < 0) + bounce_info->message_size = 0; + if (bounce_info->orig_offs < 0) + bounce_info->orig_offs = 0; + } + + /* + * Information for the Arrival-Date: attribute. + */ + else if (rec_type == REC_TYPE_TIME) { + if (bounce_info->arrival_time == 0 + && (bounce_info->arrival_time = atol(STR(bounce_info->buf))) < 0) + bounce_info->arrival_time = 0; + } + + /* + * Information for the X-Postfix-Sender: attribute. + */ + else if (rec_type == REC_TYPE_FROM) { + quote_822_local_flags(bounce_info->sender, + VSTRING_LEN(bounce_info->buf) ? + STR(bounce_info->buf) : + mail_addr_mail_daemon(), 0); + } + + /* + * Backwards compatibility: no data offset in SIZE record. + */ + else if (rec_type == REC_TYPE_MESG) { + /* XXX Future: sender+recipient after message content. */ + if (VSTRING_LEN(bounce_info->sender) == 0) + msg_warn("%s: no sender before message content record", + bounce_info->queue_id); + bounce_info->orig_offs = vstream_ftell(bounce_info->orig_fp); + if (var_threaded_bounce == 0) + skip_message_segment = 1; + else + in_envelope = 0; + } + + /* + * Extract Message-ID for threaded bounces. + */ + else if (in_envelope == 0 + && (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT)) { + const HEADER_OPTS *hdr; + char *cp; + + /* + * Skip records that we cannot use. Degrade if we could not + * skip over the message content. + */ + if (var_threaded_bounce == 0 || all_headers_seen + || prev_type == REC_TYPE_CONT) { + /* void */ ; + } + + /* + * Extract message-id header value. + */ + else if (is_header(STR(bounce_info->buf))) { + if ((hdr = header_opts_find( + vstring_str(bounce_info->buf))) != 0 + && hdr->type == HDR_MESSAGE_ID) { + vstring_truncate(bounce_info->buf, + trimblanks(STR(bounce_info->buf), + LEN(bounce_info->buf)) + - STR(bounce_info->buf)); + cp = STR(bounce_info->buf) + strlen(hdr->name) + 1; + while (ISSPACE(*cp)) + cp++; + if (*cp == '<' && vstring_end(bounce_info->buf)[-1] == '>') + vstring_strcpy(bounce_info->orig_msgid, cp); + else + msg_warn("%s: ignoring malformed Message-ID", + bounce_info->queue_id); + } + } + + /* + * Skip remainder of multiline header. + */ + else if (ISSPACE(*STR(bounce_info->buf))) { + /* void */ ; + } + + /* + * Start of body. + */ + else { + all_headers_seen = 1; + skip_message_segment = 1; + } + } + + /* + * In case we ever want to process records from the extracted + * segment, and in case there was no "start of body" event. + */ + else if (rec_type == REC_TYPE_XTRA) { + if (VSTRING_LEN(bounce_info->orig_msgid) == 0) + if (var_threaded_bounce) + all_headers_seen = 1; + in_envelope = 1; + } + + /* + * Are we done yet? + */ + if (bounce_info->orig_offs > 0 + && bounce_info->arrival_time > 0 + && VSTRING_LEN(bounce_info->sender) > 0 + && (var_threaded_bounce == 0 || all_headers_seen + || VSTRING_LEN(bounce_info->orig_msgid) > 0)) { + break; + } + + /* + * Skip over (the remainder of) the message segment. If that + * fails, degrade. + */ + if (skip_message_segment) { + if (vstream_fseek(bounce_info->orig_fp, + bounce_info->orig_offs + + bounce_info->message_size, + SEEK_SET) < 0) + /* void */ ; + skip_message_segment = 0; + } + } + } + return (bounce_info); +} + +/* bounce_mail_init - initialize */ + +BOUNCE_INFO *bounce_mail_init(const char *service, + const char *queue_name, + const char *queue_id, + const char *encoding, + int smtputf8, + const char *dsn_envid, + BOUNCE_TEMPLATE *template) +{ + BOUNCE_INFO *bounce_info; + BOUNCE_LOG *log_handle; + RCPT_BUF *rcpt_buf; + DSN_BUF *dsn_buf; + + /* + * Initialize the bounce_info structure. If the bounce log cannot be + * found, do not raise a fatal run-time error. There is nothing we can do + * about the error, and all we are doing is to inform the sender of a + * delivery problem, Bouncing a message does not have to be a perfect + * job. But if the system IS running out of resources, raise a fatal + * run-time error and force a backoff. + */ + if ((log_handle = bounce_log_open(service, queue_id, O_RDONLY, 0)) == 0) { + if (errno != ENOENT) + msg_fatal("open %s %s: %m", service, queue_id); + rcpt_buf = 0; + dsn_buf = 0; + } else { + rcpt_buf = rcpb_create(); + dsn_buf = dsb_create(); + } + bounce_info = bounce_mail_alloc(service, queue_name, queue_id, encoding, + smtputf8, dsn_envid, rcpt_buf, dsn_buf, + template, log_handle); + return (bounce_info); +} + +/* bounce_mail_one_init - initialize */ + +BOUNCE_INFO *bounce_mail_one_init(const char *queue_name, + const char *queue_id, + const char *encoding, + int smtputf8, + const char *dsn_envid, + RCPT_BUF *rcpt_buf, + DSN_BUF *dsn_buf, + BOUNCE_TEMPLATE *template) +{ + BOUNCE_INFO *bounce_info; + + /* + * Initialize the bounce_info structure for just one recipient. + */ + bounce_info = bounce_mail_alloc("none", queue_name, queue_id, encoding, + smtputf8, dsn_envid, rcpt_buf, dsn_buf, + template, (BOUNCE_LOG *) 0); + return (bounce_info); +} + +/* bounce_mail_free - undo bounce_mail_init */ + +void bounce_mail_free(BOUNCE_INFO *bounce_info) +{ + if (bounce_info->log_handle) { + if (bounce_log_close(bounce_info->log_handle)) + msg_warn("%s: read bounce log %s: %m", + bounce_info->queue_id, bounce_info->queue_id); + vstring_free(bounce_info->orig_msgid); + rcpb_free(bounce_info->rcpt_buf); + dsb_free(bounce_info->dsn_buf); + } + if (bounce_info->orig_fp && vstream_fclose(bounce_info->orig_fp)) + msg_warn("%s: read message file %s %s: %m", + bounce_info->queue_id, bounce_info->queue_name, + bounce_info->queue_id); + vstring_free(bounce_info->buf); + vstring_free(bounce_info->sender); + myfree(bounce_info->mail_name); + myfree((void *) bounce_info->mime_boundary); + myfree((void *) bounce_info); +} + +/* bounce_header - generate bounce message header */ + +int bounce_header(VSTREAM *bounce, BOUNCE_INFO *bounce_info, + const char *dest, int postmaster_copy) +{ + BOUNCE_TEMPLATE *template = bounce_info->template; + + /* + * Print a minimal bounce header. The cleanup service will add other + * headers and will make all addresses fully qualified. + */ +#define STREQ(a, b) (strcasecmp((a), (b)) == 0) +#define STRNE(a, b) (strcasecmp((a), (b)) != 0) + + /* + * Generic headers. + */ + bounce_template_headers(post_mail_fprintf, bounce, template, + STR(quote_822_local(bounce_info->buf, dest)), + postmaster_copy); + + /* + * References and Reply-To header that references the original message-id + * for better threading in MUAs. + */ + if (VSTRING_LEN(bounce_info->orig_msgid) > 0) { + post_mail_fprintf(bounce, "References: %s", STR(bounce_info->orig_msgid)); + post_mail_fprintf(bounce, "In-Reply-To: %s", STR(bounce_info->orig_msgid)); + } + + /* + * Auto-Submitted header, as per RFC 3834. + */ + post_mail_fprintf(bounce, "Auto-Submitted: %s", postmaster_copy ? + "auto-generated" : "auto-replied"); + + /* + * MIME header. Use 8bit encoding when either the bounced message or the + * template requires it. + */ + post_mail_fprintf(bounce, "MIME-Version: 1.0"); + post_mail_fprintf(bounce, "Content-Type: %s; report-type=%s;", + "multipart/report", "delivery-status"); + post_mail_fprintf(bounce, "\tboundary=\"%s\"", bounce_info->mime_boundary); + if (bounce_info->mime_encoding) + post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s", + STREQ(bounce_info->mime_encoding, MAIL_ATTR_ENC_7BIT) ? + bounce_template_encoding(template) : + bounce_info->mime_encoding); + post_mail_fputs(bounce, ""); + post_mail_fputs(bounce, "This is a MIME-encapsulated message."); + + /* + * MIME header. + */ +#define NOT_US_ASCII(tp) \ + STRNE(bounce_template_charset(template), "us-ascii") + +#define NOT_7BIT_MIME(bp) \ + (bp->mime_encoding && STRNE(bp->mime_encoding, MAIL_ATTR_ENC_7BIT)) + + post_mail_fputs(bounce, ""); + post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary); + post_mail_fprintf(bounce, "Content-Description: %s", "Notification"); + /* Fix 20140718: UTF-8 address or $myhostname expansion. */ + post_mail_fprintf(bounce, "Content-Type: %s; charset=%s", + "text/plain", NOT_US_ASCII(template) ? + bounce_template_charset(template) : + NOT_7BIT_MIME(bounce_info) ? + "utf-8" : "us-ascii"); + /* Fix 20140709: addresses may be 8bit. */ + if (NOT_7BIT_MIME(bounce_info)) + post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s", + bounce_info->mime_encoding); + post_mail_fputs(bounce, ""); + + return (vstream_ferror(bounce)); +} + +/* bounce_boilerplate - generate boiler-plate text */ + +int bounce_boilerplate(VSTREAM *bounce, BOUNCE_INFO *bounce_info) +{ + + /* + * Print the boiler-plate text. + */ + bounce_template_expand(post_mail_fputs, bounce, bounce_info->template); + return (vstream_ferror(bounce)); +} + +/* bounce_print - line_wrap callback */ + +static void bounce_print(const char *str, int len, int indent, void *context) +{ + VSTREAM *bounce = (VSTREAM *) context; + + post_mail_fprintf(bounce, "%*s%.*s", indent, "", len, str); +} + +/* bounce_print_wrap - print and wrap a line */ + +static void bounce_print_wrap(VSTREAM *bounce, BOUNCE_INFO *bounce_info, + const char *format,...) +{ + va_list ap; + +#define LENGTH 79 +#define INDENT 4 + + va_start(ap, format); + vstring_vsprintf(bounce_info->buf, format, ap); + va_end(ap); + line_wrap(STR(bounce_info->buf), LENGTH, INDENT, + bounce_print, (void *) bounce); +} + +/* bounce_recipient_log - send one bounce log report entry */ + +int bounce_recipient_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info) +{ + RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; + DSN *dsn = &bounce_info->dsn_buf->dsn; + + /* + * Mask control and non-ASCII characters (done in bounce_log_read()), + * wrap long lines and prepend one blank, so this data can safely be + * piped into other programs. Sort of like TCP Wrapper's safe_finger + * program. + */ +#define NON_NULL_EMPTY(s) ((s) && *(s)) + + post_mail_fputs(bounce, ""); + if (NON_NULL_EMPTY(rcpt->orig_addr)) { + bounce_print_wrap(bounce, bounce_info, "<%s> (expanded from <%s>): %s", + rcpt->address, rcpt->orig_addr, dsn->reason); + } else { + bounce_print_wrap(bounce, bounce_info, "<%s>: %s", + rcpt->address, dsn->reason); + } + return (vstream_ferror(bounce)); +} + +/* bounce_diagnostic_log - send bounce log report */ + +int bounce_diagnostic_log(VSTREAM *bounce, BOUNCE_INFO *bounce_info, + int notify_filter) +{ + RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; + int count = 0; + + /* + * Append a human-readable copy of the delivery error log. We're doing a + * best effort, so there is no point raising a fatal run-time error in + * case of a logfile read error. + * + * XXX DSN If the logfile with failed recipients is unavailable, pretend + * that we found something anyway, so that this notification will not be + * canceled. + */ + if (bounce_info->log_handle == 0 + || bounce_log_rewind(bounce_info->log_handle)) { + if (IS_FAILURE_TEMPLATE(bounce_info->template)) { + post_mail_fputs(bounce, ""); + post_mail_fputs(bounce, "\t--- Delivery report unavailable ---"); + count = 1; /* XXX don't abort */ + } + } else { + while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf, + bounce_info->dsn_buf) != 0) { + if (rcpt->dsn_notify == 0 /* compat */ + || (rcpt->dsn_notify & notify_filter)) { + count++; + if (bounce_recipient_log(bounce, bounce_info) != 0) + break; + } + } + } + return (vstream_ferror(bounce) ? -1 : count); +} + +/* bounce_header_dsn - send per-MTA bounce DSN records */ + +int bounce_header_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info) +{ + + /* + * MIME header. + */ + post_mail_fputs(bounce, ""); + post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary); + post_mail_fprintf(bounce, "Content-Description: %s", + "Delivery report"); + /* Generate *global* only if the original requested SMTPUTF8 support. */ + post_mail_fprintf(bounce, "Content-Type: message/%sdelivery-status", + (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED) ? + "global-" : ""); + /* Fix 20140709: addresses may be 8bit. */ + if (NOT_7BIT_MIME(bounce_info) + /* BC Fix 20170610: prevent MIME downgrade of message/delivery-status. */ + && (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED)) + post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s", + bounce_info->mime_encoding); + + /* + * According to RFC 1894: The body of a message/delivery-status consists + * of one or more "fields" formatted according to the ABNF of RFC 822 + * header "fields" (see [6]). The per-message fields appear first, + * followed by a blank line. + */ + post_mail_fputs(bounce, ""); + post_mail_fprintf(bounce, "Reporting-MTA: dns; %s", var_myhostname); +#if 0 + post_mail_fprintf(bounce, "Received-From-MTA: dns; %s", "whatever"); +#endif + if (NON_NULL_EMPTY(bounce_info->dsn_envid)) { + post_mail_fprintf(bounce, "Original-Envelope-Id: %s", + bounce_info->dsn_envid); + } + post_mail_fprintf(bounce, "X-%s-Queue-ID: %s", + bounce_info->mail_name, bounce_info->queue_id); + +#define IS_UTF8_ADDRESS(str, len) \ + ((str)[0] != 0 && !allascii(str) && valid_utf8_string((str), (len))) + + /* Fix 20140708: use "utf-8" or "rfc822" as appropriate. */ + if (VSTRING_LEN(bounce_info->sender) > 0) + post_mail_fprintf(bounce, "X-%s-Sender: %s; %s", + bounce_info->mail_name, bounce_info->smtputf8 + && IS_UTF8_ADDRESS(STR(bounce_info->sender), + VSTRING_LEN(bounce_info->sender)) ? + "utf-8" : "rfc822", STR(bounce_info->sender)); + if (bounce_info->arrival_time > 0) + post_mail_fprintf(bounce, "Arrival-Date: %s", + mail_date(bounce_info->arrival_time)); + return (vstream_ferror(bounce)); +} + +/* bounce_recipient_dsn - send per-recipient DSN records */ + +int bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info) +{ + RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; + DSN *dsn = &bounce_info->dsn_buf->dsn; + + post_mail_fputs(bounce, ""); + /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */ + post_mail_fprintf(bounce, "Final-Recipient: %s; %s", + bounce_info->smtputf8 + && IS_UTF8_ADDRESS(rcpt->address, + strlen(rcpt->address)) ? + "utf-8" : "rfc822", rcpt->address); + + /* + * XXX DSN + * + * RFC 3464 section 6.3.d: "If no ORCPT parameter was provided for this + * recipient, the Original-Recipient field MUST NOT appear." + * + * This is inconsistent with section 5.2.1.d: "If no ORCPT parameter was + * present in the RCPT command when the message was received, an ORCPT + * parameter MAY be added to the RCPT command when the message is + * relayed.". Postfix adds an ORCPT parameter under these conditions. + * + * Therefore, all down-stream MTAs will send DSNs with Original-Recipient + * field containing this same ORCPT value. When a down-stream MTA can use + * that information in their DSNs, it makes no sense that an up-stream + * MTA can't use that same information in its own DSNs. + * + * Postfix always reports an Original-Recipient field, because it is more + * more useful and more consistent. + */ + if (NON_NULL_EMPTY(rcpt->dsn_orcpt)) { + post_mail_fprintf(bounce, "Original-Recipient: %s", rcpt->dsn_orcpt); + } else if (NON_NULL_EMPTY(rcpt->orig_addr)) { + /* Fix 20140708: Don't send "utf-8" type with non-UTF8 address. */ + post_mail_fprintf(bounce, "Original-Recipient: %s; %s", + bounce_info->smtputf8 + && IS_UTF8_ADDRESS(rcpt->orig_addr, + strlen(rcpt->orig_addr)) ? + "utf-8" : "rfc822", rcpt->orig_addr); + } + post_mail_fprintf(bounce, "Action: %s", + IS_FAILURE_TEMPLATE(bounce_info->template) ? + "failed" : dsn->action); + post_mail_fprintf(bounce, "Status: %s", dsn->status); + if (NON_NULL_EMPTY(dsn->mtype) && NON_NULL_EMPTY(dsn->mname)) + bounce_print_wrap(bounce, bounce_info, "Remote-MTA: %s; %s", + dsn->mtype, dsn->mname); + if (NON_NULL_EMPTY(dsn->dtype) && NON_NULL_EMPTY(dsn->dtext)) + bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: %s; %s", + dsn->dtype, dsn->dtext); + else + bounce_print_wrap(bounce, bounce_info, "Diagnostic-Code: X-%s; %s", + bounce_info->mail_name, dsn->reason); +#if 0 + if (dsn->time > 0) + post_mail_fprintf(bounce, "Last-Attempt-Date: %s", + mail_date(dsn->time)); +#endif + if (IS_DELAY_TEMPLATE(bounce_info->template)) + post_mail_fprintf(bounce, "Will-Retry-Until: %s", + mail_date(bounce_info->arrival_time + var_max_queue_time)); + return (vstream_ferror(bounce)); +} + +/* bounce_diagnostic_dsn - send bounce log report, machine readable form */ + +int bounce_diagnostic_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info, + int notify_filter) +{ + RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; + int count = 0; + + /* + * Append a machine-readable copy of the delivery error log. We're doing + * a best effort, so there is no point raising a fatal run-time error in + * case of a logfile read error. + * + * XXX DSN If the logfile with failed recipients is unavailable, pretend + * that we found something anyway, so that this notification will not be + * canceled. + */ + if (bounce_info->log_handle == 0 + || bounce_log_rewind(bounce_info->log_handle)) { + if (IS_FAILURE_TEMPLATE(bounce_info->template)) + count = 1; /* XXX don't abort */ + } else { + while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf, + bounce_info->dsn_buf) != 0) { + if (rcpt->dsn_notify == 0 /* compat */ + || (rcpt->dsn_notify & notify_filter)) { + count++; + if (bounce_recipient_dsn(bounce, bounce_info) != 0) + break; + } + } + } + return (vstream_ferror(bounce) ? -1 : count); +} + +/* bounce_original - send a copy of the original to the victim */ + +int bounce_original(VSTREAM *bounce, BOUNCE_INFO *bounce_info, + int headers_only) +{ + int status = 0; + int rec_type = 0; + + /* + * When truncating a large message, don't damage the MIME structure: send + * the message headers only. + */ + if (var_bounce_limit > 0 + && bounce_info->orig_fp + && (bounce_info->message_size <= 0 + || bounce_info->message_size > var_bounce_limit)) + headers_only = DSN_RET_HDRS; + + /* + * MIME headers. + */ +#define IS_UNDELIVERED_TEMPLATE(template) \ + (IS_FAILURE_TEMPLATE(template) || IS_DELAY_TEMPLATE(template)) + + post_mail_fputs(bounce, ""); + post_mail_fprintf(bounce, "--%s", bounce_info->mime_boundary); + post_mail_fprintf(bounce, "Content-Description: %s%s", + IS_UNDELIVERED_TEMPLATE(bounce_info->template) ? + "Undelivered " : "", + headers_only == DSN_RET_HDRS ? + "Message Headers" : "Message"); + /* Generate *global* only if the original requested SMTPUTF8 support. */ + if (bounce_info->smtputf8 & SMTPUTF8_FLAG_REQUESTED) + post_mail_fprintf(bounce, "Content-Type: message/%s", + headers_only == DSN_RET_HDRS ? + "global-headers" : "global"); + else + post_mail_fprintf(bounce, "Content-Type: %s", + headers_only == DSN_RET_HDRS ? + "text/rfc822-headers" : "message/rfc822"); + if (NOT_7BIT_MIME(bounce_info)) + post_mail_fprintf(bounce, "Content-Transfer-Encoding: %s", + bounce_info->mime_encoding); + post_mail_fputs(bounce, ""); + + /* + * Send place holder if original is unavailable. + */ + if (bounce_info->orig_offs == 0 || vstream_fseek(bounce_info->orig_fp, + bounce_info->orig_offs, SEEK_SET) < 0) { + post_mail_fputs(bounce, "\t--- Undelivered message unavailable ---"); + return (vstream_ferror(bounce)); + } + + /* + * XXX The cleanup server removes Return-Path: headers. This should be + * done only with mail that enters via a non-SMTP channel, but changing + * this now could break other software. Removing Return-Path: could break + * digital signatures, though this is unlikely. In any case, + * header_checks are more effective when the Return-Path: header is + * present, so we prepend one to the bounce message. + */ + post_mail_fprintf(bounce, "Return-Path: <%s>", STR(bounce_info->sender)); + + /* + * Copy the original message contents. We're doing raw record output here + * so that we don't throw away binary transparency yet. + */ +#define IS_HEADER(s) (IS_SPACE_TAB(*(s)) || is_header(s)) + + while (status == 0 && (rec_type = rec_get(bounce_info->orig_fp, bounce_info->buf, 0)) > 0) { + if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT) + break; + if (headers_only == DSN_RET_HDRS + && !IS_HEADER(vstring_str(bounce_info->buf))) + break; + status = (REC_PUT_BUF(bounce, rec_type, bounce_info->buf) != rec_type); + } + + /* + * Final MIME headers. These require -- at the end of the boundary + * string. + * + * XXX This should be a separate bounce_terminate() entry so we can be + * assured that the terminator will always be sent. + */ + post_mail_fputs(bounce, ""); + post_mail_fprintf(bounce, "--%s--", bounce_info->mime_boundary); + + return (status); +} + +/* bounce_delrcpt - delete recipients from original queue file */ + +void bounce_delrcpt(BOUNCE_INFO *bounce_info) +{ + RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; + + if (bounce_info->orig_fp != 0 + && bounce_info->log_handle != 0 + && bounce_log_rewind(bounce_info->log_handle) == 0) + while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf, + bounce_info->dsn_buf) != 0) + if (rcpt->offset > 0) + deliver_completed(bounce_info->orig_fp, rcpt->offset); +} + +/* bounce_delrcpt_one - delete one recipient from original queue file */ + +void bounce_delrcpt_one(BOUNCE_INFO *bounce_info) +{ + RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; + + if (bounce_info->orig_fp != 0 && rcpt->offset > 0) + deliver_completed(bounce_info->orig_fp, rcpt->offset); +} diff --git a/src/bounce/bounce_notify_util_tester.c b/src/bounce/bounce_notify_util_tester.c new file mode 100644 index 0000000..da13f47 --- /dev/null +++ b/src/bounce/bounce_notify_util_tester.c @@ -0,0 +1,167 @@ + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include + + /* + * Bounce service. + */ +#include +#include + + /* + * Testing library. + */ +#include + +#define TEST_ENCODING "7bit" +#define NO_SMTPUTF8 (0) +#define TEST_DSN_ENVID "TEST-ENVID" +#define TEST_RECIPIENT "test-recipient" + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* test_driver - test driver */ + +static void test_driver(int argc, char **argv) +{ + BOUNCE_TEMPLATES *bounce_templates; + BOUNCE_INFO *bounce_info; + VSTRING *message_buf; + VSTREAM *message_stream; + VSTRING *rec_buf; + int rec_type; + + /* + * Sanity checks. + */ + if (argc != 4) + msg_fatal("usage: %s [options] service queue_name queue_id", argv[0]); + + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir %s: %m", var_queue_dir); + + bounce_hfrom_format = + hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format); + + /* + * Write one message to VSTRING. + */ + message_buf = vstring_alloc(100); + if ((message_stream = vstream_memopen(message_buf, O_WRONLY)) == 0) + msg_fatal("vstream_memopen O_WRONLY: %m"); + bounce_templates = bounce_templates_create(); + bounce_info = bounce_mail_init(argv[1], argv[2], argv[3], + TEST_ENCODING, NO_SMTPUTF8, TEST_DSN_ENVID, + bounce_templates->failure); + if (bounce_header(message_stream, bounce_info, TEST_RECIPIENT, + NO_POSTMASTER_COPY) < 0) + msg_fatal("bounce_header: %m"); + if (bounce_diagnostic_log(message_stream, bounce_info, + DSN_NOTIFY_OVERRIDE) <= 0) + msg_fatal("bounce_diagnostic_log: %m"); + if (bounce_header_dsn(message_stream, bounce_info) != 0) + msg_fatal("bounce_header_dsn: %m"); + if (bounce_diagnostic_dsn(message_stream, bounce_info, + DSN_NOTIFY_OVERRIDE) <= 0) + msg_fatal("bounce_diagnostic_dsn: %m"); + bounce_original(message_stream, bounce_info, DSN_RET_FULL); + if (vstream_fclose(message_stream) != 0) + msg_fatal("vstream_fclose: %m"); + bounce_mail_free(bounce_info); + + /* + * Render the bounce message in human-readable form. + */ + if ((message_stream = vstream_memopen(message_buf, O_RDONLY)) == 0) + msg_fatal("vstream_memopen O_RDONLY: %m"); + rec_buf = vstring_alloc(100); + while ((rec_type = rec_get(message_stream, rec_buf, 0)) > 0) { + switch (rec_type) { + case REC_TYPE_CONT: + vstream_printf("%.*s", (int) LEN(rec_buf), STR(rec_buf)); + break; + case REC_TYPE_NORM: + vstream_printf("%.*s\n", (int) LEN(rec_buf), STR(rec_buf)); + break; + default: + msg_panic("unexpected message record type %d", rec_type); + } + vstream_fflush(VSTREAM_OUT); + } + if (vstream_fclose(message_stream) != 0) + msg_fatal("vstream_fclose: %m"); + vstring_free(rec_buf); + + /* + * Clean up. + */ + exit(0); +} + +int var_bounce_limit; +int var_max_queue_time; +int var_delay_warn_time; +char *var_notify_classes; +char *var_bounce_rcpt; +char *var_2bounce_rcpt; +char *var_delay_rcpt; +char *var_bounce_tmpl; +bool var_threaded_bounce; +char *var_hfrom_format; /* header_from_format */ + +int bounce_hfrom_format; + +int main(int argc, char **argv) +{ + static const CONFIG_INT_TABLE int_table[] = { + VAR_BOUNCE_LIMIT, DEF_BOUNCE_LIMIT, &var_bounce_limit, 1, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 0, 8640000, + VAR_DELAY_WARN_TIME, DEF_DELAY_WARN_TIME, &var_delay_warn_time, 0, 0, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, + VAR_BOUNCE_RCPT, DEF_BOUNCE_RCPT, &var_bounce_rcpt, 1, 0, + VAR_2BOUNCE_RCPT, DEF_2BOUNCE_RCPT, &var_2bounce_rcpt, 1, 0, + VAR_DELAY_RCPT, DEF_DELAY_RCPT, &var_delay_rcpt, 1, 0, + VAR_BOUNCE_TMPL, DEF_BOUNCE_TMPL, &var_bounce_tmpl, 0, 0, + VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0, + 0, + }; + static const CONFIG_NBOOL_TABLE nbool_table[] = { + VAR_THREADED_BOUNCE, DEF_THREADED_BOUNCE, &var_threaded_bounce, + 0, + }; + + test_main(argc, argv, test_driver, + CA_TEST_MAIN_INT_TABLE(int_table), + CA_TEST_MAIN_STR_TABLE(str_table), + CA_TEST_MAIN_TIME_TABLE(time_table), + CA_TEST_MAIN_NBOOL_TABLE(nbool_table), + 0); + + exit(0); +} diff --git a/src/bounce/bounce_notify_verp.c b/src/bounce/bounce_notify_verp.c new file mode 100644 index 0000000..8bfd71b --- /dev/null +++ b/src/bounce/bounce_notify_verp.c @@ -0,0 +1,267 @@ +/*++ +/* NAME +/* bounce_notify_verp 3 +/* SUMMARY +/* send non-delivery report to sender, server side +/* SYNOPSIS +/* #include "bounce_service.h" +/* +/* int bounce_notify_verp(flags, service, queue_name, queue_id, +/* encoding, smtputf8, sender, +/* dsn_envid, dsn_ret, verp_delims, +/* templates) +/* int flags; +/* char *service; +/* char *queue_name; +/* char *queue_id; +/* char *encoding; +/* int smtputf8; +/* char *sender; +/* char *dsn_envid; +/* int dsn_ret; +/* char *verp_delims; +/* BOUNCE_TEMPLATES *templates; +/* DESCRIPTION +/* This module implements the server side of the bounce_notify() +/* (send bounce message) request. The logfile +/* is removed after and a warning is posted. +/* The bounce recipient address is encoded in VERP format. +/* This routine must be used for single bounces only. +/* +/* When a message bounces, a full copy is sent to the originator, +/* and an optional copy of the diagnostics with message headers is +/* sent to the postmaster. The result is non-zero when the operation +/* should be tried again. +/* +/* When a bounce is sent, the sender address is the empty +/* address. +/* DIAGNOSTICS +/* Fatal error: error opening existing file. +/* SEE ALSO +/* bounce(3) basic bounce service client interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "bounce_service.h" + +#define STR vstring_str + +/* bounce_notify_verp - send a bounce, VERP style */ + +int bounce_notify_verp(int flags, char *service, char *queue_name, + char *queue_id, char *encoding, + int smtputf8, char *recipient, + char *dsn_envid, int dsn_ret, + char *verp_delims, BOUNCE_TEMPLATES *ts) +{ + const char *myname = "bounce_notify_verp"; + BOUNCE_INFO *bounce_info; + int bounce_status = 0; + int postmaster_status; + VSTREAM *bounce; + int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks, + var_notify_classes); + char *postmaster; + VSTRING *verp_buf; + VSTRING *new_id; + + /* + * Sanity checks. We must be called only for undeliverable non-bounce + * messages. + */ + if (*recipient == 0) + msg_panic("%s: attempt to bounce a single bounce", myname); + if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0) + msg_panic("%s: attempt to bounce a double bounce", myname); + + /* + * Initialize. Open queue file, bounce log, etc. + */ + bounce_info = bounce_mail_init(service, queue_name, queue_id, + encoding, smtputf8, dsn_envid, + ts->failure); + + /* + * If we have no recipient list then we can't send VERP replies. Send + * *something* anyway so that the mail is not lost in a black hole. + */ + if (bounce_info->log_handle == 0) { + DSN_BUF *dsn_buf = dsb_create(); + RCPT_BUF *rcpt_buf = rcpb_create(); + + dsb_simple(dsn_buf, "5.0.0", "(error report unavailable)"); + (void) DSN_FROM_DSN_BUF(dsn_buf); + vstring_strcpy(rcpt_buf->address, "(recipient address unavailable)"); + (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf); + bounce_status = bounce_one_service(flags, queue_name, queue_id, + encoding, smtputf8, recipient, + dsn_envid, dsn_ret, rcpt_buf, + dsn_buf, ts); + rcpb_free(rcpt_buf); + dsb_free(dsn_buf); + bounce_mail_free(bounce_info); + return (bounce_status); + } +#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */ +#define NULL_TRACE_FLAGS 0 + + /* + * A non-bounce message was returned. Send a single bounce, one per + * recipient. + */ + verp_buf = vstring_alloc(100); + new_id = vstring_alloc(10); + while (bounce_log_read(bounce_info->log_handle, bounce_info->rcpt_buf, + bounce_info->dsn_buf) != 0) { + RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; + + /* + * Notify the originator, subject to DSN NOTIFY restrictions. + * + * Fix 20090114: Use the Postfix original recipient, because that is + * what the VERP consumer expects. + */ + if (rcpt->dsn_notify != 0 /* compat */ + && (rcpt->dsn_notify & DSN_NOTIFY_FAILURE) == 0) { + bounce_status = 0; + } else { + verp_sender(verp_buf, verp_delims, recipient, rcpt); + if ((bounce = post_mail_fopen_nowait(NULL_SENDER, STR(verp_buf), + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + + /* + * Send the bounce message header, some boilerplate text that + * pretends that we are a polite mail system, the text with + * reason for the bounce, and a copy of the original message. + */ + if (bounce_header(bounce, bounce_info, STR(verp_buf), + NO_POSTMASTER_COPY) == 0 + && bounce_boilerplate(bounce, bounce_info) == 0 + && bounce_recipient_log(bounce, bounce_info) == 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_recipient_dsn(bounce, bounce_info) == 0) + bounce_original(bounce, bounce_info, dsn_ret ? + dsn_ret : DSN_RET_FULL); + bounce_status = post_mail_fclose(bounce); + if (bounce_status == 0) + msg_info("%s: sender non-delivery notification: %s", + queue_id, STR(new_id)); + } else + bounce_status = 1; + + /* + * Stop at the first sign of trouble, instead of making the + * problem worse. + */ + if (bounce_status != 0) + break; + + /* + * Optionally, mark this recipient as done. + */ + if (flags & BOUNCE_FLAG_DELRCPT) + bounce_delrcpt_one(bounce_info); + } + + /* + * Optionally, send a postmaster notice, subject to notify_classes + * restrictions. + * + * This postmaster notice is not critical, so if it fails don't + * retransmit the bounce that we just generated, just log a warning. + */ +#define SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE (notify_mask & MAIL_ERROR_BOUNCE) + + if (SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE) { + + /* + * Send the text with reason for the bounce, and the headers of + * the original message. Don't bother sending the boiler-plate + * text. This postmaster notice is not critical, so if it fails + * don't retransmit the bounce that we just generated, just log a + * warning. + */ + postmaster = var_bounce_rcpt; + if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), + postmaster, + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + if (bounce_header(bounce, bounce_info, postmaster, + POSTMASTER_COPY) == 0 + && bounce_recipient_log(bounce, bounce_info) == 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_recipient_dsn(bounce, bounce_info) == 0) + bounce_original(bounce, bounce_info, DSN_RET_HDRS); + postmaster_status = post_mail_fclose(bounce); + if (postmaster_status == 0) + msg_info("%s: postmaster non-delivery notification: %s", + queue_id, STR(new_id)); + } else + postmaster_status = 1; + + if (postmaster_status) + msg_warn("%s: postmaster notice failed while bouncing to %s", + queue_id, recipient); + } + } + + /* + * Examine the completion status. Delete the bounce log file only when + * the bounce was posted successfully, and only if we are bouncing for + * real, not just warning. + */ + if (bounce_status == 0 && mail_queue_remove(service, queue_id) + && errno != ENOENT) + msg_fatal("remove %s %s: %m", service, queue_id); + + /* + * Cleanup. + */ + bounce_mail_free(bounce_info); + vstring_free(verp_buf); + vstring_free(new_id); + + return (bounce_status); +} diff --git a/src/bounce/bounce_one_service.c b/src/bounce/bounce_one_service.c new file mode 100644 index 0000000..29c4fc3 --- /dev/null +++ b/src/bounce/bounce_one_service.c @@ -0,0 +1,266 @@ +/*++ +/* NAME +/* bounce_one_service 3 +/* SUMMARY +/* send non-delivery report to sender, server side +/* SYNOPSIS +/* #include "bounce_service.h" +/* +/* int bounce_one_service(flags, queue_name, queue_id, encoding, +/* smtputf8, orig_sender, envid, ret, +/* rcpt_buf, dsn_buf, templates) +/* int flags; +/* char *queue_name; +/* char *queue_id; +/* char *encoding; +/* int smtputf8; +/* char *orig_sender; +/* char *envid; +/* int ret; +/* RCPT_BUF *rcpt_buf; +/* DSN_BUF *dsn_buf; +/* BOUNCE_TEMPLATES *templates; +/* DESCRIPTION +/* This module implements the server side of the bounce_one() +/* (send bounce message for one recipient) request. +/* +/* When a message bounces, a full copy is sent to the originator, +/* and an optional copy of the diagnostics with message headers is +/* sent to the postmaster. The result is non-zero when the operation +/* should be tried again. +/* +/* When a bounce is sent, the sender address is the empty +/* address. When a bounce bounces, an optional double bounce +/* with the entire undeliverable mail is sent to the postmaster, +/* with as sender address the double bounce address. +/* DIAGNOSTICS +/* Fatal error: error opening existing file. +/* BUGS +/* SEE ALSO +/* bounce(3) basic bounce service client interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "bounce_service.h" + +#define STR vstring_str + +/* bounce_one_service - send a bounce for one recipient */ + +int bounce_one_service(int flags, char *queue_name, char *queue_id, + char *encoding, int smtputf8, + char *orig_sender, char *dsn_envid, + int dsn_ret, RCPT_BUF *rcpt_buf, + DSN_BUF *dsn_buf, BOUNCE_TEMPLATES *ts) +{ + BOUNCE_INFO *bounce_info; + int bounce_status = 1; + int postmaster_status = 1; + VSTREAM *bounce; + int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks, + var_notify_classes); + VSTRING *new_id = vstring_alloc(10); + + /* + * Initialize. Open queue file, bounce log, etc. + */ + bounce_info = bounce_mail_one_init(queue_name, queue_id, encoding, + smtputf8, dsn_envid, rcpt_buf, + dsn_buf, ts->failure); + +#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */ +#define NULL_TRACE_FLAGS 0 + + /* + * The choice of bounce sender address depends on the original sender + * address. For a single bounce (a non-delivery notification to the + * message originator), the sender address is the empty string. For a + * double bounce (typically a failed single bounce, or a postmaster + * notification that was produced by any of the mail processes) the + * sender address is defined by the var_double_bounce_sender + * configuration variable. When a double bounce cannot be delivered, the + * queue manager blackholes the resulting triple bounce message. + */ + + /* + * Double bounce failed. Never send a triple bounce. + * + * However, this does not prevent double bounces from bouncing on other + * systems. In order to cope with this, either the queue manager must + * recognize the double-bounce original sender address and discard mail, + * or every delivery agent must recognize the double-bounce sender + * address and substitute something else so mail does not come back at + * us. + */ + if (strcasecmp_utf8(orig_sender, mail_addr_double_bounce()) == 0) { + msg_warn("%s: undeliverable postmaster notification discarded", + queue_id); + bounce_status = 0; + } + + /* + * Single bounce failed. Optionally send a double bounce to postmaster, + * subject to notify_classes restrictions. + */ +#define ANY_BOUNCE (MAIL_ERROR_2BOUNCE | MAIL_ERROR_BOUNCE) +#define SEND_POSTMASTER_ANY_BOUNCE_NOTICE (notify_mask & ANY_BOUNCE) + + else if (*orig_sender == 0) { + if (!SEND_POSTMASTER_ANY_BOUNCE_NOTICE) { + bounce_status = 0; + } else { + if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), + var_2bounce_rcpt, + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + + /* + * Double bounce to Postmaster. This is the last opportunity + * for this message to be delivered. Send the text with + * reason for the bounce, and the headers of the original + * message. Don't bother sending the boiler-plate text. + */ + if (!bounce_header(bounce, bounce_info, var_2bounce_rcpt, + POSTMASTER_COPY) + && bounce_recipient_log(bounce, bounce_info) == 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_recipient_dsn(bounce, bounce_info) == 0) + bounce_original(bounce, bounce_info, DSN_RET_FULL); + bounce_status = post_mail_fclose(bounce); + if (bounce_status == 0) + msg_info("%s: postmaster non-delivery notification: %s", + queue_id, STR(new_id)); + } + } + } + + /* + * Non-bounce failed. Send a single bounce, subject to DSN NOTIFY + * restrictions. + */ + else { + RECIPIENT *rcpt = &bounce_info->rcpt_buf->rcpt; + + if (rcpt->dsn_notify != 0 /* compat */ + && (rcpt->dsn_notify & DSN_NOTIFY_FAILURE) == 0) { + bounce_status = 0; + } else { + if ((bounce = post_mail_fopen_nowait(NULL_SENDER, orig_sender, + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + + /* + * Send the bounce message header, some boilerplate text that + * pretends that we are a polite mail system, the text with + * reason for the bounce, and a copy of the original message. + */ + if (bounce_header(bounce, bounce_info, orig_sender, + NO_POSTMASTER_COPY) == 0 + && bounce_boilerplate(bounce, bounce_info) == 0 + && bounce_recipient_log(bounce, bounce_info) == 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_recipient_dsn(bounce, bounce_info) == 0) + bounce_original(bounce, bounce_info, dsn_ret ? + dsn_ret : DSN_RET_FULL); + bounce_status = post_mail_fclose(bounce); + if (bounce_status == 0) + msg_info("%s: sender non-delivery notification: %s", + queue_id, STR(new_id)); + } + } + + /* + * Optionally send a postmaster notice, subject to notify_classes + * restrictions. + * + * This postmaster notice is not critical, so if it fails don't + * retransmit the bounce that we just generated, just log a warning. + */ +#define SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE (notify_mask & MAIL_ERROR_BOUNCE) + + if (bounce_status == 0 && SEND_POSTMASTER_SINGLE_BOUNCE_NOTICE + && strcasecmp_utf8(orig_sender, mail_addr_double_bounce()) != 0) { + + /* + * Send the text with reason for the bounce, and the headers of + * the original message. Don't bother sending the boiler-plate + * text. This postmaster notice is not critical, so if it fails + * don't retransmit the bounce that we just generated, just log a + * warning. + */ + if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), + var_bounce_rcpt, + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + if (bounce_header(bounce, bounce_info, var_bounce_rcpt, + POSTMASTER_COPY) == 0 + && bounce_recipient_log(bounce, bounce_info) == 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_recipient_dsn(bounce, bounce_info) == 0) + bounce_original(bounce, bounce_info, DSN_RET_HDRS); + postmaster_status = post_mail_fclose(bounce); + if (postmaster_status == 0) + msg_info("%s: postmaster non-delivery notification: %s", + queue_id, STR(new_id)); + } + if (postmaster_status) + msg_warn("%s: postmaster notice failed while bouncing to %s", + queue_id, orig_sender); + } + } + + /* + * Optionally, delete the recipient from the queue file. + */ + if (bounce_status == 0 && (flags & BOUNCE_FLAG_DELRCPT)) + bounce_delrcpt_one(bounce_info); + + /* + * Cleanup. + */ + bounce_mail_free(bounce_info); + vstring_free(new_id); + + return (bounce_status); +} diff --git a/src/bounce/bounce_service.h b/src/bounce/bounce_service.h new file mode 100644 index 0000000..b79542f --- /dev/null +++ b/src/bounce/bounce_service.h @@ -0,0 +1,121 @@ +/*++ +/* NAME +/* bounce_service 3h +/* SUMMARY +/* bounce message service +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include + + /* + * Application-specific. + */ +#include + + /* + * bounce_service.c + */ +extern int bounce_hfrom_format; + + /* + * bounce_append_service.c + */ +extern int bounce_append_service(int, char *, char *, RECIPIENT *, DSN *); + + /* + * bounce_notify_service.c + */ +extern int bounce_notify_service(int, char *, char *, char *, char *, int, char *, char *, int, BOUNCE_TEMPLATES *); + + /* + * bounce_warn_service.c + */ +extern int bounce_warn_service(int, char *, char *, char *, char *, int, char *, char *, int, BOUNCE_TEMPLATES *); + + /* + * bounce_trace_service.c + */ +extern int bounce_trace_service(int, char *, char *, char *, char *, int, char *, char *, int, BOUNCE_TEMPLATES *); + + /* + * bounce_notify_verp.c + */ +extern int bounce_notify_verp(int, char *, char *, char *, char *, int, char *, char *, int, char *, BOUNCE_TEMPLATES *); + + /* + * bounce_one_service.c + */ +extern int bounce_one_service(int, char *, char *, char *, int, char *, char *, int, RCPT_BUF *, DSN_BUF *, BOUNCE_TEMPLATES *); + + /* + * bounce_cleanup.c + */ +extern VSTRING *bounce_cleanup_path; +extern void bounce_cleanup_register(char *, char *); +extern void bounce_cleanup_log(void); +extern void bounce_cleanup_unregister(void); + +#define bounce_cleanup_registered() (bounce_cleanup_path != 0) + + /* + * bounce_notify_util.c + */ +typedef struct { + const char *service; /* bounce or defer */ + const char *queue_name; /* incoming, etc. */ + const char *queue_id; /* base name */ + const char *mime_encoding; /* null or encoding */ + const char *dsn_envid; /* DSN envelope ID */ + const char *mime_boundary; /* for MIME */ + BOUNCE_TEMPLATE *template; /* bounce message template */ + VSTRING *buf; /* scratch pad */ + VSTRING *sender; /* envelope sender */ + VSTREAM *orig_fp; /* open queue file */ + long orig_offs; /* start of content */ + time_t arrival_time; /* time of arrival */ + long message_size; /* size of content */ + VSTRING *orig_msgid; /* original message-id */ + RCPT_BUF *rcpt_buf; /* recipient info */ + DSN_BUF *dsn_buf; /* delivery status info */ + BOUNCE_LOG *log_handle; /* open logfile */ + char *mail_name; /* $mail_name, cooked */ + int smtputf8; /* SMTPUTF8 requested */ +} BOUNCE_INFO; + + /* */ + +extern BOUNCE_INFO *bounce_mail_init(const char *, const char *, const char *, const char *, int, const char *, BOUNCE_TEMPLATE *); +extern BOUNCE_INFO *bounce_mail_one_init(const char *, const char *, const char *, int, const char *, RCPT_BUF *, DSN_BUF *, BOUNCE_TEMPLATE *); +extern void bounce_mail_free(BOUNCE_INFO *); +extern int bounce_header(VSTREAM *, BOUNCE_INFO *, const char *, int); +extern int bounce_boilerplate(VSTREAM *, BOUNCE_INFO *); +extern int bounce_recipient_log(VSTREAM *, BOUNCE_INFO *); +extern int bounce_diagnostic_log(VSTREAM *, BOUNCE_INFO *, int); +extern int bounce_header_dsn(VSTREAM *, BOUNCE_INFO *); +extern int bounce_recipient_dsn(VSTREAM *, BOUNCE_INFO *); +extern int bounce_diagnostic_dsn(VSTREAM *, BOUNCE_INFO *, int); +extern int bounce_original(VSTREAM *, BOUNCE_INFO *, int); +extern void bounce_delrcpt(BOUNCE_INFO *); +extern void bounce_delrcpt_one(BOUNCE_INFO *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/bounce/bounce_template.c b/src/bounce/bounce_template.c new file mode 100644 index 0000000..67a5cf7 --- /dev/null +++ b/src/bounce/bounce_template.c @@ -0,0 +1,548 @@ +/*++ +/* NAME +/* bounce_template 3 +/* SUMMARY +/* bounce template support +/* SYNOPSIS +/* #include +/* +/* BOUNCE_TEMPLATE *bounce_template_create(def_template) +/* const BOUNCE_TEMPLATE *def_template; +/* +/* void bounce_template_free(template) +/* BOUNCE_TEMPLATE *template; +/* +/* void bounce_template_load(template, stream, buffer, origin) +/* BOUNCE_TEMPLATE *template; +/* VSTREAM *stream; +/* const char *buffer; +/* const char *origin; +/* +/* void bounce_template_headers(out_fn, stream, template, +/* rcpt, postmaster_copy) +/* int (*out_fn)(VSTREAM *, const char *, ...); +/* VSTREAM *stream; +/* BOUNCE_TEMPLATE *template; +/* const char *rcpt; +/* int postmaster_copy; +/* +/* const char *bounce_template_encoding(template) +/* BOUNCE_TEMPLATE *template; +/* +/* const char *bounce_template_charset(template) +/* BOUNCE_TEMPLATE *template; +/* +/* void bounce_template_expand(out_fn, stream, template) +/* int (*out_fn)(VSTREAM *, const char *); +/* VSTREAM *stream; +/* BOUNCE_TEMPLATE *template; +/* +/* void bounce_template_dump(stream, template) +/* VSTREAM *stream; +/* BOUNCE_TEMPLATE *template; +/* +/* int IS_FAILURE_TEMPLATE(template) +/* int IS_DELAY_TEMPLATE(template) +/* int IS_SUCCESS_TEMPLATE(template) +/* BOUNCE_TEMPLATE *template; +/* DESCRIPTION +/* This module implements the built-in and external bounce +/* message template support. The content of a template are +/* private. To access information within a template, use +/* the API described in this document. +/* +/* bounce_template_create() creates a template, with the +/* specified default settings. The template defaults are not +/* copied. +/* +/* bounce_template_free() destroys a bounce message template. +/* +/* bounce_template_load() overrides a bounce template with the +/* specified buffer from the specified origin. The buffer and +/* origin are copied. Specify a null buffer and origin pointer +/* to reset the template to the defaults specified with +/* bounce_template_create(). +/* +/* bounce_template_headers() sends the postmaster or non-postmaster +/* From/Subject/To message headers to the specified stream. +/* The recipient address is expected to be in RFC822 external +/* form. The postmaster_copy argument is one of POSTMASTER_COPY +/* or NO_POSTMASTER_COPY. +/* +/* bounce_template_encoding() returns the encoding (MAIL_ATTR_ENC_7BIT +/* or MAIL_ATTR_ENC_8BIT) for the bounce template message text. +/* +/* bounce_template_charset() returns the character set for the +/* bounce template message text. +/* +/* bounce_template_expand() expands the body text of the +/* specified template and writes the result to the specified +/* stream. +/* +/* bounce_template_dump() dumps the specified template to the +/* specified stream. +/* +/* The IS_MUMBLE_TEMPLATE() macros are predicates that +/* determine whether the template is of the specified type. +/* DIAGNOSTICS +/* Fatal error: out of memory, undefined macro name in template. +/* SEE ALSO +/* bounce_templates(3) bounce template group support +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#ifndef NO_EAI +#include +#endif + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include +#include + + /* + * The following tables implement support for bounce template expansions of + * $_days ($_hours, etc.). The expansion of + * these is the actual parameter value divided by the number of seconds in a + * day (hour, etc.), so that we can produce nicely formatted bounce messages + * with time values converted into the appropriate units. + * + * Ideally, the bounce template processor would strip the _days etc. suffix + * from the parameter name, and use the parameter name to look up the actual + * parameter value and its default value (the default value specifies the + * default time unit of that parameter (seconds, minutes, etc.)), and use + * this to convert the parameter string value into the corresponding number + * of seconds. The bounce template processor would then use the _hours etc. + * suffix from the bounce template to divide this number by the number of + * seconds in an hour, etc. and produce the number that is needed for the + * template. + * + * Unfortunately, there exists no code to look up default values by parameter + * name. If such code existed, then we could do the _days, _hours, etc. + * conversion with every main.cf time parameter without having to know in + * advance what time parameter names exist. + * + * So we have to either maintain our own table of all time related main.cf + * parameter names and defaults (like the postconf command does) or we make + * a special case for a few parameters of special interest. + * + * We go for the second solution. There are only a few parameters that need + * this treatment, and there will be more special cases when individual + * queue files get support for individual expiration times, and when other + * queue file information needs to be reported in bounce template messages. + * + * A really lame implementation would simply strip the optional s, h, d, etc. + * suffix from the actual (string) parameter value and not do any conversion + * at all to hours, days or weeks. But then the information in delay warning + * notices could be seriously incorrect. + */ +typedef struct { + const char *suffix; /* days, hours, etc. */ + int suffix_len; /* byte count */ + int divisor; /* divisor */ +} BOUNCE_TIME_DIVISOR; + +#define STRING_AND_LEN(x) (x), (sizeof(x) - 1) + +static const BOUNCE_TIME_DIVISOR time_divisors[] = { + STRING_AND_LEN("seconds"), 1, + STRING_AND_LEN("minutes"), 60, + STRING_AND_LEN("hours"), 60 * 60, + STRING_AND_LEN("days"), 24 * 60 * 60, + STRING_AND_LEN("weeks"), 7 * 24 * 60 * 60, + 0, 0, +}; + + /* + * The few special-case main.cf parameters that have support for _days, etc. + * suffixes for automatic conversion when expanded into a bounce template. + */ +typedef struct { + const char *param_name; /* parameter name */ + int param_name_len; /* name length */ + int *value; /* parameter value */ +} BOUNCE_TIME_PARAMETER; + +static const BOUNCE_TIME_PARAMETER time_parameter[] = { + STRING_AND_LEN(VAR_DELAY_WARN_TIME), &var_delay_warn_time, + STRING_AND_LEN(VAR_MAX_QUEUE_TIME), &var_max_queue_time, + 0, 0, +}; + + /* + * Parameters whose value may have to be converted to UTF-8 for presentation + * purposes. + */ +typedef struct { + const char *param_name; /* parameter name */ + char **value; /* parameter value */ +} BOUNCE_STR_PARAMETER; + +static const BOUNCE_STR_PARAMETER str_parameter[] = { + VAR_MYHOSTNAME, &var_myhostname, + VAR_MYDOMAIN, &var_mydomain, + 0, 0, +}; + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* bounce_template_create - create one template */ + +BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *prototype) +{ + BOUNCE_TEMPLATE *tp; + + tp = (BOUNCE_TEMPLATE *) mymalloc(sizeof(*tp)); + *tp = *prototype; + return (tp); +} + +/* bounce_template_free - destroy one template */ + +void bounce_template_free(BOUNCE_TEMPLATE *tp) +{ + if (tp->buffer) { + myfree(tp->buffer); + myfree((void *) tp->origin); + } + myfree((void *) tp); +} + +/* bounce_template_reset - reset template to default */ + +static void bounce_template_reset(BOUNCE_TEMPLATE *tp) +{ + myfree(tp->buffer); + myfree((void *) tp->origin); + *tp = *(tp->prototype); +} + +/* bounce_template_load - override one template */ + +void bounce_template_load(BOUNCE_TEMPLATE *tp, const char *origin, + const char *buffer) +{ + + /* + * Clean up after a previous call. + */ + if (tp->buffer) + bounce_template_reset(tp); + + /* + * Postpone the work of template parsing until it is really needed. Most + * bounce service calls never need a template. + */ + if (buffer && origin) { + tp->flags |= BOUNCE_TMPL_FLAG_NEW_BUFFER; + tp->buffer = mystrdup(buffer); + tp->origin = mystrdup(origin); + } +} + +/* bounce_template_parse_buffer - initialize template */ + +static void bounce_template_parse_buffer(BOUNCE_TEMPLATE *tp) +{ + char *tval = tp->buffer; + char *cp; + char **cpp; + int cpp_len; + int cpp_used; + int hlen; + char *hval; + + /* + * Sanity check. + */ + if ((tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) == 0) + msg_panic("bounce_template_parse_buffer: nothing to do here"); + tp->flags &= ~BOUNCE_TMPL_FLAG_NEW_BUFFER; + + /* + * Discard the unusable template and use the default one instead. + */ +#define CLEANUP_AND_RETURN() do { \ + bounce_template_reset(tp); \ + return; \ + } while (0) + + /* + * Parse pseudo-header labels and values. + * + * XXX EAI: allow UTF8 in template headers when responding to SMTPUTF8 + * message. Sending SMTPUTF8 in response to non-SMTPUTF8 mail would make + * no sense. + */ +#define GETLINE(line, buf) \ + (((line) = (buf)) != 0 ? ((buf) = split_at((buf), '\n'), (line)) : 0) + + while ((GETLINE(cp, tval)) != 0 && (hlen = is_header(cp)) > 0) { + for (hval = cp + hlen; *hval && (*hval == ':' || ISSPACE(*hval)); hval++) + *hval = 0; + if (*hval == 0) { + msg_warn("%s: empty \"%s\" header value in %s template " + "-- ignoring this template", + tp->origin, cp, tp->class); + CLEANUP_AND_RETURN(); + } + if (!allascii(hval)) { + msg_warn("%s: non-ASCII \"%s\" header value in %s template " + "-- ignoring this template", + tp->origin, cp, tp->class); + CLEANUP_AND_RETURN(); + } + if (strcasecmp("charset", cp) == 0) { + tp->mime_charset = hval; + } else if (strcasecmp("from", cp) == 0) { + tp->std_from = tp->obs_from = hval; + } else if (strcasecmp("subject", cp) == 0) { + tp->subject = hval; + } else if (strcasecmp("postmaster-subject", cp) == 0) { + if (tp->postmaster_subject == 0) { + msg_warn("%s: inapplicable \"%s\" header label in %s template " + "-- ignoring this template", + tp->origin, cp, tp->class); + CLEANUP_AND_RETURN(); + } + tp->postmaster_subject = hval; + } else { + msg_warn("%s: unknown \"%s\" header label in %s template " + "-- ignoring this template", + tp->origin, cp, tp->class); + CLEANUP_AND_RETURN(); + } + } + + /* + * Skip blank lines between header and message text. + */ + while (cp && (*cp == 0 || allspace(cp))) + (void) GETLINE(cp, tval); + if (cp == 0) { + msg_warn("%s: missing message text in %s template " + "-- ignoring this template", + tp->origin, tp->class); + CLEANUP_AND_RETURN(); + } + + /* + * Is this 7bit or 8bit text? If the character set is US-ASCII, then + * don't allow 8bit text. Don't assume 8bit when charset was changed. + */ +#define NON_ASCII(p) ((p) && *(p) && !allascii((p))) + + if (NON_ASCII(cp) || NON_ASCII(tval)) { + if (strcasecmp(tp->mime_charset, "us-ascii") == 0) { + msg_warn("%s: 8-bit message text in %s template", + tp->origin, tp->class); + msg_warn("please specify a charset value other than us-ascii"); + msg_warn("-- ignoring this template for now"); + CLEANUP_AND_RETURN(); + } + tp->mime_encoding = MAIL_ATTR_ENC_8BIT; + } + + /* + * Collect the message text and null-terminate the result. + */ + cpp_len = 10; + cpp_used = 0; + cpp = (char **) mymalloc(sizeof(*cpp) * cpp_len); + while (cp) { + cpp[cpp_used++] = cp; + if (cpp_used >= cpp_len) { + cpp = (char **) myrealloc((void *) cpp, + sizeof(*cpp) * 2 * cpp_len); + cpp_len *= 2; + } + (void) GETLINE(cp, tval); + } + cpp[cpp_used] = 0; + tp->message_text = (const char **) cpp; +} + +/* bounce_template_lookup - lookup $name value */ + +static const char *bounce_template_lookup(const char *key, int unused_mode, + void *context) +{ + BOUNCE_TEMPLATE *tp = (BOUNCE_TEMPLATE *) context; + const BOUNCE_TIME_PARAMETER *bp; + const BOUNCE_TIME_DIVISOR *bd; + const BOUNCE_STR_PARAMETER *sp; + static VSTRING *buf; + int result; + const char *asc_val; + const char *utf8_val; + + /* + * Look for parameter names that can have a time unit suffix, and scale + * the time value according to the suffix. + */ + for (bp = time_parameter; bp->param_name; bp++) { + if (strncmp(key, bp->param_name, bp->param_name_len) == 0 + && key[bp->param_name_len] == '_') { + for (bd = time_divisors; bd->suffix; bd++) { + if (strcmp(key + bp->param_name_len + 1, bd->suffix) == 0) { + result = bp->value[0] / bd->divisor; + if (result > 999 && bd->divisor < 86400) { + msg_warn("%s: excessive result \"%d\" in %s " + "template conversion of parameter \"%s\"", + tp->origin, result, tp->class, key); + msg_warn("please increase time unit \"%s\" of \"%s\" " + "in %s template", bd->suffix, key, tp->class); + msg_warn("for instructions see the bounce(5) manual"); + } else if (result == 0 && bp->value[0] && bd->divisor > 1) { + msg_warn("%s: zero result in %s template " + "conversion of parameter \"%s\"", + tp->origin, tp->class, key); + msg_warn("please reduce time unit \"%s\" of \"%s\" " + "in %s template", bd->suffix, key, tp->class); + msg_warn("for instructions see the bounce(5) manual"); + } + if (buf == 0) + buf = vstring_alloc(10); + vstring_sprintf(buf, "%d", result); + return (STR(buf)); + } + } + msg_fatal("%s: unrecognized suffix \"%s\" in parameter \"%s\"", + tp->origin, + key + bp->param_name_len + 1, key); + } + } + + /* + * Look for parameter names that may have to be up-converted for + * presentation purposes. + */ +#ifndef NO_EAI + if (var_smtputf8_enable) { + for (sp = str_parameter; sp->param_name; sp++) { + if (strcmp(key, sp->param_name) == 0) { + asc_val = sp->value[0]; + if (!allascii(asc_val)) { + msg_warn("%s: conversion \"%s\" failed: " + "non-ASCII input value: \"%s\"", + tp->origin, key, asc_val); + return (asc_val); + } else if ((utf8_val = midna_domain_to_utf8(asc_val)) == 0) { + msg_warn("%s: conversion \"%s\" failed: " + "input value: \"%s\"", + tp->origin, key, asc_val); + return (asc_val); + } else { + return (utf8_val); + } + } + } + } +#endif + return (mail_conf_lookup_eval(key)); +} + +/* bounce_template_headers - send template headers */ + +void bounce_template_headers(BOUNCE_XP_PRN_FN out_fn, VSTREAM *fp, + BOUNCE_TEMPLATE *tp, + const char *rcpt, + int postmaster_copy) +{ + if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) + bounce_template_parse_buffer(tp); + + out_fn(fp, "From: %s", bounce_hfrom_format == HFROM_FORMAT_CODE_STD ? + tp->std_from : tp->obs_from); + out_fn(fp, "Subject: %s", tp->postmaster_subject && postmaster_copy ? + tp->postmaster_subject : tp->subject); + out_fn(fp, "To: %s", rcpt); +} + +/* bounce_template_expand - expand template to stream */ + +void bounce_template_expand(BOUNCE_XP_PUT_FN out_fn, VSTREAM *fp, + BOUNCE_TEMPLATE *tp) +{ + VSTRING *buf = vstring_alloc(100); + const char **cpp; + int stat; + + if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) + bounce_template_parse_buffer(tp); + + for (cpp = tp->message_text; *cpp; cpp++) { + stat = mac_expand(buf, *cpp, MAC_EXP_FLAG_PRINTABLE, (char *) 0, + bounce_template_lookup, (void *) tp); + if (stat & MAC_PARSE_ERROR) + msg_fatal("%s: bad $name syntax in %s template: %s", + tp->origin, tp->class, *cpp); + if (stat & MAC_PARSE_UNDEF) + msg_fatal("%s: undefined $name in %s template: %s", + tp->origin, tp->class, *cpp); + out_fn(fp, STR(buf)); + } + vstring_free(buf); +} + +/* bounce_template_dump - dump template to stream */ + +void bounce_template_dump(VSTREAM *fp, BOUNCE_TEMPLATE *tp) +{ + const char **cpp; + + if (tp->flags & BOUNCE_TMPL_FLAG_NEW_BUFFER) + bounce_template_parse_buffer(tp); + + vstream_fprintf(fp, "Charset: %s\n", tp->mime_charset); + vstream_fprintf(fp, "From: %s\n", bounce_hfrom_format == HFROM_FORMAT_CODE_STD ? + tp->std_from : tp->obs_from); + vstream_fprintf(fp, "Subject: %s\n", tp->subject); + if (tp->postmaster_subject) + vstream_fprintf(fp, "Postmaster-Subject: %s\n", + tp->postmaster_subject); + vstream_fprintf(fp, "\n"); + for (cpp = tp->message_text; *cpp; cpp++) + vstream_fprintf(fp, "%s\n", *cpp); +} diff --git a/src/bounce/bounce_template.h b/src/bounce/bounce_template.h new file mode 100644 index 0000000..87835fc --- /dev/null +++ b/src/bounce/bounce_template.h @@ -0,0 +1,99 @@ +#ifndef _BOUNCE_TEMPLATE_H_INCLUDED_ +#define _BOUNCE_TEMPLATE_H_INCLUDED_ + +/*++ +/* NAME +/* bounce_template 3h +/* SUMMARY +/* bounce template support +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Structure of a single bounce template. Each template is manipulated by + * itself, without any external markers and delimiters. Applications are not + * supposed to access BOUNCE_TEMPLATE attributes directly. + */ +typedef struct BOUNCE_TEMPLATE { + int flags; + const char *class; /* for diagnostics (fixed) */ + const char *origin; /* built-in or pathname */ + const char *mime_charset; /* character set (configurable) */ + const char *mime_encoding; /* 7bit or 8bit (derived) */ + const char *obs_from; /* originator (configurable) */ + const char *std_from; /* originator (configurable) */ + const char *subject; /* general subject (configurable) */ + const char *postmaster_subject; /* postmaster subject (configurable) */ + const char **message_text; /* message text (configurable) */ + const struct BOUNCE_TEMPLATE *prototype; /* defaults */ + char *buffer; /* ripped text */ +} BOUNCE_TEMPLATE; + +#define BOUNCE_TMPL_FLAG_NEW_BUFFER (1<<0) + +#define BOUNCE_TMPL_CLASS_FAILURE "failure" +#define BOUNCE_TMPL_CLASS_DELAY "delay" +#define BOUNCE_TMPL_CLASS_SUCCESS "success" +#define BOUNCE_TMPL_CLASS_VERIFY "verify" + +#define IS_FAILURE_TEMPLATE(t) ((t)->class[0] == BOUNCE_TMPL_CLASS_FAILURE[0]) +#define IS_DELAY_TEMPLATE(t) ((t)->class[0] == BOUNCE_TMPL_CLASS_DELAY[0]) +#define IS_SUCCESS_TEMPLATE(t) ((t)->class[0] == BOUNCE_TMPL_CLASS_SUCCESS[0]) + +#define bounce_template_encoding(t) ((t)->mime_encoding) +#define bounce_template_charset(t) ((t)->mime_charset) + +typedef int PRINTFPTRLIKE(2, 3) (*BOUNCE_XP_PRN_FN) (VSTREAM *, const char *,...); +typedef int (*BOUNCE_XP_PUT_FN) (VSTREAM *, const char *); + +extern BOUNCE_TEMPLATE *bounce_template_create(const BOUNCE_TEMPLATE *); +extern void bounce_template_free(BOUNCE_TEMPLATE *); +extern void bounce_template_load(BOUNCE_TEMPLATE *, const char *, const char *); +extern void bounce_template_headers(BOUNCE_XP_PRN_FN, VSTREAM *, BOUNCE_TEMPLATE *, const char *, int); +extern void bounce_template_expand(BOUNCE_XP_PUT_FN, VSTREAM *, BOUNCE_TEMPLATE *); +extern void bounce_template_dump(VSTREAM *, BOUNCE_TEMPLATE *); + +#define POSTMASTER_COPY 1 /* postmaster copy */ +#define NO_POSTMASTER_COPY 0 /* not postmaster copy */ + + /* + * Structure of a bounce template collection. These templates are read and + * written in their external representation, with markers and delimiters. + */ +typedef struct { + BOUNCE_TEMPLATE *failure; + BOUNCE_TEMPLATE *delay; + BOUNCE_TEMPLATE *success; + BOUNCE_TEMPLATE *verify; +} BOUNCE_TEMPLATES; + +BOUNCE_TEMPLATES *bounce_templates_create(void); +void bounce_templates_free(BOUNCE_TEMPLATES *); +void bounce_templates_load(VSTREAM *, BOUNCE_TEMPLATES *); +void bounce_templates_expand(VSTREAM *, BOUNCE_TEMPLATES *); +void bounce_templates_dump(VSTREAM *, BOUNCE_TEMPLATES *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/bounce/bounce_templates.c b/src/bounce/bounce_templates.c new file mode 100644 index 0000000..f81dfd4 --- /dev/null +++ b/src/bounce/bounce_templates.c @@ -0,0 +1,399 @@ +/*++ +/* NAME +/* bounce_templates 3 +/* SUMMARY +/* bounce template group support +/* SYNOPSIS +/* #include +/* +/* typedef struct { +/* .in +4 +/* BOUNCE_TEMPLATE *failure; +/* BOUNCE_TEMPLATE *delay; +/* BOUNCE_TEMPLATE *success; +/* BOUNCE_TEMPLATE *verify; +/* .in -4 +/* } BOUNCE_TEMPLATES; +/* +/* BOUNCE_TEMPLATES *bounce_templates_create(void) +/* +/* void bounce_templates_free(templates) +/* BOUNCE_TEMPLATES *templates; +/* +/* void bounce_templates_load(stream, templates) +/* VSTREAM *stream; +/* BOUNCE_TEMPLATES *templates; +/* +/* void bounce_templates_expand(stream, templates) +/* VSTREAM *stream; +/* BOUNCE_TEMPLATES *templates; +/* +/* void bounce_templates_dump(stream, templates) +/* VSTREAM *stream; +/* BOUNCE_TEMPLATES *templates; +/* DESCRIPTION +/* This module implements support for bounce template groups +/* (i.e. groups that contain one template of each type). +/* +/* bounce_templates_create() creates a bounce template group, +/* with default settings. +/* +/* bounce_templates_free() destroys a bounce template group. +/* +/* bounce_templates_load() reads zero or more bounce templates +/* from the specified file to override built-in templates. +/* +/* bounce_templates_expand() expands $name macros and writes +/* the text portions of the specified bounce template group +/* to the specified stream. +/* +/* bounce_templates_dump() writes the complete content of the +/* specified bounce template group to the specified stream. +/* The format is compatible with bounce_templates_load(). +/* DIAGNOSTICS +/* Fatal error: out of memory, undefined macro name in template. +/* SEE ALSO +/* bounce_template(3) bounce template support +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include + + /* + * The fail template is for permanent failure. + */ +static const char *def_bounce_failure_body[] = { + "This is the mail system at host $myhostname.", + "", + "I'm sorry to have to inform you that your message could not", + "be delivered to one or more recipients. It's attached below.", + "", + "For further assistance, please send mail to " MAIL_ADDR_POSTMASTER ".", + "", + "If you do so, please include this problem report. You can", + "delete your own text from the attached returned message.", + "", + " The mail system", + 0, +}; + +static const BOUNCE_TEMPLATE def_bounce_failure_template = { + 0, + BOUNCE_TMPL_CLASS_FAILURE, + "[built-in]", + "us-ascii", + MAIL_ATTR_ENC_7BIT, + MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)", + "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">", + "Undelivered Mail Returned to Sender", + "Postmaster Copy: Undelivered Mail", + def_bounce_failure_body, + &def_bounce_failure_template, +}; + + /* + * The delay template is for delayed mail notifications. + */ +static const char *def_bounce_delay_body[] = { + "This is the mail system at host $myhostname.", + "", + "####################################################################", + "# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. #", + "####################################################################", + "", + "Your message could not be delivered for more than $delay_warning_time_hours hour(s).", + "It will be retried until it is $maximal_queue_lifetime_days day(s) old.", + "", + "For further assistance, please send mail to " MAIL_ADDR_POSTMASTER ".", + "", + "If you do so, please include this problem report. You can", + "delete your own text from the attached returned message.", + "", + " The mail system", + 0, +}; + +static const BOUNCE_TEMPLATE def_bounce_delay_template = { + 0, + BOUNCE_TMPL_CLASS_DELAY, + "[built-in]", + "us-ascii", + MAIL_ATTR_ENC_7BIT, + MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)", + "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">", + "Delayed Mail (still being retried)", + "Postmaster Warning: Delayed Mail", + def_bounce_delay_body, + &def_bounce_delay_template +}; + + /* + * The success template is for "delivered", "expanded" and "relayed" success + * notifications. + */ +static const char *def_bounce_success_body[] = { + "This is the mail system at host $myhostname.", + "", + "Your message was successfully delivered to the destination(s)", + "listed below. If the message was delivered to mailbox you will", + "receive no further notifications. Otherwise you may still receive", + "notifications of mail delivery errors from other systems.", + "", + " The mail system", + 0, +}; + +static const BOUNCE_TEMPLATE def_bounce_success_template = { + 0, + BOUNCE_TMPL_CLASS_SUCCESS, + "[built-in]", + "us-ascii", + MAIL_ATTR_ENC_7BIT, + MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)", + "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">", + "Successful Mail Delivery Report", + 0, + def_bounce_success_body, + &def_bounce_success_template, +}; + + /* + * The "verify" template is for verbose delivery (sendmail -v) and for + * address verification (sendmail -bv). + */ +static const char *def_bounce_verify_body[] = { + "This is the mail system at host $myhostname.", + "", + "Enclosed is the mail delivery report that you requested.", + "", + " The mail system", + 0, +}; + +static const BOUNCE_TEMPLATE def_bounce_verify_template = { + 0, + BOUNCE_TMPL_CLASS_VERIFY, + "[built-in]", + "us-ascii", + MAIL_ATTR_ENC_7BIT, + MAIL_ADDR_MAIL_DAEMON " (Mail Delivery System)", + "Mail Delivery System <" MAIL_ADDR_MAIL_DAEMON ">", + "Mail Delivery Status Report", + 0, + def_bounce_verify_body, + &def_bounce_verify_template, +}; + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* bounce_templates_create - create template group */ + +BOUNCE_TEMPLATES *bounce_templates_create(void) +{ + BOUNCE_TEMPLATES *bs; + + bs = (BOUNCE_TEMPLATES *) mymalloc(sizeof(*bs)); + bs->failure = bounce_template_create(&def_bounce_failure_template); + bs->delay = bounce_template_create(&def_bounce_delay_template); + bs->success = bounce_template_create(&def_bounce_success_template); + bs->verify = bounce_template_create(&def_bounce_verify_template); + return (bs); +} + +/* bounce_templates_free - destroy template group */ + +void bounce_templates_free(BOUNCE_TEMPLATES *bs) +{ + bounce_template_free(bs->failure); + bounce_template_free(bs->delay); + bounce_template_free(bs->success); + bounce_template_free(bs->verify); + myfree((void *) bs); +} + +/* bounce_templates_load - load template or group from stream */ + +void bounce_templates_load(VSTREAM *fp, BOUNCE_TEMPLATES *ts) +{ + VSTRING *line_buf; + char *member_name; + VSTRING *multi_line_buf = 0; + VSTRING *saved_member_name = 0; + VSTRING *saved_end_marker = 0; + char *value; + int lineno; + const char *err; + char *cp; + int len; /* Grr... */ + + /* + * XXX That's a lot of non-reusable code to parse a configuration file. + * Unfortunately, much of the "name = value" infrastructure is married to + * the dict(3) class which doesn't really help here. + */ + line_buf = vstring_alloc(100); + lineno = 1; + while (vstring_get_nonl(line_buf, fp) > 0) { + lineno++; + cp = STR(line_buf) + strspn(STR(line_buf), " \t\n\v\f\r"); + if (*cp == 0 || *cp == '#') + continue; + if ((err = split_nameval(STR(line_buf), &member_name, &value)) != 0) + msg_fatal("%s, line %d: %s: \"%s\"", + VSTREAM_PATH(fp), lineno, err, STR(line_buf)); + if (value[0] == '<' && value[1] == '<') { + value += 2; + while (ISSPACE(*value)) + value++; + if (*value == 0) + msg_fatal("%s, line %d: missing end marker after <<", + VSTREAM_PATH(fp), lineno); + if (!ISALNUM(*value)) + msg_fatal("%s, line %d: malformed end marker after <<", + VSTREAM_PATH(fp), lineno); + if (multi_line_buf == 0) { + saved_member_name = vstring_alloc(100); + saved_end_marker = vstring_alloc(100); + multi_line_buf = vstring_alloc(100); + } else + VSTRING_RESET(multi_line_buf); + vstring_strcpy(saved_member_name, member_name); + vstring_strcpy(saved_end_marker, value); + while (vstring_get_nonl(line_buf, fp) > 0) { + lineno++; + if (strcmp(STR(line_buf), STR(saved_end_marker)) == 0) + break; + if (VSTRING_LEN(multi_line_buf) > 0) + vstring_strcat(multi_line_buf, "\n"); + vstring_strcat(multi_line_buf, STR(line_buf)); + } + if (vstream_feof(fp)) + msg_warn("%s, line %d: missing \"%s\" end marker", + VSTREAM_PATH(fp), lineno, value); + member_name = STR(saved_member_name); + value = STR(multi_line_buf); + } +#define MATCH_TMPL_NAME(tname, tname_len, mname) \ + (strncmp(tname, mname, tname_len = strlen(tname)) == 0 \ + && strcmp(mname + tname_len, "_template") == 0) + + if (MATCH_TMPL_NAME(ts->failure->class, len, member_name)) + bounce_template_load(ts->failure, VSTREAM_PATH(fp), value); + else if (MATCH_TMPL_NAME(ts->delay->class, len, member_name)) + bounce_template_load(ts->delay, VSTREAM_PATH(fp), value); + else if (MATCH_TMPL_NAME(ts->success->class, len, member_name)) + bounce_template_load(ts->success, VSTREAM_PATH(fp), value); + else if (MATCH_TMPL_NAME(ts->verify->class, len, member_name)) + bounce_template_load(ts->verify, VSTREAM_PATH(fp), value); + else + msg_warn("%s, line %d: unknown template name: %s " + "-- ignoring this template", + VSTREAM_PATH(fp), lineno, member_name); + } + vstring_free(line_buf); + if (multi_line_buf) { + vstring_free(saved_member_name); + vstring_free(saved_end_marker); + vstring_free(multi_line_buf); + } +} + +/* bounce_plain_out - output line as plain text */ + +static int bounce_plain_out(VSTREAM *fp, const char *text) +{ + vstream_fprintf(fp, "%s\n", text); + return (0); +} + +/* bounce_templates_expand - dump expanded template group text to stream */ + +void bounce_templates_expand(VSTREAM *fp, BOUNCE_TEMPLATES *ts) +{ + BOUNCE_TEMPLATE *tp; + + tp = ts->failure; + vstream_fprintf(fp, "expanded_%s_text = <class); + bounce_template_expand(bounce_plain_out, fp, tp); + vstream_fprintf(fp, "EOF\n\n"); + + tp = ts->delay; + vstream_fprintf(fp, "expanded_%s_text = <class); + bounce_template_expand(bounce_plain_out, fp, tp); + vstream_fprintf(fp, "EOF\n\n"); + + tp = ts->success; + vstream_fprintf(fp, "expanded_%s_text = <class); + bounce_template_expand(bounce_plain_out, fp, tp); + vstream_fprintf(fp, "EOF\n\n"); + + tp = ts->verify; + vstream_fprintf(fp, "expanded_%s_text = <class); + bounce_template_expand(bounce_plain_out, fp, tp); + vstream_fprintf(fp, "EOF\n"); +} + +/* bounce_templates_dump - dump bounce template group to stream */ + +void bounce_templates_dump(VSTREAM *fp, BOUNCE_TEMPLATES *ts) +{ + BOUNCE_TEMPLATE *tp; + + tp = ts->failure; + vstream_fprintf(fp, "%s_template = <class); + bounce_template_dump(fp, tp); + vstream_fprintf(fp, "EOF\n\n"); + + tp = ts->delay; + vstream_fprintf(fp, "%s_template = <class); + bounce_template_dump(fp, tp); + vstream_fprintf(fp, "EOF\n\n"); + + tp = ts->success; + vstream_fprintf(fp, "%s_template = <class); + bounce_template_dump(fp, tp); + vstream_fprintf(fp, "EOF\n\n"); + + tp = ts->verify; + vstream_fprintf(fp, "%s_template = <class); + bounce_template_dump(fp, tp); + vstream_fprintf(fp, "EOF\n"); +} diff --git a/src/bounce/bounce_trace_service.c b/src/bounce/bounce_trace_service.c new file mode 100644 index 0000000..8a62305 --- /dev/null +++ b/src/bounce/bounce_trace_service.c @@ -0,0 +1,220 @@ +/*++ +/* NAME +/* bounce_trace_service 3 +/* SUMMARY +/* send status report to sender, server side +/* SYNOPSIS +/* #include "bounce_service.h" +/* +/* int bounce_trace_service(flags, service, queue_name, queue_id, +/* encoding, smtputf8, sender, envid, +/* ret, templates) +/* int flags; +/* char *service; +/* char *queue_name; +/* char *queue_id; +/* char *encoding; +/* int smtputf8; +/* char *sender; +/* char *envid; +/* int ret; +/* BOUNCE_TEMPLATES *templates; +/* DESCRIPTION +/* This module implements the server side of the trace_flush() +/* (send delivery notice) request. The logfile +/* is removed after the notice is posted. +/* +/* A status report includes a prelude with human-readable text, +/* a DSN-style report, and the email message that was subject of +/* the status report. +/* +/* When a status report is sent, the sender address is the empty +/* address. +/* DIAGNOSTICS +/* Fatal error: error opening existing file. +/* BUGS +/* SEE ALSO +/* bounce(3) basic bounce service client interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include /* USR_VRFY and RECORD flags */ + +/* Application-specific. */ + +#include "bounce_service.h" + +#define STR vstring_str + +/* bounce_trace_service - send a delivery status notice */ + +int bounce_trace_service(int flags, char *service, char *queue_name, + char *queue_id, char *encoding, + int smtputf8, + char *recipient, char *dsn_envid, + int unused_dsn_ret, + BOUNCE_TEMPLATES *ts) +{ + BOUNCE_INFO *bounce_info; + int bounce_status = 1; + VSTREAM *bounce; + int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks, + var_notify_classes); + VSTRING *new_id; + int count; + const char *sender; + + /* + * For consistency with fail/delay notifications, send notification for a + * non-bounce message as a single-bounce message, send notification for a + * single-bounce message as a double-bounce message, and drop requests to + * send notification for a double-bounce message. + */ +#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */ + + if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0) { + msg_info("%s: not sending trace/success notification for " + "double-bounce message", queue_id); + return (0); + } else if (*recipient == 0) { + if ((notify_mask & MAIL_ERROR_2BOUNCE) != 0) { + recipient = var_2bounce_rcpt; + sender = mail_addr_double_bounce(); + } else { + msg_info("%s: not sending trace/success notification " + "for single-bounce message", queue_id); + if (mail_queue_remove(service, queue_id) && errno != ENOENT) + msg_fatal("remove %s %s: %m", service, queue_id); + return (0); + } + } else { + /* Always send notification for non-bounce message. */ + sender = NULL_SENDER; + } + + /* + * Initialize. Open queue file, bounce log, etc. + * + * XXX DSN The trace service produces information from the trace logfile + * which is used for three types of reports: + * + * a) "what-if" reports that show what would happen without actually + * delivering mail (sendmail -bv). + * + * b) A report of actual deliveries (sendmail -v). + * + * c) DSN NOTIFY=SUCCESS reports of successful delivery ("delivered", + * "expanded" or "relayed"). + */ +#define NON_DSN_FLAGS (DEL_REQ_FLAG_USR_VRFY | DEL_REQ_FLAG_RECORD) + + bounce_info = bounce_mail_init(service, queue_name, queue_id, + encoding, smtputf8, dsn_envid, + flags & NON_DSN_FLAGS ? + ts->verify : ts->success); + + /* + * XXX With multi-recipient mail some queue file recipients may have + * NOTIFY=SUCCESS and others not. Depending on what subset of recipients + * are delivered, a trace file may or may not be created. Even when the + * last partial delivery attempt had no NOTIFY=SUCCESS recipients, a + * trace file may still exist from a previous partial delivery attempt. + * So as long as any recipient in the original queue file had + * NOTIFY=SUCCESS we have to always look for the trace file and be + * prepared for the file not to exist. + * + * See also comments in qmgr/qmgr_active.c. + */ + if (bounce_info->log_handle == 0) { + if (msg_verbose) + msg_info("%s: no trace file -- not sending a notification", + queue_id); + bounce_mail_free(bounce_info); + return (0); + } +#define NULL_TRACE_FLAGS 0 + + /* + * Send a single bounce with a template message header, some boilerplate + * text that pretends that we are a polite mail system, the text with + * per-recipient status, and a copy of the original message. + * + * XXX DSN We use the same trace file for "what-if", "verbose delivery" and + * "success" delivery reports. This saves file system overhead because + * there are fewer potential left-over files to remove up when we create + * a new queue file. + */ + new_id = vstring_alloc(10); + if ((bounce = post_mail_fopen_nowait(sender, recipient, + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + count = -1; + if (bounce_header(bounce, bounce_info, recipient, + NO_POSTMASTER_COPY) == 0 + && bounce_boilerplate(bounce, bounce_info) == 0 + && (count = bounce_diagnostic_log(bounce, bounce_info, + DSN_NOTIFY_OVERRIDE)) > 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_diagnostic_dsn(bounce, bounce_info, + DSN_NOTIFY_OVERRIDE) > 0) { + bounce_original(bounce, bounce_info, DSN_RET_HDRS); + bounce_status = post_mail_fclose(bounce); + if (bounce_status == 0) + msg_info("%s: sender delivery status notification: %s", + queue_id, STR(new_id)); + } else { + (void) vstream_fclose(bounce); + if (count == 0) + bounce_status = 0; + } + } + + /* + * Examine the completion status. Delete the trace log file only when the + * status notice was posted successfully. + */ + if (bounce_status == 0 && mail_queue_remove(service, queue_id) + && errno != ENOENT) + msg_fatal("remove %s %s: %m", service, queue_id); + + /* + * Cleanup. + */ + bounce_mail_free(bounce_info); + vstring_free(new_id); + + return (bounce_status); +} diff --git a/src/bounce/bounce_warn_service.c b/src/bounce/bounce_warn_service.c new file mode 100644 index 0000000..bbb805d --- /dev/null +++ b/src/bounce/bounce_warn_service.c @@ -0,0 +1,295 @@ +/*++ +/* NAME +/* bounce_warn_service 3 +/* SUMMARY +/* send non-delivery report to sender, server side +/* SYNOPSIS +/* #include "bounce_service.h" +/* +/* int bounce_warn_service(flags, service, queue_name, queue_id, +/* encoding, smtputf8, sender, envid, +/* dsn_ret, templates) +/* int flags; +/* char *service; +/* char *queue_name; +/* char *queue_id; +/* char *encoding; +/* int smtputf8; +/* char *sender; +/* char *envid; +/* int dsn_ret; +/* BOUNCE_TEMPLATES *ts; +/* DESCRIPTION +/* This module implements the server side of the bounce_warn() +/* (send delay notice) request. The logfile +/* is not removed, and a warning is sent instead of a bounce. +/* +/* When a message bounces, a full copy is sent to the originator, +/* and an optional copy of the diagnostics with message headers is +/* sent to the postmaster. The result is non-zero when the operation +/* should be tried again. +/* +/* When a bounce is sent, the sender address is the empty +/* address. When a bounce bounces, an optional double bounce +/* with the entire undeliverable mail is sent to the postmaster, +/* with as sender address the double bounce address. +/* DIAGNOSTICS +/* Fatal error: error opening existing file. +/* BUGS +/* SEE ALSO +/* bounce(3) basic bounce service client interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "bounce_service.h" + +#define STR vstring_str + +/* bounce_warn_service - send a delayed mail notice */ + +int bounce_warn_service(int unused_flags, char *service, char *queue_name, + char *queue_id, char *encoding, + int smtputf8, char *recipient, + char *dsn_envid, int dsn_ret, + BOUNCE_TEMPLATES *ts) +{ + BOUNCE_INFO *bounce_info; + int bounce_status = 1; + int postmaster_status = 1; + VSTREAM *bounce; + int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks, + var_notify_classes); + VSTRING *new_id = vstring_alloc(10); + char *postmaster; + int count; + + /* + * Initialize. Open queue file, bounce log, etc. + * + * XXX DSN This service produces RFC 3464-style "delayed mail" reports from + * information in the defer logfile. That same file is used for three + * different types of report: + * + * a) On-demand reports of all delayed deliveries by the mailq(1) command. + * This reports all recipients that have a transient delivery error. + * + * b) RFC 3464-style "delayed mail" notifications by the defer(8) service. + * This reports to the sender all recipients that have no DSN NOTIFY + * information (compatibility) and all recipients that have DSN + * NOTIFY=DELAY; this reports to postmaster all recipients, subject to + * notify_classes restrictions. + * + * c) RFC 3464-style bounce reports by the bounce(8) service when mail is + * too old. This reports to the sender all recipients that have no DSN + * NOTIFY information (compatibility) and all recipients that have DSN + * NOTIFY=FAILURE; this reports to postmaster all recipients, subject to + * notify_classes restrictions. + */ + bounce_info = bounce_mail_init(service, queue_name, queue_id, + encoding, smtputf8, dsn_envid, ts->delay); + +#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */ +#define NULL_TRACE_FLAGS 0 + + /* + * The choice of sender address depends on the recipient address. For a + * single bounce (a non-delivery notification to the message originator), + * the sender address is the empty string. For a double bounce (typically + * a failed single bounce, or a postmaster notification that was produced + * by any of the mail processes) the sender address is defined by the + * var_double_bounce_sender configuration variable. When a double bounce + * cannot be delivered, the queue manager blackholes the resulting triple + * bounce message. + */ + + /* + * Double bounce failed. Never send a triple bounce. + * + * However, this does not prevent double bounces from bouncing on other + * systems. In order to cope with this, either the queue manager must + * recognize the double-bounce recipient address and discard mail, or + * every delivery agent must recognize the double-bounce sender address + * and substitute something else so mail does not come back at us. + */ + if (strcasecmp_utf8(recipient, mail_addr_double_bounce()) == 0) { + msg_warn("%s: undeliverable postmaster notification discarded", + queue_id); + bounce_status = 0; + } + + /* + * Single bounce failed. Optionally send a double bounce to postmaster, + * subject to notify_classes restrictions. + */ +#define ANY_BOUNCE (MAIL_ERROR_2BOUNCE | MAIL_ERROR_BOUNCE) +#define SEND_POSTMASTER_DELAY_NOTICE (notify_mask & MAIL_ERROR_DELAY) + + else if (*recipient == 0) { + if (!SEND_POSTMASTER_DELAY_NOTICE) { + bounce_status = 0; + } else { + postmaster = var_delay_rcpt; + if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), + postmaster, + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + + /* + * Double bounce to Postmaster. This is the last opportunity + * for this message to be delivered. Send the text with + * reason for the bounce, and the headers of the original + * message. Don't bother sending the boiler-plate text. + */ + count = -1; + if (!bounce_header(bounce, bounce_info, postmaster, + POSTMASTER_COPY) + && (count = bounce_diagnostic_log(bounce, bounce_info, + DSN_NOTIFY_OVERRIDE)) > 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_diagnostic_dsn(bounce, bounce_info, + DSN_NOTIFY_OVERRIDE) > 0) { + bounce_original(bounce, bounce_info, DSN_RET_FULL); + bounce_status = post_mail_fclose(bounce); + if (bounce_status == 0) + msg_info("%s: postmaster delay notification: %s", + queue_id, STR(new_id)); + } else { + (void) vstream_fclose(bounce); + if (count == 0) + bounce_status = 0; + } + } + } + } + + /* + * Non-bounce failed. Send a single bounce, subject to DSN NOTIFY + * restrictions. + */ + else { + if ((bounce = post_mail_fopen_nowait(NULL_SENDER, recipient, + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + + /* + * Send the bounce message header, some boilerplate text that + * pretends that we are a polite mail system, the text with + * reason for the bounce, and a copy of the original message. + */ + count = -1; + if (bounce_header(bounce, bounce_info, recipient, + NO_POSTMASTER_COPY) == 0 + && bounce_boilerplate(bounce, bounce_info) == 0 + && (count = bounce_diagnostic_log(bounce, bounce_info, + DSN_NOTIFY_DELAY)) > 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_diagnostic_dsn(bounce, bounce_info, + DSN_NOTIFY_DELAY) > 0) { + bounce_original(bounce, bounce_info, DSN_RET_HDRS); + bounce_status = post_mail_fclose(bounce); + if (bounce_status == 0) + msg_info("%s: sender delay notification: %s", + queue_id, STR(new_id)); + } else { + (void) vstream_fclose(bounce); + if (count == 0) + bounce_status = 0; + } + } + + /* + * Optionally send a postmaster notice, subject to notify_classes + * restrictions. + * + * This postmaster notice is not critical, so if it fails don't + * retransmit the bounce that we just generated, just log a warning. + */ + if (bounce_status == 0 && SEND_POSTMASTER_DELAY_NOTICE + && strcasecmp_utf8(recipient, mail_addr_double_bounce()) != 0) { + + /* + * Send the text with reason for the bounce, and the headers of + * the original message. Don't bother sending the boiler-plate + * text. This postmaster notice is not critical, so if it fails + * don't retransmit the bounce that we just generated, just log a + * warning. + */ + postmaster = var_delay_rcpt; + if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), + postmaster, + MAIL_SRC_MASK_BOUNCE, + NULL_TRACE_FLAGS, + smtputf8, + new_id)) != 0) { + count = -1; + if (bounce_header(bounce, bounce_info, postmaster, + POSTMASTER_COPY) == 0 + && (count = bounce_diagnostic_log(bounce, bounce_info, + DSN_NOTIFY_OVERRIDE)) > 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_diagnostic_dsn(bounce, bounce_info, + DSN_NOTIFY_OVERRIDE) > 0) { + bounce_original(bounce, bounce_info, DSN_RET_HDRS); + postmaster_status = post_mail_fclose(bounce); + if (postmaster_status == 0) + msg_info("%s: postmaster delay notification: %s", + queue_id, STR(new_id)); + } else { + (void) vstream_fclose(bounce); + if (count == 0) + postmaster_status = 0; + } + } + if (postmaster_status) + msg_warn("%s: postmaster notice failed while warning %s", + queue_id, recipient); + } + } + + /* + * Cleanup. + */ + bounce_mail_free(bounce_info); + vstring_free(new_id); + + return (bounce_status); +} diff --git a/src/bounce/logfile-no-msgid-no-eoh-event b/src/bounce/logfile-no-msgid-no-eoh-event new file mode 100644 index 0000000..50233c8 --- /dev/null +++ b/src/bounce/logfile-no-msgid-no-eoh-event @@ -0,0 +1,13 @@ + +recipient = rcpt-address +original_recipient = rcpt-orig_addr +offset = 272 +notify_flags = rcpt-dsn_notify +status = dsn-status +action = dsn-action +diag_type = dsn-dtype +diag_text = dsn-dtext +mta_type = dsn-mtype +mta_mname = dsn-mname +reason = dsn-reason + diff --git a/src/bounce/logfile-no-msgid-with-eoh-event b/src/bounce/logfile-no-msgid-with-eoh-event new file mode 100644 index 0000000..00473a8 --- /dev/null +++ b/src/bounce/logfile-no-msgid-with-eoh-event @@ -0,0 +1,13 @@ + +recipient = rcpt-address +original_recipient = rcpt-orig_addr +offset = 271 +notify_flags = rcpt-dsn_notify +status = dsn-status +action = dsn-action +diag_type = dsn-dtype +diag_text = dsn-dtext +mta_type = dsn-mtype +mta_mname = dsn-mname +reason = dsn-reason + diff --git a/src/bounce/logfile-with-msgid-no-eoh-event b/src/bounce/logfile-with-msgid-no-eoh-event new file mode 100644 index 0000000..00473a8 --- /dev/null +++ b/src/bounce/logfile-with-msgid-no-eoh-event @@ -0,0 +1,13 @@ + +recipient = rcpt-address +original_recipient = rcpt-orig_addr +offset = 271 +notify_flags = rcpt-dsn_notify +status = dsn-status +action = dsn-action +diag_type = dsn-dtype +diag_text = dsn-dtext +mta_type = dsn-mtype +mta_mname = dsn-mname +reason = dsn-reason + diff --git a/src/bounce/logfile-with-msgid-with-eoh-event b/src/bounce/logfile-with-msgid-with-eoh-event new file mode 100644 index 0000000..00473a8 --- /dev/null +++ b/src/bounce/logfile-with-msgid-with-eoh-event @@ -0,0 +1,13 @@ + +recipient = rcpt-address +original_recipient = rcpt-orig_addr +offset = 271 +notify_flags = rcpt-dsn_notify +status = dsn-status +action = dsn-action +diag_type = dsn-dtype +diag_text = dsn-dtext +mta_type = dsn-mtype +mta_mname = dsn-mname +reason = dsn-reason + diff --git a/src/bounce/logfile-with-msgid-with-filter b/src/bounce/logfile-with-msgid-with-filter new file mode 100644 index 0000000..4d56216 --- /dev/null +++ b/src/bounce/logfile-with-msgid-with-filter @@ -0,0 +1,13 @@ + +recipient = rcpt-address +original_recipient = rcpt-orig_addr +offset = 291 +notify_flags = rcpt-dsn_notify +status = dsn-status +action = dsn-action +diag_type = dsn-dtype +diag_text = dsn-dtext +mta_type = dsn-mtype +mta_mname = dsn-mname +reason = dsn-reason + diff --git a/src/bounce/logfile-with-msgid-with-long-line b/src/bounce/logfile-with-msgid-with-long-line new file mode 100644 index 0000000..00473a8 --- /dev/null +++ b/src/bounce/logfile-with-msgid-with-long-line @@ -0,0 +1,13 @@ + +recipient = rcpt-address +original_recipient = rcpt-orig_addr +offset = 271 +notify_flags = rcpt-dsn_notify +status = dsn-status +action = dsn-action +diag_type = dsn-dtype +diag_text = dsn-dtext +mta_type = dsn-mtype +mta_mname = dsn-mname +reason = dsn-reason + diff --git a/src/bounce/msgfile-no-msgid-no-eoh-event b/src/bounce/msgfile-no-msgid-no-eoh-event new file mode 100755 index 0000000..694f5db Binary files /dev/null and b/src/bounce/msgfile-no-msgid-no-eoh-event differ diff --git a/src/bounce/msgfile-no-msgid-with-eoh-event b/src/bounce/msgfile-no-msgid-with-eoh-event new file mode 100755 index 0000000..b3b795e Binary files /dev/null and b/src/bounce/msgfile-no-msgid-with-eoh-event differ diff --git a/src/bounce/msgfile-with-msgid-no-eoh-event b/src/bounce/msgfile-with-msgid-no-eoh-event new file mode 100755 index 0000000..cc16c66 Binary files /dev/null and b/src/bounce/msgfile-with-msgid-no-eoh-event differ diff --git a/src/bounce/msgfile-with-msgid-with-eoh-event b/src/bounce/msgfile-with-msgid-with-eoh-event new file mode 100755 index 0000000..ffe30c4 Binary files /dev/null and b/src/bounce/msgfile-with-msgid-with-eoh-event differ diff --git a/src/bounce/msgfile-with-msgid-with-filter b/src/bounce/msgfile-with-msgid-with-filter new file mode 100755 index 0000000..d6e77c4 Binary files /dev/null and b/src/bounce/msgfile-with-msgid-with-filter differ diff --git a/src/bounce/msgfile-with-msgid-with-long-line b/src/bounce/msgfile-with-msgid-with-long-line new file mode 100755 index 0000000..69d2801 Binary files /dev/null and b/src/bounce/msgfile-with-msgid-with-long-line differ diff --git a/src/bounce/no-msgid-no-eoh-event-no-thread.ref b/src/bounce/no-msgid-no-eoh-event-no-thread.ref new file mode 100644 index 0000000..630734d --- /dev/null +++ b/src/bounce/no-msgid-no-eoh-event-no-thread.ref @@ -0,0 +1,47 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sun, 29 Nov 2020 19:04:29 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4CklpP1JjKz4w4Z; Mon, 30 Nov 2020 00:04:29 +0000 (UTC) +From: +To: +Subject: no-msgid-no-eoh-event + +--msgid.unix-time/mail.example-- diff --git a/src/bounce/no-msgid-no-eoh-event-with-thread.ref b/src/bounce/no-msgid-no-eoh-event-with-thread.ref new file mode 100644 index 0000000..630734d --- /dev/null +++ b/src/bounce/no-msgid-no-eoh-event-with-thread.ref @@ -0,0 +1,47 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sun, 29 Nov 2020 19:04:29 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4CklpP1JjKz4w4Z; Mon, 30 Nov 2020 00:04:29 +0000 (UTC) +From: +To: +Subject: no-msgid-no-eoh-event + +--msgid.unix-time/mail.example-- diff --git a/src/bounce/no-msgid-with-eoh-event-no-thread.ref b/src/bounce/no-msgid-with-eoh-event-no-thread.ref new file mode 100644 index 0000000..39b5841 --- /dev/null +++ b/src/bounce/no-msgid-with-eoh-event-no-thread.ref @@ -0,0 +1,49 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sun, 29 Nov 2020 19:11:52 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4Cklyw0HDKz4w4Z; Mon, 30 Nov 2020 00:11:52 +0000 (UTC) +From: +To: +Subject: no-msgid-with-eoh-event + +body text + +--msgid.unix-time/mail.example-- diff --git a/src/bounce/no-msgid-with-eoh-event-with-thread.ref b/src/bounce/no-msgid-with-eoh-event-with-thread.ref new file mode 100644 index 0000000..39b5841 --- /dev/null +++ b/src/bounce/no-msgid-with-eoh-event-with-thread.ref @@ -0,0 +1,49 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sun, 29 Nov 2020 19:11:52 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4Cklyw0HDKz4w4Z; Mon, 30 Nov 2020 00:11:52 +0000 (UTC) +From: +To: +Subject: no-msgid-with-eoh-event + +body text + +--msgid.unix-time/mail.example-- diff --git a/src/bounce/obs_template_test.ref b/src/bounce/obs_template_test.ref new file mode 100644 index 0000000..e03bb5b --- /dev/null +++ b/src/bounce/obs_template_test.ref @@ -0,0 +1,68 @@ +failure_template = < +Subject: Undelivered Mail Returned to Sender +Postmaster-Subject: Postmaster Copy: Undelivered Mail + +This is the mail system at host $myhostname. + +I'm sorry to have to inform you that your message could not +be delivered to one or more recipients. It's attached below. + +For further assistance, please send mail to postmaster. + +If you do so, please include this problem report. You can +delete your own text from the attached returned message. + + The mail system +EOF + +delay_template = < +Subject: Delayed Mail (still being retried) +Postmaster-Subject: Postmaster Warning: Delayed Mail + +This is the mail system at host $myhostname. + +#################################################################### +# THIS IS A WARNING ONLY. YOU DO NOT NEED TO RESEND YOUR MESSAGE. # +#################################################################### + +Your message could not be delivered for more than $delay_warning_time_hours hour(s). +It will be retried until it is $maximal_queue_lifetime_days day(s) old. + +For further assistance, please send mail to postmaster. + +If you do so, please include this problem report. You can +delete your own text from the attached returned message. + + The mail system +EOF + +success_template = < +Subject: Successful Mail Delivery Report + +This is the mail system at host $myhostname. + +Your message was successfully delivered to the destination(s) +listed below. If the message was delivered to mailbox you will +receive no further notifications. Otherwise you may still receive +notifications of mail delivery errors from other systems. + + The mail system +EOF + +verify_template = < +Subject: Mail Delivery Status Report + +This is the mail system at host $myhostname. + +Enclosed is the mail delivery report that you requested. + + The mail system +EOF diff --git a/src/bounce/with-msgid-no-eoh-event-no-thread.ref b/src/bounce/with-msgid-no-eoh-event-no-thread.ref new file mode 100644 index 0000000..020d9a4 --- /dev/null +++ b/src/bounce/with-msgid-no-eoh-event-no-thread.ref @@ -0,0 +1,49 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4CkXPY0myNz4w4g; Sun, 29 Nov 2020 15:30:41 +0000 (UTC) +From: +To: +Message-Id: <12345@mta-name.example> +Subject: with-msgid-no-eoh-event +Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC) + +--msgid.unix-time/mail.example-- diff --git a/src/bounce/with-msgid-no-eoh-event-with-thread.ref b/src/bounce/with-msgid-no-eoh-event-with-thread.ref new file mode 100644 index 0000000..d2aadd0 --- /dev/null +++ b/src/bounce/with-msgid-no-eoh-event-with-thread.ref @@ -0,0 +1,51 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +References: <12345@mta-name.example> +In-Reply-To: <12345@mta-name.example> +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4CkXPY0myNz4w4g; Sun, 29 Nov 2020 15:30:41 +0000 (UTC) +From: +To: +Message-Id: <12345@mta-name.example> +Subject: with-msgid-no-eoh-event +Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC) + +--msgid.unix-time/mail.example-- diff --git a/src/bounce/with-msgid-with-eoh-event-no-thread.ref b/src/bounce/with-msgid-with-eoh-event-no-thread.ref new file mode 100644 index 0000000..70afb5f --- /dev/null +++ b/src/bounce/with-msgid-with-eoh-event-no-thread.ref @@ -0,0 +1,51 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4CkXPY10M8z4w4l; Sun, 29 Nov 2020 15:30:41 +0000 (UTC) +From: +To: +Message-Id: <12345@mta-name.example> +Subject: with-msgid-with-eoh-event +Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC) + +body text + +--msgid.unix-time/mail.example-- diff --git a/src/bounce/with-msgid-with-eoh-event-with-thread.ref b/src/bounce/with-msgid-with-eoh-event-with-thread.ref new file mode 100644 index 0000000..e46afbe --- /dev/null +++ b/src/bounce/with-msgid-with-eoh-event-with-thread.ref @@ -0,0 +1,53 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +References: <12345@mta-name.example> +In-Reply-To: <12345@mta-name.example> +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4CkXPY10M8z4w4l; Sun, 29 Nov 2020 15:30:41 +0000 (UTC) +From: +To: +Message-Id: <12345@mta-name.example> +Subject: with-msgid-with-eoh-event +Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC) + +body text + +--msgid.unix-time/mail.example-- diff --git a/src/bounce/with-msgid-with-filter-no-thread.ref b/src/bounce/with-msgid-with-filter-no-thread.ref new file mode 100644 index 0000000..fa30ddf --- /dev/null +++ b/src/bounce/with-msgid-with-filter-no-thread.ref @@ -0,0 +1,48 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sat, 5 Dec 2020 13:31:48 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4CpJ7m6tprz4w4Y; Sat, 5 Dec 2020 18:31:48 +0000 (UTC) +From: +To: +Message-Id: <12345@mta-name.example> +Subject: with-msgid-no-eoh-event + +--msgid.unix-time/mail.example-- diff --git a/src/bounce/with-msgid-with-filter-with-thread.ref b/src/bounce/with-msgid-with-filter-with-thread.ref new file mode 100644 index 0000000..14d3373 --- /dev/null +++ b/src/bounce/with-msgid-with-filter-with-thread.ref @@ -0,0 +1,50 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +References: <12345@mta-name.example> +In-Reply-To: <12345@mta-name.example> +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sat, 5 Dec 2020 13:31:48 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4CpJ7m6tprz4w4Y; Sat, 5 Dec 2020 18:31:48 +0000 (UTC) +From: +To: +Message-Id: <12345@mta-name.example> +Subject: with-msgid-no-eoh-event + +--msgid.unix-time/mail.example-- diff --git a/src/bounce/with-msgid-with-long-line-no-thread.ref b/src/bounce/with-msgid-with-long-line-no-thread.ref new file mode 100644 index 0000000..fa5268b --- /dev/null +++ b/src/bounce/with-msgid-with-long-line-no-thread.ref @@ -0,0 +1,50 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4CkXPY194lz4w4n; Sun, 29 Nov 2020 15:30:41 +0000 (UTC) +From: +To: +Whatever: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Message-Id: <12345@mta-name.example> +Subject: with-msgid-with-long-line +Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC) + +--msgid.unix-time/mail.example-- diff --git a/src/bounce/with-msgid-with-long-line-with-thread.ref b/src/bounce/with-msgid-with-long-line-with-thread.ref new file mode 100644 index 0000000..04e96d6 --- /dev/null +++ b/src/bounce/with-msgid-with-long-line-with-thread.ref @@ -0,0 +1,52 @@ +From: Mail Delivery System +Subject: Undelivered Mail Returned to Sender +To: test-recipient +References: <12345@mta-name.example> +In-Reply-To: <12345@mta-name.example> +Auto-Submitted: auto-replied +MIME-Version: 1.0 +Content-Type: multipart/report; report-type=delivery-status; + boundary="msgid.unix-time/mail.example" +Content-Transfer-Encoding: 7bit + +This is a MIME-encapsulated message. + +--msgid.unix-time/mail.example +Content-Description: Notification +Content-Type: text/plain; charset=us-ascii + + + (expanded from ): dsn-reason + +--msgid.unix-time/mail.example +Content-Description: Delivery report +Content-Type: message/delivery-status + +Reporting-MTA: dns; mail.example +Original-Envelope-Id: TEST-ENVID +X-Postfix-Queue-ID: msgid +X-Postfix-Sender: rfc822; sender@sender.example +Arrival-Date: Sun, 29 Nov 2020 10:30:41 -0500 (EST) + +Final-Recipient: rfc822; rcpt-address +Original-Recipient: rfc822; rcpt-orig_addr +Action: failed +Status: dsn-status +Remote-MTA: dsn-mtype; dsn-mname +Diagnostic-Code: dsn-dtype; dsn-dtext + +--msgid.unix-time/mail.example +Content-Description: Undelivered Message +Content-Type: message/rfc822 + +Return-Path: +Received: by wzv.porcupine.org (Postfix, from userid 0) + id 4CkXPY194lz4w4n; Sun, 29 Nov 2020 15:30:41 +0000 (UTC) +From: +To: +Whatever: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +Message-Id: <12345@mta-name.example> +Subject: with-msgid-with-long-line +Date: Sun, 29 Nov 2020 15:30:41 +0000 (UTC) + +--msgid.unix-time/mail.example-- diff --git a/src/cleanup/.indent.pro b/src/cleanup/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/cleanup/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/cleanup/.printfck b/src/cleanup/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/cleanup/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/cleanup/Makefile.in b/src/cleanup/Makefile.in new file mode 100644 index 0000000..8a3c18c --- /dev/null +++ b/src/cleanup/Makefile.in @@ -0,0 +1,1394 @@ +SHELL = /bin/sh +SRCS = cleanup.c cleanup_out.c cleanup_envelope.c cleanup_message.c \ + cleanup_extracted.c cleanup_state.c cleanup_rewrite.c \ + cleanup_map11.c cleanup_map1n.c cleanup_masquerade.c \ + cleanup_out_recipient.c cleanup_init.c cleanup_api.c \ + cleanup_addr.c cleanup_bounce.c cleanup_milter.c \ + cleanup_body_edit.c cleanup_region.c cleanup_final.c +OBJS = cleanup.o cleanup_out.o cleanup_envelope.o cleanup_message.o \ + cleanup_extracted.o cleanup_state.o cleanup_rewrite.o \ + cleanup_map11.o cleanup_map1n.o cleanup_masquerade.o \ + cleanup_out_recipient.o cleanup_init.o cleanup_api.o \ + cleanup_addr.o cleanup_bounce.o cleanup_milter.o \ + cleanup_body_edit.o cleanup_region.o cleanup_final.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= cleanup_masquerade cleanup_milter +PROG = cleanup +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/libmilter.a \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile > printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +cleanup_masquerade: cleanup_masquerade.o + mv cleanup_masquerade.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS) + mv junk cleanup_masquerade.o + +CLEANUP_MILTER_OBJS = cleanup_state.o cleanup_out.o cleanup_addr.o \ + cleanup_out_recipient.o cleanup_body_edit.o cleanup_region.o +cleanup_milter: cleanup_milter.o $(CLEANUP_MILTER_OBJS) $(LIBS) + mv cleanup_milter.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(CLEANUP_MILTER_OBJS) $(LIBS) $(SYSLIBS) + mv junk cleanup_milter.o + +tests: cleanup_masquerade_test milter_tests + +milter_tests: cleanup_milter_test bug_tests \ + cleanup_milter_test2 cleanup_milter_test3 cleanup_milter_test4 \ + cleanup_milter_test5 cleanup_milter_test6 cleanup_milter_test7 \ + cleanup_milter_test8 cleanup_milter_test9 cleanup_milter_test10a \ + cleanup_milter_test10b cleanup_milter_test10c cleanup_milter_test10d \ + cleanup_milter_test10e cleanup_milter_test11 cleanup_milter_test12 \ + cleanup_milter_test13a cleanup_milter_test13b cleanup_milter_test13c \ + cleanup_milter_test13d cleanup_milter_test13e cleanup_milter_test13f \ + cleanup_milter_test13g cleanup_milter_test13h cleanup_milter_test13i \ + cleanup_milter_test14a cleanup_milter_test14b cleanup_milter_test14c \ + cleanup_milter_test14d cleanup_milter_test14e cleanup_milter_test14f \ + cleanup_milter_test14g \ + cleanup_milter_test15a cleanup_milter_test15b cleanup_milter_test15c \ + cleanup_milter_test15d cleanup_milter_test15e cleanup_milter_test15f \ + cleanup_milter_test15g cleanup_milter_test15h cleanup_milter_test15i \ + cleanup_milter_test16a cleanup_milter_test16b cleanup_milter_test17a \ + cleanup_milter_test17b cleanup_milter_test17c cleanup_milter_test17d \ + cleanup_milter_test17e cleanup_milter_test17f cleanup_milter_test17g + +root_tests: + +cleanup_masquerade_test: cleanup_masquerade cleanup_masq.ref + rm -f cleanup_masq.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' A.B.C,B.C xxx@aa.a.b.c >>cleanup_masq.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' a.b.c,b.c xxx@AA.A.B.C >>cleanup_masq.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade 'xxx' a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade 'yyy' a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' !a.b.c,b.c xxx@aa.a.b.c >>cleanup_masq.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' a.b.c,b.c xxx@a.b.c >>cleanup_masq.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' !a.b.c,b.c xxx@a.b.c >>cleanup_masq.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' a.b.c,b.c xxx@aaa.b.c >>cleanup_masq.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade '' a.b.c,b.c xxx@b.c >>cleanup_masq.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_masquerade 'fail:whatever' xy xxx@b.c >>cleanup_masq.tmp + diff cleanup_masq.ref cleanup_masq.tmp + rm -f cleanup_masq.tmp + +bug_tests: bug1_test bug2_test bug3_test + +../postcat/postcat: + cd ../postcat; make + +bug1_test: cleanup_milter bug1.file bug1.in bug1.ref bug1.text.ref \ + ../postcat/postcat + cp bug1.file bug1.file.tmp + chmod u+w bug1.file.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >bug1.tmp + diff bug1.ref bug1.tmp + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat bug1.file.tmp 2>/dev/null >bug1.tmp + diff bug1.text.ref bug1.tmp + rm -f bug1.file.tmp bug1.tmp + +bug2_test: cleanup_milter bug2.file bug2.in bug2.ref bug2.text.ref \ + ../postcat/postcat + cp bug2.file bug2.file.tmp + chmod u+w bug2.file.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >bug2.tmp + diff bug2.ref bug2.tmp + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat bug2.file.tmp 2>/dev/null >bug2.tmp + diff bug2.text.ref bug2.tmp + rm -f bug2.file.tmp bug2.tmp + +bug3_test: cleanup_milter bug3.file bug3.in bug3.ref bug3.text.ref \ + ../postcat/postcat + cp bug3.file bug3.file.tmp + chmod u+w bug3.file.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >bug3.tmp + diff bug3.ref bug3.tmp + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat bug3.file.tmp 2>/dev/null >bug3.tmp + diff bug3.text.ref bug3.tmp + rm -f bug3.file.tmp bug3.tmp + +# Test queue file editing routines. + +cleanup_milter_test: cleanup_milter test-queue-file cleanup_milter.in1 \ + cleanup_milter.ref1 ../postcat/postcat + cp test-queue-file test-queue-file.tmp + chmod u+w test-queue-file.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref1 cleanup_milter.tmp + rm -f test-queue-file.tmp cleanup_milter.tmp + +cleanup_milter_test2: cleanup_milter test-queue-file2 cleanup_milter.in2 \ + cleanup_milter.ref2 ../postcat/postcat + cp test-queue-file2 test-queue-file2.tmp + chmod u+w test-queue-file2.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref2 cleanup_milter.tmp + rm -f test-queue-file2.tmp cleanup_milter.tmp + +cleanup_milter_test3: cleanup_milter test-queue-file3 cleanup_milter.in3 \ + cleanup_milter.ref3 ../postcat/postcat + cp test-queue-file3 test-queue-file3.tmp + chmod u+w test-queue-file3.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref3 cleanup_milter.tmp + rm -f test-queue-file3.tmp cleanup_milter.tmp + +cleanup_milter_test4: cleanup_milter test-queue-file4 cleanup_milter.in4a \ + cleanup_milter.in4b cleanup_milter.in4c cleanup_milter.ref4 \ + test-queue-file4 ../postcat/postcat + cp test-queue-file4 test-queue-file4.tmp + chmod u+w test-queue-file4.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref4 cleanup_milter.tmp + cp test-queue-file4 test-queue-file4.tmp + chmod u+w test-queue-file4.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref4 cleanup_milter.tmp + cp test-queue-file4 test-queue-file4.tmp + chmod u+w test-queue-file4.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref4 cleanup_milter.tmp + rm -f test-queue-file4.tmp cleanup_milter.tmp + +cleanup_milter_test5: cleanup_milter test-queue-file5 cleanup_milter.in5 \ + cleanup_milter.ref5 ../postcat/postcat + cp test-queue-file5 test-queue-file5.tmp + chmod u+w test-queue-file5.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref5 cleanup_milter.tmp + rm -f test-queue-file5.tmp cleanup_milter.tmp + +cleanup_milter_test6: cleanup_milter_test6a cleanup_milter_test6b cleanup_milter_test6c + rm -f test-queue-file6.tmp cleanup_milter.tmp + +cleanup_milter_test6a: cleanup_milter test-queue-file6 cleanup_milter.in6a \ + cleanup_milter.ref6a test-queue-file6 ../postcat/postcat + cp test-queue-file6 test-queue-file6.tmp + chmod u+w test-queue-file6.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref6a cleanup_milter.tmp + rm -f test-queue-file6.tmp cleanup_milter.tmp + +cleanup_milter_test6b: cleanup_milter test-queue-file6 cleanup_milter.in6b \ + cleanup_milter.ref6b ../postcat/postcat + cp test-queue-file6 test-queue-file6.tmp + chmod u+w test-queue-file6.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref6b cleanup_milter.tmp + rm -f test-queue-file6.tmp cleanup_milter.tmp + +cleanup_milter_test6c: cleanup_milter test-queue-file6 cleanup_milter.in6c \ + cleanup_milter.ref6c ../postcat/postcat + cp test-queue-file6 test-queue-file6.tmp + chmod u+w test-queue-file6.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref6c cleanup_milter.tmp + rm -f test-queue-file6.tmp cleanup_milter.tmp + +cleanup_milter_test7: cleanup_milter test-queue-file7 cleanup_milter.in7 \ + cleanup_milter.ref7 ../postcat/postcat + cp test-queue-file7 test-queue-file7.tmp + chmod u+w test-queue-file7.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref7 cleanup_milter.tmp + rm -f test-queue-file7.tmp cleanup_milter.tmp + +cleanup_milter_test8: cleanup_milter test-queue-file8 cleanup_milter.in8 \ + cleanup_milter.ref8 ../postcat/postcat + cp test-queue-file8 test-queue-file8.tmp + chmod u+w test-queue-file8.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref8 cleanup_milter.tmp + rm -f test-queue-file8.tmp cleanup_milter.tmp + +cleanup_milter_test9: cleanup_milter test-queue-file9 cleanup_milter.in9 \ + cleanup_milter.ref9 ../postcat/postcat + cp test-queue-file9 test-queue-file9.tmp + chmod u+w test-queue-file9.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref9 cleanup_milter.tmp + rm -f test-queue-file9.tmp cleanup_milter.tmp + +cleanup_milter_test10a: cleanup_milter test-queue-file10 cleanup_milter.in10a \ + cleanup_milter.ref10a ../postcat/postcat + cp test-queue-file10 test-queue-file10.tmp + chmod u+w test-queue-file10.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref10a cleanup_milter.tmp + rm -f test-queue-file10.tmp cleanup_milter.tmp + +cleanup_milter_test10b: cleanup_milter test-queue-file10 cleanup_milter.in10b \ + cleanup_milter.ref10b ../postcat/postcat + cp test-queue-file10 test-queue-file10.tmp + chmod u+w test-queue-file10.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref10b cleanup_milter.tmp + rm -f test-queue-file10.tmp cleanup_milter.tmp + +cleanup_milter_test10c: cleanup_milter test-queue-file10 cleanup_milter.in10c \ + cleanup_milter.ref10c ../postcat/postcat + cp test-queue-file10 test-queue-file10.tmp + chmod u+w test-queue-file10.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref10c cleanup_milter.tmp + rm -f test-queue-file10.tmp cleanup_milter.tmp + +cleanup_milter_test10d: cleanup_milter test-queue-file10 cleanup_milter.in10c \ + cleanup_milter.ref10d ../postcat/postcat + cp test-queue-file10 test-queue-file10.tmp + chmod u+w test-queue-file10.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref10d cleanup_milter.tmp + rm -f test-queue-file10.tmp cleanup_milter.tmp + +cleanup_milter_test10e: cleanup_milter test-queue-file10 cleanup_milter.in10c \ + cleanup_milter.ref10e ../postcat/postcat + cp test-queue-file10 test-queue-file10.tmp + chmod u+w test-queue-file10.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref10e cleanup_milter.tmp + rm -f test-queue-file10.tmp cleanup_milter.tmp + +cleanup_milter_test11: cleanup_milter test-queue-file11 cleanup_milter.in11 \ + cleanup_milter.ref11 ../postcat/postcat + cp test-queue-file11 test-queue-file11.tmp + chmod u+w test-queue-file11.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref11 cleanup_milter.tmp + rm -f test-queue-file11.tmp cleanup_milter.tmp + +cleanup_milter_test12: cleanup_milter test-queue-file12 cleanup_milter.in12 \ + cleanup_milter.ref12 ../postcat/postcat + cp test-queue-file12 test-queue-file12.tmp + chmod u+w test-queue-file12.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref12 cleanup_milter.tmp + rm -f test-queue-file12.tmp cleanup_milter.tmp + +cleanup_milter_test13a: cleanup_milter test-queue-file13a cleanup_milter.in13a \ + cleanup_milter.ref13a ../postcat/postcat + cp test-queue-file13a test-queue-file13a.tmp + chmod u+w test-queue-file13a.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref13a cleanup_milter.tmp + rm -f test-queue-file13a.tmp cleanup_milter.tmp + +cleanup_milter_test13b: cleanup_milter test-queue-file13b cleanup_milter.in13b \ + cleanup_milter.ref13b ../postcat/postcat + cp test-queue-file13b test-queue-file13b.tmp + chmod u+w test-queue-file13b.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref13b cleanup_milter.tmp + rm -f test-queue-file13b.tmp cleanup_milter.tmp + +cleanup_milter_test13c: cleanup_milter test-queue-file13c cleanup_milter.in13c \ + cleanup_milter.ref13c ../postcat/postcat + cp test-queue-file13c test-queue-file13c.tmp + chmod u+w test-queue-file13c.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref13c cleanup_milter.tmp + rm -f test-queue-file13c.tmp cleanup_milter.tmp + +cleanup_milter_test13d: cleanup_milter test-queue-file13d cleanup_milter.in13d \ + cleanup_milter.ref13d ../postcat/postcat + cp test-queue-file13d test-queue-file13d.tmp + chmod u+w test-queue-file13d.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref13d cleanup_milter.tmp + rm -f test-queue-file13d.tmp cleanup_milter.tmp + +cleanup_milter_test13e: cleanup_milter test-queue-file13e cleanup_milter.in13e \ + cleanup_milter.ref13e ../postcat/postcat + cp test-queue-file13e test-queue-file13e.tmp + chmod u+w test-queue-file13e.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref13e cleanup_milter.tmp + rm -f test-queue-file13e.tmp cleanup_milter.tmp + +cleanup_milter_test13g: cleanup_milter test-queue-file13g cleanup_milter.in13g \ + cleanup_milter.ref13g ../postcat/postcat + cp test-queue-file13g test-queue-file13g.tmp + chmod u+w test-queue-file13g.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref13g cleanup_milter.tmp + rm -f test-queue-file13g.tmp cleanup_milter.tmp + +cleanup_milter_test13h: cleanup_milter test-queue-file13h cleanup_milter.in13h \ + cleanup_milter.ref13h ../postcat/postcat + cp test-queue-file13h test-queue-file13h.tmp + chmod u+w test-queue-file13h.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref13h cleanup_milter.tmp + rm -f test-queue-file13h.tmp cleanup_milter.tmp + +cleanup_milter_test13i: cleanup_milter test-queue-file13i cleanup_milter.in13i \ + cleanup_milter.ref13i ../postcat/postcat + cp test-queue-file13i test-queue-file13i.tmp + chmod u+w test-queue-file13i.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref13i cleanup_milter.tmp + rm -f test-queue-file13i.tmp cleanup_milter.tmp + +cleanup_milter_test13f: cleanup_milter test-queue-file13f cleanup_milter.in13f \ + cleanup_milter.ref13f ../postcat/postcat + cp test-queue-file13f test-queue-file13f.tmp + chmod u+w test-queue-file13f.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter /dev/null >cleanup_milter.tmp + diff cleanup_milter.ref13f cleanup_milter.tmp + rm -f test-queue-file13f.tmp cleanup_milter.tmp + +cleanup_milter_test14a: cleanup_milter test-queue-file14 cleanup_milter.in14a \ + cleanup_milter.ref14a1 ../postcat/postcat cleanup_milter.ref14a2 \ + cleanup_milter.reg14a + cp test-queue-file14 test-queue-file14a.tmp + chmod u+w test-queue-file14a.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref14a1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file14a.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref14a2 cleanup_milter.tmp2 + rm -f test-queue-file14a.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test14b: cleanup_milter test-queue-file14 cleanup_milter.in14b \ + cleanup_milter.ref14b1 ../postcat/postcat cleanup_milter.ref14b2 \ + cleanup_milter.reg14b + cp test-queue-file14 test-queue-file14b.tmp + chmod u+w test-queue-file14b.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref14b1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file14b.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref14b2 cleanup_milter.tmp2 + rm -f test-queue-file14b.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test14c: cleanup_milter test-queue-file14 cleanup_milter.in14c \ + cleanup_milter.ref14c1 ../postcat/postcat cleanup_milter.ref14c2 \ + cleanup_milter.reg14c + cp test-queue-file14 test-queue-file14c.tmp + chmod u+w test-queue-file14c.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref14c1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file14c.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref14c2 cleanup_milter.tmp2 + rm -f test-queue-file14c.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test14d: cleanup_milter test-queue-file14 cleanup_milter.in14d \ + cleanup_milter.ref14d1 ../postcat/postcat cleanup_milter.ref14d2 \ + cleanup_milter.reg14d + cp test-queue-file14 test-queue-file14d.tmp + chmod u+w test-queue-file14d.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref14d1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file14d.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref14d2 cleanup_milter.tmp2 + rm -f test-queue-file14d.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test14e: cleanup_milter test-queue-file14 cleanup_milter.in14e \ + cleanup_milter.ref14e1 ../postcat/postcat cleanup_milter.ref14e2 \ + cleanup_milter.reg14e + cp test-queue-file14 test-queue-file14e.tmp + chmod u+w test-queue-file14e.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref14e1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file14e.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref14e2 cleanup_milter.tmp2 + rm -f test-queue-file14e.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test14f: cleanup_milter test-queue-file14 cleanup_milter.in14f \ + cleanup_milter.ref14f1 ../postcat/postcat cleanup_milter.ref14f2 \ + cleanup_milter.reg14f + cp test-queue-file14 test-queue-file14f.tmp + chmod u+w test-queue-file14f.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref14f1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file14f.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref14f2 cleanup_milter.tmp2 + rm -f test-queue-file14f.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test14g: cleanup_milter test-queue-file14 cleanup_milter.in14g \ + cleanup_milter.ref14g1 ../postcat/postcat cleanup_milter.ref14g2 \ + cleanup_milter.reg14g + cp test-queue-file14 test-queue-file14g.tmp + chmod u+w test-queue-file14g.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref14g1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file14g.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref14g2 cleanup_milter.tmp2 + rm -f test-queue-file14g.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test15a: cleanup_milter test-queue-file15 cleanup_milter.in15a \ + cleanup_milter.ref15a1 ../postcat/postcat cleanup_milter.ref15a2 \ + cleanup_milter.reg15a + cp test-queue-file15 test-queue-file15a.tmp + chmod u+w test-queue-file15a.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref15a1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file15a.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref15a2 cleanup_milter.tmp2 + rm -f test-queue-file15a.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test15b: cleanup_milter test-queue-file15 cleanup_milter.in15b \ + cleanup_milter.ref15b1 ../postcat/postcat cleanup_milter.ref15b2 \ + cleanup_milter.reg15b + cp test-queue-file15 test-queue-file15b.tmp + chmod u+w test-queue-file15b.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref15b1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file15b.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref15b2 cleanup_milter.tmp2 + rm -f test-queue-file15b.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test15c: cleanup_milter test-queue-file15 cleanup_milter.in15c \ + cleanup_milter.ref15c1 ../postcat/postcat cleanup_milter.ref15c2 \ + cleanup_milter.reg15c + cp test-queue-file15 test-queue-file15c.tmp + chmod u+w test-queue-file15c.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref15c1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file15c.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref15c2 cleanup_milter.tmp2 + rm -f test-queue-file15c.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test15d: cleanup_milter test-queue-file15 cleanup_milter.in15d \ + cleanup_milter.ref15d1 ../postcat/postcat cleanup_milter.ref15d2 \ + cleanup_milter.reg15d + cp test-queue-file15 test-queue-file15d.tmp + chmod u+w test-queue-file15d.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref15d1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file15d.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref15d2 cleanup_milter.tmp2 + rm -f test-queue-file15d.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test15e: cleanup_milter test-queue-file15 cleanup_milter.in15e \ + cleanup_milter.ref15e1 ../postcat/postcat cleanup_milter.ref15e2 \ + cleanup_milter.reg15e + cp test-queue-file15 test-queue-file15e.tmp + chmod u+w test-queue-file15e.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref15e1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file15e.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref15e2 cleanup_milter.tmp2 + rm -f test-queue-file15e.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test15f: cleanup_milter test-queue-file15 cleanup_milter.in15f \ + cleanup_milter.ref15f1 ../postcat/postcat cleanup_milter.ref15f2 \ + cleanup_milter.reg15f + cp test-queue-file15 test-queue-file15f.tmp + chmod u+w test-queue-file15f.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref15f1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file15f.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref15f2 cleanup_milter.tmp2 + rm -f test-queue-file15f.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test15g: cleanup_milter test-queue-file15 cleanup_milter.in15g \ + cleanup_milter.ref15g1 ../postcat/postcat cleanup_milter.ref15g2 \ + cleanup_milter.reg15g + cp test-queue-file15 test-queue-file15g.tmp + chmod u+w test-queue-file15g.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref15g1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file15g.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref15g2 cleanup_milter.tmp2 + rm -f test-queue-file15g.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test15h: cleanup_milter test-queue-file15 cleanup_milter.in15h \ + cleanup_milter.ref15h1 ../postcat/postcat cleanup_milter.ref15h2 \ + cleanup_milter.reg15h + cp test-queue-file15 test-queue-file15h.tmp + chmod u+w test-queue-file15h.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref15h1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file15h.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref15h2 cleanup_milter.tmp2 + rm -f test-queue-file15h.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test15i: cleanup_milter test-queue-file15 cleanup_milter.in15i \ + cleanup_milter.ref15i1 ../postcat/postcat cleanup_milter.ref15i2 \ + cleanup_milter.reg15i + cp test-queue-file15 test-queue-file15i.tmp + chmod u+w test-queue-file15i.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref15i1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file15i.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref15i2 cleanup_milter.tmp2 + rm -f test-queue-file15i.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test16a: cleanup_milter test-queue-file16 cleanup_milter.in16a \ + cleanup_milter.ref16a1 ../postcat/postcat cleanup_milter.ref16a2 + cp test-queue-file16 test-queue-file16a.tmp + chmod u+w test-queue-file16a.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref16a1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file16a.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref16a2 cleanup_milter.tmp2 + rm -f test-queue-file16a.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test16b: cleanup_milter test-queue-file16 cleanup_milter.in16b \ + cleanup_milter.ref16b1 ../postcat/postcat cleanup_milter.ref16b2 + cp test-queue-file16 test-queue-file16b.tmp + chmod u+w test-queue-file16b.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref16b1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file16b.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref16b2 cleanup_milter.tmp2 + rm -f test-queue-file16b.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test17a: cleanup_milter test-queue-file17 cleanup_milter.in17a \ + cleanup_milter.ref17a1 ../postcat/postcat cleanup_milter.ref17a2 + cp test-queue-file17 test-queue-file17a.tmp + chmod u+w test-queue-file17a.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref17a1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17a.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref17a2 cleanup_milter.tmp2 + rm -f test-queue-file17a.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test17b: cleanup_milter test-queue-file17 cleanup_milter.in17b \ + cleanup_milter.ref17b1 ../postcat/postcat cleanup_milter.ref17b2 + cp test-queue-file17 test-queue-file17b.tmp + chmod u+w test-queue-file17b.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref17b1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17b.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref17b2 cleanup_milter.tmp2 + rm -f test-queue-file17b.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test17c: cleanup_milter test-queue-file17 cleanup_milter.in17c \ + cleanup_milter.ref17c1 ../postcat/postcat cleanup_milter.ref17c2 + cp test-queue-file17 test-queue-file17c.tmp + chmod u+w test-queue-file17c.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref17c1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17c.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref17c2 cleanup_milter.tmp2 + rm -f test-queue-file17c.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test17d: cleanup_milter test-queue-file17 cleanup_milter.in17d \ + cleanup_milter.ref17d1 ../postcat/postcat cleanup_milter.ref17d2 + cp test-queue-file17 test-queue-file17d.tmp + chmod u+w test-queue-file17d.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref17d1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17d.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref17d2 cleanup_milter.tmp2 + rm -f test-queue-file17d.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test17e: cleanup_milter test-queue-file17 cleanup_milter.in17e \ + cleanup_milter.ref17e1 ../postcat/postcat cleanup_milter.ref17e2 + cp test-queue-file17 test-queue-file17e.tmp + chmod u+w test-queue-file17e.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref17e1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17e.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref17e2 cleanup_milter.tmp2 + rm -f test-queue-file17e.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test17f: cleanup_milter test-queue-file17 cleanup_milter.in17f \ + cleanup_milter.ref17f1 ../postcat/postcat cleanup_milter.ref17f2 + cp test-queue-file17 test-queue-file17f.tmp + chmod u+w test-queue-file17f.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref17f1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17f.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref17f2 cleanup_milter.tmp2 + rm -f test-queue-file17f.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +cleanup_milter_test17g: cleanup_milter test-queue-file17 cleanup_milter.in17g \ + cleanup_milter.ref17g1 ../postcat/postcat cleanup_milter.ref17g2 + cp test-queue-file17 test-queue-file17g.tmp + chmod u+w test-queue-file17g.tmp + $(SHLIB_ENV) $(VALGRIND) ./cleanup_milter cleanup_milter.tmp1 + diff cleanup_milter.ref17g1 cleanup_milter.tmp1 + $(SHLIB_ENV) $(VALGRIND) ../postcat/postcat -ov test-queue-file17g.tmp 2>/dev/null >cleanup_milter.tmp2 + diff cleanup_milter.ref17g2 cleanup_milter.tmp2 + rm -f test-queue-file17g.tmp cleanup_milter.tmp1 cleanup_milter.tmp2 + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +cleanup.o: ../../include/argv.h +cleanup.o: ../../include/attr.h +cleanup.o: ../../include/been_here.h +cleanup.o: ../../include/check_arg.h +cleanup.o: ../../include/cleanup_user.h +cleanup.o: ../../include/dict.h +cleanup.o: ../../include/dsn_mask.h +cleanup.o: ../../include/header_body_checks.h +cleanup.o: ../../include/header_opts.h +cleanup.o: ../../include/htable.h +cleanup.o: ../../include/iostuff.h +cleanup.o: ../../include/mail_conf.h +cleanup.o: ../../include/mail_params.h +cleanup.o: ../../include/mail_proto.h +cleanup.o: ../../include/mail_server.h +cleanup.o: ../../include/mail_stream.h +cleanup.o: ../../include/mail_version.h +cleanup.o: ../../include/maps.h +cleanup.o: ../../include/match_list.h +cleanup.o: ../../include/milter.h +cleanup.o: ../../include/mime_state.h +cleanup.o: ../../include/msg.h +cleanup.o: ../../include/myflock.h +cleanup.o: ../../include/mymalloc.h +cleanup.o: ../../include/nvtable.h +cleanup.o: ../../include/rec_type.h +cleanup.o: ../../include/record.h +cleanup.o: ../../include/resolve_clnt.h +cleanup.o: ../../include/string_list.h +cleanup.o: ../../include/sys_defs.h +cleanup.o: ../../include/tok822.h +cleanup.o: ../../include/vbuf.h +cleanup.o: ../../include/vstream.h +cleanup.o: ../../include/vstring.h +cleanup.o: cleanup.c +cleanup.o: cleanup.h +cleanup_addr.o: ../../include/argv.h +cleanup_addr.o: ../../include/attr.h +cleanup_addr.o: ../../include/been_here.h +cleanup_addr.o: ../../include/canon_addr.h +cleanup_addr.o: ../../include/check_arg.h +cleanup_addr.o: ../../include/cleanup_user.h +cleanup_addr.o: ../../include/dict.h +cleanup_addr.o: ../../include/dsn_mask.h +cleanup_addr.o: ../../include/ext_prop.h +cleanup_addr.o: ../../include/header_body_checks.h +cleanup_addr.o: ../../include/header_opts.h +cleanup_addr.o: ../../include/htable.h +cleanup_addr.o: ../../include/iostuff.h +cleanup_addr.o: ../../include/mail_addr.h +cleanup_addr.o: ../../include/mail_addr_find.h +cleanup_addr.o: ../../include/mail_addr_form.h +cleanup_addr.o: ../../include/mail_conf.h +cleanup_addr.o: ../../include/mail_params.h +cleanup_addr.o: ../../include/mail_proto.h +cleanup_addr.o: ../../include/mail_stream.h +cleanup_addr.o: ../../include/maps.h +cleanup_addr.o: ../../include/match_list.h +cleanup_addr.o: ../../include/milter.h +cleanup_addr.o: ../../include/mime_state.h +cleanup_addr.o: ../../include/msg.h +cleanup_addr.o: ../../include/myflock.h +cleanup_addr.o: ../../include/mymalloc.h +cleanup_addr.o: ../../include/nvtable.h +cleanup_addr.o: ../../include/rec_type.h +cleanup_addr.o: ../../include/record.h +cleanup_addr.o: ../../include/resolve_clnt.h +cleanup_addr.o: ../../include/smtputf8.h +cleanup_addr.o: ../../include/string_list.h +cleanup_addr.o: ../../include/stringops.h +cleanup_addr.o: ../../include/sys_defs.h +cleanup_addr.o: ../../include/tok822.h +cleanup_addr.o: ../../include/vbuf.h +cleanup_addr.o: ../../include/vstream.h +cleanup_addr.o: ../../include/vstring.h +cleanup_addr.o: cleanup.h +cleanup_addr.o: cleanup_addr.c +cleanup_api.o: ../../include/argv.h +cleanup_api.o: ../../include/attr.h +cleanup_api.o: ../../include/been_here.h +cleanup_api.o: ../../include/bounce.h +cleanup_api.o: ../../include/check_arg.h +cleanup_api.o: ../../include/cleanup_user.h +cleanup_api.o: ../../include/deliver_request.h +cleanup_api.o: ../../include/dict.h +cleanup_api.o: ../../include/dsn.h +cleanup_api.o: ../../include/dsn_buf.h +cleanup_api.o: ../../include/dsn_mask.h +cleanup_api.o: ../../include/header_body_checks.h +cleanup_api.o: ../../include/header_opts.h +cleanup_api.o: ../../include/htable.h +cleanup_api.o: ../../include/iostuff.h +cleanup_api.o: ../../include/mail_conf.h +cleanup_api.o: ../../include/mail_flow.h +cleanup_api.o: ../../include/mail_params.h +cleanup_api.o: ../../include/mail_proto.h +cleanup_api.o: ../../include/mail_queue.h +cleanup_api.o: ../../include/mail_stream.h +cleanup_api.o: ../../include/maps.h +cleanup_api.o: ../../include/match_list.h +cleanup_api.o: ../../include/milter.h +cleanup_api.o: ../../include/mime_state.h +cleanup_api.o: ../../include/msg.h +cleanup_api.o: ../../include/msg_stats.h +cleanup_api.o: ../../include/myflock.h +cleanup_api.o: ../../include/mymalloc.h +cleanup_api.o: ../../include/nvtable.h +cleanup_api.o: ../../include/rec_type.h +cleanup_api.o: ../../include/recipient_list.h +cleanup_api.o: ../../include/resolve_clnt.h +cleanup_api.o: ../../include/smtputf8.h +cleanup_api.o: ../../include/string_list.h +cleanup_api.o: ../../include/sys_defs.h +cleanup_api.o: ../../include/tok822.h +cleanup_api.o: ../../include/vbuf.h +cleanup_api.o: ../../include/vstream.h +cleanup_api.o: ../../include/vstring.h +cleanup_api.o: cleanup.h +cleanup_api.o: cleanup_api.c +cleanup_body_edit.o: ../../include/argv.h +cleanup_body_edit.o: ../../include/attr.h +cleanup_body_edit.o: ../../include/been_here.h +cleanup_body_edit.o: ../../include/check_arg.h +cleanup_body_edit.o: ../../include/cleanup_user.h +cleanup_body_edit.o: ../../include/dict.h +cleanup_body_edit.o: ../../include/dsn_mask.h +cleanup_body_edit.o: ../../include/header_body_checks.h +cleanup_body_edit.o: ../../include/header_opts.h +cleanup_body_edit.o: ../../include/htable.h +cleanup_body_edit.o: ../../include/mail_conf.h +cleanup_body_edit.o: ../../include/mail_stream.h +cleanup_body_edit.o: ../../include/maps.h +cleanup_body_edit.o: ../../include/match_list.h +cleanup_body_edit.o: ../../include/milter.h +cleanup_body_edit.o: ../../include/mime_state.h +cleanup_body_edit.o: ../../include/msg.h +cleanup_body_edit.o: ../../include/myflock.h +cleanup_body_edit.o: ../../include/mymalloc.h +cleanup_body_edit.o: ../../include/nvtable.h +cleanup_body_edit.o: ../../include/rec_type.h +cleanup_body_edit.o: ../../include/record.h +cleanup_body_edit.o: ../../include/resolve_clnt.h +cleanup_body_edit.o: ../../include/string_list.h +cleanup_body_edit.o: ../../include/sys_defs.h +cleanup_body_edit.o: ../../include/tok822.h +cleanup_body_edit.o: ../../include/vbuf.h +cleanup_body_edit.o: ../../include/vstream.h +cleanup_body_edit.o: ../../include/vstring.h +cleanup_body_edit.o: cleanup.h +cleanup_body_edit.o: cleanup_body_edit.c +cleanup_bounce.o: ../../include/argv.h +cleanup_bounce.o: ../../include/attr.h +cleanup_bounce.o: ../../include/been_here.h +cleanup_bounce.o: ../../include/bounce.h +cleanup_bounce.o: ../../include/check_arg.h +cleanup_bounce.o: ../../include/cleanup_user.h +cleanup_bounce.o: ../../include/deliver_request.h +cleanup_bounce.o: ../../include/dict.h +cleanup_bounce.o: ../../include/dsn.h +cleanup_bounce.o: ../../include/dsn_buf.h +cleanup_bounce.o: ../../include/dsn_mask.h +cleanup_bounce.o: ../../include/dsn_util.h +cleanup_bounce.o: ../../include/header_body_checks.h +cleanup_bounce.o: ../../include/header_opts.h +cleanup_bounce.o: ../../include/htable.h +cleanup_bounce.o: ../../include/iostuff.h +cleanup_bounce.o: ../../include/mail_conf.h +cleanup_bounce.o: ../../include/mail_params.h +cleanup_bounce.o: ../../include/mail_proto.h +cleanup_bounce.o: ../../include/mail_queue.h +cleanup_bounce.o: ../../include/mail_stream.h +cleanup_bounce.o: ../../include/maps.h +cleanup_bounce.o: ../../include/match_list.h +cleanup_bounce.o: ../../include/milter.h +cleanup_bounce.o: ../../include/mime_state.h +cleanup_bounce.o: ../../include/msg.h +cleanup_bounce.o: ../../include/msg_stats.h +cleanup_bounce.o: ../../include/myflock.h +cleanup_bounce.o: ../../include/mymalloc.h +cleanup_bounce.o: ../../include/nvtable.h +cleanup_bounce.o: ../../include/rec_attr_map.h +cleanup_bounce.o: ../../include/rec_type.h +cleanup_bounce.o: ../../include/recipient_list.h +cleanup_bounce.o: ../../include/record.h +cleanup_bounce.o: ../../include/resolve_clnt.h +cleanup_bounce.o: ../../include/string_list.h +cleanup_bounce.o: ../../include/stringops.h +cleanup_bounce.o: ../../include/sys_defs.h +cleanup_bounce.o: ../../include/tok822.h +cleanup_bounce.o: ../../include/vbuf.h +cleanup_bounce.o: ../../include/vstream.h +cleanup_bounce.o: ../../include/vstring.h +cleanup_bounce.o: cleanup.h +cleanup_bounce.o: cleanup_bounce.c +cleanup_envelope.o: ../../include/argv.h +cleanup_envelope.o: ../../include/attr.h +cleanup_envelope.o: ../../include/been_here.h +cleanup_envelope.o: ../../include/check_arg.h +cleanup_envelope.o: ../../include/cleanup_user.h +cleanup_envelope.o: ../../include/deliver_request.h +cleanup_envelope.o: ../../include/dict.h +cleanup_envelope.o: ../../include/dsn.h +cleanup_envelope.o: ../../include/dsn_mask.h +cleanup_envelope.o: ../../include/header_body_checks.h +cleanup_envelope.o: ../../include/header_opts.h +cleanup_envelope.o: ../../include/htable.h +cleanup_envelope.o: ../../include/iostuff.h +cleanup_envelope.o: ../../include/mail_conf.h +cleanup_envelope.o: ../../include/mail_params.h +cleanup_envelope.o: ../../include/mail_proto.h +cleanup_envelope.o: ../../include/mail_stream.h +cleanup_envelope.o: ../../include/maps.h +cleanup_envelope.o: ../../include/match_list.h +cleanup_envelope.o: ../../include/milter.h +cleanup_envelope.o: ../../include/mime_state.h +cleanup_envelope.o: ../../include/msg.h +cleanup_envelope.o: ../../include/msg_stats.h +cleanup_envelope.o: ../../include/myflock.h +cleanup_envelope.o: ../../include/mymalloc.h +cleanup_envelope.o: ../../include/nvtable.h +cleanup_envelope.o: ../../include/qmgr_user.h +cleanup_envelope.o: ../../include/rec_attr_map.h +cleanup_envelope.o: ../../include/rec_type.h +cleanup_envelope.o: ../../include/recipient_list.h +cleanup_envelope.o: ../../include/record.h +cleanup_envelope.o: ../../include/resolve_clnt.h +cleanup_envelope.o: ../../include/smtputf8.h +cleanup_envelope.o: ../../include/string_list.h +cleanup_envelope.o: ../../include/stringops.h +cleanup_envelope.o: ../../include/sys_defs.h +cleanup_envelope.o: ../../include/tok822.h +cleanup_envelope.o: ../../include/vbuf.h +cleanup_envelope.o: ../../include/verp_sender.h +cleanup_envelope.o: ../../include/vstream.h +cleanup_envelope.o: ../../include/vstring.h +cleanup_envelope.o: cleanup.h +cleanup_envelope.o: cleanup_envelope.c +cleanup_extracted.o: ../../include/argv.h +cleanup_extracted.o: ../../include/attr.h +cleanup_extracted.o: ../../include/been_here.h +cleanup_extracted.o: ../../include/check_arg.h +cleanup_extracted.o: ../../include/cleanup_user.h +cleanup_extracted.o: ../../include/dict.h +cleanup_extracted.o: ../../include/dsn_mask.h +cleanup_extracted.o: ../../include/header_body_checks.h +cleanup_extracted.o: ../../include/header_opts.h +cleanup_extracted.o: ../../include/htable.h +cleanup_extracted.o: ../../include/iostuff.h +cleanup_extracted.o: ../../include/mail_conf.h +cleanup_extracted.o: ../../include/mail_params.h +cleanup_extracted.o: ../../include/mail_proto.h +cleanup_extracted.o: ../../include/mail_stream.h +cleanup_extracted.o: ../../include/maps.h +cleanup_extracted.o: ../../include/match_list.h +cleanup_extracted.o: ../../include/milter.h +cleanup_extracted.o: ../../include/mime_state.h +cleanup_extracted.o: ../../include/msg.h +cleanup_extracted.o: ../../include/myflock.h +cleanup_extracted.o: ../../include/mymalloc.h +cleanup_extracted.o: ../../include/nvtable.h +cleanup_extracted.o: ../../include/qmgr_user.h +cleanup_extracted.o: ../../include/rec_attr_map.h +cleanup_extracted.o: ../../include/rec_type.h +cleanup_extracted.o: ../../include/record.h +cleanup_extracted.o: ../../include/resolve_clnt.h +cleanup_extracted.o: ../../include/string_list.h +cleanup_extracted.o: ../../include/stringops.h +cleanup_extracted.o: ../../include/sys_defs.h +cleanup_extracted.o: ../../include/tok822.h +cleanup_extracted.o: ../../include/vbuf.h +cleanup_extracted.o: ../../include/vstream.h +cleanup_extracted.o: ../../include/vstring.h +cleanup_extracted.o: cleanup.h +cleanup_extracted.o: cleanup_extracted.c +cleanup_final.o: ../../include/argv.h +cleanup_final.o: ../../include/attr.h +cleanup_final.o: ../../include/been_here.h +cleanup_final.o: ../../include/check_arg.h +cleanup_final.o: ../../include/cleanup_user.h +cleanup_final.o: ../../include/dict.h +cleanup_final.o: ../../include/dsn_mask.h +cleanup_final.o: ../../include/header_body_checks.h +cleanup_final.o: ../../include/header_opts.h +cleanup_final.o: ../../include/htable.h +cleanup_final.o: ../../include/mail_conf.h +cleanup_final.o: ../../include/mail_stream.h +cleanup_final.o: ../../include/maps.h +cleanup_final.o: ../../include/match_list.h +cleanup_final.o: ../../include/milter.h +cleanup_final.o: ../../include/mime_state.h +cleanup_final.o: ../../include/msg.h +cleanup_final.o: ../../include/myflock.h +cleanup_final.o: ../../include/mymalloc.h +cleanup_final.o: ../../include/nvtable.h +cleanup_final.o: ../../include/rec_type.h +cleanup_final.o: ../../include/resolve_clnt.h +cleanup_final.o: ../../include/string_list.h +cleanup_final.o: ../../include/sys_defs.h +cleanup_final.o: ../../include/tok822.h +cleanup_final.o: ../../include/vbuf.h +cleanup_final.o: ../../include/vstream.h +cleanup_final.o: ../../include/vstring.h +cleanup_final.o: cleanup.h +cleanup_final.o: cleanup_final.c +cleanup_init.o: ../../include/argv.h +cleanup_init.o: ../../include/attr.h +cleanup_init.o: ../../include/been_here.h +cleanup_init.o: ../../include/check_arg.h +cleanup_init.o: ../../include/cleanup_user.h +cleanup_init.o: ../../include/dict.h +cleanup_init.o: ../../include/dsn_mask.h +cleanup_init.o: ../../include/ext_prop.h +cleanup_init.o: ../../include/flush_clnt.h +cleanup_init.o: ../../include/header_body_checks.h +cleanup_init.o: ../../include/header_opts.h +cleanup_init.o: ../../include/hfrom_format.h +cleanup_init.o: ../../include/htable.h +cleanup_init.o: ../../include/iostuff.h +cleanup_init.o: ../../include/mail_addr.h +cleanup_init.o: ../../include/mail_conf.h +cleanup_init.o: ../../include/mail_params.h +cleanup_init.o: ../../include/mail_stream.h +cleanup_init.o: ../../include/mail_version.h +cleanup_init.o: ../../include/maps.h +cleanup_init.o: ../../include/match_list.h +cleanup_init.o: ../../include/milter.h +cleanup_init.o: ../../include/mime_state.h +cleanup_init.o: ../../include/msg.h +cleanup_init.o: ../../include/myflock.h +cleanup_init.o: ../../include/mymalloc.h +cleanup_init.o: ../../include/name_code.h +cleanup_init.o: ../../include/name_mask.h +cleanup_init.o: ../../include/nvtable.h +cleanup_init.o: ../../include/resolve_clnt.h +cleanup_init.o: ../../include/string_list.h +cleanup_init.o: ../../include/stringops.h +cleanup_init.o: ../../include/sys_defs.h +cleanup_init.o: ../../include/tok822.h +cleanup_init.o: ../../include/vbuf.h +cleanup_init.o: ../../include/vstream.h +cleanup_init.o: ../../include/vstring.h +cleanup_init.o: cleanup.h +cleanup_init.o: cleanup_init.c +cleanup_map11.o: ../../include/argv.h +cleanup_map11.o: ../../include/attr.h +cleanup_map11.o: ../../include/been_here.h +cleanup_map11.o: ../../include/check_arg.h +cleanup_map11.o: ../../include/cleanup_user.h +cleanup_map11.o: ../../include/dict.h +cleanup_map11.o: ../../include/dsn_mask.h +cleanup_map11.o: ../../include/header_body_checks.h +cleanup_map11.o: ../../include/header_opts.h +cleanup_map11.o: ../../include/htable.h +cleanup_map11.o: ../../include/mail_addr_form.h +cleanup_map11.o: ../../include/mail_addr_map.h +cleanup_map11.o: ../../include/mail_conf.h +cleanup_map11.o: ../../include/mail_stream.h +cleanup_map11.o: ../../include/maps.h +cleanup_map11.o: ../../include/match_list.h +cleanup_map11.o: ../../include/milter.h +cleanup_map11.o: ../../include/mime_state.h +cleanup_map11.o: ../../include/msg.h +cleanup_map11.o: ../../include/myflock.h +cleanup_map11.o: ../../include/mymalloc.h +cleanup_map11.o: ../../include/nvtable.h +cleanup_map11.o: ../../include/quote_822_local.h +cleanup_map11.o: ../../include/quote_flags.h +cleanup_map11.o: ../../include/resolve_clnt.h +cleanup_map11.o: ../../include/string_list.h +cleanup_map11.o: ../../include/stringops.h +cleanup_map11.o: ../../include/sys_defs.h +cleanup_map11.o: ../../include/tok822.h +cleanup_map11.o: ../../include/vbuf.h +cleanup_map11.o: ../../include/vstream.h +cleanup_map11.o: ../../include/vstring.h +cleanup_map11.o: cleanup.h +cleanup_map11.o: cleanup_map11.c +cleanup_map1n.o: ../../include/argv.h +cleanup_map1n.o: ../../include/attr.h +cleanup_map1n.o: ../../include/been_here.h +cleanup_map1n.o: ../../include/check_arg.h +cleanup_map1n.o: ../../include/cleanup_user.h +cleanup_map1n.o: ../../include/dict.h +cleanup_map1n.o: ../../include/dsn_mask.h +cleanup_map1n.o: ../../include/header_body_checks.h +cleanup_map1n.o: ../../include/header_opts.h +cleanup_map1n.o: ../../include/htable.h +cleanup_map1n.o: ../../include/mail_addr_form.h +cleanup_map1n.o: ../../include/mail_addr_map.h +cleanup_map1n.o: ../../include/mail_conf.h +cleanup_map1n.o: ../../include/mail_params.h +cleanup_map1n.o: ../../include/mail_stream.h +cleanup_map1n.o: ../../include/maps.h +cleanup_map1n.o: ../../include/match_list.h +cleanup_map1n.o: ../../include/milter.h +cleanup_map1n.o: ../../include/mime_state.h +cleanup_map1n.o: ../../include/msg.h +cleanup_map1n.o: ../../include/myflock.h +cleanup_map1n.o: ../../include/mymalloc.h +cleanup_map1n.o: ../../include/nvtable.h +cleanup_map1n.o: ../../include/quote_822_local.h +cleanup_map1n.o: ../../include/quote_flags.h +cleanup_map1n.o: ../../include/resolve_clnt.h +cleanup_map1n.o: ../../include/string_list.h +cleanup_map1n.o: ../../include/stringops.h +cleanup_map1n.o: ../../include/sys_defs.h +cleanup_map1n.o: ../../include/tok822.h +cleanup_map1n.o: ../../include/vbuf.h +cleanup_map1n.o: ../../include/vstream.h +cleanup_map1n.o: ../../include/vstring.h +cleanup_map1n.o: cleanup.h +cleanup_map1n.o: cleanup_map1n.c +cleanup_masquerade.o: ../../include/argv.h +cleanup_masquerade.o: ../../include/attr.h +cleanup_masquerade.o: ../../include/been_here.h +cleanup_masquerade.o: ../../include/check_arg.h +cleanup_masquerade.o: ../../include/cleanup_user.h +cleanup_masquerade.o: ../../include/dict.h +cleanup_masquerade.o: ../../include/dsn_mask.h +cleanup_masquerade.o: ../../include/header_body_checks.h +cleanup_masquerade.o: ../../include/header_opts.h +cleanup_masquerade.o: ../../include/htable.h +cleanup_masquerade.o: ../../include/mail_conf.h +cleanup_masquerade.o: ../../include/mail_params.h +cleanup_masquerade.o: ../../include/mail_stream.h +cleanup_masquerade.o: ../../include/maps.h +cleanup_masquerade.o: ../../include/match_list.h +cleanup_masquerade.o: ../../include/milter.h +cleanup_masquerade.o: ../../include/mime_state.h +cleanup_masquerade.o: ../../include/msg.h +cleanup_masquerade.o: ../../include/myflock.h +cleanup_masquerade.o: ../../include/mymalloc.h +cleanup_masquerade.o: ../../include/nvtable.h +cleanup_masquerade.o: ../../include/quote_822_local.h +cleanup_masquerade.o: ../../include/quote_flags.h +cleanup_masquerade.o: ../../include/resolve_clnt.h +cleanup_masquerade.o: ../../include/string_list.h +cleanup_masquerade.o: ../../include/stringops.h +cleanup_masquerade.o: ../../include/sys_defs.h +cleanup_masquerade.o: ../../include/tok822.h +cleanup_masquerade.o: ../../include/vbuf.h +cleanup_masquerade.o: ../../include/vstream.h +cleanup_masquerade.o: ../../include/vstring.h +cleanup_masquerade.o: cleanup.h +cleanup_masquerade.o: cleanup_masquerade.c +cleanup_message.o: ../../include/argv.h +cleanup_message.o: ../../include/attr.h +cleanup_message.o: ../../include/been_here.h +cleanup_message.o: ../../include/check_arg.h +cleanup_message.o: ../../include/cleanup_user.h +cleanup_message.o: ../../include/conv_time.h +cleanup_message.o: ../../include/dict.h +cleanup_message.o: ../../include/dsn_mask.h +cleanup_message.o: ../../include/dsn_util.h +cleanup_message.o: ../../include/ext_prop.h +cleanup_message.o: ../../include/header_body_checks.h +cleanup_message.o: ../../include/header_opts.h +cleanup_message.o: ../../include/hfrom_format.h +cleanup_message.o: ../../include/htable.h +cleanup_message.o: ../../include/info_log_addr_form.h +cleanup_message.o: ../../include/iostuff.h +cleanup_message.o: ../../include/is_header.h +cleanup_message.o: ../../include/lex_822.h +cleanup_message.o: ../../include/mail_addr.h +cleanup_message.o: ../../include/mail_conf.h +cleanup_message.o: ../../include/mail_date.h +cleanup_message.o: ../../include/mail_params.h +cleanup_message.o: ../../include/mail_proto.h +cleanup_message.o: ../../include/mail_stream.h +cleanup_message.o: ../../include/maps.h +cleanup_message.o: ../../include/match_list.h +cleanup_message.o: ../../include/milter.h +cleanup_message.o: ../../include/mime_state.h +cleanup_message.o: ../../include/msg.h +cleanup_message.o: ../../include/myflock.h +cleanup_message.o: ../../include/mymalloc.h +cleanup_message.o: ../../include/nvtable.h +cleanup_message.o: ../../include/quote_822_local.h +cleanup_message.o: ../../include/quote_flags.h +cleanup_message.o: ../../include/rec_type.h +cleanup_message.o: ../../include/record.h +cleanup_message.o: ../../include/resolve_clnt.h +cleanup_message.o: ../../include/split_at.h +cleanup_message.o: ../../include/string_list.h +cleanup_message.o: ../../include/stringops.h +cleanup_message.o: ../../include/sys_defs.h +cleanup_message.o: ../../include/tok822.h +cleanup_message.o: ../../include/vbuf.h +cleanup_message.o: ../../include/vstream.h +cleanup_message.o: ../../include/vstring.h +cleanup_message.o: cleanup.h +cleanup_message.o: cleanup_message.c +cleanup_milter.o: ../../include/argv.h +cleanup_milter.o: ../../include/attr.h +cleanup_milter.o: ../../include/been_here.h +cleanup_milter.o: ../../include/check_arg.h +cleanup_milter.o: ../../include/cleanup_user.h +cleanup_milter.o: ../../include/dict.h +cleanup_milter.o: ../../include/dsn_mask.h +cleanup_milter.o: ../../include/dsn_util.h +cleanup_milter.o: ../../include/header_body_checks.h +cleanup_milter.o: ../../include/header_opts.h +cleanup_milter.o: ../../include/htable.h +cleanup_milter.o: ../../include/inet_proto.h +cleanup_milter.o: ../../include/info_log_addr_form.h +cleanup_milter.o: ../../include/iostuff.h +cleanup_milter.o: ../../include/is_header.h +cleanup_milter.o: ../../include/lex_822.h +cleanup_milter.o: ../../include/mail_conf.h +cleanup_milter.o: ../../include/mail_params.h +cleanup_milter.o: ../../include/mail_proto.h +cleanup_milter.o: ../../include/mail_stream.h +cleanup_milter.o: ../../include/maps.h +cleanup_milter.o: ../../include/match_list.h +cleanup_milter.o: ../../include/milter.h +cleanup_milter.o: ../../include/mime_state.h +cleanup_milter.o: ../../include/msg.h +cleanup_milter.o: ../../include/myflock.h +cleanup_milter.o: ../../include/mymalloc.h +cleanup_milter.o: ../../include/nvtable.h +cleanup_milter.o: ../../include/off_cvt.h +cleanup_milter.o: ../../include/quote_821_local.h +cleanup_milter.o: ../../include/quote_flags.h +cleanup_milter.o: ../../include/rec_attr_map.h +cleanup_milter.o: ../../include/rec_type.h +cleanup_milter.o: ../../include/record.h +cleanup_milter.o: ../../include/resolve_clnt.h +cleanup_milter.o: ../../include/string_list.h +cleanup_milter.o: ../../include/stringops.h +cleanup_milter.o: ../../include/sys_defs.h +cleanup_milter.o: ../../include/tok822.h +cleanup_milter.o: ../../include/vbuf.h +cleanup_milter.o: ../../include/vstream.h +cleanup_milter.o: ../../include/vstring.h +cleanup_milter.o: ../../include/xtext.h +cleanup_milter.o: cleanup.h +cleanup_milter.o: cleanup_milter.c +cleanup_out.o: ../../include/argv.h +cleanup_out.o: ../../include/attr.h +cleanup_out.o: ../../include/been_here.h +cleanup_out.o: ../../include/check_arg.h +cleanup_out.o: ../../include/cleanup_user.h +cleanup_out.o: ../../include/dict.h +cleanup_out.o: ../../include/dsn_mask.h +cleanup_out.o: ../../include/header_body_checks.h +cleanup_out.o: ../../include/header_opts.h +cleanup_out.o: ../../include/htable.h +cleanup_out.o: ../../include/lex_822.h +cleanup_out.o: ../../include/mail_conf.h +cleanup_out.o: ../../include/mail_params.h +cleanup_out.o: ../../include/mail_stream.h +cleanup_out.o: ../../include/maps.h +cleanup_out.o: ../../include/match_list.h +cleanup_out.o: ../../include/milter.h +cleanup_out.o: ../../include/mime_state.h +cleanup_out.o: ../../include/msg.h +cleanup_out.o: ../../include/myflock.h +cleanup_out.o: ../../include/mymalloc.h +cleanup_out.o: ../../include/nvtable.h +cleanup_out.o: ../../include/rec_type.h +cleanup_out.o: ../../include/record.h +cleanup_out.o: ../../include/resolve_clnt.h +cleanup_out.o: ../../include/smtputf8.h +cleanup_out.o: ../../include/split_at.h +cleanup_out.o: ../../include/string_list.h +cleanup_out.o: ../../include/stringops.h +cleanup_out.o: ../../include/sys_defs.h +cleanup_out.o: ../../include/tok822.h +cleanup_out.o: ../../include/vbuf.h +cleanup_out.o: ../../include/vstream.h +cleanup_out.o: ../../include/vstring.h +cleanup_out.o: cleanup.h +cleanup_out.o: cleanup_out.c +cleanup_out_recipient.o: ../../include/argv.h +cleanup_out_recipient.o: ../../include/attr.h +cleanup_out_recipient.o: ../../include/been_here.h +cleanup_out_recipient.o: ../../include/bounce.h +cleanup_out_recipient.o: ../../include/check_arg.h +cleanup_out_recipient.o: ../../include/cleanup_user.h +cleanup_out_recipient.o: ../../include/deliver_request.h +cleanup_out_recipient.o: ../../include/dict.h +cleanup_out_recipient.o: ../../include/dsn.h +cleanup_out_recipient.o: ../../include/dsn_buf.h +cleanup_out_recipient.o: ../../include/dsn_mask.h +cleanup_out_recipient.o: ../../include/ext_prop.h +cleanup_out_recipient.o: ../../include/header_body_checks.h +cleanup_out_recipient.o: ../../include/header_opts.h +cleanup_out_recipient.o: ../../include/htable.h +cleanup_out_recipient.o: ../../include/iostuff.h +cleanup_out_recipient.o: ../../include/mail_conf.h +cleanup_out_recipient.o: ../../include/mail_params.h +cleanup_out_recipient.o: ../../include/mail_proto.h +cleanup_out_recipient.o: ../../include/mail_queue.h +cleanup_out_recipient.o: ../../include/mail_stream.h +cleanup_out_recipient.o: ../../include/maps.h +cleanup_out_recipient.o: ../../include/match_list.h +cleanup_out_recipient.o: ../../include/milter.h +cleanup_out_recipient.o: ../../include/mime_state.h +cleanup_out_recipient.o: ../../include/msg.h +cleanup_out_recipient.o: ../../include/msg_stats.h +cleanup_out_recipient.o: ../../include/myflock.h +cleanup_out_recipient.o: ../../include/mymalloc.h +cleanup_out_recipient.o: ../../include/nvtable.h +cleanup_out_recipient.o: ../../include/rec_type.h +cleanup_out_recipient.o: ../../include/recipient_list.h +cleanup_out_recipient.o: ../../include/resolve_clnt.h +cleanup_out_recipient.o: ../../include/string_list.h +cleanup_out_recipient.o: ../../include/sys_defs.h +cleanup_out_recipient.o: ../../include/tok822.h +cleanup_out_recipient.o: ../../include/trace.h +cleanup_out_recipient.o: ../../include/vbuf.h +cleanup_out_recipient.o: ../../include/verify.h +cleanup_out_recipient.o: ../../include/vstream.h +cleanup_out_recipient.o: ../../include/vstring.h +cleanup_out_recipient.o: cleanup.h +cleanup_out_recipient.o: cleanup_out_recipient.c +cleanup_region.o: ../../include/argv.h +cleanup_region.o: ../../include/attr.h +cleanup_region.o: ../../include/been_here.h +cleanup_region.o: ../../include/check_arg.h +cleanup_region.o: ../../include/cleanup_user.h +cleanup_region.o: ../../include/dict.h +cleanup_region.o: ../../include/dsn_mask.h +cleanup_region.o: ../../include/header_body_checks.h +cleanup_region.o: ../../include/header_opts.h +cleanup_region.o: ../../include/htable.h +cleanup_region.o: ../../include/mail_conf.h +cleanup_region.o: ../../include/mail_stream.h +cleanup_region.o: ../../include/maps.h +cleanup_region.o: ../../include/match_list.h +cleanup_region.o: ../../include/milter.h +cleanup_region.o: ../../include/mime_state.h +cleanup_region.o: ../../include/msg.h +cleanup_region.o: ../../include/myflock.h +cleanup_region.o: ../../include/mymalloc.h +cleanup_region.o: ../../include/nvtable.h +cleanup_region.o: ../../include/resolve_clnt.h +cleanup_region.o: ../../include/string_list.h +cleanup_region.o: ../../include/sys_defs.h +cleanup_region.o: ../../include/tok822.h +cleanup_region.o: ../../include/vbuf.h +cleanup_region.o: ../../include/vstream.h +cleanup_region.o: ../../include/vstring.h +cleanup_region.o: ../../include/warn_stat.h +cleanup_region.o: cleanup.h +cleanup_region.o: cleanup_region.c +cleanup_rewrite.o: ../../include/argv.h +cleanup_rewrite.o: ../../include/attr.h +cleanup_rewrite.o: ../../include/been_here.h +cleanup_rewrite.o: ../../include/check_arg.h +cleanup_rewrite.o: ../../include/cleanup_user.h +cleanup_rewrite.o: ../../include/dict.h +cleanup_rewrite.o: ../../include/dsn_mask.h +cleanup_rewrite.o: ../../include/header_body_checks.h +cleanup_rewrite.o: ../../include/header_opts.h +cleanup_rewrite.o: ../../include/htable.h +cleanup_rewrite.o: ../../include/iostuff.h +cleanup_rewrite.o: ../../include/mail_conf.h +cleanup_rewrite.o: ../../include/mail_proto.h +cleanup_rewrite.o: ../../include/mail_stream.h +cleanup_rewrite.o: ../../include/maps.h +cleanup_rewrite.o: ../../include/match_list.h +cleanup_rewrite.o: ../../include/milter.h +cleanup_rewrite.o: ../../include/mime_state.h +cleanup_rewrite.o: ../../include/msg.h +cleanup_rewrite.o: ../../include/myflock.h +cleanup_rewrite.o: ../../include/mymalloc.h +cleanup_rewrite.o: ../../include/nvtable.h +cleanup_rewrite.o: ../../include/quote_822_local.h +cleanup_rewrite.o: ../../include/quote_flags.h +cleanup_rewrite.o: ../../include/resolve_clnt.h +cleanup_rewrite.o: ../../include/rewrite_clnt.h +cleanup_rewrite.o: ../../include/string_list.h +cleanup_rewrite.o: ../../include/sys_defs.h +cleanup_rewrite.o: ../../include/tok822.h +cleanup_rewrite.o: ../../include/vbuf.h +cleanup_rewrite.o: ../../include/vstream.h +cleanup_rewrite.o: ../../include/vstring.h +cleanup_rewrite.o: cleanup.h +cleanup_rewrite.o: cleanup_rewrite.c +cleanup_state.o: ../../include/argv.h +cleanup_state.o: ../../include/attr.h +cleanup_state.o: ../../include/been_here.h +cleanup_state.o: ../../include/check_arg.h +cleanup_state.o: ../../include/cleanup_user.h +cleanup_state.o: ../../include/dict.h +cleanup_state.o: ../../include/dsn_mask.h +cleanup_state.o: ../../include/header_body_checks.h +cleanup_state.o: ../../include/header_opts.h +cleanup_state.o: ../../include/htable.h +cleanup_state.o: ../../include/iostuff.h +cleanup_state.o: ../../include/mail_conf.h +cleanup_state.o: ../../include/mail_params.h +cleanup_state.o: ../../include/mail_proto.h +cleanup_state.o: ../../include/mail_stream.h +cleanup_state.o: ../../include/maps.h +cleanup_state.o: ../../include/match_list.h +cleanup_state.o: ../../include/milter.h +cleanup_state.o: ../../include/mime_state.h +cleanup_state.o: ../../include/myflock.h +cleanup_state.o: ../../include/mymalloc.h +cleanup_state.o: ../../include/nvtable.h +cleanup_state.o: ../../include/resolve_clnt.h +cleanup_state.o: ../../include/string_list.h +cleanup_state.o: ../../include/sys_defs.h +cleanup_state.o: ../../include/tok822.h +cleanup_state.o: ../../include/vbuf.h +cleanup_state.o: ../../include/vstream.h +cleanup_state.o: ../../include/vstring.h +cleanup_state.o: cleanup.h +cleanup_state.o: cleanup_state.c diff --git a/src/cleanup/bug1.file b/src/cleanup/bug1.file new file mode 100644 index 0000000..8412ae3 Binary files /dev/null and b/src/cleanup/bug1.file differ diff --git a/src/cleanup/bug1.file.ref b/src/cleanup/bug1.file.ref new file mode 100755 index 0000000..229d26d Binary files /dev/null and b/src/cleanup/bug1.file.ref differ diff --git a/src/cleanup/bug1.in b/src/cleanup/bug1.in new file mode 100644 index 0000000..bda18cf --- /dev/null +++ b/src/cleanup/bug1.in @@ -0,0 +1,41 @@ +#verbose on +open bug1.file.tmp + +# Symptom: +# +# infinite loop in postcat and in delivery agents +# +# Cause: +# +# Failure to update location info after following a pointer record, +# while updating a message header record +# +# Analysis: +# +# This happens with repeated updates of the same message header. +# After the first update, the update #1 header record sits in the +# heap at the end of the queue file, and is followed by a reverse +# pointer to the start of the next message header record or the +# message body, somewhere in the middle of the queue file. +# +# The problem started with update #2 of that same message header. +# While following the reverse pointer record after the update #1 +# header record to find out the start of the next header or message +# body, the header updating routine did not update its notion of +# where it was. Thus, it believed that the next header or body record +# was located after the reverse pointer record. That was not the +# middle of the message, but the end of the queue file. The second +# update would result in an update #2 header record, followed by a +# reverse pointer to what used to be the end of the queue file, but +# had meanwhile become the location of the update #2 header record. +# +# Thus, anything that tried to deliver mail would loop on the update +# #2 header record. After update update #3 of the same header, the +# delivery agent would loop on the update #3 record, etc. + +upd_header 1 Subject long header text +upd_header 1 Subject long header text +upd_header 1 Subject long header text +upd_header 1 Subject long header text + +close diff --git a/src/cleanup/bug1.ref b/src/cleanup/bug1.ref new file mode 100644 index 0000000..362d2cb --- /dev/null +++ b/src/cleanup/bug1.ref @@ -0,0 +1,56 @@ +*** ENVELOPE RECORDS bug1.file.tmp *** + 0 message_size: 441 813 3 0 441 + 81 message_arrival_time: Sat Jan 20 19:52:41 2007 + 100 create_time: Sat Jan 20 19:52:47 2007 + 124 named_attribute: rewrite_context=local + 147 sender: wietse@porcupine.org + 169 named_attribute: log_client_name=hades.porcupine.org + 206 named_attribute: log_client_address=168.100.189.10 + 241 named_attribute: log_message_origin=hades.porcupine.org[168.100.189.10] + 297 named_attribute: log_helo_name=hades.porcupine.org + 332 named_attribute: log_protocol_name=SMTP + 356 named_attribute: client_name=hades.porcupine.org + 389 named_attribute: reverse_client_name=hades.porcupine.org + 430 named_attribute: client_address=168.100.189.10 + 461 named_attribute: helo_name=hades.porcupine.org + 492 named_attribute: client_address_type=2 + 515 named_attribute: dsn_orig_rcpt=rfc822;wietse@porcupine.org + 558 original_recipient: wietse@porcupine.org + 580 recipient: wietse@porcupine.org + 602 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org + 650 original_recipient: alias@hades.porcupine.org + 677 recipient: wietse@porcupine.org + 699 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org + 747 original_recipient: alias@hades.porcupine.org + 774 recipient: root@porcupine.org + 794 pointer_record: 0 + 811 *** MESSAGE CONTENTS bug1.file.tmp *** + 813 regular_text: Received: from hades.porcupine.org (hades.porcupine.org [168.100.189.10]) + 888 regular_text: by hades.porcupine.org (Postfix) with SMTP id 38132290405; + 949 regular_text: Sat, 20 Jan 2007 19:52:41 -0500 (EST) + 989 regular_text: X: 1 + 995 padding: 0 + 1006 regular_text: 2 + 1010 regular_text: 3 + 1014 regular_text: 4 + 1018 regular_text: 5 + 1022 regular_text: 6 + 1026 regular_text: 7 + 1030 regular_text: Y: 1234567 + 1042 padding: 0 + 1047 regular_text: Message-Id: <20070121005247.38132290405@hades.porcupine.org> + 1109 regular_text: Date: Sat, 20 Jan 2007 19:52:41 -0500 (EST) + 1154 regular_text: From: wietse@porcupine.org + 1182 regular_text: To: undisclosed-recipients:; + 1212 pointer_record: 1258 + 1258 pointer_record: 1302 + 1302 pointer_record: 1346 + 1346 pointer_record: 1390 + 1390 regular_text: Subject: long header text + 1417 pointer_record: 1285 + 1285 pointer_record: 1229 + 1229 regular_text: + 1231 regular_text: text + 1237 pointer_record: 0 + 1254 *** HEADER EXTRACTED bug1.file.tmp *** + 1256 *** MESSAGE FILE END bug1.file.tmp *** diff --git a/src/cleanup/bug1.text.ref b/src/cleanup/bug1.text.ref new file mode 100644 index 0000000..72fe3df --- /dev/null +++ b/src/cleanup/bug1.text.ref @@ -0,0 +1,46 @@ +*** ENVELOPE RECORDS bug1.file.tmp *** +message_size: 441 813 3 0 441 +message_arrival_time: Sat Jan 20 19:52:41 2007 +create_time: Sat Jan 20 19:52:47 2007 +named_attribute: rewrite_context=local +sender: wietse@porcupine.org +named_attribute: log_client_name=hades.porcupine.org +named_attribute: log_client_address=168.100.189.10 +named_attribute: log_message_origin=hades.porcupine.org[168.100.189.10] +named_attribute: log_helo_name=hades.porcupine.org +named_attribute: log_protocol_name=SMTP +named_attribute: client_name=hades.porcupine.org +named_attribute: reverse_client_name=hades.porcupine.org +named_attribute: client_address=168.100.189.10 +named_attribute: helo_name=hades.porcupine.org +named_attribute: client_address_type=2 +named_attribute: dsn_orig_rcpt=rfc822;wietse@porcupine.org +original_recipient: wietse@porcupine.org +recipient: wietse@porcupine.org +named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org +original_recipient: alias@hades.porcupine.org +recipient: wietse@porcupine.org +named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org +original_recipient: alias@hades.porcupine.org +recipient: root@porcupine.org +*** MESSAGE CONTENTS bug1.file.tmp *** +Received: from hades.porcupine.org (hades.porcupine.org [168.100.189.10]) + by hades.porcupine.org (Postfix) with SMTP id 38132290405; + Sat, 20 Jan 2007 19:52:41 -0500 (EST) +X: 1 + 2 + 3 + 4 + 5 + 6 + 7 +Y: 1234567 +Message-Id: <20070121005247.38132290405@hades.porcupine.org> +Date: Sat, 20 Jan 2007 19:52:41 -0500 (EST) +From: wietse@porcupine.org +To: undisclosed-recipients:; +Subject: long header text + +text +*** HEADER EXTRACTED bug1.file.tmp *** +*** MESSAGE FILE END bug1.file.tmp *** diff --git a/src/cleanup/bug2.file b/src/cleanup/bug2.file new file mode 100644 index 0000000..27a9ec7 Binary files /dev/null and b/src/cleanup/bug2.file differ diff --git a/src/cleanup/bug2.in b/src/cleanup/bug2.in new file mode 100644 index 0000000..138ca3d --- /dev/null +++ b/src/cleanup/bug2.in @@ -0,0 +1,37 @@ +#verbose on +open bug2.file.tmp + +# Two bugs while updating a short Subject: header immediately before +# a still virgin "append header" pointer record. +# +# Symptom: +# +# warning: : malformed pointer record value: +# +# Cause: +# +# Failure to recognize the "append header" record while updating +# a short message header +# +# Analysis: +# +# This happened while updating a header record that was followed by +# the current "append header" record. The pointer could be the initial +# "append header" record between message header and body, or it could +# be a later version of that pointer somewhere in the heap. +# +# - Postfix considered the pointer record as any pointer record after +# a header record. Thus, it decided that some portion of the pointer +# record could be overwritten with the location of the new Subject: +# header on the heap. Later "append header" operations would then +# update old "append header" record and thus clobber part of the +# pointer to the new Subject: header value. +# +# - While saving the "append header" pointer record value on the +# heap, Postfix did not replace the still virgin "0" append header" +# pointer record value by the actual location of the message body +# content. + +upd_header 1 Subject hey! +add_header foo foobar +close diff --git a/src/cleanup/bug2.ref b/src/cleanup/bug2.ref new file mode 100644 index 0000000..6a0aab4 --- /dev/null +++ b/src/cleanup/bug2.ref @@ -0,0 +1,30 @@ +*** ENVELOPE RECORDS bug2.file.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS bug2.file.tmp *** + 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 regular_text: From: me@porcupine.org + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 pointer_record: 573 + 573 regular_text: Subject: hey! + 588 padding: 0 + 591 pointer_record: 489 + 489 pointer_record: 608 + 608 regular_text: foo: foobar + 621 padding: 0 + 625 pointer_record: 506 + 506 regular_text: + 508 regular_text: text + 514 pointer_record: 0 + 531 *** HEADER EXTRACTED bug2.file.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END bug2.file.tmp *** diff --git a/src/cleanup/bug2.text.ref b/src/cleanup/bug2.text.ref new file mode 100644 index 0000000..fd5cfe1 --- /dev/null +++ b/src/cleanup/bug2.text.ref @@ -0,0 +1,22 @@ +*** ENVELOPE RECORDS bug2.file.tmp *** +message_size: 332 199 1 0 332 +message_arrival_time: Sat Jan 20 20:53:54 2007 +create_time: Sat Jan 20 20:53:59 2007 +named_attribute: rewrite_context=local +sender_fullname: Wietse Venema +sender: me@porcupine.org +*** MESSAGE CONTENTS bug2.file.tmp *** +Received: by hades.porcupine.org (Postfix, from userid 1001) + id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) +From: me@porcupine.org +To: you@porcupine.org +Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> +Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) +Subject: hey! +foo: foobar + +text +*** HEADER EXTRACTED bug2.file.tmp *** +original_recipient: you@porcupine.org +recipient: you@porcupine.org +*** MESSAGE FILE END bug2.file.tmp *** diff --git a/src/cleanup/bug3.file b/src/cleanup/bug3.file new file mode 100644 index 0000000..fd9236c Binary files /dev/null and b/src/cleanup/bug3.file differ diff --git a/src/cleanup/bug3.in b/src/cleanup/bug3.in new file mode 100644 index 0000000..1920eaf --- /dev/null +++ b/src/cleanup/bug3.in @@ -0,0 +1,29 @@ +#verbose on +open bug3.file.tmp + +# This was a problem with a length check in the wrong place, causing +# a short header name to match a longer one. After successful +# substring match, the "change header" code checked the length of +# the header name that was found, instead of the header name that +# was wanted. + +#add_header X-SpamTest-Envelope-From wietse@porcupine.org +#upd_header 1 X-SpamTest-Envelope-From wietse@porcupine.org +#add_header X-SpamTest-Group-ID 00000000 +#upd_header 1 X-SpamTest-Group-ID 00000000 +#add_header X-SpamTest-Info Profiles 29362 [Feb 02 2012] +#upd_header 1 X-SpamTest-Info Profiles 29362 [Feb 02 2012] +#add_header X-SpamTest-Method none +#upd_header 1 X-SpamTest-Method none +#add_header X-SpamTest-Rate 0 +#upd_header 1 X-SpamTest-Rate 0 +#add_header X-SpamTest-SPF none +#upd_header 1 X-SpamTest-SPF none +add_header X-SpamTest-Status Not detected +#upd_header 1 X-SpamTest-Status Not detected +add_header X-SpamTest-Status-Extended not_detected +upd_header 1 X-SpamTest-Status-Extended not_detected +#add_header X-SpamTest-Version SMTP-Filter Version 3.0.0 [0284], KAS30/Release +#upd_header 1 X-SpamTest-Version SMTP-Filter Version 3.0.0 [0284], KAS30/Release + +close diff --git a/src/cleanup/bug3.ref b/src/cleanup/bug3.ref new file mode 100644 index 0000000..da4d162 --- /dev/null +++ b/src/cleanup/bug3.ref @@ -0,0 +1,29 @@ +*** ENVELOPE RECORDS bug3.file.tmp *** + 0 message_size: 307 237 1 0 307 + 81 message_arrival_time: Thu Feb 2 09:02:07 2012 + 100 create_time: Thu Feb 2 09:02:07 2012 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 original_recipient: you@porcupine.org + 199 recipient: you@porcupine.org + 218 pointer_record: 0 + 235 *** MESSAGE CONTENTS bug3.file.tmp *** + 237 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 299 regular_text: id 9210192461E; Thu, 2 Feb 2012 09:02:07 -0500 (EST) + 355 regular_text: Message-Id: <20120202140207.9210192461E@hades.porcupine.org> + 417 regular_text: Date: Thu, 2 Feb 2012 09:02:07 -0500 (EST) + 462 regular_text: From: me@porcupine.org (Wietse Venema) + 502 pointer_record: 565 + 565 regular_text: X-SpamTest-Status: Not detected + 598 pointer_record: 615 + 615 pointer_record: 674 + 674 regular_text: X-SpamTest-Status-Extended: not_detected + 716 pointer_record: 657 + 657 pointer_record: 519 + 519 regular_text: + 521 regular_text: test + 527 pointer_record: 0 + 544 *** HEADER EXTRACTED bug3.file.tmp *** + 546 pointer_record: 0 + 563 *** MESSAGE FILE END bug3.file.tmp *** diff --git a/src/cleanup/bug3.text.ref b/src/cleanup/bug3.text.ref new file mode 100644 index 0000000..9e672b8 --- /dev/null +++ b/src/cleanup/bug3.text.ref @@ -0,0 +1,21 @@ +*** ENVELOPE RECORDS bug3.file.tmp *** +message_size: 307 237 1 0 307 +message_arrival_time: Thu Feb 2 09:02:07 2012 +create_time: Thu Feb 2 09:02:07 2012 +named_attribute: rewrite_context=local +sender_fullname: Wietse Venema +sender: me@porcupine.org +original_recipient: you@porcupine.org +recipient: you@porcupine.org +*** MESSAGE CONTENTS bug3.file.tmp *** +Received: by hades.porcupine.org (Postfix, from userid 1001) + id 9210192461E; Thu, 2 Feb 2012 09:02:07 -0500 (EST) +Message-Id: <20120202140207.9210192461E@hades.porcupine.org> +Date: Thu, 2 Feb 2012 09:02:07 -0500 (EST) +From: me@porcupine.org (Wietse Venema) +X-SpamTest-Status: Not detected +X-SpamTest-Status-Extended: not_detected + +test +*** HEADER EXTRACTED bug3.file.tmp *** +*** MESSAGE FILE END bug3.file.tmp *** diff --git a/src/cleanup/cleanup.c b/src/cleanup/cleanup.c new file mode 100644 index 0000000..6fe61f8 --- /dev/null +++ b/src/cleanup/cleanup.c @@ -0,0 +1,656 @@ +/*++ +/* NAME +/* cleanup 8 +/* SUMMARY +/* canonicalize and enqueue Postfix message +/* SYNOPSIS +/* \fBcleanup\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBcleanup\fR(8) daemon processes inbound mail, inserts it +/* into the \fBincoming\fR mail queue, and informs the queue +/* manager of its arrival. +/* +/* The \fBcleanup\fR(8) daemon performs the following transformations: +/* .IP \(bu +/* Insert missing message headers: (\fBResent-\fR) \fBFrom:\fR, +/* \fBTo:\fR, \fBMessage-Id:\fR, and \fBDate:\fR. +/* .br +/* This is enabled with the \fBlocal_header_rewrite_clients\fR and +/* \fBalways_add_missing_headers\fR parameter settings. +/* .IP \(bu +/* Transform envelope and header addresses to the standard +/* \fIuser@fully-qualified-domain\fR form that is expected by other +/* Postfix programs. +/* This task depends on the \fBtrivial-rewrite\fR(8) daemon. +/* .br +/* The header transformation is enabled with the +/* \fBlocal_header_rewrite_clients\fR parameter setting. +/* .IP \(bu +/* Eliminate duplicate envelope recipient addresses. +/* .br +/* This is enabled with the \fBduplicate_filter_limit\fR +/* parameter setting. +/* .IP \(bu +/* Remove message headers: \fBBcc\fR, \fBContent-Length\fR, +/* \fBResent-Bcc\fR, \fBReturn-Path\fR. +/* .br +/* This is enabled with the message_drop_headers parameter +/* setting. +/* .IP \(bu +/* Optionally, rewrite all envelope and header addresses according +/* to the mappings specified in the \fBcanonical\fR(5) lookup tables. +/* .br +/* The header transformation is enabled with the +/* \fBlocal_header_rewrite_clients\fR parameter setting. +/* .IP \(bu +/* Optionally, masquerade envelope sender addresses and message +/* header addresses (i.e. strip host or domain information below +/* all domains listed in the \fBmasquerade_domains\fR parameter, +/* except for user names listed in \fBmasquerade_exceptions\fR). +/* By default, address masquerading does not affect envelope recipients. +/* .br +/* The header transformation is enabled with the +/* \fBlocal_header_rewrite_clients\fR parameter setting. +/* .IP \(bu +/* Optionally, expand envelope recipients according to information +/* found in the \fBvirtual_alias_maps\fR lookup tables. +/* .PP +/* The \fBcleanup\fR(8) daemon performs sanity checks on the content of +/* each message. When it finds a problem, by default it returns a +/* diagnostic status to the cleanup service client, and leaves +/* it up to the client +/* to deal with the problem. Alternatively, the client can request +/* the \fBcleanup\fR(8) daemon to bounce the message back to the sender +/* in case of trouble. +/* STANDARDS +/* RFC 822 (ARPA Internet Text Messages) +/* RFC 2045 (MIME: Format of Internet Message Bodies) +/* RFC 2046 (MIME: Media Types) +/* RFC 2822 (Internet Message Format) +/* RFC 3463 (Enhanced Status Codes) +/* RFC 3464 (Delivery status notifications) +/* RFC 5322 (Internet Message Format) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* Table-driven rewriting rules make it hard to express \fBif then +/* else\fR and other logical relationships. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically, as +/* \fBcleanup\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* .IP "\fBundisclosed_recipients_header (see 'postconf -d' output)\fR" +/* Message header that the Postfix \fBcleanup\fR(8) server inserts when a +/* message contains no To: or Cc: message header. +/* .PP +/* Available in Postfix version 2.1 only: +/* .IP "\fBenable_errors_to (no)\fR" +/* Report mail delivery errors to the address specified with the +/* non-standard Errors-To: message header, instead of the envelope +/* sender address (this feature is removed with Postfix version 2.2, is +/* turned off by default with Postfix version 2.1, and is always turned on +/* with older Postfix versions). +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBalways_add_missing_headers (no)\fR" +/* Always add (Resent-) From:, To:, Date: or Message-ID: headers +/* when not present. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBenable_long_queue_ids (no)\fR" +/* Enable long, non-repeating, queue IDs (queue file names). +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBmessage_drop_headers (bcc, content-length, resent-bcc, return-path)\fR" +/* Names of message headers that the \fBcleanup\fR(8) daemon will remove +/* after applying \fBheader_checks\fR(5) and before invoking Milter applications. +/* .IP "\fBheader_from_format (standard)\fR" +/* The format of the Postfix-generated \fBFrom:\fR header. +/* BUILT-IN CONTENT FILTERING CONTROLS +/* .ad +/* .fi +/* Postfix built-in content filtering is meant to stop a flood of +/* worms or viruses. It is not a general content filter. +/* .IP "\fBbody_checks (empty)\fR" +/* Optional lookup tables for content inspection as specified in +/* the \fBbody_checks\fR(5) manual page. +/* .IP "\fBheader_checks (empty)\fR" +/* Optional lookup tables for content inspection of primary non-MIME +/* message headers, as specified in the \fBheader_checks\fR(5) manual page. +/* .PP +/* Available in Postfix version 2.0 and later: +/* .IP "\fBbody_checks_size_limit (51200)\fR" +/* How much text in a message body segment (or attachment, if you +/* prefer to use that term) is subjected to body_checks inspection. +/* .IP "\fBmime_header_checks ($header_checks)\fR" +/* Optional lookup tables for content inspection of MIME related +/* message headers, as described in the \fBheader_checks\fR(5) manual page. +/* .IP "\fBnested_header_checks ($header_checks)\fR" +/* Optional lookup tables for content inspection of non-MIME message +/* headers in attached messages, as described in the \fBheader_checks\fR(5) +/* manual page. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBmessage_reject_characters (empty)\fR" +/* The set of characters that Postfix will reject in message +/* content. +/* .IP "\fBmessage_strip_characters (empty)\fR" +/* The set of characters that Postfix will remove from message +/* content. +/* .PP +/* Available in Postfix version 3.9, 3.8.5, 3.7.10, 3.6.14, +/* 3.5.24, and later: +/* .IP "\fBcleanup_replace_stray_cr_lf (yes)\fR" +/* Replace each stray or character in message +/* content with a space character, to prevent outbound SMTP smuggling, +/* and to make the evaluation of Postfix-added DKIM or other signatures +/* independent from how a remote mail server handles such characters. +/* BEFORE QUEUE MILTER CONTROLS +/* .ad +/* .fi +/* As of version 2.3, Postfix supports the Sendmail version 8 +/* Milter (mail filter) protocol. When mail is not received via +/* the smtpd(8) server, the cleanup(8) server will simulate +/* SMTP events to the extent that this is possible. For details +/* see the MILTER_README document. +/* .IP "\fBnon_smtpd_milters (empty)\fR" +/* A list of Milter (mail filter) applications for new mail that +/* does not arrive via the Postfix \fBsmtpd\fR(8) server. +/* .IP "\fBmilter_protocol (6)\fR" +/* The mail filter protocol version and optional protocol extensions +/* for communication with a Milter application; prior to Postfix 2.6 +/* the default protocol is 2. +/* .IP "\fBmilter_default_action (tempfail)\fR" +/* The default action when a Milter (mail filter) response is +/* unavailable (for example, bad Postfix configuration or Milter +/* failure). +/* .IP "\fBmilter_macro_daemon_name ($myhostname)\fR" +/* The {daemon_name} macro value for Milter (mail filter) applications. +/* .IP "\fBmilter_macro_v ($mail_name $mail_version)\fR" +/* The {v} macro value for Milter (mail filter) applications. +/* .IP "\fBmilter_connect_timeout (30s)\fR" +/* The time limit for connecting to a Milter (mail filter) +/* application, and for negotiating protocol options. +/* .IP "\fBmilter_command_timeout (30s)\fR" +/* The time limit for sending an SMTP command to a Milter (mail +/* filter) application, and for receiving the response. +/* .IP "\fBmilter_content_timeout (300s)\fR" +/* The time limit for sending message content to a Milter (mail +/* filter) application, and for receiving the response. +/* .IP "\fBmilter_connect_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after completion of an SMTP connection. +/* .IP "\fBmilter_helo_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP HELO or EHLO command. +/* .IP "\fBmilter_mail_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP MAIL FROM command. +/* .IP "\fBmilter_rcpt_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP RCPT TO command. +/* .IP "\fBmilter_data_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to version 4 or higher Milter (mail +/* filter) applications after the SMTP DATA command. +/* .IP "\fBmilter_unknown_command_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to version 3 or higher Milter (mail +/* filter) applications after an unknown SMTP command. +/* .IP "\fBmilter_end_of_data_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the message end-of-data. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBmilter_end_of_header_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the end of the message header. +/* .PP +/* Available in Postfix version 2.7 and later: +/* .IP "\fBmilter_header_checks (empty)\fR" +/* Optional lookup tables for content inspection of message headers +/* that are produced by Milter applications. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBmilter_macro_defaults (empty)\fR" +/* Optional list of \fIname=value\fR pairs that specify default +/* values for arbitrary macros that Postfix may send to Milter +/* applications. +/* MIME PROCESSING CONTROLS +/* .ad +/* .fi +/* Available in Postfix version 2.0 and later: +/* .IP "\fBdisable_mime_input_processing (no)\fR" +/* Turn off MIME processing while receiving mail. +/* .IP "\fBmime_boundary_length_limit (2048)\fR" +/* The maximal length of MIME multipart boundary strings. +/* .IP "\fBmime_nesting_limit (100)\fR" +/* The maximal recursion level that the MIME processor will handle. +/* .IP "\fBstrict_8bitmime (no)\fR" +/* Enable both strict_7bit_headers and strict_8bitmime_body. +/* .IP "\fBstrict_7bit_headers (no)\fR" +/* Reject mail with 8-bit text in message headers. +/* .IP "\fBstrict_8bitmime_body (no)\fR" +/* Reject 8-bit message body text without 8-bit MIME content encoding +/* information. +/* .IP "\fBstrict_mime_encoding_domain (no)\fR" +/* Reject mail with invalid Content-Transfer-Encoding: information +/* for the message/* or multipart/* MIME content types. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBdetect_8bit_encoding_header (yes)\fR" +/* Automatically detect 8BITMIME body content by looking at +/* Content-Transfer-Encoding: message headers; historically, this +/* behavior was hard-coded to be "always on". +/* AUTOMATIC BCC RECIPIENT CONTROLS +/* .ad +/* .fi +/* Postfix can automatically add BCC (blind carbon copy) +/* when mail enters the mail system: +/* .IP "\fBalways_bcc (empty)\fR" +/* Optional address that receives a "blind carbon copy" of each message +/* that is received by the Postfix mail system. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsender_bcc_maps (empty)\fR" +/* Optional BCC (blind carbon-copy) address lookup tables, indexed +/* by sender address. +/* .IP "\fBrecipient_bcc_maps (empty)\fR" +/* Optional BCC (blind carbon-copy) address lookup tables, indexed by +/* recipient address. +/* ADDRESS TRANSFORMATION CONTROLS +/* .ad +/* .fi +/* Address rewriting is delegated to the \fBtrivial-rewrite\fR(8) daemon. +/* The \fBcleanup\fR(8) server implements table driven address mapping. +/* .IP "\fBempty_address_recipient (MAILER-DAEMON)\fR" +/* The recipient of mail addressed to the null address. +/* .IP "\fBcanonical_maps (empty)\fR" +/* Optional address mapping lookup tables for message headers and +/* envelopes. +/* .IP "\fBrecipient_canonical_maps (empty)\fR" +/* Optional address mapping lookup tables for envelope and header +/* recipient addresses. +/* .IP "\fBsender_canonical_maps (empty)\fR" +/* Optional address mapping lookup tables for envelope and header +/* sender addresses. +/* .IP "\fBmasquerade_classes (envelope_sender, header_sender, header_recipient)\fR" +/* What addresses are subject to address masquerading. +/* .IP "\fBmasquerade_domains (empty)\fR" +/* Optional list of domains whose subdomain structure will be stripped +/* off in email addresses. +/* .IP "\fBmasquerade_exceptions (empty)\fR" +/* Optional list of user names that are not subjected to address +/* masquerading, even when their addresses match $masquerade_domains. +/* .IP "\fBpropagate_unmatched_extensions (canonical, virtual)\fR" +/* What address lookup tables copy an address extension from the lookup +/* key to the lookup result. +/* .PP +/* Available before Postfix version 2.0: +/* .IP "\fBvirtual_maps (empty)\fR" +/* Optional lookup tables with a) names of domains for which all +/* addresses are aliased to addresses in other local or remote domains, +/* and b) addresses that are aliased to addresses in other local or +/* remote domains. +/* .PP +/* Available in Postfix version 2.0 and later: +/* .IP "\fBvirtual_alias_maps ($virtual_maps)\fR" +/* Optional lookup tables that alias specific mail addresses or domains +/* to other local or remote address. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBcanonical_classes (envelope_sender, envelope_recipient, header_sender, header_recipient)\fR" +/* What addresses are subject to canonical_maps address mapping. +/* .IP "\fBrecipient_canonical_classes (envelope_recipient, header_recipient)\fR" +/* What addresses are subject to recipient_canonical_maps address +/* mapping. +/* .IP "\fBsender_canonical_classes (envelope_sender, header_sender)\fR" +/* What addresses are subject to sender_canonical_maps address +/* mapping. +/* .IP "\fBremote_header_rewrite_domain (empty)\fR" +/* Don't rewrite message headers from remote clients at all when +/* this parameter is empty; otherwise, rewrite message headers and +/* append the specified domain name to incomplete addresses. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBduplicate_filter_limit (1000)\fR" +/* The maximal number of addresses remembered by the address +/* duplicate filter for \fBaliases\fR(5) or \fBvirtual\fR(5) alias expansion, or +/* for \fBshowq\fR(8) queue displays. +/* .IP "\fBheader_size_limit (102400)\fR" +/* The maximal amount of memory in bytes for storing a message header. +/* .IP "\fBhopcount_limit (50)\fR" +/* The maximal number of Received: message headers that is allowed +/* in the primary message headers. +/* .IP "\fBin_flow_delay (1s)\fR" +/* Time to pause before accepting a new message, when the message +/* arrival rate exceeds the message delivery rate. +/* .IP "\fBmessage_size_limit (10240000)\fR" +/* The maximal size in bytes of a message, including envelope information. +/* .PP +/* Available in Postfix version 2.0 and later: +/* .IP "\fBheader_address_token_limit (10240)\fR" +/* The maximal number of address tokens are allowed in an address +/* message header. +/* .IP "\fBmime_boundary_length_limit (2048)\fR" +/* The maximal length of MIME multipart boundary strings. +/* .IP "\fBmime_nesting_limit (100)\fR" +/* The maximal recursion level that the MIME processor will handle. +/* .IP "\fBqueue_file_attribute_count_limit (100)\fR" +/* The maximal number of (name=value) attributes that may be stored +/* in a Postfix queue file. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBvirtual_alias_expansion_limit (1000)\fR" +/* The maximal number of addresses that virtual alias expansion produces +/* from each original recipient. +/* .IP "\fBvirtual_alias_recursion_limit (1000)\fR" +/* The maximal nesting depth of virtual alias expansion. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBvirtual_alias_address_length_limit (1000)\fR" +/* The maximal length of an email address after virtual alias expansion. +/* SMTPUTF8 CONTROLS +/* .ad +/* .fi +/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable preliminary SMTPUTF8 support for the protocols described +/* in RFC 6531..6533. +/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR" +/* Detect that a message requires SMTPUTF8 support for the specified +/* mail origin classes. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBenable_idna2003_compatibility (no)\fR" +/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008, +/* when converting UTF-8 domain names to/from the ASCII form that is +/* used for DNS lookups. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBdelay_warning_time (0h)\fR" +/* The time after which the sender receives a copy of the message +/* headers of mail that is still queued. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBmyhostname (see 'postconf -d' output)\fR" +/* The internet hostname of this mail system. +/* .IP "\fBmyorigin ($myhostname)\fR" +/* The domain name that locally-posted mail appears to come +/* from, and that locally posted mail is delivered to. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsoft_bounce (no)\fR" +/* Safety net to keep mail queued that would otherwise be returned to +/* the sender. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBenable_original_recipient (yes)\fR" +/* Enable support for the original recipient address after an +/* address is rewritten to a different address (for example with +/* aliasing or with canonical mapping). +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.5 and later: +/* .IP "\fBinfo_log_address_format (external)\fR" +/* The email address form that will be used in non-debug logging +/* (info, warning, etc.). +/* FILES +/* /etc/postfix/canonical*, canonical mapping table +/* /etc/postfix/virtual*, virtual mapping table +/* SEE ALSO +/* trivial-rewrite(8), address rewriting +/* qmgr(8), queue manager +/* header_checks(5), message header content inspection +/* body_checks(5), body parts content inspection +/* canonical(5), canonical address lookup table format +/* virtual(5), virtual alias lookup table format +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* ADDRESS_REWRITING_README Postfix address manipulation +/* CONTENT_INSPECTION_README content inspection +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Single-threaded server skeleton. */ + +#include + +/* Application-specific. */ + +#include "cleanup.h" + +/* cleanup_service - process one request to inject a message into the queue */ + +static void cleanup_service(VSTREAM *src, char *unused_service, char **argv) +{ + VSTRING *buf = vstring_alloc(100); + CLEANUP_STATE *state; + int flags; + int type = 0; + int status; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Open a queue file and initialize state. + */ + state = cleanup_open(src); + + /* + * Send the queue id to the client. Read client processing options. If we + * can't read the client processing options we can pretty much forget + * about the whole operation. + */ + attr_print(src, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP), + SEND_ATTR_STR(MAIL_ATTR_QUEUEID, state->queue_id), + ATTR_TYPE_END); + if (attr_scan(src, ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags), + ATTR_TYPE_END) != 1) { + state->errs |= CLEANUP_STAT_BAD; + flags = 0; + } + cleanup_control(state, flags); + + /* + * XXX Rely on the front-end programs to enforce record size limits. + * + * First, copy the envelope records to the queue file. Then, copy the + * message content (headers and body). Finally, attach any information + * extracted from message headers. + */ + while (CLEANUP_OUT_OK(state)) { + if ((type = rec_get_raw(src, buf, 0, REC_FLAG_NONE)) < 0) { + state->errs |= CLEANUP_STAT_BAD; + break; + } + if (REC_GET_HIDDEN_TYPE(type)) { + msg_warn("%s: record type %d not allowed - discarding this message", + state->queue_id, type); + state->errs |= CLEANUP_STAT_BAD; + break; + } + CLEANUP_RECORD(state, type, vstring_str(buf), VSTRING_LEN(buf)); + if (type == REC_TYPE_END) + break; + } + + /* + * Keep reading in case of problems, until the sender is ready to receive + * our status report. + */ + if (CLEANUP_OUT_OK(state) == 0 && type > 0) { + while (type != REC_TYPE_END + && (type = rec_get_raw(src, buf, 0, REC_FLAG_NONE)) > 0) { + if (type == REC_TYPE_MILT_COUNT) { + int milter_count = atoi(vstring_str(buf)); + + /* Avoid deadlock. */ + if (milter_count >= 0) + cleanup_milter_receive(state, milter_count); + } + } + } + + /* + * Log something to make timeout errors easier to debug. + */ + if (vstream_ftimeout(src)) + msg_warn("%s: read timeout on %s", + state->queue_id, VSTREAM_PATH(src)); + + /* + * Finish this message, and report the result status to the client. + */ + status = cleanup_flush(state); /* in case state is modified */ + attr_print(src, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + SEND_ATTR_STR(MAIL_ATTR_WHY, + (state->flags & CLEANUP_FLAG_SMTP_REPLY) + && state->smtp_reply ? state->smtp_reply : + state->reason ? state->reason : ""), + ATTR_TYPE_END); + cleanup_free(state); + + /* + * Cleanup. + */ + vstring_free(buf); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Clean up an incomplete queue file in case of a fatal run-time error, + * or after receiving SIGTERM from the master at shutdown time. + */ + signal(SIGTERM, cleanup_sig); + msg_cleanup(cleanup_all); + + /* + * Pass control to the single-threaded service skeleton. + */ + single_server_main(argc, argv, cleanup_service, + CA_MAIL_SERVER_INT_TABLE(cleanup_int_table), + CA_MAIL_SERVER_BOOL_TABLE(cleanup_bool_table), + CA_MAIL_SERVER_STR_TABLE(cleanup_str_table), + CA_MAIL_SERVER_TIME_TABLE(cleanup_time_table), + CA_MAIL_SERVER_PRE_INIT(cleanup_pre_jail), + CA_MAIL_SERVER_POST_INIT(cleanup_post_jail), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_IN_FLOW_DELAY, + CA_MAIL_SERVER_UNLIMITED, + 0); +} diff --git a/src/cleanup/cleanup.h b/src/cleanup/cleanup.h new file mode 100644 index 0000000..baecaad --- /dev/null +++ b/src/cleanup/cleanup.h @@ -0,0 +1,370 @@ +/*++ +/* NAME +/* cleanup 3h +/* SUMMARY +/* canonicalize and enqueue message +/* SYNOPSIS +/* #include "cleanup.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * Milter library. + */ +#include + + /* + * These state variables are accessed by many functions, and there is only + * one instance of each per message. + */ +typedef struct CLEANUP_STATE { + VSTRING *attr_buf; /* storage for named attribute */ + VSTRING *temp1; /* scratch buffer, local use only */ + VSTRING *temp2; /* scratch buffer, local use only */ + VSTRING *stripped_buf; /* character stripped input */ + VSTREAM *src; /* current input stream */ + VSTREAM *dst; /* current output stream */ + MAIL_STREAM *handle; /* mail stream handle */ + char *queue_name; /* queue name */ + char *queue_id; /* queue file basename */ + struct timeval arrival_time; /* arrival time */ + char *fullname; /* envelope sender full name */ + char *sender; /* envelope sender address */ + char *recip; /* envelope recipient address */ + char *orig_rcpt; /* original recipient address */ + char *return_receipt; /* return-receipt address */ + char *errors_to; /* errors-to address */ + ARGV *auto_hdrs; /* MTA's own header(s) */ + ARGV *hbc_rcpt; /* header/body checks BCC addresses */ + int flags; /* processing options, status flags */ + int tflags; /* User- or MTA-requested tracing */ + int qmgr_opts; /* qmgr processing options */ + int errs; /* any badness experienced */ + int err_mask; /* allowed badness */ + int headers_seen; /* which headers were seen */ + int hop_count; /* count of received: headers */ + char *resent; /* any resent- header seen */ + BH_TABLE *dups; /* recipient dup filter */ + void (*action) (struct CLEANUP_STATE *, int, const char *, ssize_t); + off_t data_offset; /* start of message content */ + off_t body_offset; /* start of body content */ + off_t xtra_offset; /* start of extra segment */ + off_t cont_length; /* length including Milter edits */ + off_t sender_pt_offset; /* replace sender here */ + off_t sender_pt_target; /* record after sender address */ + off_t append_rcpt_pt_offset; /* append recipient here */ + off_t append_rcpt_pt_target; /* target of above record */ + off_t append_hdr_pt_offset; /* append header here */ + off_t append_hdr_pt_target; /* target of above record */ + off_t append_meta_pt_offset; /* append meta record here */ + off_t append_meta_pt_target; /* target of above record */ + ssize_t rcpt_count; /* recipient count */ + char *reason; /* failure reason */ + char *smtp_reply; /* failure reason, SMTP-style */ + NVTABLE *attr; /* queue file attribute list */ + MIME_STATE *mime_state; /* MIME state engine */ + int mime_errs; /* MIME error flags */ + char *hdr_rewrite_context; /* header rewrite context */ + char *filter; /* from header/body patterns */ + char *redirect; /* from header/body patterns */ + char *dsn_envid; /* DSN envelope ID */ + int dsn_ret; /* DSN full/hdrs */ + int dsn_notify; /* DSN never/delay/fail/success */ + char *dsn_orcpt; /* DSN original recipient */ + char *verp_delims; /* VERP delimiters (optional) */ +#ifdef DELAY_ACTION + int defer_delay; /* deferred delivery */ +#endif + + /* + * Miscellaneous Milter support. + */ + MILTERS *milters; /* mail filters */ + const char *client_name; /* real or ersatz client */ + const char *reverse_name; /* real or ersatz client */ + const char *client_addr; /* real or ersatz client */ + int client_af; /* real or ersatz client */ + const char *client_port; /* real or ersatz client */ + const char *server_addr; /* real or ersatz server */ + const char *server_port; /* real or ersatz server */ + VSTRING *milter_ext_from; /* externalized sender */ + VSTRING *milter_ext_rcpt; /* externalized recipient */ + VSTRING *milter_err_text; /* milter call-back reply */ + VSTRING *milter_dsn_buf; /* Milter DSN parsing buffer */ + + /* + * Support for Milter body replacement requests. + */ + struct CLEANUP_REGION *free_regions;/* unused regions */ + struct CLEANUP_REGION *body_regions;/* regions with body content */ + struct CLEANUP_REGION *curr_body_region; + + /* + * Internationalization. + */ + int smtputf8; /* what support is desired */ +} CLEANUP_STATE; + + /* + * Status flags. Flags 0-15 are reserved for cleanup_user.h. + */ +#define CLEANUP_FLAG_INRCPT (1<<16) /* Processing recipient records */ +#define CLEANUP_FLAG_WARN_SEEN (1<<17) /* REC_TYPE_WARN record seen */ +#define CLEANUP_FLAG_END_SEEN (1<<18) /* REC_TYPE_END record seen */ + + /* + * Mappings. + */ +extern MAPS *cleanup_comm_canon_maps; +extern MAPS *cleanup_send_canon_maps; +extern MAPS *cleanup_rcpt_canon_maps; +extern int cleanup_comm_canon_flags; +extern int cleanup_send_canon_flags; +extern int cleanup_rcpt_canon_flags; +extern MAPS *cleanup_header_checks; +extern MAPS *cleanup_mimehdr_checks; +extern MAPS *cleanup_nesthdr_checks; +extern MAPS *cleanup_body_checks; +extern MAPS *cleanup_virt_alias_maps; +extern ARGV *cleanup_masq_domains; +extern STRING_LIST *cleanup_masq_exceptions; +extern int cleanup_masq_flags; +extern MAPS *cleanup_send_bcc_maps; +extern MAPS *cleanup_rcpt_bcc_maps; + + /* + * Character filters. + */ +extern VSTRING *cleanup_reject_chars; +extern VSTRING *cleanup_strip_chars; + + /* + * Milters. + */ +extern MILTERS *cleanup_milters; + + /* + * Address canonicalization fine control. + */ +#define CLEANUP_CANON_FLAG_ENV_FROM (1<<0) /* envelope sender */ +#define CLEANUP_CANON_FLAG_ENV_RCPT (1<<1) /* envelope recipient */ +#define CLEANUP_CANON_FLAG_HDR_FROM (1<<2) /* header sender */ +#define CLEANUP_CANON_FLAG_HDR_RCPT (1<<3) /* header recipient */ + + /* + * Address masquerading fine control. + */ +#define CLEANUP_MASQ_FLAG_ENV_FROM (1<<0) /* envelope sender */ +#define CLEANUP_MASQ_FLAG_ENV_RCPT (1<<1) /* envelope recipient */ +#define CLEANUP_MASQ_FLAG_HDR_FROM (1<<2) /* header sender */ +#define CLEANUP_MASQ_FLAG_HDR_RCPT (1<<3) /* header recipient */ + + /* + * Restrictions on extension propagation. + */ +extern int cleanup_ext_prop_mask; + + /* + * Saved queue file names, so the files can be removed in case of a fatal + * run-time error. + */ +extern char *cleanup_path; +extern VSTRING *cleanup_trace_path; +extern VSTRING *cleanup_bounce_path; + + /* + * cleanup_state.c + */ +extern CLEANUP_STATE *cleanup_state_alloc(VSTREAM *); +extern void cleanup_state_free(CLEANUP_STATE *); + + /* + * cleanup_api.c + */ +extern CLEANUP_STATE *cleanup_open(VSTREAM *); +extern void cleanup_control(CLEANUP_STATE *, int); +extern int cleanup_flush(CLEANUP_STATE *); +extern void cleanup_free(CLEANUP_STATE *); +extern void cleanup_all(void); +extern void cleanup_sig(int); +extern void cleanup_pre_jail(char *, char **); +extern void cleanup_post_jail(char *, char **); +extern const CONFIG_INT_TABLE cleanup_int_table[]; +extern const CONFIG_BOOL_TABLE cleanup_bool_table[]; +extern const CONFIG_STR_TABLE cleanup_str_table[]; +extern const CONFIG_TIME_TABLE cleanup_time_table[]; + +#define CLEANUP_RECORD(s, t, b, l) ((s)->action((s), (t), (b), (l))) + + /* + * cleanup_out.c + */ +extern void cleanup_out(CLEANUP_STATE *, int, const char *, ssize_t); +extern void cleanup_out_string(CLEANUP_STATE *, int, const char *); +extern void PRINTFLIKE(3, 4) cleanup_out_format(CLEANUP_STATE *, int, const char *,...); +extern void cleanup_out_header(CLEANUP_STATE *, VSTRING *); + +#define CLEANUP_OUT_BUF(s, t, b) \ + cleanup_out((s), (t), vstring_str((b)), VSTRING_LEN((b))) + +#define CLEANUP_OUT_OK(s) \ + (!((s)->errs & (s)->err_mask) && !((s)->flags & CLEANUP_FLAG_DISCARD)) + + /* + * cleanup_envelope.c + */ +extern void cleanup_envelope(CLEANUP_STATE *, int, const char *, ssize_t); + + /* + * cleanup_message.c + */ +extern void cleanup_message(CLEANUP_STATE *, int, const char *, ssize_t); + + /* + * cleanup_extracted.c + */ +extern void cleanup_extracted(CLEANUP_STATE *, int, const char *, ssize_t); + + /* + * cleanup_final.c + */ +extern void cleanup_final(CLEANUP_STATE *); + + /* + * cleanup_rewrite.c + */ +extern int cleanup_rewrite_external(const char *, VSTRING *, const char *); +extern int cleanup_rewrite_internal(const char *, VSTRING *, const char *); +extern int cleanup_rewrite_tree(const char *, TOK822 *); + + /* + * cleanup_map11.c + */ +extern int cleanup_map11_external(CLEANUP_STATE *, VSTRING *, MAPS *, int); +extern int cleanup_map11_internal(CLEANUP_STATE *, VSTRING *, MAPS *, int); +extern int cleanup_map11_tree(CLEANUP_STATE *, TOK822 *, MAPS *, int); + + /* + * cleanup_map1n.c + */ +ARGV *cleanup_map1n_internal(CLEANUP_STATE *, const char *, MAPS *, int); + + /* + * cleanup_masquerade.c + */ +extern int cleanup_masquerade_external(CLEANUP_STATE *, VSTRING *, ARGV *); +extern int cleanup_masquerade_internal(CLEANUP_STATE *, VSTRING *, ARGV *); +extern int cleanup_masquerade_tree(CLEANUP_STATE *, TOK822 *, ARGV *); + + /* + * cleanup_recipient.c + */ +extern void cleanup_out_recipient(CLEANUP_STATE *, const char *, int, const char *, const char *); + + /* + * cleanup_addr.c. + */ +extern off_t cleanup_addr_sender(CLEANUP_STATE *, const char *); +extern void cleanup_addr_recipient(CLEANUP_STATE *, const char *); +extern void cleanup_addr_bcc_dsn(CLEANUP_STATE *, const char *, const char *, int); + +#define NO_DSN_ORCPT ((char *) 0) +#define NO_DSN_NOTIFY DSN_NOTIFY_NEVER +#define DEF_DSN_NOTIFY (0) + +#define cleanup_addr_bcc(state, addr) \ + cleanup_addr_bcc_dsn((state), (addr), NO_DSN_ORCPT, NO_DSN_NOTIFY) + + /* + * cleanup_bounce.c. + */ +extern int cleanup_bounce(CLEANUP_STATE *); + + /* + * MSG_STATS compatibility. + */ +#define CLEANUP_MSG_STATS(stats, state) \ + MSG_STATS_INIT1(stats, incoming_arrival, state->arrival_time) + + /* + * cleanup_milter.c. + */ +extern void cleanup_milter_header_checks_init(void); +extern void cleanup_milter_receive(CLEANUP_STATE *, int); +extern void cleanup_milter_inspect(CLEANUP_STATE *, MILTERS *); +extern void cleanup_milter_emul_mail(CLEANUP_STATE *, MILTERS *, const char *); +extern void cleanup_milter_emul_rcpt(CLEANUP_STATE *, MILTERS *, const char *); +extern void cleanup_milter_emul_data(CLEANUP_STATE *, MILTERS *); + +#define CLEANUP_MILTER_OK(s) \ + (((s)->flags & CLEANUP_FLAG_MILTER) != 0 \ + && (s)->errs == 0 && ((s)->flags & CLEANUP_FLAG_DISCARD) == 0) + + /* + * cleanup_body_edit.c + */ +typedef struct CLEANUP_REGION { + off_t start; /* start of region */ + off_t len; /* length or zero (open-ended) */ + off_t write_offs; /* write offset */ + struct CLEANUP_REGION *next; /* linkage */ +} CLEANUP_REGION; + +extern void cleanup_region_init(CLEANUP_STATE *); +extern CLEANUP_REGION *cleanup_region_open(CLEANUP_STATE *, ssize_t); +extern void cleanup_region_close(CLEANUP_STATE *, CLEANUP_REGION *); +extern CLEANUP_REGION *cleanup_region_return(CLEANUP_STATE *, CLEANUP_REGION *); +extern void cleanup_region_done(CLEANUP_STATE *); + +extern int cleanup_body_edit_start(CLEANUP_STATE *); +extern int cleanup_body_edit_write(CLEANUP_STATE *, int, VSTRING *); +extern int cleanup_body_edit_finish(CLEANUP_STATE *); +extern void cleanup_body_edit_free(CLEANUP_STATE *); + + /* + * From: header formatting. + */ +extern int cleanup_hfrom_format; + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/cleanup/cleanup_addr.c b/src/cleanup/cleanup_addr.c new file mode 100644 index 0000000..fd8a511 --- /dev/null +++ b/src/cleanup/cleanup_addr.c @@ -0,0 +1,286 @@ +/*++ +/* NAME +/* cleanup_addr 3 +/* SUMMARY +/* process envelope addresses +/* SYNOPSIS +/* #include +/* +/* off_t cleanup_addr_sender(state, addr) +/* CLEANUP_STATE *state; +/* const char *addr; +/* +/* void cleanup_addr_recipient(state, addr) +/* CLEANUP_STATE *state; +/* const char *addr; +/* +/* void cleanup_addr_bcc_dsn(state, addr, dsn_orcpt, dsn_notify) +/* CLEANUP_STATE *state; +/* const char *addr; +/* const char *dsn_orcpt; +/* int dsn_notify; +/* +/* void cleanup_addr_bcc(state, addr) +/* CLEANUP_STATE *state; +/* const char *addr; +/* DESCRIPTION +/* This module processes envelope address records and writes the result +/* to the queue file. Processing includes address rewriting and +/* sender/recipient auto bcc address generation. +/* +/* cleanup_addr_sender() processes sender envelope information and updates +/* state->sender. The result value is the offset of the record that +/* follows the sender record if milters are enabled, otherwise zero. +/* +/* cleanup_addr_recipient() processes recipient envelope information +/* and updates state->recip. +/* +/* cleanup_addr_bcc_dsn() processes recipient envelope information. This +/* is a separate function to avoid invoking cleanup_addr_recipient() +/* recursively. +/* +/* cleanup_addr_bcc() is a backwards-compatibility wrapper for +/* cleanup_addr_bcc_dsn() that requests no delivery status +/* notification for the recipient. +/* +/* Arguments: +/* .IP state +/* Queue file and message processing state. This state is updated +/* as records are processed and as errors happen. +/* .IP buf +/* Record content. +/* .IP dsn_orcpt +/* The DSN original recipient (or NO_DSN_ORCPT to specify none). +/* .IP dsn_notify +/* DSN notification options. Specify NO_DSN_NOTIFY to disable +/* notification, and DEF_DSN_NOTIFY for default notification. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +#define STR vstring_str +#define LEN VSTRING_LEN +#define IGNORE_EXTENSION (char **) 0 + +/* cleanup_addr_sender - process envelope sender record */ + +off_t cleanup_addr_sender(CLEANUP_STATE *state, const char *buf) +{ + const char myname[] = "cleanup_addr_sender"; + VSTRING *clean_addr = vstring_alloc(100); + off_t after_sender_offs = 0; + const char *bcc; + size_t len; + + /* + * Note: an unqualified envelope address is for all practical purposes + * equivalent to a fully qualified local address, both for delivery and + * for replying. Having to support both forms is error prone, therefore + * an incomplete envelope address is rewritten to fully qualified form in + * the local domain context. + * + * 20000520: Replace mailer-daemon@$myorigin by the null address, to handle + * bounced mail traffic more robustly. + */ + cleanup_rewrite_internal(MAIL_ATTR_RWR_LOCAL, clean_addr, buf); + if (strncasecmp_utf8(STR(clean_addr), MAIL_ADDR_MAIL_DAEMON "@", + sizeof(MAIL_ADDR_MAIL_DAEMON)) == 0) { + canon_addr_internal(state->temp1, MAIL_ADDR_MAIL_DAEMON); + if (strcasecmp_utf8(STR(clean_addr), STR(state->temp1)) == 0) + vstring_strcpy(clean_addr, ""); + } + if (state->flags & CLEANUP_FLAG_MAP_OK) { + if (cleanup_send_canon_maps + && (cleanup_send_canon_flags & CLEANUP_CANON_FLAG_ENV_FROM)) + cleanup_map11_internal(state, clean_addr, cleanup_send_canon_maps, + cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_comm_canon_maps + && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_ENV_FROM)) + cleanup_map11_internal(state, clean_addr, cleanup_comm_canon_maps, + cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_masq_domains + && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_ENV_FROM)) + cleanup_masquerade_internal(state, clean_addr, cleanup_masq_domains); + } + /* Fix 20140711: Auto-detect an UTF8 sender. */ + if (var_smtputf8_enable && *STR(clean_addr) && !allascii(STR(clean_addr)) + && valid_utf8_string(STR(clean_addr), LEN(clean_addr))) { + state->smtputf8 |= SMTPUTF8_FLAG_SENDER; + /* Fix 20140713: request SMTPUTF8 support selectively. */ + if (state->flags & CLEANUP_FLAG_AUTOUTF8) + state->smtputf8 |= SMTPUTF8_FLAG_REQUESTED; + } + CLEANUP_OUT_BUF(state, REC_TYPE_FROM, clean_addr); + if (state->sender) /* XXX Can't happen */ + myfree(state->sender); + state->sender = mystrdup(STR(clean_addr)); /* Used by Milter client */ + /* Fix 20160310: Moved from cleanup_envelope.c. */ + if (state->milters || cleanup_milters) { + /* Make room to replace sender. */ + if ((len = LEN(clean_addr)) < REC_TYPE_PTR_PAYL_SIZE) + rec_pad(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_PAYL_SIZE - len); + /* Remember the after-sender record offset. */ + if ((after_sender_offs = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); + } + if ((state->flags & CLEANUP_FLAG_BCC_OK) + && *STR(clean_addr) + && cleanup_send_bcc_maps) { + if ((bcc = mail_addr_find_to_internal(cleanup_send_bcc_maps, + STR(clean_addr), + IGNORE_EXTENSION)) != 0) { + cleanup_addr_bcc(state, bcc); + } else if (cleanup_send_bcc_maps->error) { + msg_warn("%s: %s map lookup problem -- " + "message not accepted, try again later", + state->queue_id, cleanup_send_bcc_maps->title); + state->errs |= CLEANUP_STAT_WRITE; + } + } + vstring_free(clean_addr); + return after_sender_offs; +} + +/* cleanup_addr_recipient - process envelope recipient */ + +void cleanup_addr_recipient(CLEANUP_STATE *state, const char *buf) +{ + VSTRING *clean_addr = vstring_alloc(100); + const char *bcc; + + /* + * Note: an unqualified envelope address is for all practical purposes + * equivalent to a fully qualified local address, both for delivery and + * for replying. Having to support both forms is error prone, therefore + * an incomplete envelope address is rewritten to fully qualified form in + * the local domain context. + */ + cleanup_rewrite_internal(MAIL_ATTR_RWR_LOCAL, + clean_addr, *buf ? buf : var_empty_addr); + if (state->flags & CLEANUP_FLAG_MAP_OK) { + if (cleanup_rcpt_canon_maps + && (cleanup_rcpt_canon_flags & CLEANUP_CANON_FLAG_ENV_RCPT)) + cleanup_map11_internal(state, clean_addr, cleanup_rcpt_canon_maps, + cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_comm_canon_maps + && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_ENV_RCPT)) + cleanup_map11_internal(state, clean_addr, cleanup_comm_canon_maps, + cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_masq_domains + && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_ENV_RCPT)) + cleanup_masquerade_internal(state, clean_addr, cleanup_masq_domains); + } + /* Fix 20140711: Auto-detect an UTF8 recipient. */ + if (var_smtputf8_enable && *STR(clean_addr) && !allascii(STR(clean_addr)) + && valid_utf8_string(STR(clean_addr), LEN(clean_addr))) { + /* Fix 20140713: request SMTPUTF8 support selectively. */ + if (state->flags & CLEANUP_FLAG_AUTOUTF8) + state->smtputf8 |= SMTPUTF8_FLAG_REQUESTED; + } + /* Fix 20141024: Don't fake up a "bare" DSN original rcpt in smtp(8). */ + if (state->dsn_orcpt == 0 && *STR(clean_addr) != 0) + state->dsn_orcpt = concatenate((!allascii(STR(clean_addr)) + && (state->smtputf8 & SMTPUTF8_FLAG_REQUESTED)) ? + "utf-8" : "rfc822", ";", STR(clean_addr), (char *) 0); + cleanup_out_recipient(state, state->dsn_orcpt, state->dsn_notify, + state->orig_rcpt, STR(clean_addr)); + if (state->recip) /* This can happen */ + myfree(state->recip); + state->recip = mystrdup(STR(clean_addr)); /* Used by Milter client */ + if ((state->flags & CLEANUP_FLAG_BCC_OK) + && *STR(clean_addr) + && cleanup_rcpt_bcc_maps) { + if ((bcc = mail_addr_find_to_internal(cleanup_rcpt_bcc_maps, + STR(clean_addr), + IGNORE_EXTENSION)) != 0) { + cleanup_addr_bcc(state, bcc); + } else if (cleanup_rcpt_bcc_maps->error) { + msg_warn("%s: %s map lookup problem -- " + "message not accepted, try again later", + state->queue_id, cleanup_rcpt_bcc_maps->title); + state->errs |= CLEANUP_STAT_WRITE; + } + } + vstring_free(clean_addr); +} + +/* cleanup_addr_bcc_dsn - process automatic BCC recipient */ + +void cleanup_addr_bcc_dsn(CLEANUP_STATE *state, const char *bcc, + const char *dsn_orcpt, int dsn_notify) +{ + VSTRING *clean_addr = vstring_alloc(100); + + /* + * Note: BCC addresses are supplied locally, and must be rewritten in the + * local address rewriting context. + */ + cleanup_rewrite_internal(MAIL_ATTR_RWR_LOCAL, clean_addr, bcc); + if (state->flags & CLEANUP_FLAG_MAP_OK) { + if (cleanup_rcpt_canon_maps + && (cleanup_rcpt_canon_flags & CLEANUP_CANON_FLAG_ENV_RCPT)) + cleanup_map11_internal(state, clean_addr, cleanup_rcpt_canon_maps, + cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_comm_canon_maps + && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_ENV_RCPT)) + cleanup_map11_internal(state, clean_addr, cleanup_comm_canon_maps, + cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_masq_domains + && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_ENV_RCPT)) + cleanup_masquerade_internal(state, clean_addr, cleanup_masq_domains); + } + /* Fix 20140711: Auto-detect an UTF8 recipient. */ + if (var_smtputf8_enable && *STR(clean_addr) && !allascii(STR(clean_addr)) + && valid_utf8_string(STR(clean_addr), LEN(clean_addr))) { + /* Fix 20140713: request SMTPUTF8 support selectively. */ + if (state->flags & CLEANUP_FLAG_AUTOUTF8) + state->smtputf8 |= SMTPUTF8_FLAG_REQUESTED; + } + cleanup_out_recipient(state, dsn_orcpt, dsn_notify, + STR(clean_addr), STR(clean_addr)); + vstring_free(clean_addr); +} diff --git a/src/cleanup/cleanup_api.c b/src/cleanup/cleanup_api.c new file mode 100644 index 0000000..738bd73 --- /dev/null +++ b/src/cleanup/cleanup_api.c @@ -0,0 +1,395 @@ +/*++ +/* NAME +/* cleanup_api 3 +/* SUMMARY +/* cleanup callable interface, message processing +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* CLEANUP_STATE *cleanup_open(src) +/* VSTREAM *src; +/* +/* void cleanup_control(state, flags) +/* CLEANUP_STATE *state; +/* int flags; +/* +/* void CLEANUP_RECORD(state, type, buf, len) +/* CLEANUP_STATE *state; +/* int type; +/* char *buf; +/* int len; +/* +/* int cleanup_flush(state) +/* CLEANUP_STATE *state; +/* +/* int cleanup_free(state) +/* CLEANUP_STATE *state; +/* DESCRIPTION +/* This module implements a callable interface to the cleanup service +/* for processing one message and for writing it to queue file. +/* For a description of the cleanup service, see cleanup(8). +/* +/* cleanup_open() creates a new queue file and performs other +/* per-message initialization. The result is a handle that should be +/* given to the cleanup_control(), cleanup_record(), cleanup_flush() +/* and cleanup_free() routines. The name of the queue file is in the +/* queue_id result structure member. +/* +/* cleanup_control() processes per-message flags specified by the caller. +/* These flags control the handling of data errors, and must be set +/* before processing the first message record. +/* .IP CLEANUP_FLAG_BOUNCE +/* The cleanup server is responsible for returning undeliverable +/* mail (too many hops, message too large) to the sender. +/* .IP CLEANUP_FLAG_BCC_OK +/* It is OK to add automatic BCC recipient addresses. +/* .IP CLEANUP_FLAG_FILTER +/* Enable header/body filtering. This should be enabled only with mail +/* that enters Postfix, not with locally forwarded mail or with bounce +/* messages. +/* .IP CLEANUP_FLAG_MILTER +/* Enable Milter applications. This should be enabled only with mail +/* that enters Postfix, not with locally forwarded mail or with bounce +/* messages. +/* .IP CLEANUP_FLAG_MAP_OK +/* Enable canonical and virtual mapping, and address masquerading. +/* .PP +/* For convenience the CLEANUP_FLAG_MASK_EXTERNAL macro specifies +/* the options that are normally needed for mail that enters +/* Postfix from outside, and CLEANUP_FLAG_MASK_INTERNAL specifies +/* the options that are normally needed for internally generated or +/* forwarded mail. +/* +/* CLEANUP_RECORD() is a macro that processes one message record, +/* that copies the result to the queue file, and that maintains a +/* little state machine. The last record in a valid message has type +/* REC_TYPE_END. In order to find out if a message is corrupted, +/* the caller is encouraged to test the CLEANUP_OUT_OK(state) macro. +/* The result is false when further message processing is futile. +/* In that case, it is safe to call cleanup_flush() immediately. +/* +/* cleanup_flush() closes a queue file. In case of any errors, +/* the file is removed. The result value is non-zero in case of +/* problems. In some cases a human-readable text can be found in +/* the state->reason member. In all other cases, use cleanup_strerror() +/* to translate the result into human-readable text. +/* +/* cleanup_free() destroys its argument. +/* .IP CLEANUP_FLAG_SMTPUTF8 +/* Request SMTPUTF8 support when delivering mail. +/* .IP CLEANUP_FLAG_AUTOUTF8 +/* Autodetection: request SMTPUTF8 support if the message +/* contains an UTF8 message header, sender, or recipient. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* SEE ALSO +/* cleanup(8) cleanup service description. +/* cleanup_init(8) cleanup callable interface, initialization +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Milter library. */ + +#include + +/* Application-specific. */ + +#include "cleanup.h" + +/* cleanup_open - open queue file and initialize */ + +CLEANUP_STATE *cleanup_open(VSTREAM *src) +{ + CLEANUP_STATE *state; + static const char *log_queues[] = { + MAIL_QUEUE_DEFER, + MAIL_QUEUE_BOUNCE, + MAIL_QUEUE_TRACE, + 0, + }; + const char **cpp; + + /* + * Initialize private state. + */ + state = cleanup_state_alloc(src); + + /* + * Open the queue file. Save the queue file name in a global variable, so + * that the runtime error handler can clean up in case of problems. + * + * XXX For now, a lot of detail is frozen that could be more useful if it + * were made configurable. + */ + state->queue_name = mystrdup(MAIL_QUEUE_INCOMING); + state->handle = mail_stream_file(state->queue_name, + MAIL_CLASS_PUBLIC, var_queue_service, 0); + state->dst = state->handle->stream; + cleanup_path = mystrdup(VSTREAM_PATH(state->dst)); + state->queue_id = mystrdup(state->handle->id); + if (msg_verbose) + msg_info("cleanup_open: open %s", cleanup_path); + + /* + * If there is a time to get rid of spurious log files, this is it. The + * down side is that this costs performance for every message, while the + * probability of spurious log files is quite low. + * + * XXX The defer logfile is deleted when the message is moved into the + * active queue. We must also remove it now, otherwise mailq produces + * nonsense. + */ + for (cpp = log_queues; *cpp; cpp++) { + if (mail_queue_remove(*cpp, state->queue_id) == 0) + msg_warn("%s: removed spurious %s log", *cpp, state->queue_id); + else if (errno != ENOENT) + msg_fatal("%s: remove %s log: %m", *cpp, state->queue_id); + } + return (state); +} + +/* cleanup_control - process client options */ + +void cleanup_control(CLEANUP_STATE *state, int flags) +{ + + /* + * If the client requests us to do the bouncing in case of problems, + * throw away the input only in case of real show-stopper errors, such as + * unrecognizable data (which should never happen) or insufficient space + * for the queue file (which will happen occasionally). Otherwise, + * discard input after any lethal error. See the CLEANUP_OUT_OK() macro + * definition. + */ + if (msg_verbose) + msg_info("cleanup flags = %s", cleanup_strflags(flags)); + if ((state->flags = flags) & CLEANUP_FLAG_BOUNCE) { + state->err_mask = CLEANUP_STAT_MASK_INCOMPLETE; + } else { + state->err_mask = ~0; + } + if (state->flags & CLEANUP_FLAG_SMTPUTF8) + state->smtputf8 = SMTPUTF8_FLAG_REQUESTED; +} + +/* cleanup_flush - finish queue file */ + +int cleanup_flush(CLEANUP_STATE *state) +{ + int status; + char *junk; + VSTRING *trace_junk; + + /* + * Raise these errors only if we examined all queue file records. + */ + if (CLEANUP_OUT_OK(state)) { + if (state->recip == 0) + state->errs |= CLEANUP_STAT_RCPT; + if ((state->flags & CLEANUP_FLAG_END_SEEN) == 0) + state->errs |= CLEANUP_STAT_BAD; + } + + /* + * Status sanitization. Always report success when the discard flag was + * raised by some user-specified access rule. + */ + if (state->flags & CLEANUP_FLAG_DISCARD) + state->errs = 0; + + /* + * Apply external mail filter. + * + * XXX Include test for a built-in action to tempfail this message. + */ + if (CLEANUP_MILTER_OK(state)) { + if (state->milters) + cleanup_milter_inspect(state, state->milters); + else if (cleanup_milters) { + cleanup_milter_emul_data(state, cleanup_milters); + if (CLEANUP_MILTER_OK(state)) + cleanup_milter_inspect(state, cleanup_milters); + } + } + + /* + * Update the preliminary message size and count fields with the actual + * values. + */ + if (CLEANUP_OUT_OK(state)) + cleanup_final(state); + + /* + * If there was an error that requires us to generate a bounce message + * (mail submitted with the Postfix sendmail command, mail forwarded by + * the local(8) delivery agent, or mail re-queued with "postsuper -r"), + * send a bounce notification, reset the error flags in case of success, + * and request deletion of the incoming queue file and of the + * optional DSN SUCCESS records from virtual alias expansion. + * + * XXX It would make no sense to knowingly report success after we already + * have bounced all recipients, especially because the information in the + * DSN SUCCESS notice is completely redundant compared to the information + * in the bounce notice (however, both may be incomplete when the queue + * file size would exceed the safety limit). + * + * An alternative is to keep the DSN SUCCESS records and to delegate bounce + * notification to the queue manager, just like we already delegate + * success notification. This requires that we leave the undeliverable + * message in the incoming queue; versions up to 20050726 did exactly + * that. Unfortunately, this broke with over-size queue files, because + * the queue manager cannot handle incomplete queue files (and it should + * not try to do so). + */ +#define CAN_BOUNCE() \ + ((state->errs & CLEANUP_STAT_MASK_CANT_BOUNCE) == 0 \ + && state->sender != 0 \ + && (state->flags & CLEANUP_FLAG_BOUNCE) != 0) + + if (state->errs != 0 && CAN_BOUNCE()) + cleanup_bounce(state); + + /* + * Optionally, place the message on hold, but only if the message was + * received successfully and only if it's not being discarded for other + * reasons. This involves renaming the queue file before "finishing" it + * (or else the queue manager would grab it too early) and updating our + * own idea of the queue file name for error recovery and for error + * reporting purposes. + * + * XXX Include test for a built-in action to tempfail this message. + */ + if (state->errs == 0 && (state->flags & CLEANUP_FLAG_DISCARD) == 0) { + if ((state->flags & CLEANUP_FLAG_HOLD) != 0 +#ifdef DELAY_ACTION + || state->defer_delay > 0 +#endif + ) { + myfree(state->queue_name); +#ifdef DELAY_ACTION + state->queue_name = mystrdup((state->flags & CLEANUP_FLAG_HOLD) ? + MAIL_QUEUE_HOLD : MAIL_QUEUE_DEFERRED); +#else + state->queue_name = mystrdup(MAIL_QUEUE_HOLD); +#endif + mail_stream_ctl(state->handle, + CA_MAIL_STREAM_CTL_QUEUE(state->queue_name), + CA_MAIL_STREAM_CTL_CLASS((char *) 0), + CA_MAIL_STREAM_CTL_SERVICE((char *) 0), +#ifdef DELAY_ACTION + CA_MAIL_STREAM_CTL_DELAY(state->defer_delay), +#endif + CA_MAIL_STREAM_CTL_END); + junk = cleanup_path; + cleanup_path = mystrdup(VSTREAM_PATH(state->handle->stream)); + myfree(junk); + + /* + * XXX: When delivering to a non-incoming queue, do not consume + * in_flow tokens. Unfortunately we can't move the code that + * consumes tokens until after the mail is received, because that + * would increase the risk of duplicate deliveries (RFC 1047). + */ + (void) mail_flow_put(1); + } + state->errs = mail_stream_finish(state->handle, (VSTRING *) 0); + } else { + + /* + * XXX: When discarding mail, should we consume in_flow tokens? See + * also the comments above for mail that is placed on hold. + */ +#if 0 + (void) mail_flow_put(1); +#endif + mail_stream_cleanup(state->handle); + } + state->handle = 0; + state->dst = 0; + + /* + * If there was an error, or if the message must be discarded for other + * reasons, remove the queue file and the optional trace file with DSN + * SUCCESS records from virtual alias expansion. + */ + if (state->errs != 0 || (state->flags & CLEANUP_FLAG_DISCARD) != 0) { + if (cleanup_trace_path) + (void) REMOVE(vstring_str(cleanup_trace_path)); + if (REMOVE(cleanup_path)) + msg_warn("remove %s: %m", cleanup_path); + } + + /* + * Make sure that our queue file will not be deleted by the error handler + * AFTER we have taken responsibility for delivery. Better to deliver + * twice than to lose mail. + */ + trace_junk = cleanup_trace_path; + cleanup_trace_path = 0; /* don't delete upon error */ + junk = cleanup_path; + cleanup_path = 0; /* don't delete upon error */ + + if (trace_junk) + vstring_free(trace_junk); + myfree(junk); + + /* + * Cleanup internal state. This is simply complementary to the + * initializations at the beginning of cleanup_open(). + */ + if (msg_verbose) + msg_info("cleanup_flush: status %d", state->errs); + status = state->errs; + return (status); +} + +/* cleanup_free - pay the last respects */ + +void cleanup_free(CLEANUP_STATE *state) +{ + + /* + * Emulate disconnect event. CLEANUP_FLAG_MILTER may be turned off after + * we have started. + */ + if (cleanup_milters != 0 && state->milters == 0) + milter_disc_event(cleanup_milters); + cleanup_state_free(state); +} diff --git a/src/cleanup/cleanup_body_edit.c b/src/cleanup/cleanup_body_edit.c new file mode 100644 index 0000000..3d4e866 --- /dev/null +++ b/src/cleanup/cleanup_body_edit.c @@ -0,0 +1,243 @@ +/*++ +/* NAME +/* cleanup_body_edit 3 +/* SUMMARY +/* edit body content +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* int cleanup_body_edit_start(state) +/* CLEANUP_STATE *state; +/* +/* int cleanup_body_edit_write(state, type, buf) +/* CLEANUP_STATE *state; +/* int type; +/* VSTRING *buf; +/* +/* int cleanup_body_edit_finish(state) +/* CLEANUP_STATE *state; +/* +/* void cleanup_body_edit_free(state) +/* CLEANUP_STATE *state; +/* DESCRIPTION +/* This module maintains queue file regions with body content. +/* Regions are created on the fly, and can be reused multiple +/* times. This module must not be called until the queue file +/* is complete, and there must be no other read/write access +/* to the queue file between the cleanup_body_edit_start() and +/* cleanup_body_edit_finish() calls. +/* +/* cleanup_body_edit_start() performs initialization and sets +/* the queue file write pointer to the start of the first body +/* region. +/* +/* cleanup_body_edit_write() adds a queue file record to the +/* queue file. When the current body region fills up, some +/* unused region is reused, or a new region is created. +/* +/* cleanup_body_edit_finish() makes some final adjustments +/* after the last body content record is written. +/* +/* cleanup_body_edit_free() frees up memory that was allocated +/* by cleanup_body_edit_start() and cleanup_body_edit_write(). +/* +/* Arguments: +/* .IP state +/* Queue file and message processing state. This state is updated +/* as records are processed and as errors happen. +/* .IP type +/* Record type. +/* .IP buf +/* Record content. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include + +#define LEN(s) VSTRING_LEN(s) + +static int cleanup_body_edit_ptr_rec_len; + +/* cleanup_body_edit_start - rewrite body region pool */ + +int cleanup_body_edit_start(CLEANUP_STATE *state) +{ + const char *myname = "cleanup_body_edit_start"; + CLEANUP_REGION *curr_rp; + + /* + * Calculate the payload size sans body. + */ + state->cont_length = state->body_offset - state->data_offset; + + /* + * One-time initialization. + */ + if (state->body_regions == 0) { + REC_SPACE_NEED(REC_TYPE_PTR_PAYL_SIZE, cleanup_body_edit_ptr_rec_len); + cleanup_region_init(state); + } + + /* + * Return all body regions to the free pool. + */ + cleanup_region_return(state, state->body_regions); + + /* + * Select the first region. XXX This will usually be the original body + * segment, but we must not count on that. Region assignments may change + * when header editing also uses queue file regions. XXX We don't really + * know if the first region will be large enough to hold the first body + * text record, but this problem is so rare that we will not complicate + * the code for it. If the first region is too small then we will simply + * waste it. + */ + curr_rp = state->curr_body_region = state->body_regions = + cleanup_region_open(state, cleanup_body_edit_ptr_rec_len); + + /* + * Link the first body region to the last message header. + */ + if (vstream_fseek(state->dst, state->append_hdr_pt_offset, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + return (-1); + } + state->append_hdr_pt_target = curr_rp->start; + rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) state->append_hdr_pt_target); + + /* + * Move the file write pointer to the start of the current region. + */ + if (vstream_ftell(state->dst) != curr_rp->start + && vstream_fseek(state->dst, curr_rp->start, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + return (-1); + } + return (0); +} + +/* cleanup_body_edit_write - add record to body region pool */ + +int cleanup_body_edit_write(CLEANUP_STATE *state, int rec_type, + VSTRING *buf) +{ + const char *myname = "cleanup_body_edit_write"; + CLEANUP_REGION *curr_rp = state->curr_body_region; + CLEANUP_REGION *next_rp; + off_t space_used; + ssize_t space_needed; + ssize_t rec_len; + + if (msg_verbose) + msg_info("%s: where %ld, buflen %ld region start %ld len %ld", + myname, (long) curr_rp->write_offs, (long) LEN(buf), + (long) curr_rp->start, (long) curr_rp->len); + + /* + * Switch to the next body region if we filled up the current one (we + * always append to an open-ended region). Besides space to write the + * specified record, we need to leave space for a final pointer record + * that will link this body region to the next region or to the content + * terminator record. + */ + if (curr_rp->len > 0) { + space_used = curr_rp->write_offs - curr_rp->start; + REC_SPACE_NEED(LEN(buf), rec_len); + space_needed = rec_len + cleanup_body_edit_ptr_rec_len; + if (space_needed > curr_rp->len - space_used) { + + /* + * Update the payload size. Connect the filled up body region to + * its successor. + */ + state->cont_length += space_used; + next_rp = cleanup_region_open(state, space_needed); + if (msg_verbose) + msg_info("%s: link %ld -> %ld", myname, + (long) curr_rp->write_offs, (long) next_rp->start); + rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) next_rp->start); + curr_rp->write_offs = vstream_ftell(state->dst); + cleanup_region_close(state, curr_rp); + curr_rp->next = next_rp; + + /* + * Select the new body region. + */ + state->curr_body_region = curr_rp = next_rp; + if (vstream_fseek(state->dst, curr_rp->start, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + return (-1); + } + } + } + + /* + * Finally, output the queue file record. + */ + CLEANUP_OUT_BUF(state, rec_type, buf); + curr_rp->write_offs = vstream_ftell(state->dst); + + /* + * Sanity check. + */ + if (curr_rp->len > 0 + && curr_rp->write_offs > curr_rp->start + curr_rp->len) + msg_panic("%s: write past end of body segment", myname); + + return (0); +} + +/* cleanup_body_edit_finish - wrap up body region pool */ + +int cleanup_body_edit_finish(CLEANUP_STATE *state) +{ + CLEANUP_REGION *curr_rp = state->curr_body_region; + + /* + * Update the payload size. + */ + state->cont_length += curr_rp->write_offs - curr_rp->start; + + /* + * Link the last body region to the content terminator record. + */ + rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) state->xtra_offset); + curr_rp->write_offs = vstream_ftell(state->dst); + cleanup_region_close(state, curr_rp); + + return (CLEANUP_OUT_OK(state) ? 0 : -1); +} diff --git a/src/cleanup/cleanup_bounce.c b/src/cleanup/cleanup_bounce.c new file mode 100644 index 0000000..361875e --- /dev/null +++ b/src/cleanup/cleanup_bounce.c @@ -0,0 +1,257 @@ +/*++ +/* NAME +/* cleanup_bounce 3 +/* SUMMARY +/* bounce all recipients +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* void cleanup_bounce(state) +/* CLEANUP_STATE *state; +/* DESCRIPTION +/* cleanup_bounce() updates the bounce log on request by client +/* programs that cannot handle such problems themselves. +/* +/* Upon successful completion, all error flags are reset, +/* and the message is scheduled for deletion. +/* Otherwise, the CLEANUP_STAT_WRITE error flag is raised. +/* +/* Arguments: +/* .IP state +/* Queue file and message processing state. This state is +/* updated as records are processed and as errors happen. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +#define STR(x) vstring_str(x) + +/* cleanup_bounce_append - update bounce logfile */ + +static void cleanup_bounce_append(CLEANUP_STATE *state, RECIPIENT *rcpt, + DSN *dsn) +{ + MSG_STATS stats; + + /* + * Don't log a spurious warning (for example, when soft_bounce is turned + * on). bounce_append() already logs a record when the logfile can't be + * updated. Set the write error flag, so that a maildrop queue file won't + * be destroyed. + */ + if (bounce_append(BOUNCE_FLAG_CLEAN, state->queue_id, + CLEANUP_MSG_STATS(&stats, state), + rcpt, "none", dsn) != 0) { + state->errs |= CLEANUP_STAT_WRITE; + } +} + +/* cleanup_bounce - bounce all recipients */ + +int cleanup_bounce(CLEANUP_STATE *state) +{ + const char *myname = "cleanup_bounce"; + VSTRING *buf = vstring_alloc(100); + const CLEANUP_STAT_DETAIL *detail; + DSN_SPLIT dp; + const char *dsn_status; + const char *dsn_text; + char *rcpt = 0; + RECIPIENT recipient; + DSN dsn; + char *attr_name; + char *attr_value; + char *dsn_orcpt = 0; + int dsn_notify = 0; + char *orig_rcpt = 0; + char *start; + int rec_type; + int junk; + long curr_offset; + const char *encoding; + const char *dsn_envid; + int dsn_ret; + int bounce_err; + + /* + * Parse the failure reason if one was given, otherwise use a generic + * mapping from cleanup-internal error code to (DSN + text). + */ + if (state->reason) { + dsn_split(&dp, "5.0.0", state->reason); + dsn_status = DSN_STATUS(dp.dsn); + dsn_text = dp.text; + } else { + detail = cleanup_stat_detail(state->errs); + dsn_status = detail->dsn; + dsn_text = detail->text; + } + + /* + * Create a bounce logfile with one entry for each final recipient. + * Degrade gracefully in case of no recipients or no queue file. + * + * Victor Duchovni observes that the number of recipients in the queue file + * can potentially be very large due to virtual alias expansion. This can + * expand the recipient count by virtual_alias_expansion_limit (default: + * 1000) times. + * + * After a queue file write error, purge any unwritten data (so that + * vstream_fseek() won't fail while trying to flush it) and reset the + * stream error flags to avoid false alarms. + */ + if (vstream_ferror(state->dst) || vstream_fflush(state->dst)) { + (void) vstream_fpurge(state->dst, VSTREAM_PURGE_BOTH); + vstream_clearerr(state->dst); + } + if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0) + msg_fatal("%s: seek %s: %m", myname, cleanup_path); + + while ((state->errs & CLEANUP_STAT_WRITE) == 0) { + if ((curr_offset = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path); + if ((rec_type = rec_get(state->dst, buf, 0)) <= 0 + || rec_type == REC_TYPE_END) + break; + start = STR(buf); + if (rec_type == REC_TYPE_ATTR) { + if (split_nameval(STR(buf), &attr_name, &attr_value) != 0 + || *attr_value == 0) + continue; + /* Map attribute names to pseudo record type. */ + if ((junk = rec_attr_map(attr_name)) != 0) { + start = attr_value; + rec_type = junk; + } + } + switch (rec_type) { + case REC_TYPE_DSN_ORCPT: /* RCPT TO ORCPT parameter */ + if (dsn_orcpt != 0) /* can't happen */ + myfree(dsn_orcpt); + dsn_orcpt = mystrdup(start); + break; + case REC_TYPE_DSN_NOTIFY: /* RCPT TO NOTIFY parameter */ + if (alldig(start) && (junk = atoi(start)) > 0 + && DSN_NOTIFY_OK(junk)) + dsn_notify = junk; + else + dsn_notify = 0; + break; + case REC_TYPE_ORCP: /* unmodified RCPT TO address */ + if (orig_rcpt != 0) /* can't happen */ + myfree(orig_rcpt); + orig_rcpt = mystrdup(start); + break; + case REC_TYPE_RCPT: /* rewritten RCPT TO address */ + rcpt = start; + RECIPIENT_ASSIGN(&recipient, curr_offset, + dsn_orcpt ? dsn_orcpt : "", dsn_notify, + orig_rcpt ? orig_rcpt : rcpt, rcpt); + (void) DSN_SIMPLE(&dsn, dsn_status, dsn_text); + cleanup_bounce_append(state, &recipient, &dsn); + /* FALLTHROUGH */ + case REC_TYPE_DRCP: /* canceled recipient */ + case REC_TYPE_DONE: /* can't happen */ + if (orig_rcpt != 0) { + myfree(orig_rcpt); + orig_rcpt = 0; + } + if (dsn_orcpt != 0) { + myfree(dsn_orcpt); + dsn_orcpt = 0; + } + dsn_notify = 0; + break; + } + } + if (orig_rcpt != 0) /* can't happen */ + myfree(orig_rcpt); + if (dsn_orcpt != 0) /* can't happen */ + myfree(dsn_orcpt); + + /* + * No recipients. Yes, this can happen. + */ + if ((state->errs & CLEANUP_STAT_WRITE) == 0 && rcpt == 0) { + RECIPIENT_ASSIGN(&recipient, 0, "", 0, "", "unknown"); + (void) DSN_SIMPLE(&dsn, dsn_status, dsn_text); + cleanup_bounce_append(state, &recipient, &dsn); + } + vstring_free(buf); + + /* + * Flush the bounce logfile to the sender. See also qmgr_active.c. + */ + if ((state->errs & CLEANUP_STAT_WRITE) == 0) { + if ((encoding = nvtable_find(state->attr, MAIL_ATTR_ENCODING)) == 0) + encoding = MAIL_ATTR_ENC_NONE; + dsn_envid = state->dsn_envid ? + state->dsn_envid : ""; + /* Do not send unfiltered (body) content. */ + dsn_ret = (state->errs & (CLEANUP_STAT_CONT | CLEANUP_STAT_SIZE)) ? + DSN_RET_HDRS : state->dsn_ret; + + if (state->verp_delims == 0 || var_verp_bounce_off) { + bounce_err = + bounce_flush(BOUNCE_FLAG_CLEAN, + state->queue_name, state->queue_id, + encoding, state->smtputf8, state->sender, + dsn_envid, dsn_ret); + } else { + bounce_err = + bounce_flush_verp(BOUNCE_FLAG_CLEAN, + state->queue_name, state->queue_id, + encoding, state->smtputf8, state->sender, + dsn_envid, dsn_ret, state->verp_delims); + } + if (bounce_err != 0) { + msg_warn("%s: bounce message failure", state->queue_id); + state->errs |= CLEANUP_STAT_WRITE; + } + } + + /* + * Schedule this message (and trace logfile) for deletion when all is + * well. When all is not well these files would be deleted too, but the + * client would get a different completion status so we have to carefully + * maintain the bits anyway. + */ + if ((state->errs &= CLEANUP_STAT_WRITE) == 0) + state->flags |= CLEANUP_FLAG_DISCARD; + + return (state->errs); +} diff --git a/src/cleanup/cleanup_envelope.c b/src/cleanup/cleanup_envelope.c new file mode 100644 index 0000000..a4b991d --- /dev/null +++ b/src/cleanup/cleanup_envelope.c @@ -0,0 +1,502 @@ +/*++ +/* NAME +/* cleanup_envelope 3 +/* SUMMARY +/* process envelope segment +/* SYNOPSIS +/* #include +/* +/* void cleanup_envelope(state, type, buf, len) +/* CLEANUP_STATE *state; +/* int type; +/* const char *buf; +/* ssize_t len; +/* DESCRIPTION +/* This module processes envelope records and writes the result +/* to the queue file. It validates the message structure, rewrites +/* sender/recipient addresses to canonical form, and expands recipients +/* according to entries in the virtual table. This routine absorbs but +/* does not emit the envelope to content boundary record. +/* +/* Arguments: +/* .IP state +/* Queue file and message processing state. This state is updated +/* as records are processed and as errors happen. +/* .IP type +/* Record type. +/* .IP buf +/* Record content. +/* .IP len +/* Record content length. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include /* ssscanf() */ +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +#define STR vstring_str +#define STREQ(x,y) (strcmp((x), (y)) == 0) + +static void cleanup_envelope_process(CLEANUP_STATE *, int, const char *, ssize_t); + +/* cleanup_envelope - initialize message envelope */ + +void cleanup_envelope(CLEANUP_STATE *state, int type, + const char *str, ssize_t len) +{ + + /* + * The message size and count record goes first, so it can easily be + * updated in place. This information takes precedence over any size + * estimate provided by the client. It's all in one record, data size + * first, for backwards compatibility reasons. + */ + cleanup_out_format(state, REC_TYPE_SIZE, REC_TYPE_SIZE_FORMAT, + (REC_TYPE_SIZE_CAST1) 0, /* extra offs - content offs */ + (REC_TYPE_SIZE_CAST2) 0, /* content offset */ + (REC_TYPE_SIZE_CAST3) 0, /* recipient count */ + (REC_TYPE_SIZE_CAST4) 0, /* qmgr options */ + (REC_TYPE_SIZE_CAST5) 0, /* content length */ + (REC_TYPE_SIZE_CAST6) 0); /* smtputf8 */ + + /* + * Pass control to the actual envelope processing routine. + */ + state->action = cleanup_envelope_process; + cleanup_envelope_process(state, type, str, len); +} + +/* cleanup_envelope_process - process one envelope record */ + +static void cleanup_envelope_process(CLEANUP_STATE *state, int type, + const char *buf, ssize_t len) +{ + const char *myname = "cleanup_envelope_process"; + char *attr_name; + char *attr_value; + const char *error_text; + int extra_opts; + int junk; + int mapped_type = type; + const char *mapped_buf = buf; + int milter_count; + +#ifdef DELAY_ACTION + int defer_delay; + +#endif + + if (msg_verbose) + msg_info("initial envelope %c %.*s", type, (int) len, buf); + + if (type == REC_TYPE_FLGS) { + /* Not part of queue file format. */ + extra_opts = atoi(buf); + if (extra_opts & ~CLEANUP_FLAG_MASK_EXTRA) + msg_warn("%s: ignoring bad extra flags: 0x%x", + state->queue_id, extra_opts); + else + state->flags |= extra_opts; + return; + } +#ifdef DELAY_ACTION + if (type == REC_TYPE_DELAY) { + /* Not part of queue file format. */ + defer_delay = atoi(buf); + if (defer_delay <= 0) + msg_warn("%s: ignoring bad delay time: %s", state->queue_id, buf); + else + state->defer_delay = defer_delay; + return; + } +#endif + + /* + * XXX We instantiate a MILTERS structure even when the filter count is + * zero (for example, all filters are in ACCEPT state, or the SMTP server + * sends a dummy MILTERS structure without any filters), otherwise the + * cleanup server would apply the non_smtpd_milters setting + * inappropriately. + */ + if (type == REC_TYPE_MILT_COUNT) { + /* Not part of queue file format. */ + if ((milter_count = atoi(buf)) >= 0) + cleanup_milter_receive(state, milter_count); + return; + } + + /* + * Map DSN attribute name to pseudo record type so that we don't have to + * pollute the queue file with records that are incompatible with past + * Postfix versions. Preferably, people should be able to back out from + * an upgrade without losing mail. + */ + if (type == REC_TYPE_ATTR) { + vstring_strcpy(state->attr_buf, buf); + error_text = split_nameval(STR(state->attr_buf), &attr_name, &attr_value); + if (error_text != 0) { + msg_warn("%s: message rejected: malformed attribute: %s: %.100s", + state->queue_id, error_text, buf); + state->errs |= CLEANUP_STAT_BAD; + return; + } + /* Zero-length values are place holders for unavailable values. */ + if (*attr_value == 0) { + msg_warn("%s: spurious null attribute value for \"%s\" -- ignored", + state->queue_id, attr_name); + return; + } + if ((junk = rec_attr_map(attr_name)) != 0) { + mapped_buf = attr_value; + mapped_type = junk; + } + } + + /* + * Sanity check. + */ + if (strchr(REC_TYPE_ENVELOPE, type) == 0) { + msg_warn("%s: message rejected: unexpected record type %d in envelope", + state->queue_id, type); + state->errs |= CLEANUP_STAT_BAD; + return; + } + + /* + * Although recipient records appear at the end of the initial or + * extracted envelope, the code for processing recipient records is first + * because there can be lots of them. + * + * Recipient records may be mixed with other information (such as FILTER or + * REDIRECT actions from SMTPD). In that case the queue manager needs to + * examine all queue file records before it can start delivery. This is + * not a problem when SMTPD recipient lists are small. + * + * However, if recipient records are not mixed with other records + * (typically, mailing list mail) then we can make an optimization: the + * queue manager does not need to examine every envelope record before it + * can start deliveries. This can help with very large mailing lists. + */ + + /* + * On the transition from non-recipient records to recipient records, + * emit some records and do some sanity checks. + * + * XXX Moving the envelope sender (and the test for its presence) to the + * extracted segment can reduce qmqpd memory requirements because it no + * longer needs to read the entire message into main memory. + */ + if ((state->flags & CLEANUP_FLAG_INRCPT) == 0 + && strchr(REC_TYPE_ENV_RECIPIENT, type) != 0) { + if (state->sender == 0) { + msg_warn("%s: message rejected: missing sender envelope record", + state->queue_id); + state->errs |= CLEANUP_STAT_BAD; + return; + } + if (state->arrival_time.tv_sec == 0) { + msg_warn("%s: message rejected: missing time envelope record", + state->queue_id); + state->errs |= CLEANUP_STAT_BAD; + return; + } + + /* + * XXX This works by accident, because the sender is recorded at the + * beginning of the envelope segment. + */ + if ((state->flags & CLEANUP_FLAG_WARN_SEEN) == 0 + && state->sender && *state->sender + && var_delay_warn_time > 0) { + cleanup_out_format(state, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT, + REC_TYPE_WARN_ARG(state->arrival_time.tv_sec + + var_delay_warn_time)); + } + state->flags |= CLEANUP_FLAG_INRCPT; + } + + /* + * Initial envelope recipient record processing. + */ + if (type == REC_TYPE_RCPT) { + if (state->sender == 0) { /* protect showq */ + msg_warn("%s: message rejected: envelope recipient precedes sender", + state->queue_id); + state->errs |= CLEANUP_STAT_BAD; + return; + } + if (state->orig_rcpt == 0) + state->orig_rcpt = mystrdup(buf); + cleanup_addr_recipient(state, buf); + if (cleanup_milters != 0 + && state->milters == 0 + && CLEANUP_MILTER_OK(state)) + cleanup_milter_emul_rcpt(state, cleanup_milters, state->recip); + myfree(state->orig_rcpt); + state->orig_rcpt = 0; + if (state->dsn_orcpt != 0) { + myfree(state->dsn_orcpt); + state->dsn_orcpt = 0; + } + state->dsn_notify = 0; + return; + } + if (type == REC_TYPE_DONE || type == REC_TYPE_DRCP) { + if (state->orig_rcpt != 0) { + myfree(state->orig_rcpt); + state->orig_rcpt = 0; + } + if (state->dsn_orcpt != 0) { + myfree(state->dsn_orcpt); + state->dsn_orcpt = 0; + } + state->dsn_notify = 0; + return; + } + if (mapped_type == REC_TYPE_DSN_ORCPT) { + if (state->dsn_orcpt) { + msg_warn("%s: ignoring out-of-order DSN original recipient record <%.200s>", + state->queue_id, state->dsn_orcpt); + myfree(state->dsn_orcpt); + } + state->dsn_orcpt = mystrdup(mapped_buf); + return; + } + if (mapped_type == REC_TYPE_DSN_NOTIFY) { + if (state->dsn_notify) { + msg_warn("%s: ignoring out-of-order DSN notify record <%d>", + state->queue_id, state->dsn_notify); + state->dsn_notify = 0; + } + if (!alldig(mapped_buf) || (junk = atoi(mapped_buf)) == 0 + || DSN_NOTIFY_OK(junk) == 0) + msg_warn("%s: ignoring malformed DSN notify record <%.200s>", + state->queue_id, buf); + else + state->qmgr_opts |= + QMGR_READ_FLAG_FROM_DSN(state->dsn_notify = junk); + return; + } + if (type == REC_TYPE_ORCP) { + if (state->orig_rcpt != 0) { + msg_warn("%s: ignoring out-of-order original recipient record <%.200s>", + state->queue_id, state->orig_rcpt); + myfree(state->orig_rcpt); + } + state->orig_rcpt = mystrdup(buf); + return; + } + if (type == REC_TYPE_MESG) { + state->action = cleanup_message; + if (state->flags & CLEANUP_FLAG_INRCPT) { + if (state->milters || cleanup_milters) { + /* Make room to append recipient. */ + if ((state->append_rcpt_pt_offset = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L); + if ((state->append_rcpt_pt_target = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); + } + state->flags &= ~CLEANUP_FLAG_INRCPT; + } + return; + } + + /* + * Initial envelope non-recipient record processing. + * + * If the message was requeued with "postsuper -r" use their + * SMTPUTF8_REQUESTED flag. + */ + if (state->flags & CLEANUP_FLAG_INRCPT) + /* Tell qmgr that recipient records are mixed with other information. */ + state->qmgr_opts |= QMGR_READ_FLAG_MIXED_RCPT_OTHER; + if (type == REC_TYPE_SIZE) { + /* Use our own SIZE record, except for the SMTPUTF8_REQUESTED flag. */ + (void) sscanf(buf, "%*s $*s %*s %*s %*s %d", &state->smtputf8); + state->smtputf8 &= SMTPUTF8_FLAG_REQUESTED; + return; + } + if (mapped_type == REC_TYPE_CTIME) + /* Use our own expiration time base record instead. */ + return; + if (type == REC_TYPE_TIME) { + /* First instance wins. */ + if (state->arrival_time.tv_sec == 0) { + REC_TYPE_TIME_SCAN(buf, state->arrival_time); + cleanup_out(state, type, buf, len); + } + /* Generate our own expiration time base record. */ + cleanup_out_format(state, REC_TYPE_ATTR, "%s=%ld", + MAIL_ATTR_CREATE_TIME, (long) time((time_t *) 0)); + return; + } + if (type == REC_TYPE_FULL) { + /* First instance wins. */ + if (state->fullname == 0) { + state->fullname = mystrdup(buf); + cleanup_out(state, type, buf, len); + } + return; + } + if (type == REC_TYPE_FROM) { + off_t after_sender_offs; + + /* Allow only one instance. */ + if (state->sender != 0) { + msg_warn("%s: message rejected: multiple envelope sender records", + state->queue_id); + state->errs |= CLEANUP_STAT_BAD; + return; + } + if (state->milters || cleanup_milters) { + /* Remember the sender record offset. */ + if ((state->sender_pt_offset = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); + } + after_sender_offs = cleanup_addr_sender(state, buf); + if (state->milters || cleanup_milters) { + /* Remember the after-sender record offset. */ + state->sender_pt_target = after_sender_offs; + } + if (cleanup_milters != 0 + && state->milters == 0 + && CLEANUP_MILTER_OK(state)) + cleanup_milter_emul_mail(state, cleanup_milters, state->sender); + return; + } + if (mapped_type == REC_TYPE_DSN_ENVID) { + /* Don't break "postsuper -r" after Milter overrides ENVID. */ + if (!allprint(mapped_buf)) { + msg_warn("%s: message rejected: bad DSN envelope ID record", + state->queue_id); + state->errs |= CLEANUP_STAT_BAD; + return; + } + if (state->dsn_envid != 0) + myfree(state->dsn_envid); + state->dsn_envid = mystrdup(mapped_buf); + cleanup_out(state, type, buf, len); + return; + } + if (mapped_type == REC_TYPE_DSN_RET) { + /* Don't break "postsuper -r" after Milter overrides RET. */ + if (!alldig(mapped_buf) || (junk = atoi(mapped_buf)) == 0 + || DSN_RET_OK(junk) == 0) { + msg_warn("%s: message rejected: bad DSN RET record <%.200s>", + state->queue_id, buf); + state->errs |= CLEANUP_STAT_BAD; + return; + } + state->dsn_ret = junk; + cleanup_out(state, type, buf, len); + return; + } + if (type == REC_TYPE_WARN) { + /* First instance wins. */ + if ((state->flags & CLEANUP_FLAG_WARN_SEEN) == 0) { + state->flags |= CLEANUP_FLAG_WARN_SEEN; + cleanup_out(state, type, buf, len); + } + return; + } + /* XXX Needed for cleanup_bounce(); sanity check usage. */ + if (type == REC_TYPE_VERP) { + if (state->verp_delims == 0) { + if (state->sender == 0 || state->sender[0] == 0) { + msg_warn("%s: ignoring VERP request for null sender", + state->queue_id); + } else if (verp_delims_verify(buf) != 0) { + msg_warn("%s: ignoring bad VERP request: \"%.100s\"", + state->queue_id, buf); + } else { + state->verp_delims = mystrdup(buf); + cleanup_out(state, type, buf, len); + } + } + return; + } + if (type == REC_TYPE_ATTR) { + if (state->attr->used >= var_qattr_count_limit) { + msg_warn("%s: message rejected: attribute count exceeds limit %d", + state->queue_id, var_qattr_count_limit); + state->errs |= CLEANUP_STAT_BAD; + return; + } + if (strcmp(attr_name, MAIL_ATTR_RWR_CONTEXT) == 0) { + /* Choose header rewriting context. See also cleanup_addr.c. */ + if (STREQ(attr_value, MAIL_ATTR_RWR_LOCAL)) { + state->hdr_rewrite_context = MAIL_ATTR_RWR_LOCAL; + } else if (STREQ(attr_value, MAIL_ATTR_RWR_REMOTE)) { + state->hdr_rewrite_context = + (*var_remote_rwr_domain ? MAIL_ATTR_RWR_REMOTE : 0); + } else { + msg_warn("%s: message rejected: bad rewriting context: %.100s", + state->queue_id, attr_value); + state->errs |= CLEANUP_STAT_BAD; + return; + } + } + if (strcmp(attr_name, MAIL_ATTR_TRACE_FLAGS) == 0) { + if (!alldig(attr_value)) { + msg_warn("%s: message rejected: bad TFLAG record <%.200s>", + state->queue_id, buf); + state->errs |= CLEANUP_STAT_BAD; + return; + } + if (state->tflags == 0) + state->tflags = DEL_REQ_TRACE_FLAGS(atoi(attr_value)); + } + nvtable_update(state->attr, attr_name, attr_value); + cleanup_out(state, type, buf, len); + return; + } else { + cleanup_out(state, type, buf, len); + return; + } +} diff --git a/src/cleanup/cleanup_extracted.c b/src/cleanup/cleanup_extracted.c new file mode 100644 index 0000000..e6c2122 --- /dev/null +++ b/src/cleanup/cleanup_extracted.c @@ -0,0 +1,328 @@ +/*++ +/* NAME +/* cleanup_extracted 3 +/* SUMMARY +/* process extracted segment +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* void cleanup_extracted(state, type, buf, len) +/* CLEANUP_STATE *state; +/* int type; +/* const char *buf; +/* ssize_t len; +/* DESCRIPTION +/* This module processes message records with information extracted +/* from message content, or with recipients that are stored after the +/* message content. It updates recipient records, writes extracted +/* information records to the output, and writes the queue +/* file end marker. The queue file is left in a state that +/* is suitable for Milter inspection, but the size record still +/* contains dummy values. +/* +/* Arguments: +/* .IP state +/* Queue file and message processing state. This state is updated +/* as records are processed and as errors happen. +/* .IP type +/* Record type. +/* .IP buf +/* Record content. +/* .IP len +/* Record content length. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +#define STR(x) vstring_str(x) + +static void cleanup_extracted_process(CLEANUP_STATE *, int, const char *, ssize_t); +static void cleanup_extracted_finish(CLEANUP_STATE *); + +/* cleanup_extracted - initialize extracted segment */ + +void cleanup_extracted(CLEANUP_STATE *state, int type, + const char *buf, ssize_t len) +{ + + /* + * Start the extracted segment. + */ + cleanup_out_string(state, REC_TYPE_XTRA, ""); + + /* + * Pass control to the actual envelope processing routine. + */ + state->action = cleanup_extracted_process; + cleanup_extracted_process(state, type, buf, len); +} + +/* cleanup_extracted_process - process one extracted envelope record */ + +void cleanup_extracted_process(CLEANUP_STATE *state, int type, + const char *buf, ssize_t len) +{ + const char *myname = "cleanup_extracted_process"; + const char *encoding; + char *attr_name; + char *attr_value; + const char *error_text; + int extra_opts; + int junk; + +#ifdef DELAY_ACTION + int defer_delay; + +#endif + + if (msg_verbose) + msg_info("extracted envelope %c %.*s", type, (int) len, buf); + + if (type == REC_TYPE_FLGS) { + /* Not part of queue file format. */ + extra_opts = atoi(buf); + if (extra_opts & ~CLEANUP_FLAG_MASK_EXTRA) + msg_warn("%s: ignoring bad extra flags: 0x%x", + state->queue_id, extra_opts); + else + state->flags |= extra_opts; + return; + } +#ifdef DELAY_ACTION + if (type == REC_TYPE_DELAY) { + /* Not part of queue file format. */ + defer_delay = atoi(buf); + if (defer_delay <= 0) + msg_warn("%s: ignoring bad delay time: %s", state->queue_id, buf); + else + state->defer_delay = defer_delay; + return; + } +#endif + + if (strchr(REC_TYPE_EXTRACT, type) == 0) { + msg_warn("%s: message rejected: " + "unexpected record type %d in extracted envelope", + state->queue_id, type); + state->errs |= CLEANUP_STAT_BAD; + return; + } + + /* + * Map DSN attribute name to pseudo record type so that we don't have to + * pollute the queue file with records that are incompatible with past + * Postfix versions. Preferably, people should be able to back out from + * an upgrade without losing mail. + */ + if (type == REC_TYPE_ATTR) { + vstring_strcpy(state->attr_buf, buf); + error_text = split_nameval(STR(state->attr_buf), &attr_name, &attr_value); + if (error_text != 0) { + msg_warn("%s: message rejected: malformed attribute: %s: %.100s", + state->queue_id, error_text, buf); + state->errs |= CLEANUP_STAT_BAD; + return; + } + /* Zero-length values are place holders for unavailable values. */ + if (*attr_value == 0) { + msg_warn("%s: spurious null attribute value for \"%s\" -- ignored", + state->queue_id, attr_name); + return; + } + if ((junk = rec_attr_map(attr_name)) != 0) { + buf = attr_value; + type = junk; + } + } + + /* + * On the transition from non-recipient records to recipient records, + * emit optional information from header/body content. + */ + if ((state->flags & CLEANUP_FLAG_INRCPT) == 0 + && strchr(REC_TYPE_EXT_RECIPIENT, type) != 0) { + if (state->filter != 0) + cleanup_out_string(state, REC_TYPE_FILT, state->filter); + if (state->redirect != 0) + cleanup_out_string(state, REC_TYPE_RDR, state->redirect); + if ((encoding = nvtable_find(state->attr, MAIL_ATTR_ENCODING)) != 0) + cleanup_out_format(state, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ENCODING, encoding); + state->flags |= CLEANUP_FLAG_INRCPT; + /* Make room to append more meta records. */ + if (state->milters || cleanup_milters) { + if ((state->append_meta_pt_offset = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L); + if ((state->append_meta_pt_target = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); + } + } + + /* + * Extracted envelope recipient record processing. + */ + if (type == REC_TYPE_RCPT) { + if (state->sender == 0) { /* protect showq */ + msg_warn("%s: message rejected: envelope recipient precedes sender", + state->queue_id); + state->errs |= CLEANUP_STAT_BAD; + return; + } + if (state->orig_rcpt == 0) + state->orig_rcpt = mystrdup(buf); + cleanup_addr_recipient(state, buf); + if (cleanup_milters != 0 + && state->milters == 0 + && CLEANUP_MILTER_OK(state)) + cleanup_milter_emul_rcpt(state, cleanup_milters, state->recip); + myfree(state->orig_rcpt); + state->orig_rcpt = 0; + if (state->dsn_orcpt != 0) { + myfree(state->dsn_orcpt); + state->dsn_orcpt = 0; + } + state->dsn_notify = 0; + return; + } + if (type == REC_TYPE_DONE || type == REC_TYPE_DRCP) { + if (state->orig_rcpt != 0) { + myfree(state->orig_rcpt); + state->orig_rcpt = 0; + } + if (state->dsn_orcpt != 0) { + myfree(state->dsn_orcpt); + state->dsn_orcpt = 0; + } + state->dsn_notify = 0; + return; + } + if (type == REC_TYPE_DSN_ORCPT) { + if (state->dsn_orcpt) { + msg_warn("%s: ignoring out-of-order DSN original recipient record <%.200s>", + state->queue_id, state->dsn_orcpt); + myfree(state->dsn_orcpt); + } + state->dsn_orcpt = mystrdup(buf); + return; + } + if (type == REC_TYPE_DSN_NOTIFY) { + if (state->dsn_notify) { + msg_warn("%s: ignoring out-of-order DSN notify record <%d>", + state->queue_id, state->dsn_notify); + state->dsn_notify = 0; + } + if (!alldig(buf) || (junk = atoi(buf)) == 0 || DSN_NOTIFY_OK(junk) == 0) + msg_warn("%s: ignoring malformed dsn notify record <%.200s>", + state->queue_id, buf); + else + state->qmgr_opts |= + QMGR_READ_FLAG_FROM_DSN(state->dsn_notify = junk); + return; + } + if (type == REC_TYPE_ORCP) { + if (state->orig_rcpt != 0) { + msg_warn("%s: ignoring out-of-order original recipient record <%.200s>", + state->queue_id, buf); + myfree(state->orig_rcpt); + } + state->orig_rcpt = mystrdup(buf); + return; + } + if (type == REC_TYPE_END) { + /* Make room to append recipient. */ + if ((state->milters || cleanup_milters) + && state->append_rcpt_pt_offset < 0) { + if ((state->append_rcpt_pt_offset = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L); + if ((state->append_rcpt_pt_target = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m:", myname, cleanup_path); + } + state->flags &= ~CLEANUP_FLAG_INRCPT; + state->flags |= CLEANUP_FLAG_END_SEEN; + cleanup_extracted_finish(state); + return; + } + + /* + * Extracted envelope non-recipient record processing. + */ + if (state->flags & CLEANUP_FLAG_INRCPT) + /* Tell qmgr that recipient records are mixed with other information. */ + state->qmgr_opts |= QMGR_READ_FLAG_MIXED_RCPT_OTHER; + cleanup_out(state, type, buf, len); + return; +} + +/* cleanup_extracted_finish - complete the third message segment */ + +void cleanup_extracted_finish(CLEANUP_STATE *state) +{ + + /* + * On the way out, add the optional automatic BCC recipient. + */ + if ((state->flags & CLEANUP_FLAG_BCC_OK) + && state->recip != 0 && *var_always_bcc) + cleanup_addr_bcc(state, var_always_bcc); + + /* + * Flush non-Milter header/body_checks BCC recipients. Clear hbc_rcpt + * so that it can be used for other purposes. + */ + if (state->hbc_rcpt) { + if (CLEANUP_OUT_OK(state) && state->recip != 0) { + char **cpp; + + for (cpp = state->hbc_rcpt->argv; *cpp; cpp++) + cleanup_addr_bcc(state, *cpp); + } + argv_free(state->hbc_rcpt); + state->hbc_rcpt = 0; + } + + /* + * Terminate the extracted segment. + */ + cleanup_out_string(state, REC_TYPE_END, ""); +} diff --git a/src/cleanup/cleanup_final.c b/src/cleanup/cleanup_final.c new file mode 100644 index 0000000..db77fc7 --- /dev/null +++ b/src/cleanup/cleanup_final.c @@ -0,0 +1,78 @@ +/*++ +/* NAME +/* cleanup_final 3 +/* SUMMARY +/* finalize queue file +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* void cleanup_final(state) +/* CLEANUP_STATE *state; +/* DESCRIPTION +/* cleanup_final() performs final queue file content (not +/* attribute) updates so that the file is ready to be closed. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +/* cleanup_final - final queue file content updates */ + +void cleanup_final(CLEANUP_STATE *state) +{ + const char *myname = "cleanup_final"; + + /* + * vstream_fseek() would flush the buffer anyway, but the code just reads + * better if we flush first, because it makes seek error handling more + * straightforward. + */ + if (vstream_fflush(state->dst)) { + if (errno == EFBIG) { + msg_warn("%s: queue file size limit exceeded", state->queue_id); + state->errs |= CLEANUP_STAT_SIZE; + } else { + msg_warn("%s: write queue file: %m", state->queue_id); + state->errs |= CLEANUP_STAT_WRITE; + } + return; + } + + /* + * Update the preliminary message size and count fields with the actual + * values. + */ + if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0) + msg_fatal("%s: vstream_fseek %s: %m", myname, cleanup_path); + cleanup_out_format(state, REC_TYPE_SIZE, REC_TYPE_SIZE_FORMAT, + (REC_TYPE_SIZE_CAST1) (state->xtra_offset - state->data_offset), + (REC_TYPE_SIZE_CAST2) state->data_offset, + (REC_TYPE_SIZE_CAST3) state->rcpt_count, + (REC_TYPE_SIZE_CAST4) state->qmgr_opts, + (REC_TYPE_SIZE_CAST5) state->cont_length, + (REC_TYPE_SIZE_CAST6) state->smtputf8); +} diff --git a/src/cleanup/cleanup_init.c b/src/cleanup/cleanup_init.c new file mode 100644 index 0000000..369a019 --- /dev/null +++ b/src/cleanup/cleanup_init.c @@ -0,0 +1,476 @@ +/*++ +/* NAME +/* cleanup_init 3 +/* SUMMARY +/* cleanup callable interface, initializations +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* CONFIG_INT_TABLE cleanup_int_table[]; +/* +/* CONFIG_BOOL_TABLE cleanup_bool_table[]; +/* +/* CONFIG_STR_TABLE cleanup_str_table[]; +/* +/* CONFIG_TIME_TABLE cleanup_time_table[]; +/* +/* void cleanup_pre_jail(service_name, argv) +/* char *service_name; +/* char **argv; +/* +/* void cleanup_post_jail(service_name, argv) +/* char *service_name; +/* char **argv; +/* +/* char *cleanup_path; +/* VSTRING *cleanup_trace_path; +/* +/* void cleanup_all() +/* +/* void cleanup_sig(sigval) +/* int sigval; +/* DESCRIPTION +/* This module implements a callable interface to the cleanup service +/* for one-time initializations that must be done before any message +/* processing can take place. +/* +/* cleanup_{int,str,time}_table[] specify configuration +/* parameters that must be initialized before calling any functions +/* in this module. These tables satisfy the interface as specified in +/* single_service(3). +/* +/* cleanup_pre_jail() and cleanup_post_jail() perform mandatory +/* initializations before and after the process enters the optional +/* chroot jail. These functions satisfy the interface as specified +/* in single_service(3). +/* +/* cleanup_path is either a null pointer or it is the name of a queue +/* file that currently is being written. This information is used +/* by cleanup_all() to remove incomplete files after a fatal error, +/* or by cleanup_sig() after arrival of a SIGTERM signal. +/* +/* cleanup_trace_path is either a null pointer or the pathname of a +/* trace logfile with DSN SUCCESS notifications. This information is +/* used to remove a trace file when the mail transaction is canceled. +/* +/* cleanup_all() must be called in case of fatal error, in order +/* to remove an incomplete queue file. +/* +/* cleanup_sig() must be called in case of SIGTERM, in order +/* to remove an incomplete queue file. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* SEE ALSO +/* cleanup_api(3) cleanup callable interface, message processing +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include /* milter_macro_v */ +#include +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + + /* + * Global state: any queue files that we have open, so that the error + * handler can clean up in case of trouble. + */ +char *cleanup_path; /* queue file name */ + + /* + * Another piece of global state: pathnames of partial bounce or trace + * logfiles that need to be cleaned up when the cleanup request is aborted. + */ +VSTRING *cleanup_trace_path; + + /* + * Tunable parameters. + */ +int var_hopcount_limit; /* max mailer hop count */ +char *var_canonical_maps; /* common canonical maps */ +char *var_send_canon_maps; /* sender canonical maps */ +char *var_rcpt_canon_maps; /* recipient canonical maps */ +char *var_canon_classes; /* what to canonicalize */ +char *var_send_canon_classes; /* what sender to canonicalize */ +char *var_rcpt_canon_classes; /* what recipient to canonicalize */ +char *var_virt_alias_maps; /* virtual alias maps */ +char *var_masq_domains; /* masquerade domains */ +char *var_masq_exceptions; /* users not masqueraded */ +char *var_header_checks; /* primary header checks */ +char *var_mimehdr_checks; /* mime header checks */ +char *var_nesthdr_checks; /* nested header checks */ +char *var_body_checks; /* any body checks */ +int var_dup_filter_limit; /* recipient dup filter */ +char *var_empty_addr; /* destination of bounced bounces */ +int var_delay_warn_time; /* delay that triggers warning */ +char *var_prop_extension; /* propagate unmatched extension */ +char *var_always_bcc; /* big brother */ +char *var_rcpt_witheld; /* recipients not disclosed */ +char *var_masq_classes; /* what to masquerade */ +int var_qattr_count_limit; /* named attribute limit */ +int var_virt_recur_limit; /* maximum virtual alias recursion */ +int var_virt_expan_limit; /* maximum virtual alias expansion */ +int var_body_check_len; /* when to stop body scan */ +char *var_send_bcc_maps; /* sender auto-bcc maps */ +char *var_rcpt_bcc_maps; /* recipient auto-bcc maps */ +char *var_remote_rwr_domain; /* header-only surrogate */ +char *var_msg_reject_chars; /* reject these characters */ +char *var_msg_strip_chars; /* strip these characters */ +int var_verp_bounce_off; /* don't verp the bounces */ +int var_milt_conn_time; /* milter connect/handshake timeout */ +int var_milt_cmd_time; /* milter command timeout */ +int var_milt_msg_time; /* milter content timeout */ +char *var_milt_protocol; /* Sendmail 8 milter protocol */ +char *var_milt_def_action; /* default milter action */ +char *var_milt_daemon_name; /* {daemon_name} macro value */ +char *var_milt_v; /* {v} macro value */ +char *var_milt_conn_macros; /* connect macros */ +char *var_milt_helo_macros; /* HELO macros */ +char *var_milt_mail_macros; /* MAIL FROM macros */ +char *var_milt_rcpt_macros; /* RCPT TO macros */ +char *var_milt_data_macros; /* DATA macros */ +char *var_milt_eoh_macros; /* end-of-header macros */ +char *var_milt_eod_macros; /* end-of-data macros */ +char *var_milt_unk_macros; /* unknown command macros */ +char *var_cleanup_milters; /* non-SMTP mail */ +char *var_milt_head_checks; /* post-Milter header checks */ +char *var_milt_macro_deflts; /* default macro settings */ +int var_auto_8bit_enc_hdr; /* auto-detect 8bit encoding header */ +int var_always_add_hdrs; /* always add missing headers */ +int var_virt_addrlen_limit; /* stop exponential growth */ +char *var_hfrom_format; /* header_from_format */ +int var_cleanup_mask_stray_cr_lf; /* replace stray CR or LF with space */ + +const CONFIG_INT_TABLE cleanup_int_table[] = { + VAR_HOPCOUNT_LIMIT, DEF_HOPCOUNT_LIMIT, &var_hopcount_limit, 1, 0, + VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0, + VAR_QATTR_COUNT_LIMIT, DEF_QATTR_COUNT_LIMIT, &var_qattr_count_limit, 1, 0, + VAR_VIRT_RECUR_LIMIT, DEF_VIRT_RECUR_LIMIT, &var_virt_recur_limit, 1, 0, + VAR_VIRT_EXPAN_LIMIT, DEF_VIRT_EXPAN_LIMIT, &var_virt_expan_limit, 1, 0, + VAR_VIRT_ADDRLEN_LIMIT, DEF_VIRT_ADDRLEN_LIMIT, &var_virt_addrlen_limit, 1, 0, + VAR_BODY_CHECK_LEN, DEF_BODY_CHECK_LEN, &var_body_check_len, 0, 0, + 0, +}; + +const CONFIG_BOOL_TABLE cleanup_bool_table[] = { + VAR_VERP_BOUNCE_OFF, DEF_VERP_BOUNCE_OFF, &var_verp_bounce_off, + VAR_AUTO_8BIT_ENC_HDR, DEF_AUTO_8BIT_ENC_HDR, &var_auto_8bit_enc_hdr, + VAR_ALWAYS_ADD_HDRS, DEF_ALWAYS_ADD_HDRS, &var_always_add_hdrs, + VAR_CLEANUP_MASK_STRAY_CR_LF, DEF_CLEANUP_MASK_STRAY_CR_LF, &var_cleanup_mask_stray_cr_lf, + 0, +}; + +const CONFIG_TIME_TABLE cleanup_time_table[] = { + VAR_DELAY_WARN_TIME, DEF_DELAY_WARN_TIME, &var_delay_warn_time, 0, 0, + VAR_MILT_CONN_TIME, DEF_MILT_CONN_TIME, &var_milt_conn_time, 1, 0, + VAR_MILT_CMD_TIME, DEF_MILT_CMD_TIME, &var_milt_cmd_time, 1, 0, + VAR_MILT_MSG_TIME, DEF_MILT_MSG_TIME, &var_milt_msg_time, 1, 0, + 0, +}; + +const CONFIG_STR_TABLE cleanup_str_table[] = { + VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0, + VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0, + VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0, + VAR_CANON_CLASSES, DEF_CANON_CLASSES, &var_canon_classes, 1, 0, + VAR_SEND_CANON_CLASSES, DEF_SEND_CANON_CLASSES, &var_send_canon_classes, 1, 0, + VAR_RCPT_CANON_CLASSES, DEF_RCPT_CANON_CLASSES, &var_rcpt_canon_classes, 1, 0, + VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0, + VAR_MASQ_DOMAINS, DEF_MASQ_DOMAINS, &var_masq_domains, 0, 0, + VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0, + VAR_MASQ_EXCEPTIONS, DEF_MASQ_EXCEPTIONS, &var_masq_exceptions, 0, 0, + VAR_HEADER_CHECKS, DEF_HEADER_CHECKS, &var_header_checks, 0, 0, + VAR_MIMEHDR_CHECKS, DEF_MIMEHDR_CHECKS, &var_mimehdr_checks, 0, 0, + VAR_NESTHDR_CHECKS, DEF_NESTHDR_CHECKS, &var_nesthdr_checks, 0, 0, + VAR_BODY_CHECKS, DEF_BODY_CHECKS, &var_body_checks, 0, 0, + VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0, + VAR_ALWAYS_BCC, DEF_ALWAYS_BCC, &var_always_bcc, 0, 0, + VAR_RCPT_WITHELD, DEF_RCPT_WITHELD, &var_rcpt_witheld, 0, 0, + VAR_MASQ_CLASSES, DEF_MASQ_CLASSES, &var_masq_classes, 0, 0, + VAR_SEND_BCC_MAPS, DEF_SEND_BCC_MAPS, &var_send_bcc_maps, 0, 0, + VAR_RCPT_BCC_MAPS, DEF_RCPT_BCC_MAPS, &var_rcpt_bcc_maps, 0, 0, + VAR_REM_RWR_DOMAIN, DEF_REM_RWR_DOMAIN, &var_remote_rwr_domain, 0, 0, + VAR_MSG_REJECT_CHARS, DEF_MSG_REJECT_CHARS, &var_msg_reject_chars, 0, 0, + VAR_MSG_STRIP_CHARS, DEF_MSG_STRIP_CHARS, &var_msg_strip_chars, 0, 0, + VAR_MILT_PROTOCOL, DEF_MILT_PROTOCOL, &var_milt_protocol, 1, 0, + VAR_MILT_DEF_ACTION, DEF_MILT_DEF_ACTION, &var_milt_def_action, 1, 0, + VAR_MILT_DAEMON_NAME, DEF_MILT_DAEMON_NAME, &var_milt_daemon_name, 1, 0, + VAR_MILT_V, DEF_MILT_V, &var_milt_v, 1, 0, + VAR_MILT_CONN_MACROS, DEF_MILT_CONN_MACROS, &var_milt_conn_macros, 0, 0, + VAR_MILT_HELO_MACROS, DEF_MILT_HELO_MACROS, &var_milt_helo_macros, 0, 0, + VAR_MILT_MAIL_MACROS, DEF_MILT_MAIL_MACROS, &var_milt_mail_macros, 0, 0, + VAR_MILT_RCPT_MACROS, DEF_MILT_RCPT_MACROS, &var_milt_rcpt_macros, 0, 0, + VAR_MILT_DATA_MACROS, DEF_MILT_DATA_MACROS, &var_milt_data_macros, 0, 0, + VAR_MILT_EOH_MACROS, DEF_MILT_EOH_MACROS, &var_milt_eoh_macros, 0, 0, + VAR_MILT_EOD_MACROS, DEF_MILT_EOD_MACROS, &var_milt_eod_macros, 0, 0, + VAR_MILT_UNK_MACROS, DEF_MILT_UNK_MACROS, &var_milt_unk_macros, 0, 0, + VAR_CLEANUP_MILTERS, DEF_CLEANUP_MILTERS, &var_cleanup_milters, 0, 0, + VAR_MILT_HEAD_CHECKS, DEF_MILT_HEAD_CHECKS, &var_milt_head_checks, 0, 0, + VAR_MILT_MACRO_DEFLTS, DEF_MILT_MACRO_DEFLTS, &var_milt_macro_deflts, 0, 0, + VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0, + 0, +}; + + /* + * Mappings. + */ +MAPS *cleanup_comm_canon_maps; +MAPS *cleanup_send_canon_maps; +MAPS *cleanup_rcpt_canon_maps; +int cleanup_comm_canon_flags; +int cleanup_send_canon_flags; +int cleanup_rcpt_canon_flags; +MAPS *cleanup_header_checks; +MAPS *cleanup_mimehdr_checks; +MAPS *cleanup_nesthdr_checks; +MAPS *cleanup_body_checks; +MAPS *cleanup_virt_alias_maps; +ARGV *cleanup_masq_domains; +STRING_LIST *cleanup_masq_exceptions; +int cleanup_masq_flags; +MAPS *cleanup_send_bcc_maps; +MAPS *cleanup_rcpt_bcc_maps; + + /* + * Character filters. + */ +VSTRING *cleanup_reject_chars; +VSTRING *cleanup_strip_chars; + + /* + * Address extension propagation restrictions. + */ +int cleanup_ext_prop_mask; + + /* + * Milter support. + */ +MILTERS *cleanup_milters; + + /* + * From: header format. + */ +int cleanup_hfrom_format; + +/* cleanup_all - callback for the runtime error handler */ + +void cleanup_all(void) +{ + cleanup_sig(0); +} + +/* cleanup_sig - callback for the SIGTERM handler */ + +void cleanup_sig(int sig) +{ + + /* + * msg_fatal() is safe against calling itself recursively, but signals + * need extra safety. + * + * XXX While running as a signal handler, can't ask the memory manager to + * release VSTRING storage. + */ + if (signal(SIGTERM, SIG_IGN) != SIG_IGN) { + if (cleanup_trace_path) { + (void) REMOVE(vstring_str(cleanup_trace_path)); + cleanup_trace_path = 0; + } + if (cleanup_path) { + (void) REMOVE(cleanup_path); + cleanup_path = 0; + } + if (sig) + _exit(sig); + } +} + +/* cleanup_pre_jail - initialize before entering the chroot jail */ + +void cleanup_pre_jail(char *unused_name, char **unused_argv) +{ + static const NAME_MASK send_canon_class_table[] = { + CANON_CLASS_ENV_FROM, CLEANUP_CANON_FLAG_ENV_FROM, + CANON_CLASS_HDR_FROM, CLEANUP_CANON_FLAG_HDR_FROM, + 0, + }; + static const NAME_MASK rcpt_canon_class_table[] = { + CANON_CLASS_ENV_RCPT, CLEANUP_CANON_FLAG_ENV_RCPT, + CANON_CLASS_HDR_RCPT, CLEANUP_CANON_FLAG_HDR_RCPT, + 0, + }; + static const NAME_MASK canon_class_table[] = { + CANON_CLASS_ENV_FROM, CLEANUP_CANON_FLAG_ENV_FROM, + CANON_CLASS_ENV_RCPT, CLEANUP_CANON_FLAG_ENV_RCPT, + CANON_CLASS_HDR_FROM, CLEANUP_CANON_FLAG_HDR_FROM, + CANON_CLASS_HDR_RCPT, CLEANUP_CANON_FLAG_HDR_RCPT, + 0, + }; + static const NAME_MASK masq_class_table[] = { + MASQ_CLASS_ENV_FROM, CLEANUP_MASQ_FLAG_ENV_FROM, + MASQ_CLASS_ENV_RCPT, CLEANUP_MASQ_FLAG_ENV_RCPT, + MASQ_CLASS_HDR_FROM, CLEANUP_MASQ_FLAG_HDR_FROM, + MASQ_CLASS_HDR_RCPT, CLEANUP_MASQ_FLAG_HDR_RCPT, + 0, + }; + + if (*var_canonical_maps) + cleanup_comm_canon_maps = + maps_create(VAR_CANONICAL_MAPS, var_canonical_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if (*var_send_canon_maps) + cleanup_send_canon_maps = + maps_create(VAR_SEND_CANON_MAPS, var_send_canon_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if (*var_rcpt_canon_maps) + cleanup_rcpt_canon_maps = + maps_create(VAR_RCPT_CANON_MAPS, var_rcpt_canon_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if (*var_virt_alias_maps) + cleanup_virt_alias_maps = maps_create(VAR_VIRT_ALIAS_MAPS, + var_virt_alias_maps, + DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if (*var_canon_classes) + cleanup_comm_canon_flags = + name_mask(VAR_CANON_CLASSES, canon_class_table, + var_canon_classes); + if (*var_send_canon_classes) + cleanup_send_canon_flags = + name_mask(VAR_CANON_CLASSES, send_canon_class_table, + var_send_canon_classes); + if (*var_rcpt_canon_classes) + cleanup_rcpt_canon_flags = + name_mask(VAR_CANON_CLASSES, rcpt_canon_class_table, + var_rcpt_canon_classes); + if (*var_masq_domains) + cleanup_masq_domains = argv_split(var_masq_domains, CHARS_COMMA_SP); + if (*var_header_checks) + cleanup_header_checks = + maps_create(VAR_HEADER_CHECKS, var_header_checks, DICT_FLAG_LOCK); + if (*var_mimehdr_checks) + cleanup_mimehdr_checks = + maps_create(VAR_MIMEHDR_CHECKS, var_mimehdr_checks, DICT_FLAG_LOCK); + if (*var_nesthdr_checks) + cleanup_nesthdr_checks = + maps_create(VAR_NESTHDR_CHECKS, var_nesthdr_checks, DICT_FLAG_LOCK); + if (*var_body_checks) + cleanup_body_checks = + maps_create(VAR_BODY_CHECKS, var_body_checks, DICT_FLAG_LOCK); + if (*var_masq_exceptions) + cleanup_masq_exceptions = + string_list_init(VAR_MASQ_EXCEPTIONS, MATCH_FLAG_RETURN, + var_masq_exceptions); + if (*var_masq_classes) + cleanup_masq_flags = name_mask(VAR_MASQ_CLASSES, masq_class_table, + var_masq_classes); + if (*var_send_bcc_maps) + cleanup_send_bcc_maps = + maps_create(VAR_SEND_BCC_MAPS, var_send_bcc_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if (*var_rcpt_bcc_maps) + cleanup_rcpt_bcc_maps = + maps_create(VAR_RCPT_BCC_MAPS, var_rcpt_bcc_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if (*var_cleanup_milters) + cleanup_milters = milter_create(var_cleanup_milters, + var_milt_conn_time, + var_milt_cmd_time, + var_milt_msg_time, + var_milt_protocol, + var_milt_def_action, + var_milt_conn_macros, + var_milt_helo_macros, + var_milt_mail_macros, + var_milt_rcpt_macros, + var_milt_data_macros, + var_milt_eoh_macros, + var_milt_eod_macros, + var_milt_unk_macros, + var_milt_macro_deflts); + if (*var_milt_head_checks) + cleanup_milter_header_checks_init(); + + flush_init(); +} + +/* cleanup_post_jail - initialize after entering the chroot jail */ + +void cleanup_post_jail(char *unused_name, char **unused_argv) +{ + + /* + * Optionally set the file size resource limit. XXX This limits the + * message content to somewhat less than requested, because the total + * queue file size also includes envelope information. Unless people set + * really low limit, the difference is going to matter only when a queue + * file has lots of recipients. + */ + if (ENFORCING_SIZE_LIMIT(var_message_limit)) + set_file_limit((off_t) var_message_limit); + + /* + * Control how unmatched extensions are propagated. + */ + cleanup_ext_prop_mask = + ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension); + + /* + * Setup the filters for characters that should be rejected, and for + * characters that should be removed. + */ + if (*var_msg_reject_chars) { + cleanup_reject_chars = vstring_alloc(strlen(var_msg_reject_chars)); + unescape(cleanup_reject_chars, var_msg_reject_chars); + } + if (*var_msg_strip_chars) { + cleanup_strip_chars = vstring_alloc(strlen(var_msg_strip_chars)); + unescape(cleanup_strip_chars, var_msg_strip_chars); + } + + /* + * From: header formatting. + */ + cleanup_hfrom_format = hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format); +} diff --git a/src/cleanup/cleanup_map11.c b/src/cleanup/cleanup_map11.c new file mode 100644 index 0000000..4934aac --- /dev/null +++ b/src/cleanup/cleanup_map11.c @@ -0,0 +1,183 @@ +/*++ +/* NAME +/* cleanup_map11 3 +/* SUMMARY +/* one-to-one mapping +/* SYNOPSIS +/* #include +/* +/* int cleanup_map11_external(state, addr, maps, propagate) +/* CLEANUP_STATE *state; +/* VSTRING *addr; +/* MAPS *maps; +/* int propagate; +/* +/* int cleanup_map11_internal(state, addr, maps, propagate) +/* CLEANUP_STATE *state; +/* VSTRING *addr; +/* MAPS *maps; +/* int propagate; +/* +/* int cleanup_map11_tree(state, tree, maps, propagate) +/* CLEANUP_STATE *state; +/* TOK822 *tree; +/* MAPS *maps; +/* int propagate; +/* DESCRIPTION +/* This module performs one-to-one map lookups. +/* +/* If an address has a mapping, the lookup result is +/* subjected to another iteration of rewriting and mapping. +/* Recursion continues until an address maps onto itself, +/* or until an unreasonable recursion level is reached. +/* An unmatched address extension is propagated when +/* \fIpropagate\fR is non-zero. +/* These functions return non-zero when the address was changed. +/* +/* cleanup_map11_external() looks up the external (quoted) string +/* form of an address in the maps specified via the \fImaps\fR argument. +/* +/* cleanup_map11_internal() is a wrapper around the +/* cleanup_map11_external() routine that transforms from +/* internal (quoted) string form to external form and back. +/* +/* cleanup_map11_tree() is a wrapper around the +/* cleanup_map11_external() routine that transforms from +/* internal parse tree form to external form and back. +/* DIAGNOSTICS +/* Recoverable errors: the global \fIcleanup_errs\fR flag is updated. +/* SEE ALSO +/* mail_addr_find(3) address lookups +/* mail_addr_map(3) address mappings +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +#define STR vstring_str +#define MAX_RECURSION 10 + +/* cleanup_map11_external - one-to-one table lookups */ + +int cleanup_map11_external(CLEANUP_STATE *state, VSTRING *addr, + MAPS *maps, int propagate) +{ + int count; + int expand_to_self; + ARGV *new_addr; + char *saved_addr; + int did_rewrite = 0; + + /* + * Produce sensible output even in the face of a recoverable error. This + * simplifies error recovery considerably because we can do delayed error + * checking in one place, instead of having error handling code all over + * the place. + */ + for (count = 0; count < MAX_RECURSION; count++) { + if ((new_addr = mail_addr_map_opt(maps, STR(addr), propagate, + MA_FORM_EXTERNAL, MA_FORM_EXTERNAL, + MA_FORM_EXTERNAL)) != 0) { + if (new_addr->argc > 1) + msg_warn("%s: multi-valued %s entry for %s", + state->queue_id, maps->title, STR(addr)); + saved_addr = mystrdup(STR(addr)); + did_rewrite |= strcmp(new_addr->argv[0], STR(addr)); + vstring_strcpy(addr, new_addr->argv[0]); + expand_to_self = !strcasecmp_utf8(saved_addr, STR(addr)); + myfree(saved_addr); + argv_free(new_addr); + if (expand_to_self) + return (did_rewrite); + } else if (maps->error != 0) { + msg_warn("%s: %s map lookup problem for %s -- " + "message not accepted, try again later", + state->queue_id, maps->title, STR(addr)); + state->errs |= CLEANUP_STAT_WRITE; + return (did_rewrite); + } else { + return (did_rewrite); + } + } + msg_warn("%s: unreasonable %s map nesting for %s -- " + "message not accepted, try again later", + state->queue_id, maps->title, STR(addr)); + return (did_rewrite); +} + +/* cleanup_map11_tree - rewrite address node */ + +int cleanup_map11_tree(CLEANUP_STATE *state, TOK822 *tree, + MAPS *maps, int propagate) +{ + VSTRING *temp = vstring_alloc(100); + int did_rewrite; + + /* + * Produce sensible output even in the face of a recoverable error. This + * simplifies error recovery considerably because we can do delayed error + * checking in one place, instead of having error handling code all over + * the place. + */ + tok822_externalize(temp, tree->head, TOK822_STR_DEFL); + did_rewrite = cleanup_map11_external(state, temp, maps, propagate); + tok822_free_tree(tree->head); + tree->head = tok822_scan(STR(temp), &tree->tail); + vstring_free(temp); + return (did_rewrite); +} + +/* cleanup_map11_internal - rewrite address internal form */ + +int cleanup_map11_internal(CLEANUP_STATE *state, VSTRING *addr, + MAPS *maps, int propagate) +{ + VSTRING *temp = vstring_alloc(100); + int did_rewrite; + + /* + * Produce sensible output even in the face of a recoverable error. This + * simplifies error recovery considerably because we can do delayed error + * checking in one place, instead of having error handling code all over + * the place. + */ + quote_822_local(temp, STR(addr)); + did_rewrite = cleanup_map11_external(state, temp, maps, propagate); + unquote_822_local(addr, STR(temp)); + vstring_free(temp); + return (did_rewrite); +} diff --git a/src/cleanup/cleanup_map1n.c b/src/cleanup/cleanup_map1n.c new file mode 100644 index 0000000..9cecfcb --- /dev/null +++ b/src/cleanup/cleanup_map1n.c @@ -0,0 +1,182 @@ +/*++ +/* NAME +/* cleanup_map1n 3 +/* SUMMARY +/* one-to-many address mapping +/* SYNOPSIS +/* #include +/* +/* ARGV *cleanup_map1n_internal(state, addr, maps, propagate) +/* CLEANUP_STATE *state; +/* const char *addr; +/* MAPS *maps; +/* int propagate; +/* DESCRIPTION +/* This module implements one-to-many table mapping via table lookup. +/* Table lookups are done with quoted (externalized) address forms. +/* The process is recursive. The recursion terminates when the +/* left-hand side appears in its own expansion. +/* +/* cleanup_map1n_internal() is the interface for addresses in +/* internal (unquoted) form. +/* DIAGNOSTICS +/* When the maximal expansion or recursion limit is reached, +/* the alias is not expanded and the CLEANUP_STAT_DEFER error +/* is raised with reason "4.6.0 Alias expansion error". +/* +/* When table lookup fails, the alias is not expanded and the +/* CLEANUP_STAT_WRITE error is raised with reason "4.6.0 Alias +/* expansion error". +/* SEE ALSO +/* mail_addr_map(3) address mappings +/* mail_addr_find(3) address lookups +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +/* cleanup_map1n_internal - one-to-many table lookups */ + +ARGV *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr, + MAPS *maps, int propagate) +{ + ARGV *argv; + ARGV *lookup; + int count; + int i; + int arg; + BH_TABLE *been_here; + char *saved_lhs; + + /* + * Initialize. + */ + argv = argv_alloc(1); + argv_add(argv, addr, ARGV_END); + argv_terminate(argv); + been_here = been_here_init(0, BH_FLAG_FOLD); + + /* + * Rewrite the address vector in place. With each map lookup result, + * split it into separate addresses, then rewrite and flatten each + * address, and repeat the process. Beware: argv is being changed, so we + * must index the array explicitly, instead of running along it with a + * pointer. + */ +#define UPDATE(ptr,new) do { \ + if (ptr) myfree(ptr); ptr = mystrdup(new); \ + } while (0) +#define STR vstring_str +#define RETURN(x) do { \ + been_here_free(been_here); return (x); \ + } while (0) +#define UNEXPAND(argv, addr) do { \ + argv_truncate((argv), 0); argv_add((argv), (addr), (char *) 0); \ + } while (0) + + for (arg = 0; arg < argv->argc; arg++) { + if (argv->argc > var_virt_expan_limit) { + msg_warn("%s: unreasonable %s map expansion size for %s -- " + "message not accepted, try again later", + state->queue_id, maps->title, addr); + state->errs |= CLEANUP_STAT_DEFER; + UPDATE(state->reason, "4.6.0 Alias expansion error"); + UNEXPAND(argv, addr); + RETURN(argv); + } + for (count = 0; /* void */ ; count++) { + + /* + * Don't expand an address that already expanded into itself. + */ + if (been_here_check_fixed(been_here, argv->argv[arg]) != 0) + break; + if (count >= var_virt_recur_limit) { + msg_warn("%s: unreasonable %s map nesting for %s -- " + "message not accepted, try again later", + state->queue_id, maps->title, addr); + state->errs |= CLEANUP_STAT_DEFER; + UPDATE(state->reason, "4.6.0 Alias expansion error"); + UNEXPAND(argv, addr); + RETURN(argv); + } + if ((lookup = mail_addr_map_internal(maps, argv->argv[arg], + propagate)) != 0) { + saved_lhs = mystrdup(argv->argv[arg]); + for (i = 0; i < lookup->argc; i++) { + if (strlen(lookup->argv[i]) > var_virt_addrlen_limit) { + msg_warn("%s: unreasonable %s result %.300s... -- " + "message not accepted, try again later", + state->queue_id, maps->title, lookup->argv[i]); + state->errs |= CLEANUP_STAT_DEFER; + UPDATE(state->reason, "4.6.0 Alias expansion error"); + UNEXPAND(argv, addr); + RETURN(argv); + } + if (i == 0) { + UPDATE(argv->argv[arg], lookup->argv[i]); + } else { + argv_add(argv, lookup->argv[i], ARGV_END); + argv_terminate(argv); + } + + /* + * Allow an address to expand into itself once. + */ + if (strcasecmp_utf8(saved_lhs, lookup->argv[i]) == 0) + been_here_fixed(been_here, saved_lhs); + } + myfree(saved_lhs); + argv_free(lookup); + } else if (maps->error != 0) { + msg_warn("%s: %s map lookup problem for %s -- " + "message not accepted, try again later", + state->queue_id, maps->title, addr); + state->errs |= CLEANUP_STAT_WRITE; + UPDATE(state->reason, "4.6.0 Alias expansion error"); + UNEXPAND(argv, addr); + RETURN(argv); + } else { + break; + } + } + } + RETURN(argv); +} diff --git a/src/cleanup/cleanup_masq.ref b/src/cleanup/cleanup_masq.ref new file mode 100644 index 0000000..6838894 --- /dev/null +++ b/src/cleanup/cleanup_masq.ref @@ -0,0 +1,66 @@ +---------- +exceptions: +masq_list: a.b.c,b.c +address: xxx@aa.a.b.c +result: xxx@a.b.c +errs: 0 +---------- +exceptions: +masq_list: A.B.C,B.C +address: xxx@aa.a.b.c +result: xxx@a.b.c +errs: 0 +---------- +exceptions: +masq_list: a.b.c,b.c +address: xxx@AA.A.B.C +result: xxx@a.b.c +errs: 0 +---------- +exceptions: xxx +masq_list: a.b.c,b.c +address: xxx@aa.a.b.c +result: xxx@aa.a.b.c +errs: 0 +---------- +exceptions: yyy +masq_list: a.b.c,b.c +address: xxx@aa.a.b.c +result: xxx@a.b.c +errs: 0 +---------- +exceptions: +masq_list: !a.b.c,b.c +address: xxx@aa.a.b.c +result: xxx@aa.a.b.c +errs: 0 +---------- +exceptions: +masq_list: a.b.c,b.c +address: xxx@a.b.c +result: xxx@a.b.c +errs: 0 +---------- +exceptions: +masq_list: !a.b.c,b.c +address: xxx@a.b.c +result: xxx@a.b.c +errs: 0 +---------- +exceptions: +masq_list: a.b.c,b.c +address: xxx@aaa.b.c +result: xxx@b.c +errs: 0 +---------- +exceptions: +masq_list: a.b.c,b.c +address: xxx@b.c +result: xxx@b.c +errs: 0 +---------- +exceptions: fail:whatever +masq_list: xy +address: xxx@b.c +result: xxx@b.c +errs: 2 diff --git a/src/cleanup/cleanup_masquerade.c b/src/cleanup/cleanup_masquerade.c new file mode 100644 index 0000000..4fc3a13 --- /dev/null +++ b/src/cleanup/cleanup_masquerade.c @@ -0,0 +1,239 @@ +/*++ +/* NAME +/* cleanup_masquerade 3 +/* SUMMARY +/* address masquerading +/* SYNOPSIS +/* #include +/* +/* int cleanup_masquerade_external(addr, masq_domains) +/* VSTRING *addr; +/* ARGV *masq_domains; +/* +/* int cleanup_masquerade_internal(addr, masq_domains) +/* VSTRING *addr; +/* ARGV *masq_domains; +/* +/* int cleanup_masquerade_tree(tree, masq_domains) +/* TOK822 *tree; +/* ARGV *masq_domains; +/* DESCRIPTION +/* This module masquerades addresses, that is, it strips subdomains +/* below domain names that are listed in the masquerade_domains +/* configuration parameter, except for user names listed in the +/* masquerade_exceptions configuration parameter. +/* These functions return non-zero when the address was changed. +/* +/* cleanup_masquerade_external() rewrites the external (quoted) string +/* form of an address. +/* +/* cleanup_masquerade_internal() is a wrapper around the +/* cleanup_masquerade_external() routine that transforms from +/* internal (quoted) string form to external form and back. +/* +/* cleanup_masquerade_tree() is a wrapper around the +/* cleanup_masquerade_external() routine that transforms from +/* internal parse tree form to external form and back. +/* DIAGNOSTICS +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +#define STR vstring_str + +/* cleanup_masquerade_external - masquerade address external form */ + +int cleanup_masquerade_external(CLEANUP_STATE *state, VSTRING *addr, + ARGV *masq_domains) +{ + char *domain; + ssize_t domain_len; + char **masqp; + char *masq; + ssize_t masq_len; + char *parent; + int truncate; + int did_rewrite = 0; + + /* Stuff for excluded names. */ + char *name; + ssize_t name_len; + int excluded; + + /* + * Find the domain part. + */ + if ((domain = strrchr(STR(addr), '@')) == 0) + return (0); + name_len = domain - STR(addr); + domain = casefold(state->temp2, domain + 1); + domain_len = strlen(domain); + + /* + * Don't masquerade excluded names (regardless of domain). + */ + if (*var_masq_exceptions) { + name = mystrndup(STR(addr), name_len); + excluded = (string_list_match(cleanup_masq_exceptions, name) != 0); + myfree(name); + if (cleanup_masq_exceptions->error) { + msg_info("%s: %s map lookup problem -- " + "message not accepted, try again later", + state->queue_id, VAR_MASQ_EXCEPTIONS); + state->errs |= CLEANUP_STAT_WRITE; + } + if (excluded) + return (0); + } + + /* + * If any parent domain matches the list of masquerade domains, replace + * the domain in the address and terminate. If the domain matches a + * masquerade domain, leave it alone. Order of specification matters. + */ + for (masqp = masq_domains->argv; (masq = *masqp) != 0; masqp++) { + for (truncate = 1; *masq == '!'; masq++) + truncate = !truncate; + masq = casefold(state->temp1, masq); + masq_len = strlen(masq); + if (masq_len == 0) + continue; + if (masq_len == domain_len) { + if (strcmp(masq, domain) == 0) + break; + } else if (masq_len < domain_len) { + parent = domain + domain_len - masq_len; + if (parent[-1] == '.' && strcmp(masq, parent) == 0) { + if (truncate) { + if (msg_verbose) + msg_info("masquerade: %s -> %s", domain, masq); + vstring_truncate(addr, name_len + 1); + vstring_strcat(addr, masq); + did_rewrite = 1; + } + break; + } + } + } + return (did_rewrite); +} + +/* cleanup_masquerade_tree - masquerade address node */ + +int cleanup_masquerade_tree(CLEANUP_STATE *state, TOK822 *tree, + ARGV *masq_domains) +{ + VSTRING *temp = vstring_alloc(100); + int did_rewrite; + + tok822_externalize(temp, tree->head, TOK822_STR_DEFL); + did_rewrite = cleanup_masquerade_external(state, temp, masq_domains); + tok822_free_tree(tree->head); + tree->head = tok822_scan(STR(temp), &tree->tail); + + vstring_free(temp); + return (did_rewrite); +} + +/* cleanup_masquerade_internal - masquerade address internal form */ + +int cleanup_masquerade_internal(CLEANUP_STATE *state, VSTRING *addr, + ARGV *masq_domains) +{ + VSTRING *temp = vstring_alloc(100); + int did_rewrite; + + quote_822_local(temp, STR(addr)); + did_rewrite = cleanup_masquerade_external(state, temp, masq_domains); + unquote_822_local(addr, STR(temp)); + + vstring_free(temp); + return (did_rewrite); +} + + /* + * Code for stand-alone testing. Instead of using main.cf, specify the strip + * list and the candidate domain on the command line. Specify null arguments + * for data that should be empty. + */ +#ifdef TEST + +#include + +char *var_masq_exceptions; +STRING_LIST *cleanup_masq_exceptions; + +int main(int argc, char **argv) +{ + VSTRING *addr; + ARGV *masq_domains; + CLEANUP_STATE state; + + if (argc != 4) + msg_fatal("usage: %s exceptions masquerade_list address", argv[0]); + + var_masq_exceptions = argv[1]; + cleanup_masq_exceptions = + string_list_init(VAR_MASQ_EXCEPTIONS, MATCH_FLAG_RETURN, + var_masq_exceptions); + masq_domains = argv_split(argv[2], CHARS_COMMA_SP); + addr = vstring_alloc(1); + if (strchr(argv[3], '@') == 0) + msg_fatal("address must be in user@domain form"); + vstring_strcpy(addr, argv[3]); + + vstream_printf("----------\n"); + vstream_printf("exceptions: %s\n", argv[1]); + vstream_printf("masq_list: %s\n", argv[2]); + vstream_printf("address: %s\n", argv[3]); + + state.errs = 0; + state.queue_id = "NOQUEUE"; + state.temp1 = vstring_alloc(100); + state.temp2 = vstring_alloc(100); + cleanup_masquerade_external(&state, addr, masq_domains); + + vstream_printf("result: %s\n", STR(addr)); + vstream_printf("errs: %d\n", state.errs); + vstream_fflush(VSTREAM_OUT); + + vstring_free(state.temp1); + vstring_free(state.temp2); + vstring_free(addr); + argv_free(masq_domains); + + return (0); +} + +#endif diff --git a/src/cleanup/cleanup_message.c b/src/cleanup/cleanup_message.c new file mode 100644 index 0000000..1ee0a52 --- /dev/null +++ b/src/cleanup/cleanup_message.c @@ -0,0 +1,1111 @@ +/*++ +/* NAME +/* cleanup_message 3 +/* SUMMARY +/* process message segment +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* void cleanup_message(state, type, buf, len) +/* CLEANUP_STATE *state; +/* int type; +/* const char *buf; +/* ssize_t len; +/* DESCRIPTION +/* This module processes message content records and copies the +/* result to the queue file. It validates the input, rewrites +/* sender/recipient addresses to canonical form, inserts missing +/* message headers, and extracts information from message headers +/* to be used later when generating the extracted output segment. +/* This routine absorbs but does not emit the content to extracted +/* boundary record. +/* +/* Arguments: +/* .IP state +/* Queue file and message processing state. This state is updated +/* as records are processed and as errors happen. +/* .IP type +/* Record type. +/* .IP buf +/* Record content. +/* .IP len +/* Record content length. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +/* cleanup_fold_header - wrap address list header */ + +static void cleanup_fold_header(CLEANUP_STATE *state, VSTRING *header_buf) +{ + char *start_line = vstring_str(header_buf); + char *end_line; + char *next_line; + char *line; + + /* + * A rewritten address list contains one address per line. The code below + * replaces newlines by spaces, to fit as many addresses on a line as + * possible (without rearranging the order of addresses). Prepending + * white space to the beginning of lines is delegated to the output + * routine. + */ + for (line = start_line; line != 0; line = next_line) { + end_line = line + strcspn(line, "\n"); + if (line > start_line) { + if (end_line - start_line < 70) { /* TAB counts as one */ + line[-1] = ' '; + } else { + start_line = line; + } + } + next_line = *end_line ? end_line + 1 : 0; + } + cleanup_out_header(state, header_buf); +} + +/* cleanup_extract_internal - save unquoted copy of extracted address */ + +static char *cleanup_extract_internal(VSTRING *buffer, TOK822 *addr) +{ + + /* + * A little routine to stash away a copy of an address that we extracted + * from a message header line. + */ + tok822_internalize(buffer, addr->head, TOK822_STR_DEFL); + return (mystrdup(vstring_str(buffer))); +} + +/* cleanup_rewrite_sender - sender address rewriting */ + +static void cleanup_rewrite_sender(CLEANUP_STATE *state, + const HEADER_OPTS *hdr_opts, + VSTRING *header_buf) +{ + TOK822 *tree; + TOK822 **addr_list; + TOK822 **tpp; + int did_rewrite = 0; + + if (msg_verbose) + msg_info("rewrite_sender: %s", hdr_opts->name); + + /* + * Parse the header line, rewrite each address found, and regenerate the + * header line. Finally, pipe the result through the header line folding + * routine. + */ + tree = tok822_parse_limit(vstring_str(header_buf) + + strlen(hdr_opts->name) + 1, + var_token_limit); + addr_list = tok822_grep(tree, TOK822_ADDR); + for (tpp = addr_list; *tpp; tpp++) { + did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp); + if (state->flags & CLEANUP_FLAG_MAP_OK) { + if (cleanup_send_canon_maps + && (cleanup_send_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM)) + did_rewrite |= + cleanup_map11_tree(state, *tpp, cleanup_send_canon_maps, + cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_comm_canon_maps + && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_FROM)) + did_rewrite |= + cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps, + cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_masq_domains + && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_FROM)) + did_rewrite |= + cleanup_masquerade_tree(state, *tpp, cleanup_masq_domains); + } + } + if (did_rewrite) { + vstring_truncate(header_buf, strlen(hdr_opts->name)); + vstring_strcat(header_buf, ": "); + tok822_externalize(header_buf, tree, TOK822_STR_HEAD); + } + myfree((void *) addr_list); + tok822_free_tree(tree); + if ((hdr_opts->flags & HDR_OPT_DROP) == 0) { + if (did_rewrite) + cleanup_fold_header(state, header_buf); + else + cleanup_out_header(state, header_buf); + } +} + +/* cleanup_rewrite_recip - recipient address rewriting */ + +static void cleanup_rewrite_recip(CLEANUP_STATE *state, + const HEADER_OPTS *hdr_opts, + VSTRING *header_buf) +{ + TOK822 *tree; + TOK822 **addr_list; + TOK822 **tpp; + int did_rewrite = 0; + + if (msg_verbose) + msg_info("rewrite_recip: %s", hdr_opts->name); + + /* + * Parse the header line, rewrite each address found, and regenerate the + * header line. Finally, pipe the result through the header line folding + * routine. + */ + tree = tok822_parse_limit(vstring_str(header_buf) + + strlen(hdr_opts->name) + 1, + var_token_limit); + addr_list = tok822_grep(tree, TOK822_ADDR); + for (tpp = addr_list; *tpp; tpp++) { + did_rewrite |= cleanup_rewrite_tree(state->hdr_rewrite_context, *tpp); + if (state->flags & CLEANUP_FLAG_MAP_OK) { + if (cleanup_rcpt_canon_maps + && (cleanup_rcpt_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT)) + did_rewrite |= + cleanup_map11_tree(state, *tpp, cleanup_rcpt_canon_maps, + cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_comm_canon_maps + && (cleanup_comm_canon_flags & CLEANUP_CANON_FLAG_HDR_RCPT)) + did_rewrite |= + cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps, + cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_masq_domains + && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_RCPT)) + did_rewrite |= + cleanup_masquerade_tree(state, *tpp, cleanup_masq_domains); + } + } + if (did_rewrite) { + vstring_truncate(header_buf, strlen(hdr_opts->name)); + vstring_strcat(header_buf, ": "); + tok822_externalize(header_buf, tree, TOK822_STR_HEAD); + } + myfree((void *) addr_list); + tok822_free_tree(tree); + if ((hdr_opts->flags & HDR_OPT_DROP) == 0) { + if (did_rewrite) + cleanup_fold_header(state, header_buf); + else + cleanup_out_header(state, header_buf); + } +} + +/* cleanup_act_log - log action with context */ + +static void cleanup_act_log(CLEANUP_STATE *state, + const char *action, const char *class, + const char *content, const char *text) +{ + const char *attr; + + if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0) + attr = "unknown"; + vstring_sprintf(state->temp1, "%s: %s: %s %.200s from %s;", + state->queue_id, action, class, content, attr); + if (state->sender) + vstring_sprintf_append(state->temp1, " from=<%s>", + info_log_addr_form_sender(state->sender)); + if (state->recip) + vstring_sprintf_append(state->temp1, " to=<%s>", + info_log_addr_form_recipient(state->recip)); + if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0) + vstring_sprintf_append(state->temp1, " proto=%s", attr); + if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0) + vstring_sprintf_append(state->temp1, " helo=<%s>", attr); + if (text && *text) + vstring_sprintf_append(state->temp1, ": %s", text); + msg_info("%s", vstring_str(state->temp1)); +} + +#define CLEANUP_ACT_CTXT_HEADER "header" +#define CLEANUP_ACT_CTXT_BODY "body" +#define CLEANUP_ACT_CTXT_ANY "content" + +/* cleanup_act - act upon a header/body match */ + +static const char *cleanup_act(CLEANUP_STATE *state, char *context, + const char *buf, const char *value, + const char *map_class) +{ + const char *optional_text = value + strcspn(value, " \t"); + int command_len = optional_text - value; + +#ifdef DELAY_ACTION + int defer_delay; + +#endif + + while (*optional_text && ISSPACE(*optional_text)) + optional_text++; + +#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0) +#define CLEANUP_ACT_DROP 0 + + /* + * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason + * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates + * queue record processing, and prevents bounces from being sent. + */ + if (STREQUAL(value, "REJECT", command_len)) { + const CLEANUP_STAT_DETAIL *detail; + + if (state->reason) + myfree(state->reason); + if (*optional_text) { + state->reason = dsn_prepend("5.7.1", optional_text); + if (*state->reason != '4' && *state->reason != '5') { + msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x", + optional_text); + *state->reason = '4'; + } + } else { + detail = cleanup_stat_detail(CLEANUP_STAT_CONT); + state->reason = dsn_prepend(detail->dsn, detail->text); + } + if (*state->reason == '4') + state->errs |= CLEANUP_STAT_DEFER; + else + state->errs |= CLEANUP_STAT_CONT; + state->flags &= ~CLEANUP_FLAG_FILTER_ALL; + cleanup_act_log(state, "reject", context, buf, state->reason); + return (buf); + } + if (STREQUAL(value, "WARN", command_len)) { + cleanup_act_log(state, "warning", context, buf, optional_text); + return (buf); + } + if (STREQUAL(value, "INFO", command_len)) { + cleanup_act_log(state, "info", context, buf, optional_text); + return (buf); + } + if (STREQUAL(value, "FILTER", command_len)) { + if (*optional_text == 0) { + msg_warn("missing FILTER command argument in %s map", map_class); + } else if (strchr(optional_text, ':') == 0) { + msg_warn("bad FILTER command %s in %s -- " + "need transport:destination", + optional_text, map_class); + } else { + if (state->filter) + myfree(state->filter); + state->filter = mystrdup(optional_text); + cleanup_act_log(state, "filter", context, buf, optional_text); + } + return (buf); + } + if (STREQUAL(value, "PASS", command_len)) { + cleanup_act_log(state, "pass", context, buf, optional_text); + state->flags &= ~CLEANUP_FLAG_FILTER_ALL; + return (buf); + } + if (STREQUAL(value, "DISCARD", command_len)) { + cleanup_act_log(state, "discard", context, buf, optional_text); + state->flags |= CLEANUP_FLAG_DISCARD; + state->flags &= ~CLEANUP_FLAG_FILTER_ALL; + return (buf); + } + if (STREQUAL(value, "HOLD", command_len)) { + if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) { + cleanup_act_log(state, "hold", context, buf, optional_text); + state->flags |= CLEANUP_FLAG_HOLD; + } + return (buf); + } + + /* + * The DELAY feature is disabled because it has too many problems. 1) It + * does not work on some remote file systems; 2) mail will be delivered + * anyway with "sendmail -q" etc.; 3) while the mail is queued it bogs + * down the deferred queue scan with huge amounts of useless disk I/O + * operations. + */ +#ifdef DELAY_ACTION + if (STREQUAL(value, "DELAY", command_len)) { + if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) { + if (*optional_text == 0) { + msg_warn("missing DELAY argument in %s map", map_class); + } else if (conv_time(optional_text, &defer_delay, 's') == 0) { + msg_warn("ignoring bad DELAY argument %s in %s map", + optional_text, map_class); + } else { + cleanup_act_log(state, "delay", context, buf, optional_text); + state->defer_delay = defer_delay; + } + } + return (buf); + } +#endif + if (STREQUAL(value, "PREPEND", command_len)) { + if (*optional_text == 0) { + msg_warn("PREPEND action without text in %s map", map_class); + } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0) { + if (!is_header(optional_text)) { + msg_warn("bad PREPEND header text \"%s\" in %s map -- " + "need \"headername: headervalue\"", + optional_text, map_class); + } + + /* + * By design, cleanup_out_header() may modify content. Play safe + * and prepare for future developments. + */ + else { + VSTRING *temp; + + cleanup_act_log(state, "prepend", context, buf, optional_text); + temp = vstring_strcpy(vstring_alloc(strlen(optional_text)), + optional_text); + cleanup_out_header(state, temp); + vstring_free(temp); + } + } else { + cleanup_act_log(state, "prepend", context, buf, optional_text); + cleanup_out_string(state, REC_TYPE_NORM, optional_text); + } + return (buf); + } + if (STREQUAL(value, "REPLACE", command_len)) { + if (*optional_text == 0) { + msg_warn("REPLACE action without text in %s map", map_class); + return (buf); + } else if (strcmp(context, CLEANUP_ACT_CTXT_HEADER) == 0 + && !is_header(optional_text)) { + msg_warn("bad REPLACE header text \"%s\" in %s map -- " + "need \"headername: headervalue\"", + optional_text, map_class); + return (buf); + } else { + cleanup_act_log(state, "replace", context, buf, optional_text); + return (mystrdup(optional_text)); + } + } + if (STREQUAL(value, "REDIRECT", command_len)) { + if (strchr(optional_text, '@') == 0) { + msg_warn("bad REDIRECT target \"%s\" in %s map -- " + "need user@domain", + optional_text, map_class); + } else { + if (state->redirect) + myfree(state->redirect); + state->redirect = mystrdup(optional_text); + cleanup_act_log(state, "redirect", context, buf, optional_text); + state->flags &= ~CLEANUP_FLAG_FILTER_ALL; + } + return (buf); + } + if (STREQUAL(value, "BCC", command_len)) { + if (strchr(optional_text, '@') == 0) { + msg_warn("bad BCC address \"%s\" in %s map -- " + "need user@domain", + optional_text, map_class); + } else { + if (state->hbc_rcpt == 0) + state->hbc_rcpt = argv_alloc(1); + argv_add(state->hbc_rcpt, optional_text, (char *) 0); + cleanup_act_log(state, "bcc", context, buf, optional_text); + } + return (buf); + } + if (STREQUAL(value, "STRIP", command_len)) { + cleanup_act_log(state, "strip", context, buf, optional_text); + return (CLEANUP_ACT_DROP); + } + /* Allow and ignore optional text after the action. */ + + if (STREQUAL(value, "IGNORE", command_len)) + return (CLEANUP_ACT_DROP); + + if (STREQUAL(value, "DUNNO", command_len)) /* preferred */ + return (buf); + + if (STREQUAL(value, "OK", command_len)) /* compat */ + return (buf); + + msg_warn("unknown command in %s map: %s", map_class, value); + return (buf); +} + +/* cleanup_header_callback - process one complete header line */ + +static void cleanup_header_callback(void *context, int header_class, + const HEADER_OPTS *hdr_opts, + VSTRING *header_buf, + off_t unused_offset) +{ + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + const char *myname = "cleanup_header_callback"; + char *hdrval; + struct code_map { + const char *name; + const char *encoding; + }; + static struct code_map code_map[] = { /* RFC 2045 */ + "7bit", MAIL_ATTR_ENC_7BIT, + "8bit", MAIL_ATTR_ENC_8BIT, + "binary", MAIL_ATTR_ENC_8BIT, /* XXX Violation */ + "quoted-printable", MAIL_ATTR_ENC_7BIT, + "base64", MAIL_ATTR_ENC_7BIT, + 0, + }; + struct code_map *cmp; + MAPS *checks; + const char *map_class; + + if (msg_verbose) + msg_info("%s: '%.200s'", myname, vstring_str(header_buf)); + + /* + * Crude header filtering. This stops malware that isn't sophisticated + * enough to use fancy header encodings. + */ +#define CHECK(class, maps, var_name) \ + (header_class == class && (map_class = var_name, checks = maps) != 0) + + if (hdr_opts && (hdr_opts->flags & HDR_OPT_MIME)) + header_class = MIME_HDR_MULTIPART; + + /* Update the Received: header count before maybe dropping headers below. */ + if (hdr_opts && hdr_opts->type == HDR_RECEIVED) + state->hop_count += 1; + + if ((state->flags & CLEANUP_FLAG_FILTER) + && (CHECK(MIME_HDR_PRIMARY, cleanup_header_checks, VAR_HEADER_CHECKS) + || CHECK(MIME_HDR_MULTIPART, cleanup_mimehdr_checks, VAR_MIMEHDR_CHECKS) + || CHECK(MIME_HDR_NESTED, cleanup_nesthdr_checks, VAR_NESTHDR_CHECKS))) { + char *header = vstring_str(header_buf); + const char *value; + + if ((value = maps_find(checks, header, 0)) != 0) { + const char *result; + + if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_HEADER, + header, value, map_class)) + == CLEANUP_ACT_DROP) { + return; + } else if (result != header) { + vstring_strcpy(header_buf, result); + hdr_opts = header_opts_find(result); + myfree((void *) result); + } + } else if (checks->error) { + msg_warn("%s: %s map lookup problem -- " + "message not accepted, try again later", + state->queue_id, checks->title); + state->errs |= CLEANUP_STAT_WRITE; + } + } + + /* + * If this is an "unknown" header, just copy it to the output without + * even bothering to fold long lines. cleanup_out() will split long + * headers that do not fit a REC_TYPE_NORM record. + */ + if (hdr_opts == 0) { + cleanup_out_header(state, header_buf); + return; + } + + /* + * Allow 8-bit type info to override 7-bit type info. XXX Should reuse + * the effort that went into MIME header parsing. + */ + hdrval = vstring_str(header_buf) + strlen(hdr_opts->name) + 1; + while (ISSPACE(*hdrval)) + hdrval++; + /* trimblanks(hdrval, 0)[0] = 0; */ + if (var_auto_8bit_enc_hdr + && hdr_opts->type == HDR_CONTENT_TRANSFER_ENCODING) { + for (cmp = code_map; cmp->name != 0; cmp++) { + if (strcasecmp(hdrval, cmp->name) == 0) { + if (strcasecmp(cmp->encoding, MAIL_ATTR_ENC_8BIT) == 0) + nvtable_update(state->attr, MAIL_ATTR_ENCODING, + cmp->encoding); + break; + } + } + } + + /* + * Copy attachment etc. header blocks without further inspection. + */ + if (header_class != MIME_HDR_PRIMARY) { + cleanup_out_header(state, header_buf); + return; + } + + /* + * Known header. Remember that we have seen at least one. Find out what + * we should do with this header: delete, count, rewrite. Note that we + * should examine headers even when they will be deleted from the output, + * because the addresses in those headers might be needed elsewhere. + * + * XXX 2821: Return-path breakage. + * + * RFC 821 specifies: When the receiver-SMTP makes the "final delivery" of a + * message it inserts at the beginning of the mail data a return path + * line. The return path line preserves the information in the + * from the MAIL command. Here, final delivery means the + * message leaves the SMTP world. Normally, this would mean it has been + * delivered to the destination user, but in some cases it may be further + * processed and transmitted by another mail system. + * + * And that is what Postfix implements. Delivery agents prepend + * Return-Path:. In order to avoid cluttering up the message with + * possibly inconsistent Return-Path: information (the sender can change + * as the result of mail forwarding or mailing list delivery), Postfix + * removes any existing Return-Path: headers. + * + * RFC 2821 Section 4.4 specifies: A message-originating SMTP system + * SHOULD NOT send a message that already contains a Return-path header. + * SMTP servers performing a relay function MUST NOT inspect the message + * data, and especially not to the extent needed to determine if + * Return-path headers are present. SMTP servers making final delivery + * MAY remove Return-path headers before adding their own. + */ + else { + state->headers_seen |= (1 << hdr_opts->type); + if (hdr_opts->type == HDR_MESSAGE_ID) + msg_info("%s: message-id=%s", state->queue_id, hdrval); + if (hdr_opts->type == HDR_RESENT_MESSAGE_ID) + msg_info("%s: resent-message-id=%s", state->queue_id, hdrval); + if (hdr_opts->type == HDR_RECEIVED) { + if (state->hop_count >= var_hopcount_limit) { + msg_warn("%s: message rejected: hopcount exceeded", + state->queue_id); + state->errs |= CLEANUP_STAT_HOPS; + } + /* Save our Received: header after maybe updating headers above. */ + if (state->hop_count == 1) + argv_add(state->auto_hdrs, vstring_str(header_buf), ARGV_END); + } + if (CLEANUP_OUT_OK(state)) { + if (hdr_opts->flags & HDR_OPT_RR) + state->resent = "Resent-"; + if ((hdr_opts->flags & HDR_OPT_SENDER) + && state->hdr_rewrite_context) { + cleanup_rewrite_sender(state, hdr_opts, header_buf); + } else if ((hdr_opts->flags & HDR_OPT_RECIP) + && state->hdr_rewrite_context) { + cleanup_rewrite_recip(state, hdr_opts, header_buf); + } else if ((hdr_opts->flags & HDR_OPT_DROP) == 0) { + cleanup_out_header(state, header_buf); + } + } + } +} + +/* cleanup_header_done_callback - insert missing message headers */ + +static void cleanup_header_done_callback(void *context) +{ + const char *myname = "cleanup_header_done_callback"; + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + char time_stamp[1024]; /* XXX locale dependent? */ + struct tm *tp; + TOK822 *token; + TOK822 *dummy_token; + time_t tv; + + /* + * XXX Workaround: when we reach the end of headers, mime_state_update() + * may execute up to three call-backs before returning to the caller: + * head_out(), head_end(), and body_out() or body_end(). As long as + * call-backs don't return a result, each call-back has to check for + * itself if the previous call-back experienced a problem. + */ + if (CLEANUP_OUT_OK(state) == 0) + return; + + /* + * Future proofing: the Milter client's header suppression algorithm + * assumes that the MTA prepends its own Received: header. This + * assumption may be violated after some source-code update. The + * following check ensures consistency, at least for local submission. + */ + if (state->hop_count < 1) { + msg_warn("%s: message rejected: no Received: header", + state->queue_id); + state->errs |= CLEANUP_STAT_BAD; + return; + } + + /* + * Add a missing (Resent-)Message-Id: header. The message ID gives the + * time in GMT units, plus the local queue ID. + * + * XXX Message-Id is not a required message header (RFC 822 and RFC 2822). + * + * XXX It is the queue ID non-inode bits that prevent messages from getting + * the same Message-Id within the same second. + * + * XXX An arbitrary amount of time may pass between the start of the mail + * transaction and the creation of a queue file. Since we guarantee queue + * ID uniqueness only within a second, we must ensure that the time in + * the message ID matches the queue ID creation time, as long as we use + * the queue ID in the message ID. + * + * XXX We log a dummy name=value record so that we (hopefully) don't break + * compatibility with existing logfile analyzers, and so that we don't + * complicate future code that wants to log more name=value attributes. + */ + if ((state->hdr_rewrite_context || var_always_add_hdrs) + && (state->headers_seen & (1 << (state->resent[0] ? + HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID))) == 0) { + if (var_long_queue_ids) { + vstring_sprintf(state->temp1, "%s@%s", + state->queue_id, var_myhostname); + } else { + tv = state->handle->ctime.tv_sec; + tp = gmtime(&tv); + strftime(time_stamp, sizeof(time_stamp), "%Y%m%d%H%M%S", tp); + vstring_sprintf(state->temp1, "%s.%s@%s", + time_stamp, state->queue_id, var_myhostname); + } + cleanup_out_format(state, REC_TYPE_NORM, "%sMessage-Id: <%s>", + state->resent, vstring_str(state->temp1)); + msg_info("%s: %smessage-id=<%s>", + state->queue_id, *state->resent ? "resent-" : "", + vstring_str(state->temp1)); + state->headers_seen |= (1 << (state->resent[0] ? + HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID)); + } + if ((state->headers_seen & (1 << HDR_MESSAGE_ID)) == 0) + msg_info("%s: message-id=<>", state->queue_id); + + /* + * Add a missing (Resent-)Date: header. The date is in local time units, + * with the GMT offset at the end. + */ + if ((state->hdr_rewrite_context || var_always_add_hdrs) + && (state->headers_seen & (1 << (state->resent[0] ? + HDR_RESENT_DATE : HDR_DATE))) == 0) { + cleanup_out_format(state, REC_TYPE_NORM, "%sDate: %s", + state->resent, mail_date(state->arrival_time.tv_sec)); + } + + /* + * Add a missing (Resent-)From: header. + */ + if ((state->hdr_rewrite_context || var_always_add_hdrs) + && (state->headers_seen & (1 << (state->resent[0] ? + HDR_RESENT_FROM : HDR_FROM))) == 0) { + quote_822_local(state->temp1, *state->sender ? + state->sender : MAIL_ADDR_MAIL_DAEMON); + if (*state->sender && state->fullname && *state->fullname) { + char *cp; + + /* Enforce some sanity on full name content. */ + while ((cp = strchr(state->fullname, '\r')) != 0 + || (cp = strchr(state->fullname, '\n')) != 0) + *cp = ' '; + + /* + * "From: phrase ". Quote the phrase if it contains + * specials or the "%!" legacy address operators. + */ + if (cleanup_hfrom_format == HFROM_FORMAT_CODE_STD) { + vstring_sprintf(state->temp2, "%sFrom: ", state->resent); + if (state->fullname[strcspn(state->fullname, + "%!" LEX_822_SPECIALS)] == 0) { + /* Normalize whitespace. */ + token = tok822_scan_limit(state->fullname, &dummy_token, + var_token_limit); + } else { + token = tok822_alloc(TOK822_QSTRING, state->fullname); + } + if (token) { + tok822_externalize(state->temp2, token, TOK822_STR_NONE); + tok822_free(token); + vstring_strcat(state->temp2, " "); + } + vstring_sprintf_append(state->temp2, "<%s>", + vstring_str(state->temp1)); + } + + /* + * "From: addr-spec (ctext)". This is the obsolete form. + */ + else { + vstring_sprintf(state->temp2, "%sFrom: %s ", + state->resent, vstring_str(state->temp1)); + vstring_sprintf(state->temp1, "(%s)", state->fullname); + token = tok822_parse(vstring_str(state->temp1)); + tok822_externalize(state->temp2, token, TOK822_STR_NONE); + tok822_free_tree(token); + } + } + + /* + * "From: addr-spec". This is the form in the absence of full name + * information, also used for mail from mailer-daemon. + */ + else { + vstring_sprintf(state->temp2, "%sFrom: %s", + state->resent, vstring_str(state->temp1)); + } + CLEANUP_OUT_BUF(state, REC_TYPE_NORM, state->temp2); + } + + /* + * XXX 2821: Appendix B: The return address in the MAIL command SHOULD, + * if possible, be derived from the system's identity for the submitting + * (local) user, and the "From:" header field otherwise. If there is a + * system identity available, it SHOULD also be copied to the Sender + * header field if it is different from the address in the From header + * field. (Any Sender field that was already there SHOULD be removed.) + * Similar wording appears in RFC 2822 section 3.6.2. + * + * Postfix presently does not insert a Sender: header if envelope and From: + * address differ. Older Postfix versions assumed that the envelope + * sender address specifies the system identity and inserted Sender: + * whenever envelope and From: differed. This was wrong with relayed + * mail, and was often not even desirable with original submissions. + * + * XXX 2822 Section 3.6.2, as well as RFC 822 Section 4.1: FROM headers can + * contain multiple addresses. If this is the case, then a Sender: header + * must be provided with a single address. + * + * Postfix does not count the number of addresses in a From: header + * (although doing so is trivial, once the address is parsed). + */ + + /* + * Add a missing destination header. + */ +#define VISIBLE_RCPT ((1 << HDR_TO) | (1 << HDR_RESENT_TO) \ + | (1 << HDR_CC) | (1 << HDR_RESENT_CC)) + + if ((state->hdr_rewrite_context || var_always_add_hdrs) + && (state->headers_seen & VISIBLE_RCPT) == 0 && *var_rcpt_witheld) { + if (!is_header(var_rcpt_witheld)) { + msg_warn("bad %s header text \"%s\" -- " + "need \"headername: headervalue\"", + VAR_RCPT_WITHELD, var_rcpt_witheld); + } else { + cleanup_out_format(state, REC_TYPE_NORM, "%s", var_rcpt_witheld); + } + } + + /* + * Place a dummy PTR record right after the last header so that we can + * append headers without having to worry about clobbering the + * end-of-content marker. + */ + if (state->milters || cleanup_milters) { + if ((state->append_hdr_pt_offset = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path); + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L); + if ((state->append_hdr_pt_target = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path); + state->body_offset = state->append_hdr_pt_target; + } +} + +/* cleanup_body_callback - output one body record */ + +static void cleanup_body_callback(void *context, int type, + const char *buf, ssize_t len, + off_t offset) +{ + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + + /* + * XXX Workaround: when we reach the end of headers, mime_state_update() + * may execute up to three call-backs before returning to the caller: + * head_out(), head_end(), and body_out() or body_end(). As long as + * call-backs don't return a result, each call-back has to check for + * itself if the previous call-back experienced a problem. + */ + if (CLEANUP_OUT_OK(state) == 0) + return; + + /* + * Crude message body content filter for emergencies. This code has + * several problems: it sees one line at a time; it looks at long lines + * only in chunks of line_length_limit (2048) characters; it is easily + * bypassed with encodings and other tricks. + */ + if ((state->flags & CLEANUP_FLAG_FILTER) + && cleanup_body_checks + && (var_body_check_len == 0 || offset < var_body_check_len)) { + const char *value; + + if ((value = maps_find(cleanup_body_checks, buf, 0)) != 0) { + const char *result; + + if ((result = cleanup_act(state, CLEANUP_ACT_CTXT_BODY, + buf, value, VAR_BODY_CHECKS)) + == CLEANUP_ACT_DROP) { + return; + } else if (result != buf) { + cleanup_out(state, type, result, strlen(result)); + myfree((void *) result); + return; + } + } else if (cleanup_body_checks->error) { + msg_warn("%s: %s map lookup problem -- " + "message not accepted, try again later", + state->queue_id, cleanup_body_checks->title); + state->errs |= CLEANUP_STAT_WRITE; + } + } + cleanup_out(state, type, buf, len); +} + +/* cleanup_message_headerbody - process message content, header and body */ + +static void cleanup_message_headerbody(CLEANUP_STATE *state, int type, + const char *buf, ssize_t len) +{ + const char *myname = "cleanup_message_headerbody"; + const MIME_STATE_DETAIL *detail; + const char *cp; + char *dst; + + /* + * Replace each stray CR or LF with one space. These are not allowed in + * SMTP, and can be used to enable outbound (remote) SMTP smuggling. + * Replacing these early ensures that our later DKIM etc. signature will + * not be invalidated. Besides preventing SMTP smuggling, replacing stray + * or ensures that the result of signature validation by a + * later mail system will not depend on how that mail system handles + * those stray characters in an implementation-dependent manner. + * + * The input length is not changed, therefore it is safe to overwrite the + * input. + */ + if (var_cleanup_mask_stray_cr_lf) + for (dst = (char *) buf; dst < buf + len; dst++) + if (*dst == '\r' || *dst == '\n') + *dst = ' '; + + /* + * Reject unwanted characters. + * + * XXX Possible optimization: simplify the loop when the "reject" set + * contains only one character. + */ + if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_reject_chars) { + for (cp = buf; cp < buf + len; cp++) { + if (memchr(vstring_str(cleanup_reject_chars), + *(const unsigned char *) cp, + VSTRING_LEN(cleanup_reject_chars))) { + cleanup_act(state, CLEANUP_ACT_CTXT_ANY, + buf, "REJECT disallowed character", + "character reject"); + return; + } + } + } + + /* + * Strip unwanted characters. Don't overwrite the input. + * + * XXX Possible space+time optimization: use a bitset. + * + * XXX Possible optimization: simplify the loop when the "strip" set + * contains only one character. + * + * XXX Possible optimization: copy the input only if we really have to. + */ + if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_strip_chars) { + VSTRING_RESET(state->stripped_buf); + VSTRING_SPACE(state->stripped_buf, len + 1); + dst = vstring_str(state->stripped_buf); + for (cp = buf; cp < buf + len; cp++) + if (!memchr(vstring_str(cleanup_strip_chars), + *(const unsigned char *) cp, + VSTRING_LEN(cleanup_strip_chars))) + *dst++ = *cp; + *dst = 0; + buf = vstring_str(state->stripped_buf); + len = dst - buf; + } + + /* + * Copy text record to the output. + */ + if (type == REC_TYPE_NORM || type == REC_TYPE_CONT) { + state->mime_errs = mime_state_update(state->mime_state, type, buf, len); + } + + /* + * If we have reached the end of the message content segment, record the + * current file position so we can compute the message size lateron. + */ + else if (type == REC_TYPE_XTRA) { + state->mime_errs = mime_state_update(state->mime_state, type, buf, len); + if (state->milters || cleanup_milters) + /* Make room for body modification. */ + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, 0L); + /* Ignore header truncation after primary message headers. */ + state->mime_errs &= ~MIME_ERR_TRUNC_HEADER; + if (state->mime_errs && state->reason == 0) { + state->errs |= CLEANUP_STAT_CONT; + detail = mime_state_detail(state->mime_errs); + state->reason = dsn_prepend(detail->dsn, detail->text); + } + state->mime_state = mime_state_free(state->mime_state); + if ((state->xtra_offset = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path); + state->cont_length = state->xtra_offset - state->data_offset; + state->action = cleanup_extracted; + } + + /* + * This should never happen. + */ + else { + msg_warn("%s: message rejected: " + "unexpected record type %d in message content", myname, type); + state->errs |= CLEANUP_STAT_BAD; + } +} + +/* cleanup_mime_error_callback - error report call-back routine */ + +static void cleanup_mime_error_callback(void *context, int err_code, + const char *text, ssize_t len) +{ + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + const char *origin; + + /* + * Message header too large errors are handled after the end of the + * primary message headers. + */ + if ((err_code & ~MIME_ERR_TRUNC_HEADER) != 0) { + if ((origin = nvtable_find(state->attr, MAIL_ATTR_LOG_ORIGIN)) == 0) + origin = MAIL_ATTR_ORG_NONE; +#define TEXT_LEN (len < 100 ? (int) len : 100) + msg_info("%s: reject: mime-error %s: %.*s from %s; from=<%s> to=<%s>", + state->queue_id, mime_state_error(err_code), TEXT_LEN, text, + origin, info_log_addr_form_sender(state->sender), + info_log_addr_form_recipient(state->recip ? + state->recip : "unknown")); + } +} + +/* cleanup_message - initialize message content segment */ + +void cleanup_message(CLEANUP_STATE *state, int type, const char *buf, ssize_t len) +{ + const char *myname = "cleanup_message"; + int mime_options; + + /* + * Write the start-of-content segment marker. + */ + cleanup_out_string(state, REC_TYPE_MESG, ""); + if ((state->data_offset = vstream_ftell(state->dst)) < 0) + msg_fatal("%s: vstream_ftell %s: %m", myname, cleanup_path); + + /* + * Set up MIME processing options, if any. MIME_OPT_DISABLE_MIME disables + * special processing of Content-Type: headers, and thus, causes all text + * after the primary headers to be treated as the message body. + */ + mime_options = 0; + if (var_disable_mime_input) { + mime_options |= MIME_OPT_DISABLE_MIME; + } else { + /* Turn off content checks if bouncing or forwarding mail. */ + if (state->flags & CLEANUP_FLAG_FILTER) { + if (var_strict_8bitmime || var_strict_7bit_hdrs) + mime_options |= MIME_OPT_REPORT_8BIT_IN_HEADER; + if (var_strict_8bitmime || var_strict_8bit_body) + mime_options |= MIME_OPT_REPORT_8BIT_IN_7BIT_BODY; + if (var_strict_encoding) + mime_options |= MIME_OPT_REPORT_ENCODING_DOMAIN; + if (var_strict_8bitmime || var_strict_7bit_hdrs + || var_strict_8bit_body || var_strict_encoding + || *var_header_checks || *var_mimehdr_checks + || *var_nesthdr_checks) + mime_options |= MIME_OPT_REPORT_NESTING; + } + } + state->mime_state = mime_state_alloc(mime_options, + cleanup_header_callback, + cleanup_header_done_callback, + cleanup_body_callback, + (MIME_STATE_ANY_END) 0, + cleanup_mime_error_callback, + (void *) state); + + /* + * XXX Workaround: truncate a long message header so that we don't exceed + * the default Sendmail libmilter request size limit of 65535. + */ +#define KLUDGE_HEADER_LIMIT 60000 + if ((cleanup_milters || state->milters) + && var_header_limit > KLUDGE_HEADER_LIMIT) + var_header_limit = KLUDGE_HEADER_LIMIT; + + /* + * Pass control to the header processing routine. + */ + state->action = cleanup_message_headerbody; + cleanup_message_headerbody(state, type, buf, len); +} diff --git a/src/cleanup/cleanup_milter.c b/src/cleanup/cleanup_milter.c new file mode 100644 index 0000000..11510b5 --- /dev/null +++ b/src/cleanup/cleanup_milter.c @@ -0,0 +1,2775 @@ +/*++ +/* NAME +/* cleanup_milter 3 +/* SUMMARY +/* external mail filter support +/* SYNOPSIS +/* #include +/* +/* void cleanup_milter_header_checks_init(void) +/* +/* void cleanup_milter_receive(state, count) +/* CLEANUP_STATE *state; +/* int count; +/* +/* void cleanup_milter_inspect(state, milters) +/* CLEANUP_STATE *state; +/* MILTERS *milters; +/* +/* cleanup_milter_emul_mail(state, milters, sender) +/* CLEANUP_STATE *state; +/* MILTERS *milters; +/* const char *sender; +/* +/* cleanup_milter_emul_rcpt(state, milters, recipient) +/* CLEANUP_STATE *state; +/* MILTERS *milters; +/* const char *recipient; +/* +/* cleanup_milter_emul_data(state, milters) +/* CLEANUP_STATE *state; +/* MILTERS *milters; +/* DESCRIPTION +/* This module implements support for Sendmail-style mail +/* filter (milter) applications, including in-place queue file +/* modification. +/* +/* cleanup_milter_header_checks_init() does pre-jail +/* initializations. +/* +/* cleanup_milter_receive() receives mail filter definitions, +/* typically from an smtpd(8) server process, and registers +/* local call-back functions for macro expansion and for queue +/* file modification. +/* +/* cleanup_milter_inspect() sends the current message headers +/* and body to the mail filters that were received with +/* cleanup_milter_receive(), or that are specified with the +/* cleanup_milters configuration parameter. +/* +/* cleanup_milter_emul_mail() emulates connect, helo and mail +/* events for mail that does not arrive via the smtpd(8) server. +/* The emulation pretends that mail arrives from localhost/127.0.0.1 +/* via ESMTP. Milters can reject emulated connect, helo, mail +/* or data events, but not emulated rcpt events as described +/* next. +/* +/* cleanup_milter_emul_rcpt() emulates an rcpt event for mail +/* that does not arrive via the smtpd(8) server. This reports +/* a server configuration error condition when the milter +/* rejects an emulated rcpt event. +/* +/* cleanup_milter_emul_data() emulates a data event for mail +/* that does not arrive via the smtpd(8) server. It's OK for +/* milters to reject emulated data events. +/* SEE ALSO +/* milter(3) generic mail filter interface +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* Panic: interface violation. +/* Warnings: I/O errors (state->errs is updated accordingly). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include /* AF_INET */ +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include + + /* + * How Postfix 2.4 edits queue file information: + * + * Mail filter applications (Milters) can send modification requests after + * receiving the end of the message body. Postfix implements these + * modifications in the cleanup server, so that it can edit the queue file + * in place. This avoids the temporary files that would be needed when + * modifications were implemented in the SMTP server (Postfix normally does + * not store the whole message in main memory). Once a Milter is done + * editing, the queue file can be used as input for the next Milter, and so + * on. Finally, the cleanup server changes file permissions, calls fsync(), + * and waits for successful completion. + * + * To implement in-place queue file edits, we need to introduce surprisingly + * little change to the existing Postfix queue file structure. All we need + * is a way to mark a record as deleted, and to jump from one place in the + * queue file to another. We could implement deleted records with jumps, but + * marking is sometimes simpler. + * + * Postfix does not store queue files as plain text files. Instead all + * information is stored in records with an explicit type and length, for + * sender, recipient, arrival time, and so on. Even the content that makes + * up the message header and body is stored as records with explicit types + * and lengths. This organization makes it very easy to mark a record as + * deleted, and to introduce the pointer records that we will use to jump + * from one place in a queue file to another place. + * + * - Deleting a recipient is easiest - simply modify the record type into one + * that is skipped by the software that delivers mail. We won't try to reuse + * the deleted recipient for other purposes. When deleting a recipient, we + * may need to delete multiple recipient records that result from virtual + * alias expansion of the original recipient address. + * + * - Replacing a header record involves pointer records. A record is replaced + * by overwriting it with a forward pointer to space after the end of the + * queue file, putting the new record there, followed by a reverse pointer + * to the record that follows the replaced header. To simplify + * implementation we follow a short header record with a filler record so + * that we can always overwrite a header record with a pointer. + * + * N.B. This is a major difference with Postfix version 2.3, which needed + * complex code to save records that follow a short header, before it could + * overwrite a short header record. This code contained two of the three + * post-release bugs that were found with Postfix header editing. + * + * - Inserting a header record is like replacing one, except that we also + * relocate the record that is being overwritten by the forward pointer. + * + * - Deleting a message header is simplest when we replace it by a "skip" + * pointer to the information that follows the header. With a multi-line + * header we need to update only the first line. + * + * - Appending a recipient or header record involves pointer records as well. + * To make this convenient, the queue file already contains dummy pointer + * records at the locations where we want to append recipient or header + * content. To append, change the dummy pointer into a forward pointer to + * space after the end of a message, put the new recipient or header record + * there, followed by a reverse pointer to the record that follows the + * forward pointer. + * + * - To append another header or recipient record, replace the reverse pointer + * by a forward pointer to space after the end of a message, put the new + * record there, followed by the value of the reverse pointer that we + * replace. Thus, there is no one-to-one correspondence between forward and + * backward pointers. Instead, there can be multiple forward pointers for + * one reverse pointer. + * + * - When a mail filter wants to replace an entire body, we overwrite existing + * body records until we run out of space, and then write a pointer to space + * after the end of the queue file, followed by more body content. There may + * be multiple regions with body content; regions are connected by forward + * pointers, and the last region ends with a pointer to the marker that ends + * the message content segment. Body regions can be large and therefore they + * are reused to avoid wasting space. Sendmail mail filters currently do not + * replace individual body records, and that is a good thing. + * + * Making queue file modifications safe: + * + * Postfix queue files are segmented. The first segment is for envelope + * records, the second for message header and body content, and the third + * segment is for information that was extracted or generated from the + * message header or body content. Each segment is terminated by a marker + * record. For now we don't want to change their location. That is, we want + * to avoid moving the records that mark the start or end of a queue file + * segment. + * + * To ensure that we can always replace a header or body record by a pointer + * record, without having to relocate a marker record, the cleanup server + * places a dummy pointer record at the end of the recipients and at the end + * of the message header. To support message body modifications, a dummy + * pointer record is also placed at the end of the message content. + * + * With all these changes in queue file organization, REC_TYPE_END is no longer + * guaranteed to be the last record in a queue file. If an application were + * to read beyond the REC_TYPE_END marker, it would go into an infinite + * loop, because records after REC_TYPE_END alternate with reverse pointers + * to the middle of the queue file. For robustness, the record reading + * routine skips forward to the end-of-file position after reading the + * REC_TYPE_END marker. + */ + +/*#define msg_verbose 2*/ + +static HBC_CHECKS *cleanup_milter_hbc_checks; +static VSTRING *cleanup_milter_hbc_reply; +static void cleanup_milter_set_error(CLEANUP_STATE *, int); +static const char *cleanup_add_rcpt_par(void *, const char *, const char *); + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* cleanup_milter_hbc_log - log post-milter header/body_checks action */ + +static void cleanup_milter_hbc_log(void *context, const char *action, + const char *where, const char *line, + const char *optional_text) +{ + const CLEANUP_STATE *state = (CLEANUP_STATE *) context; + const char *attr; + + vstring_sprintf(state->temp1, "%s: milter-%s-%s: %s %.60s from %s[%s];", + state->queue_id, where, action, where, line, + state->client_name, state->client_addr); + if (state->sender) + vstring_sprintf_append(state->temp1, " from=<%s>", + info_log_addr_form_sender(state->sender)); + if (state->recip) + vstring_sprintf_append(state->temp1, " to=<%s>", + info_log_addr_form_recipient(state->recip)); + if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0) + vstring_sprintf_append(state->temp1, " proto=%s", attr); + if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0) + vstring_sprintf_append(state->temp1, " helo=<%s>", attr); + if (optional_text) + vstring_sprintf_append(state->temp1, ": %s", optional_text); + msg_info("%s", vstring_str(state->temp1)); +} + +/* cleanup_milter_header_prepend - prepend header to milter-generated header */ + +static void cleanup_milter_header_prepend(void *context, int rec_type, + const char *buf, ssize_t len, off_t offset) +{ + /* XXX save prepended header to buffer. */ + msg_warn("the milter_header/body_checks prepend action is not implemented"); +} + +/* cleanup_milter_hbc_extend - additional header/body_checks actions */ + +static char *cleanup_milter_hbc_extend(void *context, const char *command, + ssize_t cmd_len, const char *optional_text, + const char *where, const char *buf, + ssize_t buf_len, off_t offset) +{ + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + const char *map_class = VAR_MILT_HEAD_CHECKS; /* XXX */ + +#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0) + + /* + * These are currently our mutually-exclusive ways of not receiving mail: + * "reject" and "discard". Only these can be reported to the up-stream + * Postfix libmilter code, because sending any reply there causes Postfix + * libmilter to skip further "edit" requests. By way of safety net, each + * of these must also reset CLEANUP_FLAG_FILTER_ALL. + */ +#define CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state) \ + ((state->flags & CLEANUP_FLAG_DISCARD) || (state->errs & CLEANUP_STAT_CONT)) + + /* + * We log all header/body-checks actions here, because we know the + * details of the message content that triggered the action. We report + * detail-free milter-reply values (reject/discard, stored in the + * milter_hbc_reply state member) to the Postfix libmilter code, so that + * Postfix libmilter can stop sending requests. + * + * We also set all applicable cleanup flags here, because there is no + * guarantee that Postfix libmilter will propagate our own milter-reply + * value to cleanup_milter_inspect() which calls cleanup_milter_apply(). + * The latter translates responses from Milter applications into cleanup + * flags, and logs the response text. Postfix libmilter can convey only + * one milter-reply value per email message, and that reply may even come + * from outside Postfix. + * + * To suppress redundant logging, cleanup_milter_apply() does nothing when + * the milter-reply value matches the saved text in the milter_hbc_reply + * state member. As we remember only one milter-reply value, we can't + * report multiple milter-reply values per email message. We satisfy this + * constraint, because we already clear the CLEANUP_FLAG_FILTER_ALL flags + * to terminate further header inspection. + */ + if ((state->flags & CLEANUP_FLAG_FILTER_ALL) == 0) + return ((char *) buf); + + if (STREQUAL(command, "BCC", cmd_len)) { + if (strchr(optional_text, '@') == 0) { + msg_warn("bad BCC address \"%s\" in %s map -- " + "need user@domain", + optional_text, VAR_MILT_HEAD_CHECKS); + } else { + cleanup_milter_hbc_log(context, "bcc", where, buf, optional_text); + /* Caller checks state error flags. */ + (void) cleanup_add_rcpt_par(state, optional_text, ""); + } + return ((char *) buf); + } + if (STREQUAL(command, "REJECT", cmd_len)) { + const CLEANUP_STAT_DETAIL *detail; + + if (state->reason) + myfree(state->reason); + detail = cleanup_stat_detail(CLEANUP_STAT_CONT); + if (*optional_text) { + state->reason = dsn_prepend(detail->dsn, optional_text); + if (*state->reason != '4' && *state->reason != '5') { + msg_warn("bad DSN action in %s -- need 4.x.x or 5.x.x", + optional_text); + *state->reason = '4'; + } + } else { + state->reason = dsn_prepend(detail->dsn, detail->text); + } + if (*state->reason == '4') + state->errs |= CLEANUP_STAT_DEFER; + else + state->errs |= CLEANUP_STAT_CONT; + state->flags &= ~CLEANUP_FLAG_FILTER_ALL; + cleanup_milter_hbc_log(context, "reject", where, buf, state->reason); + vstring_sprintf(cleanup_milter_hbc_reply, "%d %s", + detail->smtp, state->reason); + STR(cleanup_milter_hbc_reply)[0] = *state->reason; + return ((char *) buf); + } + if (STREQUAL(command, "FILTER", cmd_len)) { + if (*optional_text == 0) { + msg_warn("missing FILTER command argument in %s map", map_class); + } else if (strchr(optional_text, ':') == 0) { + msg_warn("bad FILTER command %s in %s -- " + "need transport:destination", + optional_text, map_class); + } else { + if (state->filter) + myfree(state->filter); + state->filter = mystrdup(optional_text); + cleanup_milter_hbc_log(context, "filter", where, buf, + optional_text); + } + return ((char *) buf); + } + if (STREQUAL(command, "DISCARD", cmd_len)) { + cleanup_milter_hbc_log(context, "discard", where, buf, optional_text); + vstring_strcpy(cleanup_milter_hbc_reply, "D"); + state->flags |= CLEANUP_FLAG_DISCARD; + state->flags &= ~CLEANUP_FLAG_FILTER_ALL; + return ((char *) buf); + } + if (STREQUAL(command, "HOLD", cmd_len)) { + if ((state->flags & (CLEANUP_FLAG_HOLD | CLEANUP_FLAG_DISCARD)) == 0) { + cleanup_milter_hbc_log(context, "hold", where, buf, optional_text); + state->flags |= CLEANUP_FLAG_HOLD; + } + return ((char *) buf); + } + if (STREQUAL(command, "REDIRECT", cmd_len)) { + if (strchr(optional_text, '@') == 0) { + msg_warn("bad REDIRECT target \"%s\" in %s map -- " + "need user@domain", + optional_text, map_class); + } else { + if (state->redirect) + myfree(state->redirect); + state->redirect = mystrdup(optional_text); + cleanup_milter_hbc_log(context, "redirect", where, buf, + optional_text); + state->flags &= ~CLEANUP_FLAG_FILTER_ALL; + } + return ((char *) buf); + } + return ((char *) HBC_CHECKS_STAT_UNKNOWN); +} + +/* cleanup_milter_header_checks - inspect Milter-generated header */ + +static int cleanup_milter_header_checks(CLEANUP_STATE *state, VSTRING *buf) +{ + char *ret; + + /* + * Milter application "add/insert/replace header" requests happen at the + * end-of-message stage, therefore all the header operations are relative + * to the primary message header. + */ + ret = hbc_header_checks((void *) state, cleanup_milter_hbc_checks, + MIME_HDR_PRIMARY, (HEADER_OPTS *) 0, + buf, (off_t) 0); + if (ret == 0) { + return (0); + } else if (ret == HBC_CHECKS_STAT_ERROR) { + msg_warn("%s: %s map lookup problem -- " + "message not accepted, try again later", + state->queue_id, VAR_MILT_HEAD_CHECKS); + state->errs |= CLEANUP_STAT_WRITE; + return (0); + } else { + if (ret != STR(buf)) { + vstring_strcpy(buf, ret); + myfree(ret); + } + return (1); + } +} + +/* cleanup_milter_hbc_add_meta_records - add REDIRECT or FILTER meta records */ + +static void cleanup_milter_hbc_add_meta_records(CLEANUP_STATE *state) +{ + const char *myname = "cleanup_milter_hbc_add_meta_records"; + off_t reverse_ptr_offset; + off_t new_meta_offset; + + /* + * Note: this code runs while the Milter infrastructure is being torn + * down. For this reason we handle all I/O errors here on the spot, + * instead of reporting them back through the Milter infrastructure. + */ + + /* + * Sanity check. + */ + if (state->append_meta_pt_offset < 0) + msg_panic("%s: no meta append pointer location", myname); + if (state->append_meta_pt_target < 0) + msg_panic("%s: no meta append pointer target", myname); + + /* + * Allocate space after the end of the queue file, and write the meta + * record(s), followed by a reverse pointer record that points to the + * target of the old "meta record append" pointer record. This reverse + * pointer record becomes the new "meta record append" pointer record. + * Although the new "meta record append" pointer record will never be + * used, we update it here to make the code more similar to other code + * that inserts/appends content, so that common code can be factored out + * later. + */ + if ((new_meta_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) { + cleanup_milter_set_error(state, errno); + return; + } + if (state->filter != 0) + cleanup_out_string(state, REC_TYPE_FILT, state->filter); + if (state->redirect != 0) + cleanup_out_string(state, REC_TYPE_RDR, state->redirect); + if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) { + msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); + state->errs |= CLEANUP_STAT_WRITE; + return; + } + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) state->append_meta_pt_target); + + /* + * Pointer flipping: update the old "meta record append" pointer record + * value with the location of the new meta record. + */ + if (vstream_fseek(state->dst, state->append_meta_pt_offset, SEEK_SET) < 0) { + cleanup_milter_set_error(state, errno); + return; + } + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) new_meta_offset); + + /* + * Update the in-memory "meta append" pointer record location with the + * location of the reverse pointer record that follows the new meta + * record. The target of the "meta append" pointer record does not + * change; it's always the record that follows the dummy pointer record + * that was written while Postfix received the message. + */ + state->append_meta_pt_offset = reverse_ptr_offset; + + /* + * Note: state->append_meta_pt_target never changes. + */ +} + +/* cleanup_milter_header_checks_init - initialize post-Milter header checks */ + +void cleanup_milter_header_checks_init(void) +{ + static const char myname[] = "cleanup_milter_header_checks_init"; + +#define NO_NESTED_HDR_NAME "" +#define NO_NESTED_HDR_VALUE "" +#define NO_MIME_HDR_NAME "" +#define NO_MIME_HDR_VALUE "" + + static /* XXX not const */ HBC_CALL_BACKS call_backs = { + cleanup_milter_hbc_log, + cleanup_milter_header_prepend, + cleanup_milter_hbc_extend, + }; + + if (*var_milt_head_checks == 0) + msg_panic("%s: %s is empty", myname, VAR_MILT_HEAD_CHECKS); + + if (cleanup_milter_hbc_checks) + msg_panic("%s: cleanup_milter_hbc_checks is not null", myname); + cleanup_milter_hbc_checks = + hbc_header_checks_create(VAR_MILT_HEAD_CHECKS, var_milt_head_checks, + NO_MIME_HDR_NAME, NO_MIME_HDR_VALUE, + NO_NESTED_HDR_NAME, NO_NESTED_HDR_VALUE, + &call_backs); + + if (cleanup_milter_hbc_reply) + msg_panic("%s: cleanup_milter_hbc_reply is not null", myname); + cleanup_milter_hbc_reply = vstring_alloc(100); +} + +#ifdef TEST + +/* cleanup_milter_header_checks_deinit - undo cleanup_milter_header_checks_init */ + +static void cleanup_milter_header_checks_deinit(void) +{ + static const char myname[] = "cleanup_milter_header_checks_deinit"; + + if (cleanup_milter_hbc_checks == 0) + msg_panic("%s: cleanup_milter_hbc_checks is null", myname); + hbc_header_checks_free(cleanup_milter_hbc_checks); + cleanup_milter_hbc_checks = 0; + + if (cleanup_milter_hbc_reply == 0) + msg_panic("%s: cleanup_milter_hbc_reply is null", myname); + vstring_free(cleanup_milter_hbc_reply); + cleanup_milter_hbc_reply = 0; +} + +#endif + +/* cleanup_milter_header_checks_reinit - re-init post-Milter header checks */ + +static void cleanup_milter_header_checks_reinit(CLEANUP_STATE *state) +{ + if (state->filter) + myfree(state->filter); + state->filter = 0; + if (state->redirect) + myfree(state->redirect); + state->redirect = 0; + VSTRING_RESET(cleanup_milter_hbc_reply); +} + +/* cleanup_milter_hbc_finish - finalize post-Milter header checks */ + +static void cleanup_milter_hbc_finish(CLEANUP_STATE *state) +{ + if (CLEANUP_OUT_OK(state) + && !CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state) + && (state->filter || state->redirect)) + cleanup_milter_hbc_add_meta_records(state); +} + + /* + * Milter replies. + */ +#define CLEANUP_MILTER_SET_REASON(__state, __reason) do { \ + if ((__state)->reason) \ + myfree((__state)->reason); \ + (__state)->reason = mystrdup(__reason); \ + if ((__state)->smtp_reply) { \ + myfree((__state)->smtp_reply); \ + (__state)->smtp_reply = 0; \ + } \ + } while (0) + +#define CLEANUP_MILTER_SET_SMTP_REPLY(__state, __smtp_reply) do { \ + if ((__state)->reason) \ + myfree((__state)->reason); \ + (__state)->reason = mystrdup(__smtp_reply + 4); \ + printable((__state)->reason, '_'); \ + if ((__state)->smtp_reply) \ + myfree((__state)->smtp_reply); \ + (__state)->smtp_reply = mystrdup(__smtp_reply); \ + } while (0) + +/* cleanup_milter_set_error - set error flag from errno */ + +static void cleanup_milter_set_error(CLEANUP_STATE *state, int err) +{ + if (err == EFBIG) { + msg_warn("%s: queue file size limit exceeded", state->queue_id); + state->errs |= CLEANUP_STAT_SIZE; + } else { + msg_warn("%s: write queue file: %m", state->queue_id); + state->errs |= CLEANUP_STAT_WRITE; + } +} + +/* cleanup_milter_error - return dummy error description */ + +static const char *cleanup_milter_error(CLEANUP_STATE *state, int err) +{ + const char *myname = "cleanup_milter_error"; + const CLEANUP_STAT_DETAIL *dp; + + /* + * For consistency with error reporting within the milter infrastructure, + * content manipulation routines return a null pointer on success, and an + * SMTP-like response on error. + * + * However, when cleanup_milter_apply() receives this error response from + * the milter infrastructure, it ignores the text since the appropriate + * cleanup error flags were already set by cleanup_milter_set_error(). + * + * Specify a null error number when the "errno to error flag" mapping was + * already done elsewhere, possibly outside this module. + */ + if (err) + cleanup_milter_set_error(state, err); + else if (CLEANUP_OUT_OK(state)) + msg_panic("%s: missing errno to error flag mapping", myname); + if (state->milter_err_text == 0) + state->milter_err_text = vstring_alloc(50); + dp = cleanup_stat_detail(state->errs); + return (STR(vstring_sprintf(state->milter_err_text, + "%d %s %s", dp->smtp, dp->dsn, dp->text))); +} + +/* cleanup_add_header - append message header */ + +static const char *cleanup_add_header(void *context, const char *name, + const char *space, + const char *value) +{ + const char *myname = "cleanup_add_header"; + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + VSTRING *buf; + off_t reverse_ptr_offset; + off_t new_hdr_offset; + + /* + * To simplify implementation, the cleanup server writes a dummy "header + * append" pointer record after the last message header. We cache both + * the location and the target of the current "header append" pointer + * record. + */ + if (state->append_hdr_pt_offset < 0) + msg_panic("%s: no header append pointer location", myname); + if (state->append_hdr_pt_target < 0) + msg_panic("%s: no header append pointer target", myname); + + /* + * Return early when Milter header checks request that this header record + * be dropped, or that the message is discarded. Note: CLEANUP_OUT_OK() + * tests CLEANUP_FLAG_DISCARD. We don't want to report the latter as an + * error. + */ + buf = vstring_alloc(100); + vstring_sprintf(buf, "%s:%s%s", name, space, value); + if (cleanup_milter_hbc_checks) { + if (cleanup_milter_header_checks(state, buf) == 0 + || (state->flags & CLEANUP_FLAG_DISCARD)) { + vstring_free(buf); + return (0); + } + if (CLEANUP_OUT_OK(state) == 0) { + vstring_free(buf); + return (cleanup_milter_error(state, 0)); + } + } + + /* + * Allocate space after the end of the queue file, and write the header + * record(s), followed by a reverse pointer record that points to the + * target of the old "header append" pointer record. This reverse pointer + * record becomes the new "header append" pointer record. + */ + if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + vstring_free(buf); + return (cleanup_milter_error(state, errno)); + } + /* XXX emit prepended header, then clear it. */ + cleanup_out_header(state, buf); /* Includes padding */ + vstring_free(buf); + if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) { + msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); + return (cleanup_milter_error(state, errno)); + } + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) state->append_hdr_pt_target); + + /* + * Pointer flipping: update the old "header append" pointer record value + * with the location of the new header record. + * + * XXX To avoid unnecessary seek operations when the new header immediately + * follows the old append header pointer, write a null pointer or make + * the record reading loop smarter. Making vstream_fseek() smarter does + * not help, because it doesn't know if we're going to read or write + * after a write+seek sequence. + */ + if (vstream_fseek(state->dst, state->append_hdr_pt_offset, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + return (cleanup_milter_error(state, errno)); + } + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) new_hdr_offset); + + /* + * Update the in-memory "header append" pointer record location with the + * location of the reverse pointer record that follows the new header. + * The target of the "header append" pointer record does not change; it's + * always the record that follows the dummy pointer record that was + * written while Postfix received the message. + */ + state->append_hdr_pt_offset = reverse_ptr_offset; + + /* + * In case of error while doing record output. + */ + return (CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) : + cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply) ? + STR(cleanup_milter_hbc_reply) : 0); + + /* + * Note: state->append_hdr_pt_target never changes. + */ +} + +/* cleanup_find_header_start - find specific header instance */ + +static off_t cleanup_find_header_start(CLEANUP_STATE *state, ssize_t index, + const char *header_label, + VSTRING *buf, + int *prec_type, + int allow_ptr_backup, + int skip_headers) +{ + const char *myname = "cleanup_find_header_start"; + off_t curr_offset; /* offset after found record */ + off_t ptr_offset; /* pointer to found record */ + VSTRING *ptr_buf = 0; + int rec_type = REC_TYPE_ERROR; + int last_type; + ssize_t len; + int hdr_count = 0; + + if (msg_verbose) + msg_info("%s: index %ld name \"%s\"", + myname, (long) index, header_label ? header_label : "(none)"); + + /* + * Sanity checks. + */ + if (index < 1) + msg_panic("%s: bad header index %ld", myname, (long) index); + + /* + * Skip to the start of the message content, and read records until we + * either find the specified header, or until we hit the end of the + * headers. + * + * The index specifies the header instance: 1 is the first one. The header + * label specifies the header name. A null pointer matches any header. + * + * When the specified header is not found, the result value is -1. + * + * When the specified header is found, its first record is stored in the + * caller-provided read buffer, and the result value is the queue file + * offset of that record. The file read position is left at the start of + * the next (non-filler) queue file record, which can be the remainder of + * a multi-record header. + * + * When a header is found and allow_ptr_backup is non-zero, then the result + * is either the first record of that header, or it is the pointer record + * that points to the first record of that header. In the latter case, + * the file read position is undefined. Returning the pointer allows us + * to do some optimizations when inserting text multiple times at the + * same place. + * + * XXX We can't use the MIME processor here. It not only buffers up the + * input, it also reads the record that follows a complete header before + * it invokes the header call-back action. This complicates the way that + * we discover header offsets and boundaries. Worse is that the MIME + * processor is unaware that multi-record message headers can have PTR + * records in the middle. + * + * XXX The draw-back of not using the MIME processor is that we have to + * duplicate some of its logic here and in the routine that finds the end + * of the header record. To minimize the duplication we define an ugly + * macro that is used in all code that scans for header boundaries. + * + * XXX Sendmail compatibility (based on Sendmail 8.13.6 measurements). + * + * - When changing Received: header #1, we change the Received: header that + * follows our own one; a request to change Received: header #0 is + * silently treated as a request to change Received: header #1. + * + * - When changing Date: header #1, we change the first Date: header; a + * request to change Date: header #0 is silently treated as a request to + * change Date: header #1. + * + * Thus, header change requests are relative to the content as received, + * that is, the content after our own Received: header. They can affect + * only the headers that the MTA actually exposes to mail filter + * applications. + * + * - However, when inserting a header at position 0, the new header appears + * before our own Received: header, and when inserting at position 1, the + * new header appears after our own Received: header. + * + * Thus, header insert operations are relative to the content as delivered, + * that is, the content including our own Received: header. + * + * None of the above is applicable after a Milter inserts a header before + * our own Received: header. From then on, our own Received: header + * becomes just like other headers. + */ +#define CLEANUP_FIND_HEADER_NOTFOUND (-1) +#define CLEANUP_FIND_HEADER_IOERROR (-2) + +#define CLEANUP_FIND_HEADER_RETURN(offs) do { \ + if (ptr_buf) \ + vstring_free(ptr_buf); \ + return (offs); \ + } while (0) + +#define GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset, quit) \ + if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0) { \ + msg_warn("%s: read file %s: %m", myname, cleanup_path); \ + cleanup_milter_set_error(state, errno); \ + do { quit; } while (0); \ + } \ + if (msg_verbose > 1) \ + msg_info("%s: read: %ld: %.*s", myname, (long) curr_offset, \ + LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf)); \ + if (rec_type == REC_TYPE_DTXT) \ + continue; \ + if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT \ + && rec_type != REC_TYPE_PTR) \ + break; + /* End of hairy macros. */ + + if (vstream_fseek(state->dst, state->data_offset, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + cleanup_milter_set_error(state, errno); + CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR); + } + for (ptr_offset = 0, last_type = 0; /* void */ ; /* void */ ) { + if ((curr_offset = vstream_ftell(state->dst)) < 0) { + msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); + cleanup_milter_set_error(state, errno); + CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR); + } + /* Don't follow the "append header" pointer. */ + if (curr_offset == state->append_hdr_pt_offset) + break; + /* Caution: this macro terminates the loop at end-of-message. */ + /* Don't do complex processing while breaking out of this loop. */ + GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, buf, curr_offset, + CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR)); + /* Caution: don't assume ptr->header. This may be header-ptr->body. */ + if (rec_type == REC_TYPE_PTR) { + if (rec_goto(state->dst, STR(buf)) < 0) { + msg_warn("%s: read file %s: %m", myname, cleanup_path); + cleanup_milter_set_error(state, errno); + CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR); + } + /* Save PTR record, in case it points to the start of a header. */ + if (allow_ptr_backup) { + ptr_offset = curr_offset; + if (ptr_buf == 0) + ptr_buf = vstring_alloc(100); + vstring_strcpy(ptr_buf, STR(buf)); + } + /* Don't update last_type; PTR can happen after REC_TYPE_CONT. */ + continue; + } + /* The middle of a multi-record header. */ + else if (last_type == REC_TYPE_CONT || IS_SPACE_TAB(STR(buf)[0])) { + /* Reset the saved PTR record and update last_type. */ + } + /* No more message headers. */ + else if ((len = is_header(STR(buf))) == 0) { + break; + } + /* This the start of a message header. */ + else if (hdr_count++ < skip_headers) + /* Reset the saved PTR record and update last_type. */ ; + else if ((header_label == 0 + || (strncasecmp(header_label, STR(buf), len) == 0 + && (strlen(header_label) == len))) + && --index == 0) { + /* If we have a saved PTR record, it points to start of header. */ + break; + } + ptr_offset = 0; + last_type = rec_type; + } + + /* + * In case of failure, return negative start position. + */ + if (index > 0) { + curr_offset = CLEANUP_FIND_HEADER_NOTFOUND; + } else { + + /* + * Skip over short-header padding, so that the file read pointer is + * always positioned at the first non-padding record after the header + * record. Insist on padding after short a header record, so that a + * short header record can safely be overwritten by a pointer record. + */ + if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE) { + VSTRING *rbuf = (ptr_offset ? buf : + (ptr_buf ? ptr_buf : + (ptr_buf = vstring_alloc(100)))); + int rval; + + if ((rval = rec_get_raw(state->dst, rbuf, 0, REC_FLAG_NONE)) < 0) { + cleanup_milter_set_error(state, errno); + CLEANUP_FIND_HEADER_RETURN(CLEANUP_FIND_HEADER_IOERROR); + } + if (rval != REC_TYPE_DTXT) + msg_panic("%s: short header without padding", myname); + } + + /* + * Optionally return a pointer to the message header, instead of the + * start of the message header itself. In that case the file read + * position is undefined (actually it is at the first non-padding + * record that follows the message header record). + */ + if (ptr_offset != 0) { + rec_type = REC_TYPE_PTR; + curr_offset = ptr_offset; + vstring_strcpy(buf, STR(ptr_buf)); + } + *prec_type = rec_type; + } + if (msg_verbose) + msg_info("%s: index %ld name %s type %d offset %ld", + myname, (long) index, header_label ? + header_label : "(none)", rec_type, (long) curr_offset); + + CLEANUP_FIND_HEADER_RETURN(curr_offset); +} + +/* cleanup_find_header_end - find end of header */ + +static off_t cleanup_find_header_end(CLEANUP_STATE *state, + VSTRING *rec_buf, + int last_type) +{ + const char *myname = "cleanup_find_header_end"; + off_t read_offset; + int rec_type; + + /* + * This routine is called immediately after cleanup_find_header_start(). + * rec_buf is the cleanup_find_header_start() result record; last_type is + * the corresponding record type: REC_TYPE_PTR or REC_TYPE_NORM; the file + * read position is at the first non-padding record after the result + * header record. + */ + for (;;) { + if ((read_offset = vstream_ftell(state->dst)) < 0) { + msg_warn("%s: read file %s: %m", myname, cleanup_path); + cleanup_milter_error(state, errno); + return (-1); + } + /* Don't follow the "append header" pointer. */ + if (read_offset == state->append_hdr_pt_offset) + break; + /* Caution: this macro terminates the loop at end-of-message. */ + /* Don't do complex processing while breaking out of this loop. */ + GET_NEXT_TEXT_OR_PTR_RECORD(rec_type, state, rec_buf, read_offset, + /* Warning and errno->error mapping are done elsewhere. */ + return (-1)); + if (rec_type == REC_TYPE_PTR) { + if (rec_goto(state->dst, STR(rec_buf)) < 0) { + msg_warn("%s: read file %s: %m", myname, cleanup_path); + cleanup_milter_error(state, errno); + return (-1); + } + /* Don't update last_type; PTR may follow REC_TYPE_CONT. */ + continue; + } + /* Start of header or message body. */ + if (last_type != REC_TYPE_CONT && !IS_SPACE_TAB(STR(rec_buf)[0])) + break; + last_type = rec_type; + } + return (read_offset); +} + +/* cleanup_patch_header - patch new header into an existing header */ + +static const char *cleanup_patch_header(CLEANUP_STATE *state, + const char *new_hdr_name, + const char *hdr_space, + const char *new_hdr_value, + off_t old_rec_offset, + int old_rec_type, + VSTRING *old_rec_buf, + off_t next_offset) +{ + const char *myname = "cleanup_patch_header"; + VSTRING *buf = vstring_alloc(100); + off_t new_hdr_offset; + +#define CLEANUP_PATCH_HEADER_RETURN(ret) do { \ + vstring_free(buf); \ + return (ret); \ + } while (0) + + if (msg_verbose) + msg_info("%s: \"%s\" \"%s\" at %ld", + myname, new_hdr_name, new_hdr_value, (long) old_rec_offset); + + /* + * Allocate space after the end of the queue file for the new header and + * optionally save an existing record to make room for a forward pointer + * record. If the saved record was not a PTR record, follow the saved + * record by a reverse pointer record that points to the record after the + * original location of the saved record. + * + * We update the queue file in a safe manner: save the new header and the + * existing records after the end of the queue file, write the reverse + * pointer, and only then overwrite the saved records with the forward + * pointer to the new header. + * + * old_rec_offset, old_rec_type, and old_rec_buf specify the record that we + * are about to overwrite with a pointer record. If the record needs to + * be saved (i.e. old_rec_type > 0), the buffer contains the data content + * of exactly one PTR or text record. + * + * next_offset specifies the record that follows the to-be-overwritten + * record. It is ignored when the to-be-saved record is a pointer record. + */ + + /* + * Return early when Milter header checks request that this header record + * be dropped. + */ + vstring_sprintf(buf, "%s:%s%s", new_hdr_name, hdr_space, new_hdr_value); + if (cleanup_milter_hbc_checks + && cleanup_milter_header_checks(state, buf) == 0) + CLEANUP_PATCH_HEADER_RETURN(0); + + /* + * Write the new header to a new location after the end of the queue + * file. + */ + if ((new_hdr_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno)); + } + /* XXX emit prepended header, then clear it. */ + cleanup_out_header(state, buf); /* Includes padding */ + if (msg_verbose > 1) + msg_info("%s: %ld: write %.*s", myname, (long) new_hdr_offset, + LEN(buf) > 30 ? 30 : (int) LEN(buf), STR(buf)); + + /* + * Optionally, save the existing text record or pointer record that will + * be overwritten with the forward pointer. Pad a short saved record to + * ensure that it, too, can be overwritten by a pointer. + */ + if (old_rec_type > 0) { + CLEANUP_OUT_BUF(state, old_rec_type, old_rec_buf); + if (LEN(old_rec_buf) < REC_TYPE_PTR_PAYL_SIZE) + rec_pad(state->dst, REC_TYPE_DTXT, + REC_TYPE_PTR_PAYL_SIZE - LEN(old_rec_buf)); + if (msg_verbose > 1) + msg_info("%s: write %.*s", myname, LEN(old_rec_buf) > 30 ? + 30 : (int) LEN(old_rec_buf), STR(old_rec_buf)); + } + + /* + * If the saved record wasn't a PTR record, write the reverse pointer + * after the saved records. A reverse pointer value of -1 means we were + * confused about what we were going to save. + */ + if (old_rec_type != REC_TYPE_PTR) { + if (next_offset < 0) + msg_panic("%s: bad reverse pointer %ld", + myname, (long) next_offset); + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) next_offset); + if (msg_verbose > 1) + msg_info("%s: write PTR %ld", myname, (long) next_offset); + } + + /* + * Write the forward pointer over the old record. Generally, a pointer + * record will be shorter than a header record, so there will be a gap in + * the queue file before the next record. In other words, we must always + * follow pointer records otherwise we get out of sync with the data. + */ + if (vstream_fseek(state->dst, old_rec_offset, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + CLEANUP_PATCH_HEADER_RETURN(cleanup_milter_error(state, errno)); + } + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) new_hdr_offset); + if (msg_verbose > 1) + msg_info("%s: %ld: write PTR %ld", myname, (long) old_rec_offset, + (long) new_hdr_offset); + + /* + * In case of error while doing record output. + */ + CLEANUP_PATCH_HEADER_RETURN( + CLEANUP_OUT_OK(state) == 0 ? cleanup_milter_error(state, 0) : + cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply) ? + STR(cleanup_milter_hbc_reply) : 0); + + /* + * Note: state->append_hdr_pt_target never changes. + */ +} + +/* cleanup_ins_header - insert message header */ + +static const char *cleanup_ins_header(void *context, ssize_t index, + const char *new_hdr_name, + const char *hdr_space, + const char *new_hdr_value) +{ + const char *myname = "cleanup_ins_header"; + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + VSTRING *old_rec_buf = vstring_alloc(100); + off_t old_rec_offset; + int old_rec_type; + off_t next_offset; + const char *ret; + +#define CLEANUP_INS_HEADER_RETURN(ret) do { \ + vstring_free(old_rec_buf); \ + return (ret); \ + } while (0) + + if (msg_verbose) + msg_info("%s: %ld \"%s\" \"%s\"", + myname, (long) index, new_hdr_name, new_hdr_value); + + /* + * Look for a header at the specified position. + * + * The lookup result may be a pointer record. This allows us to make some + * optimization when multiple insert operations happen in the same place. + * + * Index 1 is the top-most header. + */ +#define NO_HEADER_NAME ((char *) 0) +#define ALLOW_PTR_BACKUP 1 +#define SKIP_ONE_HEADER 1 +#define DONT_SKIP_HEADERS 0 + + if (index < 1) + index = 1; + old_rec_offset = cleanup_find_header_start(state, index, NO_HEADER_NAME, + old_rec_buf, &old_rec_type, + ALLOW_PTR_BACKUP, + DONT_SKIP_HEADERS); + if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR) + /* Warning and errno->error mapping are done elsewhere. */ + CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, 0)); + + /* + * If the header does not exist, simply append the header to the linked + * list at the "header append" pointer record. + */ + if (old_rec_offset < 0) + CLEANUP_INS_HEADER_RETURN(cleanup_add_header(context, new_hdr_name, + hdr_space, new_hdr_value)); + + /* + * If the header does exist, save both the new and the existing header to + * new storage at the end of the queue file, and link the new storage + * with a forward and reverse pointer (don't write a reverse pointer if + * we are starting with a pointer record). + */ + if (old_rec_type == REC_TYPE_PTR) { + next_offset = -1; + } else { + if ((next_offset = vstream_ftell(state->dst)) < 0) { + msg_warn("%s: read file %s: %m", myname, cleanup_path); + CLEANUP_INS_HEADER_RETURN(cleanup_milter_error(state, errno)); + } + } + ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value, + old_rec_offset, old_rec_type, + old_rec_buf, next_offset); + CLEANUP_INS_HEADER_RETURN(ret); +} + +/* cleanup_upd_header - modify or append message header */ + +static const char *cleanup_upd_header(void *context, ssize_t index, + const char *new_hdr_name, + const char *hdr_space, + const char *new_hdr_value) +{ + const char *myname = "cleanup_upd_header"; + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + VSTRING *rec_buf; + off_t old_rec_offset; + off_t next_offset; + int last_type; + const char *ret; + + if (msg_verbose) + msg_info("%s: %ld \"%s\" \"%s\"", + myname, (long) index, new_hdr_name, new_hdr_value); + + /* + * Sanity check. + */ + if (*new_hdr_name == 0) + msg_panic("%s: null header name", myname); + + /* + * Find the header that is being modified. + * + * The lookup result will never be a pointer record. + * + * Index 1 is the first matching header instance. + * + * XXX When a header is updated repeatedly we create jumps to jumps. To + * eliminate this, rewrite the loop below so that we can start with the + * pointer record that points to the header that's being edited. + */ +#define DONT_SAVE_RECORD 0 +#define NO_PTR_BACKUP 0 + +#define CLEANUP_UPD_HEADER_RETURN(ret) do { \ + vstring_free(rec_buf); \ + return (ret); \ + } while (0) + + rec_buf = vstring_alloc(100); + old_rec_offset = cleanup_find_header_start(state, index, new_hdr_name, + rec_buf, &last_type, + NO_PTR_BACKUP, + SKIP_ONE_HEADER); + if (old_rec_offset == CLEANUP_FIND_HEADER_IOERROR) + /* Warning and errno->error mapping are done elsewhere. */ + CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0)); + + /* + * If no old header is found, simply append the new header to the linked + * list at the "header append" pointer record. + */ + if (old_rec_offset < 0) + CLEANUP_UPD_HEADER_RETURN(cleanup_add_header(context, new_hdr_name, + hdr_space, new_hdr_value)); + + /* + * If the old header is found, find the end of the old header, save the + * new header to new storage at the end of the queue file, and link the + * new storage with a forward and reverse pointer. + */ + if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0) + /* Warning and errno->error mapping are done elsewhere. */ + CLEANUP_UPD_HEADER_RETURN(cleanup_milter_error(state, 0)); + ret = cleanup_patch_header(state, new_hdr_name, hdr_space, new_hdr_value, + old_rec_offset, DONT_SAVE_RECORD, + (VSTRING *) 0, next_offset); + CLEANUP_UPD_HEADER_RETURN(ret); +} + +/* cleanup_del_header - delete message header */ + +static const char *cleanup_del_header(void *context, ssize_t index, + const char *hdr_name) +{ + const char *myname = "cleanup_del_header"; + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + VSTRING *rec_buf; + off_t header_offset; + off_t next_offset; + int last_type; + + if (msg_verbose) + msg_info("%s: %ld \"%s\"", myname, (long) index, hdr_name); + + /* + * Sanity check. + */ + if (*hdr_name == 0) + msg_panic("%s: null header name", myname); + + /* + * Find the header that is being deleted. + * + * The lookup result will never be a pointer record. + * + * Index 1 is the first matching header instance. + */ +#define CLEANUP_DEL_HEADER_RETURN(ret) do { \ + vstring_free(rec_buf); \ + return (ret); \ + } while (0) + + rec_buf = vstring_alloc(100); + header_offset = cleanup_find_header_start(state, index, hdr_name, rec_buf, + &last_type, NO_PTR_BACKUP, + SKIP_ONE_HEADER); + if (header_offset == CLEANUP_FIND_HEADER_IOERROR) + /* Warning and errno->error mapping are done elsewhere. */ + CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0)); + + /* + * Overwrite the beginning of the header record with a pointer to the + * information that follows the header. We can't simply overwrite the + * header with cleanup_out_header() and a special record type, because + * there may be a PTR record in the middle of a multi-line header. + */ + if (header_offset > 0) { + if ((next_offset = cleanup_find_header_end(state, rec_buf, last_type)) < 0) + /* Warning and errno->error mapping are done elsewhere. */ + CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, 0)); + /* Mark the header as deleted. */ + if (vstream_fseek(state->dst, header_offset, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + CLEANUP_DEL_HEADER_RETURN(cleanup_milter_error(state, errno)); + } + rec_fprintf(state->dst, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) next_offset); + } + vstring_free(rec_buf); + + /* + * In case of error while doing record output. + */ + return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0)); +} + +/* cleanup_chg_from - replace sender address, ignore ESMTP arguments */ + +static const char *cleanup_chg_from(void *context, const char *ext_from, + const char *esmtp_args) +{ + const char *myname = "cleanup_chg_from"; + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + off_t new_offset; + off_t new_sender_offset; + off_t after_sender_offs; + int addr_count; + TOK822 *tree; + TOK822 *tp; + VSTRING *int_sender_buf; + int dsn_envid = 0; + int dsn_ret = 0; + + if (msg_verbose) + msg_info("%s: \"%s\" \"%s\"", myname, ext_from, esmtp_args); + + /* + * ESMTP support is limited to RET and ENVID, i.e. things that are stored + * together with the sender queue file record. + */ + if (esmtp_args[0]) { + ARGV *esmtp_argv; + int i; + const char *arg; + + esmtp_argv = argv_split(esmtp_args, " "); + for (i = 0; i < esmtp_argv->argc; ++i) { + arg = esmtp_argv->argv[i]; + if (strncasecmp(arg, "RET=", 4) == 0) { + if ((dsn_ret = dsn_ret_code(arg + 4)) == 0) { + msg_warn("Ignoring bad ESMTP parameter \"%s\" in " + "SMFI_CHGFROM request", arg); + } else { + state->dsn_ret = dsn_ret; + } + } else if (strncasecmp(arg, "ENVID=", 6) == 0) { + if (state->milter_dsn_buf == 0) + state->milter_dsn_buf = vstring_alloc(20); + dsn_envid = (xtext_unquote(state->milter_dsn_buf, arg + 6) + && allprint(STR(state->milter_dsn_buf))); + if (!dsn_envid) { + msg_warn("Ignoring bad ESMTP parameter \"%s\" in " + "SMFI_CHGFROM request", arg); + } else { + if (state->dsn_envid) + myfree(state->dsn_envid); + state->dsn_envid = mystrdup(STR(state->milter_dsn_buf)); + } + } else { + msg_warn("Ignoring bad ESMTP parameter \"%s\" in " + "SMFI_CHGFROM request", arg); + } + } + argv_free(esmtp_argv); + } + + /* + * The cleanup server remembers the file offset of the current sender + * address record (offset in sender_pt_offset) and the file offset of the + * record that follows the sender address (offset in sender_pt_target). + * Short original sender records are padded, so that they can safely be + * overwritten with a pointer record to the new sender address record. + */ + if (state->sender_pt_offset < 0) + msg_panic("%s: no original sender record offset", myname); + if (state->sender_pt_target < 0) + msg_panic("%s: no post-sender record offset", myname); + + /* + * Allocate space after the end of the queue file, and write the new {DSN + * envid, DSN ret, sender address, sender BCC} records, followed by a + * reverse pointer record that points to the record that follows the + * original sender record. + * + * We update the queue file in a safe manner: save the new sender after the + * end of the queue file, write the reverse pointer, and only then + * overwrite the old sender record with the forward pointer to the new + * sender. + */ + if ((new_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + return (cleanup_milter_error(state, errno)); + } + + /* + * Sender DSN attribute records precede the sender record. + */ + if (dsn_envid) + rec_fprintf(state->dst, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_DSN_ENVID, STR(state->milter_dsn_buf)); + if (dsn_ret) + rec_fprintf(state->dst, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_RET, dsn_ret); + if (dsn_envid == 0 && dsn_ret == 0) { + new_sender_offset = new_offset; + } else if ((new_sender_offset = vstream_ftell(state->dst)) < 0) { + msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); + return (cleanup_milter_error(state, errno)); + } + + /* + * Transform the address from external form to internal form. This also + * removes the enclosing <>, if present. + * + * XXX vstring_alloc() rejects zero-length requests. + */ + int_sender_buf = vstring_alloc(strlen(ext_from) + 1); + tree = tok822_parse(ext_from); + for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { + if (tp->type == TOK822_ADDR) { + if (addr_count == 0) { + tok822_internalize(int_sender_buf, tp->head, TOK822_STR_DEFL); + addr_count += 1; + } else { + msg_warn("%s: Milter request to add multi-sender: \"%s\"", + state->queue_id, ext_from); + break; + } + } + } + tok822_free_tree(tree); + after_sender_offs = cleanup_addr_sender(state, STR(int_sender_buf)); + vstring_free(int_sender_buf); + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) state->sender_pt_target); + state->sender_pt_target = after_sender_offs; + + /* + * Overwrite the current sender record with the pointer to the new {DSN + * envid, DSN ret, sender address, sender BCC} records. + */ + if (vstream_fseek(state->dst, state->sender_pt_offset, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + return (cleanup_milter_error(state, errno)); + } + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) new_offset); + + /* + * Remember the location of the new current sender record. + */ + state->sender_pt_offset = new_sender_offset; + + /* + * In case of error while doing record output. + */ + return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0)); +} + +/* cleanup_add_rcpt_par - append recipient address, with ESMTP arguments */ + +static const char *cleanup_add_rcpt_par(void *context, const char *ext_rcpt, + const char *esmtp_args) +{ + const char *myname = "cleanup_add_rcpt_par"; + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + off_t new_rcpt_offset; + off_t reverse_ptr_offset; + int addr_count; + TOK822 *tree; + TOK822 *tp; + VSTRING *int_rcpt_buf; + VSTRING *orcpt_buf = 0; + ARGV *esmtp_argv; + int dsn_notify = 0; + const char *dsn_orcpt_info = 0; + size_t type_len; + int i; + const char *arg; + const char *arg_val; + + if (msg_verbose) + msg_info("%s: \"%s\" \"%s\"", myname, ext_rcpt, esmtp_args); + + /* + * To simplify implementation, the cleanup server writes a dummy + * "recipient append" pointer record after the last recipient. We cache + * both the location and the target of the current "recipient append" + * pointer record. + */ + if (state->append_rcpt_pt_offset < 0) + msg_panic("%s: no recipient append pointer location", myname); + if (state->append_rcpt_pt_target < 0) + msg_panic("%s: no recipient append pointer target", myname); + + /* + * Allocate space after the end of the queue file, and write the + * recipient record, followed by a reverse pointer record that points to + * the target of the old "recipient append" pointer record. This reverse + * pointer record becomes the new "recipient append" pointer record. + * + * We update the queue file in a safe manner: save the new recipient after + * the end of the queue file, write the reverse pointer, and only then + * overwrite the old "recipient append" pointer with the forward pointer + * to the new recipient. + */ + if ((new_rcpt_offset = vstream_fseek(state->dst, (off_t) 0, SEEK_END)) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + return (cleanup_milter_error(state, errno)); + } + + /* + * Parse ESMTP parameters. XXX UTF8SMTP don't assume ORCPT is xtext. + */ + if (esmtp_args[0]) { + esmtp_argv = argv_split(esmtp_args, " "); + for (i = 0; i < esmtp_argv->argc; ++i) { + arg = esmtp_argv->argv[i]; + if (strncasecmp(arg, "NOTIFY=", 7) == 0) { /* RFC 3461 */ + if (dsn_notify || (dsn_notify = dsn_notify_mask(arg + 7)) == 0) + msg_warn("%s: Bad NOTIFY parameter from Milter or " + "header/body_checks: \"%.100s\"", + state->queue_id, arg); + } else if (strncasecmp(arg, "ORCPT=", 6) == 0) { /* RFC 3461 */ + if (orcpt_buf == 0) + orcpt_buf = vstring_alloc(100); + if (dsn_orcpt_info + || (type_len = strcspn(arg_val = arg + 6, ";")) == 0 + || (arg_val)[type_len] != ';' + || xtext_unquote_append(vstring_sprintf(orcpt_buf, + "%.*s;", (int) type_len, + arg_val), + arg_val + type_len + 1) == 0) { + msg_warn("%s: Bad ORCPT parameter from Milter or " + "header/body_checks: \"%.100s\"", + state->queue_id, arg); + } else { + dsn_orcpt_info = STR(orcpt_buf); + } + } else { + msg_warn("%s: ignoring ESMTP argument from Milter or " + "header/body_checks: \"%.100s\"", + state->queue_id, arg); + } + } + argv_free(esmtp_argv); + } + + /* + * Transform recipient from external form to internal form. This also + * removes the enclosing <>, if present. + * + * XXX vstring_alloc() rejects zero-length requests. + */ + int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1); + tree = tok822_parse(ext_rcpt); + for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { + if (tp->type == TOK822_ADDR) { + if (addr_count == 0) { + tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL); + addr_count += 1; + } else { + msg_warn("%s: Milter or header/body_checks request to " + "add multi-recipient: \"%s\"", + state->queue_id, ext_rcpt); + break; + } + } + } + tok822_free_tree(tree); + if (addr_count != 0) + cleanup_addr_bcc_dsn(state, STR(int_rcpt_buf), dsn_orcpt_info, + dsn_notify ? dsn_notify : DEF_DSN_NOTIFY); + else + msg_warn("%s: ignoring attempt from Milter to add null recipient", + state->queue_id); + vstring_free(int_rcpt_buf); + if (orcpt_buf) + vstring_free(orcpt_buf); + + /* + * Don't update the queue file when we did not write a recipient record + * (malformed or duplicate BCC recipient). + */ + if (vstream_ftell(state->dst) == new_rcpt_offset) + return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0)); + + /* + * Follow the recipient with a "reverse" pointer to the old recipient + * append target. + */ + if ((reverse_ptr_offset = vstream_ftell(state->dst)) < 0) { + msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); + return (cleanup_milter_error(state, errno)); + } + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) state->append_rcpt_pt_target); + + /* + * Pointer flipping: update the old "recipient append" pointer record + * value to the location of the new recipient record. + */ + if (vstream_fseek(state->dst, state->append_rcpt_pt_offset, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + return (cleanup_milter_error(state, errno)); + } + cleanup_out_format(state, REC_TYPE_PTR, REC_TYPE_PTR_FORMAT, + (long) new_rcpt_offset); + + /* + * Update the in-memory "recipient append" pointer record location with + * the location of the reverse pointer record that follows the new + * recipient. The target of the "recipient append" pointer record does + * not change; it's always the record that follows the dummy pointer + * record that was written while Postfix received the message. + */ + state->append_rcpt_pt_offset = reverse_ptr_offset; + + /* + * In case of error while doing record output. + */ + return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, 0)); +} + +/* cleanup_add_rcpt - append recipient address */ + +static const char *cleanup_add_rcpt(void *context, const char *ext_rcpt) +{ + return (cleanup_add_rcpt_par(context, ext_rcpt, "")); +} + +/* cleanup_del_rcpt - remove recipient and all its expansions */ + +static const char *cleanup_del_rcpt(void *context, const char *ext_rcpt) +{ + const char *myname = "cleanup_del_rcpt"; + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + off_t curr_offset; + VSTRING *buf; + char *attr_name; + char *attr_value; + char *dsn_orcpt = 0; /* XXX for dup filter cleanup */ + int dsn_notify = 0; /* XXX for dup filter cleanup */ + char *orig_rcpt = 0; + char *start; + int rec_type; + int junk; + int count = 0; + TOK822 *tree; + TOK822 *tp; + VSTRING *int_rcpt_buf; + int addr_count; + + if (msg_verbose) + msg_info("%s: \"%s\"", myname, ext_rcpt); + + /* + * Virtual aliasing and other address rewriting happens after the mail + * filter sees the envelope address. Therefore we must delete all + * recipient records whose Postfix (not DSN) original recipient address + * matches the specified address. + * + * As the number of recipients may be very large we can't do an efficient + * two-pass implementation (collect record offsets first, then mark + * records as deleted). Instead we mark records as soon as we find them. + * This is less efficient because we do (seek-write-read) for each marked + * recipient, instead of (seek-write). It's unlikely that VSTREAMs will + * be made smart enough to eliminate unnecessary I/O with small seeks. + * + * XXX When Postfix original recipients are turned off, we have no option + * but to match against the expanded and rewritten recipient address. + * + * XXX Remove the (dsn_orcpt, dsn_notify, orcpt, recip) tuple from the + * duplicate recipient filter. This requires that we maintain reference + * counts. + */ + if (vstream_fseek(state->dst, 0L, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + return (cleanup_milter_error(state, errno)); + } +#define CLEANUP_DEL_RCPT_RETURN(ret) do { \ + if (orig_rcpt != 0) \ + myfree(orig_rcpt); \ + if (dsn_orcpt != 0) \ + myfree(dsn_orcpt); \ + vstring_free(buf); \ + vstring_free(int_rcpt_buf); \ + return (ret); \ + } while (0) + + /* + * Transform recipient from external form to internal form. This also + * removes the enclosing <>, if present. + * + * XXX vstring_alloc() rejects zero-length requests. + */ + int_rcpt_buf = vstring_alloc(strlen(ext_rcpt) + 1); + tree = tok822_parse(ext_rcpt); + for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { + if (tp->type == TOK822_ADDR) { + if (addr_count == 0) { + tok822_internalize(int_rcpt_buf, tp->head, TOK822_STR_DEFL); + addr_count += 1; + } else { + msg_warn("%s: Milter request to drop multi-recipient: \"%s\"", + state->queue_id, ext_rcpt); + break; + } + } + } + tok822_free_tree(tree); + + buf = vstring_alloc(100); + for (;;) { + if (CLEANUP_OUT_OK(state) == 0) + /* Warning and errno->error mapping are done elsewhere. */ + CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, 0)); + if ((curr_offset = vstream_ftell(state->dst)) < 0) { + msg_warn("%s: vstream_ftell file %s: %m", myname, cleanup_path); + CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); + } + if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) <= 0) { + msg_warn("%s: read file %s: %m", myname, cleanup_path); + CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); + } + if (rec_type == REC_TYPE_END) + break; + /* Skip over message content. */ + if (rec_type == REC_TYPE_MESG) { + if (vstream_fseek(state->dst, state->xtra_offset, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); + } + continue; + } + start = STR(buf); + if (rec_type == REC_TYPE_PTR) { + if (rec_goto(state->dst, start) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); + } + continue; + } + /* Map attribute names to pseudo record type. */ + if (rec_type == REC_TYPE_ATTR) { + if (split_nameval(STR(buf), &attr_name, &attr_value) != 0 + || *attr_value == 0) + continue; + if ((junk = rec_attr_map(attr_name)) != 0) { + start = attr_value; + rec_type = junk; + } + } + switch (rec_type) { + case REC_TYPE_DSN_ORCPT: /* RCPT TO ORCPT parameter */ + if (dsn_orcpt != 0) /* can't happen */ + myfree(dsn_orcpt); + dsn_orcpt = mystrdup(start); + break; + case REC_TYPE_DSN_NOTIFY: /* RCPT TO NOTIFY parameter */ + if (alldig(start) && (junk = atoi(start)) > 0 + && DSN_NOTIFY_OK(junk)) + dsn_notify = junk; + else + dsn_notify = 0; + break; + case REC_TYPE_ORCP: /* unmodified RCPT TO address */ + if (orig_rcpt != 0) /* can't happen */ + myfree(orig_rcpt); + orig_rcpt = mystrdup(start); + break; + case REC_TYPE_RCPT: /* rewritten RCPT TO address */ + if (strcmp(orig_rcpt ? orig_rcpt : start, STR(int_rcpt_buf)) == 0) { + if (vstream_fseek(state->dst, curr_offset, SEEK_SET) < 0) { + msg_warn("%s: seek file %s: %m", myname, cleanup_path); + CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); + } + if (REC_PUT_BUF(state->dst, REC_TYPE_DRCP, buf) < 0) { + msg_warn("%s: write queue file: %m", state->queue_id); + CLEANUP_DEL_RCPT_RETURN(cleanup_milter_error(state, errno)); + } + count++; + } + if (var_enable_orcpt) + /* Matches been_here() call in cleanup_out_recipient(). */ + been_here_drop(state->dups, "%s\n%d\n%s\n%s", + dsn_orcpt ? dsn_orcpt : "", dsn_notify, + orig_rcpt ? orig_rcpt : "", STR(int_rcpt_buf)); + /* FALLTHROUGH */ + case REC_TYPE_DRCP: /* canceled recipient */ + case REC_TYPE_DONE: /* can't happen */ + if (orig_rcpt != 0) { + myfree(orig_rcpt); + orig_rcpt = 0; + } + if (dsn_orcpt != 0) { + myfree(dsn_orcpt); + dsn_orcpt = 0; + } + dsn_notify = 0; + break; + } + } + /* Matches been_here_fixed() call in cleanup_out_recipient(). */ + if (var_enable_orcpt == 0 && count > 0) + been_here_drop_fixed(state->dups, STR(int_rcpt_buf)); + + if (msg_verbose) + msg_info("%s: deleted %d records for recipient \"%s\"", + myname, count, ext_rcpt); + + CLEANUP_DEL_RCPT_RETURN(0); +} + +/* cleanup_repl_body - replace message body */ + +static const char *cleanup_repl_body(void *context, int cmd, int rec_type, + VSTRING *buf) +{ + const char *myname = "cleanup_repl_body"; + CLEANUP_STATE *state = (CLEANUP_STATE *) context; + static VSTRING empty; + + /* + * XXX Sendmail compatibility: milters don't see the first body line, so + * don't expect they will send one. + */ + switch (cmd) { + case MILTER_BODY_LINE: + if (cleanup_body_edit_write(state, rec_type, buf) < 0) + return (cleanup_milter_error(state, errno)); + break; + case MILTER_BODY_START: + VSTRING_RESET(&empty); + if (cleanup_body_edit_start(state) < 0 + || cleanup_body_edit_write(state, REC_TYPE_NORM, &empty) < 0) + return (cleanup_milter_error(state, errno)); + break; + case MILTER_BODY_END: + if (cleanup_body_edit_finish(state) < 0) + return (cleanup_milter_error(state, errno)); + break; + default: + msg_panic("%s: bad command: %d", myname, cmd); + } + return (CLEANUP_OUT_OK(state) ? 0 : cleanup_milter_error(state, errno)); +} + +/* cleanup_milter_eval - expand macro */ + +static const char *cleanup_milter_eval(const char *name, void *ptr) +{ + CLEANUP_STATE *state = (CLEANUP_STATE *) ptr; + + /* + * Note: if we use XFORWARD attributes here, then consistency requires + * that we forward all Sendmail macros via XFORWARD. + */ + + /* + * System macros. + */ + if (strcmp(name, S8_MAC_DAEMON_NAME) == 0) + return (var_milt_daemon_name); + if (strcmp(name, S8_MAC_V) == 0) + return (var_milt_v); + + /* + * Connect macros. + */ +#ifndef CLIENT_ATTR_UNKNOWN +#define CLIENT_ATTR_UNKNOWN "unknown" +#define SERVER_ATTR_UNKNOWN "unknown" +#endif + + if (strcmp(name, S8_MAC__) == 0) { + vstring_sprintf(state->temp1, "%s [%s]", + state->reverse_name, state->client_addr); + if (strcasecmp(state->client_name, state->reverse_name) != 0) + vstring_strcat(state->temp1, " (may be forged)"); + return (STR(state->temp1)); + } + if (strcmp(name, S8_MAC_J) == 0) + return (var_myhostname); + if (strcmp(name, S8_MAC_CLIENT_ADDR) == 0) + return (state->client_addr); + if (strcmp(name, S8_MAC_CLIENT_NAME) == 0) + return (state->client_name); + if (strcmp(name, S8_MAC_CLIENT_PORT) == 0) + return (state->client_port + && strcmp(state->client_port, CLIENT_ATTR_UNKNOWN) ? + state->client_port : "0"); + if (strcmp(name, S8_MAC_CLIENT_PTR) == 0) + return (state->reverse_name); + /* XXX S8_MAC_CLIENT_RES needs SMTPD_PEER_CODE_XXX from smtpd. */ + if (strcmp(name, S8_MAC_DAEMON_ADDR) == 0) + return (state->server_addr); + if (strcmp(name, S8_MAC_DAEMON_PORT) == 0) + return (state->server_port + && strcmp(state->server_port, SERVER_ATTR_UNKNOWN) ? + state->server_port : "0"); + + /* + * MAIL FROM macros. + */ + if (strcmp(name, S8_MAC_I) == 0) + return (state->queue_id); +#ifdef USE_SASL_AUTH + if (strcmp(name, S8_MAC_AUTH_TYPE) == 0) + return (nvtable_find(state->attr, MAIL_ATTR_SASL_METHOD)); + if (strcmp(name, S8_MAC_AUTH_AUTHEN) == 0) + return (nvtable_find(state->attr, MAIL_ATTR_SASL_USERNAME)); + if (strcmp(name, S8_MAC_AUTH_AUTHOR) == 0) + return (nvtable_find(state->attr, MAIL_ATTR_SASL_SENDER)); +#endif + if (strcmp(name, S8_MAC_MAIL_ADDR) == 0) + return (state->milter_ext_from ? STR(state->milter_ext_from) : 0); + + /* + * RCPT TO macros. + */ + if (strcmp(name, S8_MAC_RCPT_ADDR) == 0) + return (state->milter_ext_rcpt ? STR(state->milter_ext_rcpt) : 0); + return (0); +} + +/* cleanup_milter_receive - receive milter instances */ + +void cleanup_milter_receive(CLEANUP_STATE *state, int count) +{ + if (state->milters) + milter_free(state->milters); + state->milters = milter_receive(state->src, count); + if (state->milters == 0) + msg_fatal("cleanup_milter_receive: milter receive failed"); + if (count <= 0) + return; + milter_macro_callback(state->milters, cleanup_milter_eval, (void *) state); + milter_edit_callback(state->milters, + cleanup_add_header, cleanup_upd_header, + cleanup_ins_header, cleanup_del_header, + cleanup_chg_from, cleanup_add_rcpt, + cleanup_add_rcpt_par, cleanup_del_rcpt, + cleanup_repl_body, (void *) state); +} + +/* cleanup_milter_apply - apply Milter response, non-zero if rejecting */ + +static const char *cleanup_milter_apply(CLEANUP_STATE *state, const char *event, + const char *resp) +{ + const char *myname = "cleanup_milter_apply"; + const char *action; + const char *text; + const char *attr; + const char *ret = 0; + + if (msg_verbose) + msg_info("%s: %s", myname, resp); + + /* + * Don't process our own milter_header/body checks replies. See comments + * in cleanup_milter_hbc_extend(). + */ + if (cleanup_milter_hbc_reply && + strcmp(resp, STR(cleanup_milter_hbc_reply)) == 0) + return (0); + + /* + * Don't process Milter replies that are redundant because header/body + * checks already decided that we will not receive the message; or Milter + * replies that would have conflicting effect with the outcome of + * header/body checks (for example, header_checks "discard" action + * followed by Milter "reject" reply). Logging both actions would look + * silly. + */ + if (CLEANUP_MILTER_REJECTING_OR_DISCARDING_MESSAGE(state)) { + if (msg_verbose) + msg_info("%s: ignoring redundant or conflicting milter reply: %s", + state->queue_id, resp); + return (0); + } + + /* + * Sanity check. + */ + if (state->client_name == 0) + msg_panic("%s: missing client info initialization", myname); + + /* + * We don't report errors that were already reported by the content + * editing call-back routines. See cleanup_milter_error() above. + */ + if (CLEANUP_OUT_OK(state) == 0) + return (0); + switch (resp[0]) { + case 'H': + /* XXX Should log the reason here. */ + if (state->flags & CLEANUP_FLAG_HOLD) + return (0); + state->flags |= CLEANUP_FLAG_HOLD; + action = "milter-hold"; + text = "milter triggers HOLD action"; + break; + case 'D': + state->flags |= CLEANUP_FLAG_DISCARD; + action = "milter-discard"; + text = "milter triggers DISCARD action"; + break; + case 'S': + /* XXX Can this happen after end-of-message? */ + state->flags |= CLEANUP_STAT_CONT; + action = "milter-reject"; + text = cleanup_strerror(CLEANUP_STAT_CONT); + break; + + /* + * Override permanent reject with temporary reject. This happens when + * the cleanup server has to bounce (hard reject) but is unable to + * store the message (soft reject). After a temporary reject we stop + * inspecting queue file records, so it can't be overruled by + * something else. + * + * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason + * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates + * queue record processing, and prevents bounces from being sent. + */ + case '4': + CLEANUP_MILTER_SET_SMTP_REPLY(state, resp); + ret = state->reason; + state->errs |= CLEANUP_STAT_DEFER; + action = "milter-reject"; + text = resp + 4; + break; + case '5': + CLEANUP_MILTER_SET_SMTP_REPLY(state, resp); + ret = state->reason; + state->errs |= CLEANUP_STAT_CONT; + action = "milter-reject"; + text = resp + 4; + break; + default: + msg_panic("%s: unexpected mail filter reply: %s", myname, resp); + } + vstring_sprintf(state->temp1, "%s: %s: %s from %s[%s]: %s;", + state->queue_id, action, event, state->client_name, + state->client_addr, text); + if (state->sender) + vstring_sprintf_append(state->temp1, " from=<%s>", + info_log_addr_form_sender(state->sender)); + if (state->recip) + vstring_sprintf_append(state->temp1, " to=<%s>", + info_log_addr_form_recipient(state->recip)); + if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_PROTO_NAME)) != 0) + vstring_sprintf_append(state->temp1, " proto=%s", attr); + if ((attr = nvtable_find(state->attr, MAIL_ATTR_LOG_HELO_NAME)) != 0) + vstring_sprintf_append(state->temp1, " helo=<%s>", attr); + msg_info("%s", vstring_str(state->temp1)); + + return (ret); +} + +/* cleanup_milter_client_init - initialize real or ersatz client info */ + +static void cleanup_milter_client_init(CLEANUP_STATE *state) +{ + static const INET_PROTO_INFO *proto_info; + const char *proto_attr; + + /* + * Either the cleanup client specifies a name, address and protocol, or + * we have a local submission and pretend localhost/127.0.0.1/AF_INET. + */ +#define NO_CLIENT_PORT "0" + + state->client_name = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_NAME); + state->reverse_name = + nvtable_find(state->attr, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME); + state->client_addr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_ADDR); + state->client_port = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_PORT); + proto_attr = nvtable_find(state->attr, MAIL_ATTR_ACT_CLIENT_AF); + state->server_addr = nvtable_find(state->attr, MAIL_ATTR_ACT_SERVER_ADDR); + state->server_port = nvtable_find(state->attr, MAIL_ATTR_ACT_SERVER_PORT); + + if (state->client_name == 0 || state->client_addr == 0 || proto_attr == 0 + || !alldig(proto_attr)) { + state->client_name = "localhost"; +#ifdef AF_INET6 + if (proto_info == 0) + proto_info = inet_proto_info(); + if (proto_info->sa_family_list[0] == PF_INET6) { + state->client_addr = "::1"; + state->client_af = AF_INET6; + } else +#endif + { + state->client_addr = "127.0.0.1"; + state->client_af = AF_INET; + } + state->server_addr = state->client_addr; + } else + state->client_af = atoi(proto_attr); + if (state->reverse_name == 0) + state->reverse_name = state->client_name; + /* Compatibility with pre-2.5 queue files. */ + if (state->client_port == 0) { + state->client_port = NO_CLIENT_PORT; + state->server_port = state->client_port; + } +} + +/* cleanup_milter_inspect - run message through mail filter */ + +void cleanup_milter_inspect(CLEANUP_STATE *state, MILTERS *milters) +{ + const char *myname = "cleanup_milter"; + const char *resp; + + if (msg_verbose) + msg_info("enter %s", myname); + + /* + * Initialize, in case we're called via smtpd(8). + */ + if (state->client_name == 0) + cleanup_milter_client_init(state); + + /* + * Prologue: prepare for Milter header/body checks. + */ + if (*var_milt_head_checks) + cleanup_milter_header_checks_reinit(state); + + /* + * Process mail filter replies. The reply format is verified by the mail + * filter library. + */ + if ((resp = milter_message(milters, state->handle->stream, + state->data_offset, state->auto_hdrs)) != 0) + cleanup_milter_apply(state, "END-OF-MESSAGE", resp); + + /* + * Epilogue: finalize Milter header/body checks. + */ + if (*var_milt_head_checks) + cleanup_milter_hbc_finish(state); + + if (msg_verbose) + msg_info("leave %s", myname); +} + +/* cleanup_milter_emul_mail - emulate connect/ehlo/mail event */ + +void cleanup_milter_emul_mail(CLEANUP_STATE *state, + MILTERS *milters, + const char *addr) +{ + const char *resp; + const char *helo; + const char *argv[2]; + + /* + * Per-connection initialization. + */ + milter_macro_callback(milters, cleanup_milter_eval, (void *) state); + milter_edit_callback(milters, + cleanup_add_header, cleanup_upd_header, + cleanup_ins_header, cleanup_del_header, + cleanup_chg_from, cleanup_add_rcpt, + cleanup_add_rcpt_par, cleanup_del_rcpt, + cleanup_repl_body, (void *) state); + if (state->client_name == 0) + cleanup_milter_client_init(state); + + /* + * Emulate SMTP events. + */ + if ((resp = milter_conn_event(milters, state->client_name, state->client_addr, + state->client_port, state->client_af)) != 0) { + cleanup_milter_apply(state, "CONNECT", resp); + return; + } +#define PRETEND_ESMTP 1 + + if (CLEANUP_MILTER_OK(state)) { + if ((helo = nvtable_find(state->attr, MAIL_ATTR_ACT_HELO_NAME)) == 0) + helo = state->client_name; + if ((resp = milter_helo_event(milters, helo, PRETEND_ESMTP)) != 0) { + cleanup_milter_apply(state, "EHLO", resp); + return; + } + } + if (CLEANUP_MILTER_OK(state)) { + if (state->milter_ext_from == 0) + state->milter_ext_from = vstring_alloc(100); + /* Sendmail 8.13 does not externalize the null address. */ + if (*addr) + quote_821_local(state->milter_ext_from, addr); + else + vstring_strcpy(state->milter_ext_from, addr); + argv[0] = STR(state->milter_ext_from); + argv[1] = 0; + if ((resp = milter_mail_event(milters, argv)) != 0) { + cleanup_milter_apply(state, "MAIL", resp); + return; + } + } +} + +/* cleanup_milter_emul_rcpt - emulate rcpt event */ + +void cleanup_milter_emul_rcpt(CLEANUP_STATE *state, + MILTERS *milters, + const char *addr) +{ + const char *myname = "cleanup_milter_emul_rcpt"; + const char *resp; + const char *argv[2]; + + /* + * Sanity check. + */ + if (state->client_name == 0) + msg_panic("%s: missing client info initialization", myname); + + /* + * CLEANUP_STAT_CONT and CLEANUP_STAT_DEFER both update the reason + * attribute, but CLEANUP_STAT_DEFER takes precedence. It terminates + * queue record processing, and prevents bounces from being sent. + */ + if (state->milter_ext_rcpt == 0) + state->milter_ext_rcpt = vstring_alloc(100); + /* Sendmail 8.13 does not externalize the null address. */ + if (*addr) + quote_821_local(state->milter_ext_rcpt, addr); + else + vstring_strcpy(state->milter_ext_rcpt, addr); + argv[0] = STR(state->milter_ext_rcpt); + argv[1] = 0; + if ((resp = milter_rcpt_event(milters, MILTER_FLAG_NONE, argv)) != 0 + && cleanup_milter_apply(state, "RCPT", resp) != 0) { + msg_warn("%s: milter configuration error: can't reject recipient " + "in non-smtpd(8) submission", state->queue_id); + msg_warn("%s: message not accepted, try again later", state->queue_id); + CLEANUP_MILTER_SET_REASON(state, "4.3.5 Server configuration error"); + state->errs |= CLEANUP_STAT_DEFER; + } +} + +/* cleanup_milter_emul_data - emulate data event */ + +void cleanup_milter_emul_data(CLEANUP_STATE *state, MILTERS *milters) +{ + const char *myname = "cleanup_milter_emul_data"; + const char *resp; + + /* + * Sanity check. + */ + if (state->client_name == 0) + msg_panic("%s: missing client info initialization", myname); + + if ((resp = milter_data_event(milters)) != 0) + cleanup_milter_apply(state, "DATA", resp); +} + +#ifdef TEST + + /* + * Queue file editing driver for regression tests. In this case it is OK to + * report fatal errors after I/O errors. + */ +#include +#include +#include +#include +#include + +#undef msg_verbose + +char *cleanup_path; +VSTRING *cleanup_trace_path; +VSTRING *cleanup_strip_chars; +int cleanup_comm_canon_flags; +MAPS *cleanup_comm_canon_maps; +int cleanup_ext_prop_mask; +ARGV *cleanup_masq_domains; +int cleanup_masq_flags; +MAPS *cleanup_rcpt_bcc_maps; +int cleanup_rcpt_canon_flags; +MAPS *cleanup_rcpt_canon_maps; +MAPS *cleanup_send_bcc_maps; +int cleanup_send_canon_flags; +MAPS *cleanup_send_canon_maps; +int var_dup_filter_limit = DEF_DUP_FILTER_LIMIT; +char *var_empty_addr = DEF_EMPTY_ADDR; +MAPS *cleanup_virt_alias_maps; +char *var_milt_daemon_name = "host.example.com"; +char *var_milt_v = DEF_MILT_V; +MILTERS *cleanup_milters = (MILTERS *) ((char *) sizeof(*cleanup_milters)); +char *var_milt_head_checks = ""; + +/* Dummies to satisfy unused external references. */ + +int cleanup_masquerade_internal(CLEANUP_STATE *state, VSTRING *addr, ARGV *masq_domains) +{ + msg_panic("cleanup_masquerade_internal dummy"); +} + +int cleanup_rewrite_internal(const char *context, VSTRING *result, + const char *addr) +{ + vstring_strcpy(result, addr); + return (0); +} + +int cleanup_map11_internal(CLEANUP_STATE *state, VSTRING *addr, + MAPS *maps, int propagate) +{ + msg_panic("cleanup_map11_internal dummy"); +} + +ARGV *cleanup_map1n_internal(CLEANUP_STATE *state, const char *addr, + MAPS *maps, int propagate) +{ + msg_panic("cleanup_map1n_internal dummy"); +} + +void cleanup_envelope(CLEANUP_STATE *state, int type, const char *buf, + ssize_t len) +{ + msg_panic("cleanup_envelope dummy"); +} + +static void usage(void) +{ + msg_warn("usage:"); + msg_warn(" verbose on|off"); + msg_warn(" open pathname"); + msg_warn(" close"); + msg_warn(" add_header index name [value]"); + msg_warn(" ins_header index name [value]"); + msg_warn(" upd_header index name [value]"); + msg_warn(" del_header index name"); + msg_warn(" chg_from addr parameters"); + msg_warn(" add_rcpt addr"); + msg_warn(" add_rcpt_par addr parameters"); + msg_warn(" del_rcpt addr"); + msg_warn(" replbody pathname"); + msg_warn(" header_checks type:name"); +} + +/* flatten_args - unparse partial command line */ + +static void flatten_args(VSTRING *buf, char **argv) +{ + char **cpp; + + VSTRING_RESET(buf); + for (cpp = argv; *cpp; cpp++) { + vstring_strcat(buf, *cpp); + if (cpp[1]) + VSTRING_ADDCH(buf, ' '); + } + VSTRING_TERMINATE(buf); +} + +/* open_queue_file - open an unedited queue file (all-zero dummy PTRs) */ + +static void open_queue_file(CLEANUP_STATE *state, const char *path) +{ + VSTRING *buf = vstring_alloc(100); + off_t curr_offset; + int rec_type; + long msg_seg_len; + long data_offset; + long rcpt_count; + long qmgr_opts; + + if (state->dst != 0) { + msg_warn("closing %s", cleanup_path); + vstream_fclose(state->dst); + state->dst = 0; + myfree(cleanup_path); + cleanup_path = 0; + } + if ((state->dst = vstream_fopen(path, O_RDWR, 0)) == 0) { + msg_warn("open %s: %m", path); + } else { + cleanup_path = mystrdup(path); + for (;;) { + if ((curr_offset = vstream_ftell(state->dst)) < 0) + msg_fatal("file %s: vstream_ftell: %m", cleanup_path); + if ((rec_type = rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE)) < 0) + msg_fatal("file %s: missing SIZE or PTR record", cleanup_path); + if (rec_type == REC_TYPE_SIZE) { + if (sscanf(STR(buf), "%ld %ld %ld %ld", + &msg_seg_len, &data_offset, + &rcpt_count, &qmgr_opts) != 4) + msg_fatal("file %s: bad SIZE record: %s", + cleanup_path, STR(buf)); + state->data_offset = data_offset; + state->xtra_offset = data_offset + msg_seg_len; + } else if (rec_type == REC_TYPE_FROM) { + state->sender_pt_offset = curr_offset; + if (LEN(buf) < REC_TYPE_PTR_PAYL_SIZE + && rec_get_raw(state->dst, buf, 0, REC_FLAG_NONE) != REC_TYPE_PTR) + msg_fatal("file %s: missing PTR record after short sender", + cleanup_path); + if ((state->sender_pt_target = vstream_ftell(state->dst)) < 0) + msg_fatal("file %s: missing END record", cleanup_path); + } else if (rec_type == REC_TYPE_PTR) { + if (state->data_offset < 0) + msg_fatal("file %s: missing SIZE record", cleanup_path); + if (curr_offset < state->data_offset + || curr_offset > state->xtra_offset) { + if (state->append_rcpt_pt_offset < 0) { + state->append_rcpt_pt_offset = curr_offset; + if (atol(STR(buf)) != 0) + msg_fatal("file %s: bad dummy recipient PTR record: %s", + cleanup_path, STR(buf)); + if ((state->append_rcpt_pt_target = + vstream_ftell(state->dst)) < 0) + msg_fatal("file %s: vstream_ftell: %m", cleanup_path); + } else if (curr_offset > state->xtra_offset + && state->append_meta_pt_offset < 0) { + state->append_meta_pt_offset = curr_offset; + if (atol(STR(buf)) != 0) + msg_fatal("file %s: bad dummy meta PTR record: %s", + cleanup_path, STR(buf)); + if ((state->append_meta_pt_target = + vstream_ftell(state->dst)) < 0) + msg_fatal("file %s: vstream_ftell: %m", cleanup_path); + } + } else { + if (state->append_hdr_pt_offset < 0) { + state->append_hdr_pt_offset = curr_offset; + if (atol(STR(buf)) != 0) + msg_fatal("file %s: bad dummy header PTR record: %s", + cleanup_path, STR(buf)); + if ((state->append_hdr_pt_target = + vstream_ftell(state->dst)) < 0) + msg_fatal("file %s: vstream_ftell: %m", cleanup_path); + } + } + } + if (state->append_rcpt_pt_offset > 0 + && state->append_hdr_pt_offset > 0 + && (rec_type == REC_TYPE_END + || state->append_meta_pt_offset > 0)) + break; + } + if (msg_verbose) { + msg_info("append_rcpt_pt_offset %ld append_rcpt_pt_target %ld", + (long) state->append_rcpt_pt_offset, + (long) state->append_rcpt_pt_target); + msg_info("append_hdr_pt_offset %ld append_hdr_pt_target %ld", + (long) state->append_hdr_pt_offset, + (long) state->append_hdr_pt_target); + } + } + vstring_free(buf); +} + +static void close_queue_file(CLEANUP_STATE *state) +{ + (void) vstream_fclose(state->dst); + state->dst = 0; + myfree(cleanup_path); + cleanup_path = 0; +} + +int main(int unused_argc, char **argv) +{ + VSTRING *inbuf = vstring_alloc(100); + VSTRING *arg_buf = vstring_alloc(100); + char *bufp; + int istty = isatty(vstream_fileno(VSTREAM_IN)); + CLEANUP_STATE *state = cleanup_state_alloc((VSTREAM *) 0); + const char *parens = "{}"; + + state->queue_id = mystrdup("NOQUEUE"); + state->sender = mystrdup("sender"); + state->recip = mystrdup("recipient"); + state->client_name = "client_name"; + state->client_addr = "client_addr"; + state->flags |= CLEANUP_FLAG_FILTER_ALL; + + msg_vstream_init(argv[0], VSTREAM_ERR); + var_line_limit = DEF_LINE_LIMIT; + var_header_limit = DEF_HEADER_LIMIT; + var_enable_orcpt = DEF_ENABLE_ORCPT; + var_info_log_addr_form = DEF_INFO_LOG_ADDR_FORM; + + for (;;) { + ARGV *argv; + ssize_t index; + const char *resp = 0; + + if (istty) { + vstream_printf("- "); + vstream_fflush(VSTREAM_OUT); + } + if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0) + break; + + bufp = vstring_str(inbuf); + if (!istty) { + vstream_printf("> %s\n", bufp); + vstream_fflush(VSTREAM_OUT); + } + if (*bufp == '#' || *bufp == 0 || allspace(bufp)) + continue; + argv = argv_splitq(bufp, " ", parens); + if (argv->argc == 0) { + msg_warn("missing command"); + } else if (strcmp(argv->argv[0], "?") == 0) { + usage(); + } else if (strcmp(argv->argv[0], "verbose") == 0) { + if (argv->argc != 2) { + msg_warn("bad verbose argument count: %ld", (long) argv->argc); + } else if (strcmp(argv->argv[1], "on") == 0) { + msg_verbose = 2; + } else if (strcmp(argv->argv[1], "off") == 0) { + msg_verbose = 0; + } else { + msg_warn("bad verbose argument"); + } + } else if (strcmp(argv->argv[0], "line_length_limit") == 0) { + if (argv->argc != 2) { + msg_warn("bad line_length_limit argument count: %ld", + (long) argv->argc); + } else if (alldig(argv->argv[1]) == 0) { + msg_warn("bad line_length_limit argument count: %ld", + (long) argv->argc); + } else if ((var_line_limit = atoi(argv->argv[1])) < DEF_LINE_LIMIT) { + msg_warn("bad line_length_limit argument"); + } + } else if (strcmp(argv->argv[0], "open") == 0) { + if (state->dst != 0) { + msg_info("closing %s", VSTREAM_PATH(state->dst)); + close_queue_file(state); + } + if (argv->argc != 2) { + msg_warn("bad open argument count: %ld", (long) argv->argc); + } else { + open_queue_file(state, argv->argv[1]); + } + } else if (strcmp(argv->argv[0], "enable_original_recipient") == 0) { + if (argv->argc == 1) { + msg_info("enable_original_recipient: %d", var_enable_orcpt); + } else if (argv->argc != 2) { + msg_warn("bad enable_original_recipient argument count: %ld", + (long) argv->argc); + } else if (!alldig(argv->argv[1])) { + msg_warn("non-numeric enable_original_recipient argument: %s", + argv->argv[1]); + } else { + var_enable_orcpt = atoi(argv->argv[1]); + } + } else if (state->dst == 0) { + msg_warn("no open queue file"); + } else if (strcmp(argv->argv[0], "close") == 0) { + if (*var_milt_head_checks) { + cleanup_milter_hbc_finish(state); + myfree(var_milt_head_checks); + var_milt_head_checks = ""; + cleanup_milter_header_checks_deinit(); + } + close_queue_file(state); + } else if (cleanup_milter_hbc_reply && LEN(cleanup_milter_hbc_reply)) { + /* Postfix libmilter would skip further requests. */ + msg_info("ignoring: %s %s %s", argv->argv[0], + argv->argc > 1 ? argv->argv[1] : "", + argv->argc > 2 ? argv->argv[2] : ""); + } else if (strcmp(argv->argv[0], "add_header") == 0) { + if (argv->argc < 2) { + msg_warn("bad add_header argument count: %ld", + (long) argv->argc); + } else { + flatten_args(arg_buf, argv->argv + 2); + resp = cleanup_add_header(state, argv->argv[1], " ", STR(arg_buf)); + } + } else if (strcmp(argv->argv[0], "ins_header") == 0) { + if (argv->argc < 3) { + msg_warn("bad ins_header argument count: %ld", + (long) argv->argc); + } else if ((index = atoi(argv->argv[1])) < 1) { + msg_warn("bad ins_header index value"); + } else { + flatten_args(arg_buf, argv->argv + 3); + resp = cleanup_ins_header(state, index, argv->argv[2], " ", STR(arg_buf)); + } + } else if (strcmp(argv->argv[0], "upd_header") == 0) { + if (argv->argc < 3) { + msg_warn("bad upd_header argument count: %ld", + (long) argv->argc); + } else if ((index = atoi(argv->argv[1])) < 1) { + msg_warn("bad upd_header index value"); + } else { + flatten_args(arg_buf, argv->argv + 3); + resp = cleanup_upd_header(state, index, argv->argv[2], " ", STR(arg_buf)); + } + } else if (strcmp(argv->argv[0], "del_header") == 0) { + if (argv->argc != 3) { + msg_warn("bad del_header argument count: %ld", + (long) argv->argc); + } else if ((index = atoi(argv->argv[1])) < 1) { + msg_warn("bad del_header index value"); + } else { + cleanup_del_header(state, index, argv->argv[2]); + } + } else if (strcmp(argv->argv[0], "chg_from") == 0) { + if (argv->argc != 3) { + msg_warn("bad chg_from argument count: %ld", (long) argv->argc); + } else { + char *arg = argv->argv[2]; + const char *err; + + if (*arg == parens[0] + && (err = extpar(&arg, parens, EXTPAR_FLAG_NONE)) != 0) { + msg_warn("%s in \"%s\"", err, arg); + } else { + cleanup_chg_from(state, argv->argv[1], arg); + } + } + } else if (strcmp(argv->argv[0], "add_rcpt") == 0) { + if (argv->argc != 2) { + msg_warn("bad add_rcpt argument count: %ld", (long) argv->argc); + } else { + cleanup_add_rcpt(state, argv->argv[1]); + } + } else if (strcmp(argv->argv[0], "add_rcpt_par") == 0) { + if (argv->argc != 3) { + msg_warn("bad add_rcpt_par argument count: %ld", + (long) argv->argc); + } else { + cleanup_add_rcpt_par(state, argv->argv[1], argv->argv[2]); + } + } else if (strcmp(argv->argv[0], "del_rcpt") == 0) { + if (argv->argc != 2) { + msg_warn("bad del_rcpt argument count: %ld", (long) argv->argc); + } else { + cleanup_del_rcpt(state, argv->argv[1]); + } + } else if (strcmp(argv->argv[0], "replbody") == 0) { + if (argv->argc != 2) { + msg_warn("bad replbody argument count: %ld", (long) argv->argc); + } else { + VSTREAM *fp; + VSTRING *buf; + + if ((fp = vstream_fopen(argv->argv[1], O_RDONLY, 0)) == 0) { + msg_warn("open %s file: %m", argv->argv[1]); + } else { + buf = vstring_alloc(100); + cleanup_repl_body(state, MILTER_BODY_START, + REC_TYPE_NORM, buf); + while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) + cleanup_repl_body(state, MILTER_BODY_LINE, + REC_TYPE_NORM, buf); + cleanup_repl_body(state, MILTER_BODY_END, + REC_TYPE_NORM, buf); + vstring_free(buf); + vstream_fclose(fp); + } + } + } else if (strcmp(argv->argv[0], "header_checks") == 0) { + if (argv->argc != 2) { + msg_warn("bad header_checks argument count: %ld", + (long) argv->argc); + } else if (*var_milt_head_checks) { + msg_warn("can't change header checks"); + } else { + var_milt_head_checks = mystrdup(argv->argv[1]); + cleanup_milter_header_checks_init(); + } + } else if (strcmp(argv->argv[0], "sender_bcc_maps") == 0) { + if (argv->argc != 2) { + msg_warn("bad sender_bcc_maps argument count: %ld", + (long) argv->argc); + } else { + if (cleanup_send_bcc_maps) + maps_free(cleanup_send_bcc_maps); + cleanup_send_bcc_maps = + maps_create("sender_bcc_maps", argv->argv[1], + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + state->flags |= CLEANUP_FLAG_BCC_OK; + var_rcpt_delim = ""; + } + } else { + msg_warn("bad command: %s", argv->argv[0]); + } + argv_free(argv); + if (resp) + cleanup_milter_apply(state, "END-OF-MESSAGE", resp); + } + vstring_free(inbuf); + vstring_free(arg_buf); + if (state->append_meta_pt_offset >= 0) { + if (state->flags) + msg_info("flags = %s", cleanup_strflags(state->flags)); + if (state->errs) + msg_info("errs = %s", cleanup_strerror(state->errs)); + } + cleanup_state_free(state); + if (*var_milt_head_checks) + myfree(var_milt_head_checks); + + return (0); +} + +#endif diff --git a/src/cleanup/cleanup_milter.in1 b/src/cleanup/cleanup_milter.in1 new file mode 100644 index 0000000..85cc3ea --- /dev/null +++ b/src/cleanup/cleanup_milter.in1 @@ -0,0 +1,42 @@ +#verbose on +open test-queue-file.tmp + +# Add and remove some recipient records. We verify that all results +# from virtual alias expansion are deleted. We don't yet attempt to +# remove non-existent recipients. + +add_rcpt xxxx +add_rcpt yyyy +del_rcpt alias@hades.porcupine.org +del_rcpt yyyy + +# Insert a short header X2 at the position of a short multi-line +# header X, so that the first part of the multi-line header X needs +# to be copied to the heap, right after the inserted header. Then +# update the inserted header X2, so that a smaller portion of the +# saved multi-line header X needs to be copied again. Thus we end +# up with a multi-line header X that is broken up into three pieces. +# Finally, delete the inserted header X2. All this tests if an insert +# operation properly saves a portion of a multi-line header, to make +# space for the forward pointer to the inserted content. + +ins_header 2 X2 v1 +upd_header 1 X2 v2 +del_header 1 X2 + +# Insert a header at the position of a single-line short header Y, +# so that both header Y, and the single-line Message-ID header that +# follows Y, need to be copied to the heap. This tests if an insert +# operation properly saves records to make space for the forward +# pointer record to the inserted content. + +ins_header 3 X2 test header value 3 + +# Update the multiply broken, multi-line, header X. This tests if +# we correcly link the new header to the header that comes after the +# modified header. + +upd_header 1 X X-replaced-header replacement header text +#upd_header 1 X X-replaced-header replacement header text +#upd_header 1 X X-replaced-header replacement header text +close diff --git a/src/cleanup/cleanup_milter.in10a b/src/cleanup/cleanup_milter.in10a new file mode 100644 index 0000000..997b8a7 --- /dev/null +++ b/src/cleanup/cleanup_milter.in10a @@ -0,0 +1,11 @@ +# +# Replace a short body by a longer one. The result is two body +# regions: the original region with the head of the new text, and +# one region at the end of the queue file with the remainder of the +# new text. +# +open test-queue-file10.tmp + +replbody loremipsum + +close diff --git a/src/cleanup/cleanup_milter.in10b b/src/cleanup/cleanup_milter.in10b new file mode 100644 index 0000000..ba3b07f --- /dev/null +++ b/src/cleanup/cleanup_milter.in10b @@ -0,0 +1,12 @@ +# +# Replace a short body by a longer one, and then replace the longer +# body by itself. The result should be identical to what we had after +# one replacement. +# +#verbose on +open test-queue-file10.tmp + +replbody loremipsum +replbody loremipsum + +close diff --git a/src/cleanup/cleanup_milter.in10c b/src/cleanup/cleanup_milter.in10c new file mode 100644 index 0000000..79af0ce --- /dev/null +++ b/src/cleanup/cleanup_milter.in10c @@ -0,0 +1,14 @@ +# +# Replace a short body by a longer one, and then clobber that with +# an even longer body. The result is three body regions: the original +# region, the region that contained the tail of the first replacement, +# and a region that contains the tail of the second replacement. +# + +#verbose on +open test-queue-file10.tmp + +replbody loremipsum +replbody loremipsum2 + +close diff --git a/src/cleanup/cleanup_milter.in10d b/src/cleanup/cleanup_milter.in10d new file mode 100644 index 0000000..b7c515a --- /dev/null +++ b/src/cleanup/cleanup_milter.in10d @@ -0,0 +1,14 @@ +# +# As test 10c, but then replace the longer body by the shorter one. The +# result is three regions: the original region, the region with the +# tail of the shorter replacement, and an unused region that contained +# the tail of the larger region. +# +#verbose on +open test-queue-file10.tmp + +replbody loremipsum +replbody loremipsum2 +replbody loremipsum + +close diff --git a/src/cleanup/cleanup_milter.in10e b/src/cleanup/cleanup_milter.in10e new file mode 100644 index 0000000..763f79b --- /dev/null +++ b/src/cleanup/cleanup_milter.in10e @@ -0,0 +1,13 @@ +# +# Replace a short body by increasingly longer ones and do header +# updates in between. +# +#verbose on +open test-queue-file10.tmp + +add_header foo1 foobar +replbody loremipsum +add_header foo2 foobar +replbody loremipsum2 + +close diff --git a/src/cleanup/cleanup_milter.in11 b/src/cleanup/cleanup_milter.in11 new file mode 100644 index 0000000..8e69f71 --- /dev/null +++ b/src/cleanup/cleanup_milter.in11 @@ -0,0 +1,10 @@ +# +# Replace a non-existent body by a non-empty one. +# +#verbose on +open test-queue-file11.tmp + +replbody loremipsum +replbody loremipsum + +close diff --git a/src/cleanup/cleanup_milter.in12 b/src/cleanup/cleanup_milter.in12 new file mode 100644 index 0000000..bf44e60 --- /dev/null +++ b/src/cleanup/cleanup_milter.in12 @@ -0,0 +1,22 @@ +#verbose on +open test-queue-file12.tmp + +# Add a recipient to a message that was received with "sendmail -t" +# so that all the recipients are in the extracted queue file segment. + +add_rcpt me@porcupine.org + +# Delete the recipient added above. + +del_rcpt me@porcupine.org + +# Add a new recipient, using a different address than above, so that +# the duplicate filter won't suppress it. + +add_rcpt em@porcupine.org + +# Delete the recipient. + +del_rcpt em@porcupine.org + +close diff --git a/src/cleanup/cleanup_milter.in13a b/src/cleanup/cleanup_milter.in13a new file mode 100644 index 0000000..ab0f531 --- /dev/null +++ b/src/cleanup/cleanup_milter.in13a @@ -0,0 +1,22 @@ +#verbose on +open test-queue-file13a.tmp + +# Add a recipient to a message that was received with "sendmail -t" +# so that all the recipients are in the extracted queue file segment. + +add_rcpt_par me@porcupine.org esmtpstuff + +# Delete the recipient added above. + +del_rcpt me@porcupine.org + +# Add a new recipient, using a different address than above, so that +# the duplicate filter won't suppress it. + +add_rcpt_par em@porcupine.org esmtpstuff + +# Delete the recipient. + +del_rcpt em@porcupine.org + +close diff --git a/src/cleanup/cleanup_milter.in13b b/src/cleanup/cleanup_milter.in13b new file mode 100644 index 0000000..04ef9e2 --- /dev/null +++ b/src/cleanup/cleanup_milter.in13b @@ -0,0 +1,8 @@ +#verbose on +open test-queue-file13b.tmp + +# Change the sender. + +chg_from m@porcupine.org esmtpstuff + +close diff --git a/src/cleanup/cleanup_milter.in13c b/src/cleanup/cleanup_milter.in13c new file mode 100644 index 0000000..8bfa292 --- /dev/null +++ b/src/cleanup/cleanup_milter.in13c @@ -0,0 +1,9 @@ +#verbose on +open test-queue-file13c.tmp + +# Change the sender. + +chg_from m@porcupine.org esmtpstuff +chg_from n@porcupine.org esmtpstuff + +close diff --git a/src/cleanup/cleanup_milter.in13d b/src/cleanup/cleanup_milter.in13d new file mode 100644 index 0000000..da673fe --- /dev/null +++ b/src/cleanup/cleanup_milter.in13d @@ -0,0 +1,9 @@ +#verbose on +open test-queue-file13d.tmp + +# Change the null sender, to test correct padding of short sender records. + +chg_from m@porcupine.org esmtpstuff +chg_from n@porcupine.org esmtpstuff + +close diff --git a/src/cleanup/cleanup_milter.in13e b/src/cleanup/cleanup_milter.in13e new file mode 100644 index 0000000..a657c3c --- /dev/null +++ b/src/cleanup/cleanup_milter.in13e @@ -0,0 +1,10 @@ +#verbose on +open test-queue-file13e.tmp + +# Change the sender. + +sender_bcc_maps static:a@porcupine.org +chg_from m@porcupine.org esmtpstuff +#chg_from n@porcupine.org esmtpstuff + +close diff --git a/src/cleanup/cleanup_milter.in13f b/src/cleanup/cleanup_milter.in13f new file mode 100644 index 0000000..aeb7f5a --- /dev/null +++ b/src/cleanup/cleanup_milter.in13f @@ -0,0 +1,10 @@ +#verbose on +open test-queue-file13f.tmp + +# Change the sender. + +sender_bcc_maps static:a@porcupine.org +chg_from m@porcupine.org esmtpstuff +chg_from n@porcupine.org esmtpstuff + +close diff --git a/src/cleanup/cleanup_milter.in13g b/src/cleanup/cleanup_milter.in13g new file mode 100644 index 0000000..c88e3c9 --- /dev/null +++ b/src/cleanup/cleanup_milter.in13g @@ -0,0 +1,11 @@ +#verbose on +open test-queue-file13g.tmp + +# Change the sender. + +sender_bcc_maps static:a@porcupine.org +chg_from m@porcupine.org esmtpstuff +chg_from n@porcupine.org esmtpstuff +chg_from o@porcupine.org esmtpstuff + +close diff --git a/src/cleanup/cleanup_milter.in13h b/src/cleanup/cleanup_milter.in13h new file mode 100644 index 0000000..b5af323 --- /dev/null +++ b/src/cleanup/cleanup_milter.in13h @@ -0,0 +1,8 @@ +#verbose on +open test-queue-file13h.tmp + +# Change the sender. + +chg_from m@porcupine.org { ret=hdrs envid=env-for-m } + +close diff --git a/src/cleanup/cleanup_milter.in13i b/src/cleanup/cleanup_milter.in13i new file mode 100644 index 0000000..85dfeb0 --- /dev/null +++ b/src/cleanup/cleanup_milter.in13i @@ -0,0 +1,9 @@ +#verbose on +open test-queue-file13i.tmp + +# Change the sender. + +chg_from m@porcupine.org { ret=hdrs envid=env-for-m } +chg_from n@porcupine.org { ret=full envid=env-for-n } + +close diff --git a/src/cleanup/cleanup_milter.in14a b/src/cleanup/cleanup_milter.in14a new file mode 100644 index 0000000..6fc21f5 --- /dev/null +++ b/src/cleanup/cleanup_milter.in14a @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file14a.tmp + +header_checks regexp:cleanup_milter.reg14a +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in14b b/src/cleanup/cleanup_milter.in14b new file mode 100644 index 0000000..539112c --- /dev/null +++ b/src/cleanup/cleanup_milter.in14b @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file14b.tmp + +header_checks regexp:cleanup_milter.reg14b +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in14c b/src/cleanup/cleanup_milter.in14c new file mode 100644 index 0000000..0a247b2 --- /dev/null +++ b/src/cleanup/cleanup_milter.in14c @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file14c.tmp + +header_checks regexp:cleanup_milter.reg14c +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in14d b/src/cleanup/cleanup_milter.in14d new file mode 100644 index 0000000..13aa2ef --- /dev/null +++ b/src/cleanup/cleanup_milter.in14d @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file14d.tmp + +header_checks regexp:cleanup_milter.reg14d +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in14e b/src/cleanup/cleanup_milter.in14e new file mode 100644 index 0000000..f54ccd0 --- /dev/null +++ b/src/cleanup/cleanup_milter.in14e @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file14e.tmp + +header_checks regexp:cleanup_milter.reg14e +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in14f b/src/cleanup/cleanup_milter.in14f new file mode 100644 index 0000000..68124a7 --- /dev/null +++ b/src/cleanup/cleanup_milter.in14f @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file14f.tmp + +header_checks regexp:cleanup_milter.reg14f +ins_header 2 X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in14g b/src/cleanup/cleanup_milter.in14g new file mode 100644 index 0000000..ebd866f --- /dev/null +++ b/src/cleanup/cleanup_milter.in14g @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file14g.tmp + +header_checks regexp:cleanup_milter.reg14g +upd_header 1 X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in15a b/src/cleanup/cleanup_milter.in15a new file mode 100644 index 0000000..8c2be9e --- /dev/null +++ b/src/cleanup/cleanup_milter.in15a @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file15a.tmp + +header_checks regexp:cleanup_milter.reg15a +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in15b b/src/cleanup/cleanup_milter.in15b new file mode 100644 index 0000000..fb209ad --- /dev/null +++ b/src/cleanup/cleanup_milter.in15b @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file15b.tmp + +header_checks regexp:cleanup_milter.reg15b +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in15c b/src/cleanup/cleanup_milter.in15c new file mode 100644 index 0000000..3b3ef36 --- /dev/null +++ b/src/cleanup/cleanup_milter.in15c @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file15c.tmp + +header_checks regexp:cleanup_milter.reg15c +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in15d b/src/cleanup/cleanup_milter.in15d new file mode 100644 index 0000000..a00b143 --- /dev/null +++ b/src/cleanup/cleanup_milter.in15d @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file15d.tmp + +header_checks regexp:cleanup_milter.reg15d +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in15e b/src/cleanup/cleanup_milter.in15e new file mode 100644 index 0000000..1c26f59 --- /dev/null +++ b/src/cleanup/cleanup_milter.in15e @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file15e.tmp + +header_checks regexp:cleanup_milter.reg15e +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in15f b/src/cleanup/cleanup_milter.in15f new file mode 100644 index 0000000..8dc6a26 --- /dev/null +++ b/src/cleanup/cleanup_milter.in15f @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file15f.tmp + +header_checks regexp:cleanup_milter.reg15f +ins_header 2 X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in15g b/src/cleanup/cleanup_milter.in15g new file mode 100644 index 0000000..0e90d9f --- /dev/null +++ b/src/cleanup/cleanup_milter.in15g @@ -0,0 +1,7 @@ +#verbose on +open test-queue-file15g.tmp + +header_checks regexp:cleanup_milter.reg15g +ins_header 2 X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in15h b/src/cleanup/cleanup_milter.in15h new file mode 100644 index 0000000..3538f79 --- /dev/null +++ b/src/cleanup/cleanup_milter.in15h @@ -0,0 +1,11 @@ +#verbose on +open test-queue-file15h.tmp + +# Test the CLEANUP_FILTER_FLAG_ALL feature. The first header with +# YES clears the flag, and the second add_header is ignored. + +header_checks regexp:cleanup_milter.reg15h +add_header X-SPAM-FLAG YES +add_header X-SPAM-FLAG NO + +close diff --git a/src/cleanup/cleanup_milter.in15i b/src/cleanup/cleanup_milter.in15i new file mode 100644 index 0000000..454036a --- /dev/null +++ b/src/cleanup/cleanup_milter.in15i @@ -0,0 +1,11 @@ +#verbose on +open test-queue-file15i.tmp + +# Test the CLEANUP_STAT_CONT flag. The first header triggers FILTER, +# but the second header triggers REJECT, so the filter is not saved. + +header_checks regexp:cleanup_milter.reg15i +add_header X-SPAM-FLAG NO +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in16a b/src/cleanup/cleanup_milter.in16a new file mode 100644 index 0000000..3b7da44 --- /dev/null +++ b/src/cleanup/cleanup_milter.in16a @@ -0,0 +1,10 @@ +#verbose on +open test-queue-file16a.tmp + +# Test the BCC action in milter_header_checks. + +header_checks regexp:cleanup_milter.reg16a +add_header X-SPAM-FLAG NO +add_header X-SPAM-FLAG YES + +close diff --git a/src/cleanup/cleanup_milter.in16b b/src/cleanup/cleanup_milter.in16b new file mode 100644 index 0000000..57f6e24 --- /dev/null +++ b/src/cleanup/cleanup_milter.in16b @@ -0,0 +1,12 @@ +#verbose on +open test-queue-file16b.tmp + +# Test the add_rcpt_par action + +add_rcpt_par foo@example.com notify=never +add_rcpt_par foo@example.com notify=never +add_rcpt bar@example.com +add_rcpt_par bar@example.com orcpt=rfc822;orig-bar@example.com +add_rcpt_par bar@example.com notify=delay + +close diff --git a/src/cleanup/cleanup_milter.in17a b/src/cleanup/cleanup_milter.in17a new file mode 100644 index 0000000..c7eb361 --- /dev/null +++ b/src/cleanup/cleanup_milter.in17a @@ -0,0 +1,9 @@ +#verbose on +open test-queue-file17a.tmp +# +# Delete a recipient. Then add the recipient back. + +del_rcpt user@example.com +add_rcpt user@example.com + +close diff --git a/src/cleanup/cleanup_milter.in17b b/src/cleanup/cleanup_milter.in17b new file mode 100644 index 0000000..654f9f5 --- /dev/null +++ b/src/cleanup/cleanup_milter.in17b @@ -0,0 +1,10 @@ +#verbose on +open test-queue-file17b.tmp +# +# Delete a recipient. Then add the recipient back, with a DSN NOTIFY +# record instead of a DSN original recipient. + +del_rcpt user@example.com +add_rcpt_par user@example.com NOTIFY=never + +close diff --git a/src/cleanup/cleanup_milter.in17c b/src/cleanup/cleanup_milter.in17c new file mode 100644 index 0000000..8c34db4 --- /dev/null +++ b/src/cleanup/cleanup_milter.in17c @@ -0,0 +1,12 @@ +#verbose on +open test-queue-file17c.tmp +# +# Delete a recipient. Then add the recipient back, with a DSN NOTIFY +# record instead of a DSN original recipient. Then add the same again. +# The second command should be ignored as a duplicate. + +del_rcpt user@example.com +add_rcpt_par user@example.com NOTIFY=never +add_rcpt_par user@example.com NOTIFY=never + +close diff --git a/src/cleanup/cleanup_milter.in17d b/src/cleanup/cleanup_milter.in17d new file mode 100644 index 0000000..46ac8ff --- /dev/null +++ b/src/cleanup/cleanup_milter.in17d @@ -0,0 +1,18 @@ +#verbose on +open test-queue-file17d.tmp +# +# Delete a recipient. Then add the recipient back, with a DSN NOTIFY +# record instead of a DSN original recipient. Then add the same again. +# The second command should be ignored as a duplicate. + +del_rcpt user@example.com +add_rcpt_par user@example.com NOTIFY=never +add_rcpt_par user@example.com NOTIFY=never + +# The above has confirmed that recipient is in the duplicate filter. +# Now verify that del_rcpt will delete it, and that a subsequent +# add_rcpt_par will NOT be ignored. +del_rcpt user@example.com +add_rcpt_par user@example.com NOTIFY=never + +close diff --git a/src/cleanup/cleanup_milter.in17e b/src/cleanup/cleanup_milter.in17e new file mode 100644 index 0000000..e2e2546 --- /dev/null +++ b/src/cleanup/cleanup_milter.in17e @@ -0,0 +1,12 @@ +#verbose on +open test-queue-file17e.tmp +# +# Delete a recipient. Then add the recipient back. + +enable_original_recipient 0 +enable_original_recipient + +del_rcpt user@example.com +add_rcpt user@example.com + +close diff --git a/src/cleanup/cleanup_milter.in17f b/src/cleanup/cleanup_milter.in17f new file mode 100644 index 0000000..03d85c0 --- /dev/null +++ b/src/cleanup/cleanup_milter.in17f @@ -0,0 +1,15 @@ +#verbose on +open test-queue-file17f.tmp +# +# Delete a recipient. Then add the recipient back. + +enable_original_recipient 0 +enable_original_recipient + +del_rcpt user@example.com +add_rcpt user@example.com + +# Adding the recipient another time should be a NOOP. +add_rcpt user@example.com + +close diff --git a/src/cleanup/cleanup_milter.in17g b/src/cleanup/cleanup_milter.in17g new file mode 100644 index 0000000..e7225ec --- /dev/null +++ b/src/cleanup/cleanup_milter.in17g @@ -0,0 +1,23 @@ +#verbose on +open test-queue-file17g.tmp +# +# Delete a recipient. This leaves a deleted recipient in the queue +# file. Then add the recipient back. +# +enable_original_recipient 0 +enable_original_recipient + +del_rcpt user@example.com +add_rcpt user@example.com + +# Adding the recipient another time should be a NOOP. +add_rcpt user@example.com + +# Deleting the recipient should remove it from the duplicate filter. +# This leaves a deleted recipient in the queue file. +# Therefore adding the recipient will not be a NOOP. + +del_rcpt user@example.com +add_rcpt user@example.com + +close diff --git a/src/cleanup/cleanup_milter.in2 b/src/cleanup/cleanup_milter.in2 new file mode 100644 index 0000000..c459a9f --- /dev/null +++ b/src/cleanup/cleanup_milter.in2 @@ -0,0 +1,28 @@ +#verbose on +open test-queue-file2.tmp + +# Update a short Subject: header that immediately precedes the "append +# header" pointer record. The new Subject: header value is stored +# at the end of the heap, followed by the saved "append header" +# pointer record value. +# +# - Postfix must not consider the "append header" pointer record as +# if it were part of the short Subject: header. Instead, the record +# must be saved to the heap, right after the new Subject: header +# value. +# +# - Postfix must update its idea of the current "append header" +# pointer record location. +# +# - While saving the "append header" pointer record value, Postfix +# must replace a "0" append header" pointer record value by the +# actual location of the message body content. + +upd_header 1 Subject hey! +upd_header 1 Subject hey! +upd_header 1 Subject hey! +add_header foo foobar +upd_header 1 foo foobar +upd_header 1 foo foobar +upd_header 1 foo foobar +close diff --git a/src/cleanup/cleanup_milter.in3 b/src/cleanup/cleanup_milter.in3 new file mode 100644 index 0000000..8a9b412 --- /dev/null +++ b/src/cleanup/cleanup_milter.in3 @@ -0,0 +1,60 @@ +#verbose on +open test-queue-file3.tmp + +# Another torture test for mail with a short last message header. +# This complements test #2 with the same message where we update the +# short Subject header, but none of the other headers. Like test #1, +# this also tests possible interactions with envelope recipient +# updates, which share the same heap with message header updates. + +# Add a recipient and update all headers in reverse order. + +add_rcpt me@porcupine.org +upd_header 1 Subject hey! +upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT) +upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org> +upd_header 1 To you@porcupine.org +upd_header 1 From me@porcupine.org + +# Delete the recipient added above, and update headers in reverse +# order, twice. This tests repeated updates of short headers, but +# doesn't test much for the longer ones. + +del_rcpt me@porcupine.org +upd_header 1 Subject hey! +upd_header 1 Subject hey! +upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT) +upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT) +upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org> +upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org> +upd_header 1 To you@porcupine.org +upd_header 1 To you@porcupine.org +upd_header 1 From me@porcupine.org +upd_header 1 From me@porcupine.org + +# Add a new recipient, using a different address than above, so that +# the duplicate filter won't suppress it. Update the headers in the +# normal order, in case it makes a difference. + +add_rcpt em@porcupine.org +upd_header 1 From me@porcupine.org +upd_header 1 To you@porcupine.org +upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org> +upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT) +upd_header 1 Subject hey! + +# Delete the recipient and update the headers again. + +del_rcpt em@porcupine.org +upd_header 1 From me@porcupine.org +upd_header 1 From me@porcupine.org +upd_header 1 To you@porcupine.org +upd_header 1 To you@porcupine.org +upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org> +upd_header 1 Message-Id <20060725192735.5EC2D29013F@hades.porcupine.org> +upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT) +upd_header 1 Date Tue, 25 Jul 2006 15:27:19 -0400 (EDT) +upd_header 1 Subject hey! +upd_header 1 Subject hey! + +close diff --git a/src/cleanup/cleanup_milter.in4a b/src/cleanup/cleanup_milter.in4a new file mode 100644 index 0000000..ce07907 --- /dev/null +++ b/src/cleanup/cleanup_milter.in4a @@ -0,0 +1,9 @@ +#verbose on +open test-queue-file4.tmp +add_rcpt 01 +add_rcpt 02 +add_rcpt 03 +del_rcpt 03 +del_rcpt 02 +del_rcpt 01 +close diff --git a/src/cleanup/cleanup_milter.in4b b/src/cleanup/cleanup_milter.in4b new file mode 100644 index 0000000..3e6202d --- /dev/null +++ b/src/cleanup/cleanup_milter.in4b @@ -0,0 +1,9 @@ +#verbose on +open test-queue-file4.tmp +add_rcpt 01 +add_rcpt 02 +add_rcpt 03 +del_rcpt 01 +del_rcpt 02 +del_rcpt 03 +close diff --git a/src/cleanup/cleanup_milter.in4c b/src/cleanup/cleanup_milter.in4c new file mode 100644 index 0000000..25dc6d6 --- /dev/null +++ b/src/cleanup/cleanup_milter.in4c @@ -0,0 +1,12 @@ +#verbose on +open test-queue-file4.tmp +add_rcpt 01 +del_rcpt 01 +del_rcpt 03 +add_rcpt 02 +del_rcpt 02 +del_rcpt 01 +add_rcpt 03 +del_rcpt 03 +del_rcpt 02 +close diff --git a/src/cleanup/cleanup_milter.in5 b/src/cleanup/cleanup_milter.in5 new file mode 100644 index 0000000..393e300 --- /dev/null +++ b/src/cleanup/cleanup_milter.in5 @@ -0,0 +1,19 @@ +open test-queue-file5.tmp + +# Test with a series of multiple short headers. + +# Update short multi-line X header in the middle of other headers, +# so that the next header gets copied right after the new X header. +# Then update the X header another time so that it separates from +# the header that follows it. + +upd_header 1 X whatevershebringswesing +upd_header 1 X whatevershebringswesing + +# Update a short Subject header that precedes the updated X header, +# and see if pointers are updated properly. + +upd_header 1 Subject hya +#upd_header 1 Subject hya + +close diff --git a/src/cleanup/cleanup_milter.in6a b/src/cleanup/cleanup_milter.in6a new file mode 100644 index 0000000..aec3d3a --- /dev/null +++ b/src/cleanup/cleanup_milter.in6a @@ -0,0 +1,5 @@ +open test-queue-file6.tmp + +ins_header 1 X-Virus-Scanned hya + +close diff --git a/src/cleanup/cleanup_milter.in6b b/src/cleanup/cleanup_milter.in6b new file mode 100644 index 0000000..832a54a --- /dev/null +++ b/src/cleanup/cleanup_milter.in6b @@ -0,0 +1,6 @@ +open test-queue-file6.tmp + +ins_header 1 X-Virus-Scanned hya +ins_header 2 Domainkey-Signature hya + +close diff --git a/src/cleanup/cleanup_milter.in6c b/src/cleanup/cleanup_milter.in6c new file mode 100644 index 0000000..3ef53ff --- /dev/null +++ b/src/cleanup/cleanup_milter.in6c @@ -0,0 +1,7 @@ +open test-queue-file6.tmp + +ins_header 1 X-Virus-Scanned hya +ins_header 2 Domainkey-Signature hya +ins_header 2 DKIM-Signature hya + +close diff --git a/src/cleanup/cleanup_milter.in7 b/src/cleanup/cleanup_milter.in7 new file mode 100644 index 0000000..13ff78c --- /dev/null +++ b/src/cleanup/cleanup_milter.in7 @@ -0,0 +1,7 @@ +open test-queue-file7.tmp + +ins_header 2 X-Virus-Scanned hya +ins_header 2 Domainkey-Signature hya +ins_header 2 DKIM-Signature hya + +close diff --git a/src/cleanup/cleanup_milter.in8 b/src/cleanup/cleanup_milter.in8 new file mode 100644 index 0000000..508b4ad --- /dev/null +++ b/src/cleanup/cleanup_milter.in8 @@ -0,0 +1,7 @@ +open test-queue-file8.tmp + +ins_header 1 inserted-at-1 hya +ins_header 2 inserted-at-2 hya +ins_header 3 inserted-at-3 hya + +close diff --git a/src/cleanup/cleanup_milter.in9 b/src/cleanup/cleanup_milter.in9 new file mode 100644 index 0000000..3bbdc3f --- /dev/null +++ b/src/cleanup/cleanup_milter.in9 @@ -0,0 +1,7 @@ +open test-queue-file9.tmp + +ins_header 1 inserted-at-1 hya +ins_header 3 inserted-at-3 hya +ins_header 5 inserted-at-5 hya + +close diff --git a/src/cleanup/cleanup_milter.ref1 b/src/cleanup/cleanup_milter.ref1 new file mode 100644 index 0000000..a529c62 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref1 @@ -0,0 +1,56 @@ +*** ENVELOPE RECORDS test-queue-file.tmp *** + 0 message_size: 441 813 3 0 441 + 81 message_arrival_time: Sat Jan 20 19:52:41 2007 + 100 create_time: Sat Jan 20 19:52:47 2007 + 124 named_attribute: rewrite_context=local + 147 sender: wietse@porcupine.org + 169 named_attribute: log_client_name=hades.porcupine.org + 206 named_attribute: log_client_address=168.100.189.10 + 241 named_attribute: log_message_origin=hades.porcupine.org[168.100.189.10] + 297 named_attribute: log_helo_name=hades.porcupine.org + 332 named_attribute: log_protocol_name=SMTP + 356 named_attribute: client_name=hades.porcupine.org + 389 named_attribute: reverse_client_name=hades.porcupine.org + 430 named_attribute: client_address=168.100.189.10 + 461 named_attribute: helo_name=hades.porcupine.org + 492 named_attribute: client_address_type=2 + 515 named_attribute: dsn_orig_rcpt=rfc822;wietse@porcupine.org + 558 original_recipient: wietse@porcupine.org + 580 recipient: wietse@porcupine.org + 602 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org + 650 original_recipient: alias@hades.porcupine.org + 677 canceled_recipient: wietse@porcupine.org + 699 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org + 747 original_recipient: alias@hades.porcupine.org + 774 canceled_recipient: root@porcupine.org + 794 pointer_record: 1258 + 1258 original_recipient: xxxx + 1264 recipient: xxxx + 1270 pointer_record: 1287 + 1287 original_recipient: yyyy + 1293 canceled_recipient: yyyy + 1299 pointer_record: 811 + 811 *** MESSAGE CONTENTS test-queue-file.tmp *** + 813 regular_text: Received: from hades.porcupine.org (hades.porcupine.org [168.100.189.10]) + 888 regular_text: by hades.porcupine.org (Postfix) with SMTP id 38132290405; + 949 regular_text: Sat, 20 Jan 2007 19:52:41 -0500 (EST) + 989 pointer_record: 1316 + 1316 pointer_record: 1367 + 1367 pointer_record: 1333 + 1333 pointer_record: 1460 + 1460 regular_text: X: X-replaced-header replacement header text + 1506 pointer_record: 1401 + 1401 regular_text: X2: test header value 3 + 1426 regular_text: Y: 1234567 + 1438 padding: 0 + 1443 pointer_record: 1047 + 1047 regular_text: Message-Id: <20070121005247.38132290405@hades.porcupine.org> + 1109 regular_text: Date: Sat, 20 Jan 2007 19:52:41 -0500 (EST) + 1154 regular_text: From: wietse@porcupine.org + 1182 regular_text: To: undisclosed-recipients:; + 1212 pointer_record: 0 + 1229 regular_text: + 1231 regular_text: text + 1237 pointer_record: 0 + 1254 *** HEADER EXTRACTED test-queue-file.tmp *** + 1256 *** MESSAGE FILE END test-queue-file.tmp *** diff --git a/src/cleanup/cleanup_milter.ref10a b/src/cleanup/cleanup_milter.ref10a new file mode 100644 index 0000000..3f5c1cb --- /dev/null +++ b/src/cleanup/cleanup_milter.ref10a @@ -0,0 +1,53 @@ +*** ENVELOPE RECORDS test-queue-file10.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file10.tmp *** + 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 regular_text: From: me@porcupine.org + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 regular_text: Subject: hey! + 486 padding: 0 + 489 pointer_record: 506 + 506 regular_text: + 508 pointer_record: 573 + 573 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem + 638 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa + 705 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae + 773 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit + 842 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores + 911 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam + 977 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci + 1045 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore + 1112 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima + 1177 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, + 1251 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure + 1320 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae + 1388 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla + 1455 regular_text: pariatur? + 1467 regular_text: + 1470 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui + 1535 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos + 1600 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non + 1668 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia + 1735 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis + 1806 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis + 1873 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime + 1940 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor + 2009 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut + 2079 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae + 2145 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a + 2214 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias + 2280 regular_text: consequatur aut perferendis doloribus asperiores repellat. + 2341 pointer_record: 531 + 531 *** HEADER EXTRACTED test-queue-file10.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file10.tmp *** diff --git a/src/cleanup/cleanup_milter.ref10b b/src/cleanup/cleanup_milter.ref10b new file mode 100644 index 0000000..3f5c1cb --- /dev/null +++ b/src/cleanup/cleanup_milter.ref10b @@ -0,0 +1,53 @@ +*** ENVELOPE RECORDS test-queue-file10.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file10.tmp *** + 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 regular_text: From: me@porcupine.org + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 regular_text: Subject: hey! + 486 padding: 0 + 489 pointer_record: 506 + 506 regular_text: + 508 pointer_record: 573 + 573 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem + 638 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa + 705 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae + 773 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit + 842 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores + 911 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam + 977 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci + 1045 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore + 1112 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima + 1177 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, + 1251 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure + 1320 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae + 1388 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla + 1455 regular_text: pariatur? + 1467 regular_text: + 1470 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui + 1535 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos + 1600 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non + 1668 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia + 1735 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis + 1806 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis + 1873 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime + 1940 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor + 2009 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut + 2079 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae + 2145 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a + 2214 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias + 2280 regular_text: consequatur aut perferendis doloribus asperiores repellat. + 2341 pointer_record: 531 + 531 *** HEADER EXTRACTED test-queue-file10.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file10.tmp *** diff --git a/src/cleanup/cleanup_milter.ref10c b/src/cleanup/cleanup_milter.ref10c new file mode 100644 index 0000000..d920c6b --- /dev/null +++ b/src/cleanup/cleanup_milter.ref10c @@ -0,0 +1,83 @@ +*** ENVELOPE RECORDS test-queue-file10.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file10.tmp *** + 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 regular_text: From: me@porcupine.org + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 regular_text: Subject: hey! + 486 padding: 0 + 489 pointer_record: 506 + 506 regular_text: + 508 pointer_record: 573 + 573 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem + 638 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa + 705 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae + 773 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit + 842 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores + 911 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam + 977 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci + 1045 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore + 1112 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima + 1177 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, + 1251 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure + 1320 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae + 1388 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla + 1455 regular_text: pariatur? + 1467 regular_text: + 1470 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui + 1535 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos + 1600 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non + 1668 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia + 1735 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis + 1806 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis + 1873 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime + 1940 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor + 2009 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut + 2079 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae + 2145 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a + 2214 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias + 2280 regular_text: consequatur aut perferendis doloribus asperiores repellat. + 2341 pointer_record: 2358 + 2358 regular_text: + 2361 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem + 2426 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa + 2493 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae + 2561 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit + 2630 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores + 2699 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam + 2765 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci + 2833 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore + 2900 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima + 2965 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, + 3039 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure + 3108 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae + 3176 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla + 3243 regular_text: pariatur? + 3255 regular_text: + 3258 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui + 3323 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos + 3388 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non + 3456 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia + 3523 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis + 3594 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis + 3661 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime + 3728 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor + 3797 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut + 3867 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae + 3933 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a + 4002 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias + 4068 regular_text: consequatur aut perferendis doloribus asperiores repellat. + 4129 pointer_record: 531 + 531 *** HEADER EXTRACTED test-queue-file10.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file10.tmp *** diff --git a/src/cleanup/cleanup_milter.ref10d b/src/cleanup/cleanup_milter.ref10d new file mode 100644 index 0000000..3f5c1cb --- /dev/null +++ b/src/cleanup/cleanup_milter.ref10d @@ -0,0 +1,53 @@ +*** ENVELOPE RECORDS test-queue-file10.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file10.tmp *** + 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 regular_text: From: me@porcupine.org + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 regular_text: Subject: hey! + 486 padding: 0 + 489 pointer_record: 506 + 506 regular_text: + 508 pointer_record: 573 + 573 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem + 638 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa + 705 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae + 773 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit + 842 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores + 911 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam + 977 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci + 1045 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore + 1112 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima + 1177 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, + 1251 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure + 1320 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae + 1388 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla + 1455 regular_text: pariatur? + 1467 regular_text: + 1470 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui + 1535 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos + 1600 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non + 1668 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia + 1735 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis + 1806 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis + 1873 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime + 1940 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor + 2009 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut + 2079 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae + 2145 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a + 2214 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias + 2280 regular_text: consequatur aut perferendis doloribus asperiores repellat. + 2341 pointer_record: 531 + 531 *** HEADER EXTRACTED test-queue-file10.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file10.tmp *** diff --git a/src/cleanup/cleanup_milter.ref10e b/src/cleanup/cleanup_milter.ref10e new file mode 100644 index 0000000..490c622 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref10e @@ -0,0 +1,89 @@ +*** ENVELOPE RECORDS test-queue-file10.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file10.tmp *** + 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 regular_text: From: me@porcupine.org + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 regular_text: Subject: hey! + 486 padding: 0 + 489 pointer_record: 573 + 573 regular_text: foo1: foobar + 587 padding: 0 + 590 pointer_record: 2392 + 2392 regular_text: foo2: foobar + 2406 padding: 0 + 2409 pointer_record: 506 + 506 regular_text: + 508 pointer_record: 607 + 607 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem + 672 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa + 739 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae + 807 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit + 876 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores + 945 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam + 1011 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci + 1079 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore + 1146 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima + 1211 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, + 1285 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure + 1354 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae + 1422 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla + 1489 regular_text: pariatur? + 1501 regular_text: + 1504 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui + 1569 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos + 1634 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non + 1702 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia + 1769 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis + 1840 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis + 1907 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime + 1974 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor + 2043 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut + 2113 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae + 2179 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a + 2248 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias + 2314 regular_text: consequatur aut perferendis doloribus asperiores repellat. + 2375 pointer_record: 2426 + 2426 regular_text: + 2429 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem + 2494 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa + 2561 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae + 2629 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit + 2698 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores + 2767 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam + 2833 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci + 2901 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore + 2968 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima + 3033 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, + 3107 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure + 3176 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae + 3244 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla + 3311 regular_text: pariatur? + 3323 regular_text: + 3326 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui + 3391 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos + 3456 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non + 3524 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia + 3591 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis + 3662 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis + 3729 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime + 3796 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor + 3865 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut + 3935 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae + 4001 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a + 4070 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias + 4136 regular_text: consequatur aut perferendis doloribus asperiores repellat. + 4197 pointer_record: 531 + 531 *** HEADER EXTRACTED test-queue-file10.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file10.tmp *** diff --git a/src/cleanup/cleanup_milter.ref11 b/src/cleanup/cleanup_milter.ref11 new file mode 100644 index 0000000..f8be5d4 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref11 @@ -0,0 +1,66 @@ +*** ENVELOPE RECORDS test-queue-file11.tmp *** + 0 message_size: 366 605 1 0 366 + 81 message_arrival_time: Mon Apr 27 20:41:30 2009 + 100 create_time: Mon Apr 27 20:41:41 2009 + 124 named_attribute: rewrite_context=local + 147 sender: + 149 pointer_record: 0 + 164 named_attribute: log_client_name=localhost + 191 named_attribute: log_client_address=127.0.0.1 + 221 named_attribute: log_client_port=51286 + 244 named_attribute: log_message_origin=localhost[127.0.0.1] + 285 named_attribute: log_helo_name=localhost + 310 named_attribute: log_protocol_name=SMTP + 334 named_attribute: client_name=localhost + 357 named_attribute: reverse_client_name=localhost + 388 named_attribute: client_address=127.0.0.1 + 414 named_attribute: client_port=51286 + 433 named_attribute: helo_name=localhost + 454 named_attribute: protocol_name=SMTP + 474 named_attribute: client_address_type=2 + 497 named_attribute: dsn_orig_rcpt=rfc822;wietse@localhost + 536 original_recipient: wietse@localhost + 554 recipient: wietse@localhost.porcupine.org + 586 pointer_record: 0 + 603 *** MESSAGE CONTENTS test-queue-file11.tmp *** + 605 regular_text: Received: from localhost (localhost [127.0.0.1]) + 655 regular_text: by hades.porcupine.org (Postfix) with SMTP id 382B12B3292 + 715 regular_text: for ; Mon, 27 Apr 2009 20:41:30 -0400 (EDT) + 779 regular_text: Message-Id: <20090428004141.382B12B3292@hades.porcupine.org> + 841 regular_text: Date: Mon, 27 Apr 2009 20:41:30 -0400 (EDT) + 886 regular_text: From: MAILER-DAEMON + 907 regular_text: To: undisclosed-recipients:; + 937 pointer_record: 954 + 954 pointer_record: 975 + 975 regular_text: + 977 regular_text: Sed ut perspiciatis unde omnis iste natus error sit voluptatem + 1042 regular_text: accusantium doloremque laudantium, totam rem aperiam, eaque ipsa + 1109 regular_text: quae ab illo inventore veritatis et quasi architecto beatae vitae + 1177 regular_text: dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit + 1246 regular_text: aspernatur aut odit aut fugit, sed quia consequuntur magni dolores + 1315 regular_text: eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam + 1381 regular_text: est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci + 1449 regular_text: velit, sed quia non numquam eius modi tempora incidunt ut labore + 1516 regular_text: et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima + 1581 regular_text: veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, + 1655 regular_text: nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure + 1724 regular_text: reprehenderit qui in ea voluptate velit esse quam nihil molestiae + 1792 regular_text: consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla + 1859 regular_text: pariatur? + 1871 regular_text: + 1874 regular_text: At vero eos et accusamus et iusto odio dignissimos ducimus qui + 1939 regular_text: blanditiis praesentium voluptatum deleniti atque corrupti quos + 2004 regular_text: dolores et quas molestias excepturi sint occaecati cupiditate non + 2072 regular_text: provident, similique sunt in culpa qui officia deserunt mollitia + 2139 regular_text: animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis + 2210 regular_text: est et expedita distinctio. Nam libero tempore, cum soluta nobis + 2277 regular_text: est eligendi optio cumque nihil impedit quo minus id quod maxime + 2344 regular_text: placeat facere possimus, omnis voluptas assumenda est, omnis dolor + 2413 regular_text: repellendus. Temporibus autem quibusdam et aut officiis debitis aut + 2483 regular_text: rerum necessitatibus saepe eveniet ut et voluptates repudiandae + 2549 regular_text: sint et molestiae non recusandae. Itaque earum rerum hic tenetur a + 2618 regular_text: sapiente delectus, ut aut reiciendis voluptatibus maiores alias + 2684 regular_text: consequatur aut perferendis doloribus asperiores repellat. + 2745 pointer_record: 971 + 971 *** HEADER EXTRACTED test-queue-file11.tmp *** + 973 *** MESSAGE FILE END test-queue-file11.tmp *** diff --git a/src/cleanup/cleanup_milter.ref12 b/src/cleanup/cleanup_milter.ref12 new file mode 100644 index 0000000..d5d0f2c --- /dev/null +++ b/src/cleanup/cleanup_milter.ref12 @@ -0,0 +1,31 @@ +*** ENVELOPE RECORDS test-queue-file12.tmp *** + 0 message_size: 332 182 1 0 332 + 81 message_arrival_time: Sun Jan 21 13:32:59 2007 + 100 create_time: Sun Jan 21 13:33:08 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 *** MESSAGE CONTENTS test-queue-file12.tmp *** + 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) + 300 regular_text: From: me@porcupine.org + 324 regular_text: To: you@porcupine.org + 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 454 regular_text: Subject: hey! + 469 padding: 0 + 472 pointer_record: 0 + 489 regular_text: + 491 regular_text: text + 497 pointer_record: 0 + 514 *** HEADER EXTRACTED test-queue-file12.tmp *** + 516 original_recipient: you@porcupine.org + 535 recipient: you@porcupine.org + 554 pointer_record: 573 + 573 original_recipient: me@porcupine.org + 591 canceled_recipient: me@porcupine.org + 609 pointer_record: 626 + 626 original_recipient: em@porcupine.org + 644 canceled_recipient: em@porcupine.org + 662 pointer_record: 571 + 571 *** MESSAGE FILE END test-queue-file12.tmp *** diff --git a/src/cleanup/cleanup_milter.ref13a b/src/cleanup/cleanup_milter.ref13a new file mode 100644 index 0000000..3ccf028 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref13a @@ -0,0 +1,31 @@ +*** ENVELOPE RECORDS test-queue-file13a.tmp *** + 0 message_size: 332 182 1 0 332 + 81 message_arrival_time: Sun Jan 21 13:32:59 2007 + 100 create_time: Sun Jan 21 13:33:08 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 *** MESSAGE CONTENTS test-queue-file13a.tmp *** + 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) + 300 regular_text: From: me@porcupine.org + 324 regular_text: To: you@porcupine.org + 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 454 regular_text: Subject: hey! + 469 padding: 0 + 472 pointer_record: 0 + 489 regular_text: + 491 regular_text: text + 497 pointer_record: 0 + 514 *** HEADER EXTRACTED test-queue-file13a.tmp *** + 516 original_recipient: you@porcupine.org + 535 recipient: you@porcupine.org + 554 pointer_record: 573 + 573 original_recipient: me@porcupine.org + 591 canceled_recipient: me@porcupine.org + 609 pointer_record: 626 + 626 original_recipient: em@porcupine.org + 644 canceled_recipient: em@porcupine.org + 662 pointer_record: 571 + 571 *** MESSAGE FILE END test-queue-file13a.tmp *** diff --git a/src/cleanup/cleanup_milter.ref13b b/src/cleanup/cleanup_milter.ref13b new file mode 100644 index 0000000..bb55fb6 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref13b @@ -0,0 +1,27 @@ +*** ENVELOPE RECORDS test-queue-file13b.tmp *** + 0 message_size: 332 182 1 0 332 + 81 message_arrival_time: Sun Jan 21 13:32:59 2007 + 100 create_time: Sun Jan 21 13:33:08 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 pointer_record: 573 + 573 sender: m@porcupine.org + 590 pointer_record: 180 + 180 *** MESSAGE CONTENTS test-queue-file13b.tmp *** + 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) + 300 regular_text: From: me@porcupine.org + 324 regular_text: To: you@porcupine.org + 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 454 regular_text: Subject: hey! + 469 padding: 0 + 472 pointer_record: 0 + 489 regular_text: + 491 regular_text: text + 497 pointer_record: 0 + 514 *** HEADER EXTRACTED test-queue-file13b.tmp *** + 516 original_recipient: you@porcupine.org + 535 recipient: you@porcupine.org + 554 pointer_record: 0 + 571 *** MESSAGE FILE END test-queue-file13b.tmp *** diff --git a/src/cleanup/cleanup_milter.ref13c b/src/cleanup/cleanup_milter.ref13c new file mode 100644 index 0000000..3ec0531 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref13c @@ -0,0 +1,29 @@ +*** ENVELOPE RECORDS test-queue-file13c.tmp *** + 0 message_size: 332 182 1 0 332 + 81 message_arrival_time: Sun Jan 21 13:32:59 2007 + 100 create_time: Sun Jan 21 13:33:08 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 pointer_record: 573 + 573 pointer_record: 607 + 607 sender: n@porcupine.org + 624 pointer_record: 590 + 590 pointer_record: 180 + 180 *** MESSAGE CONTENTS test-queue-file13c.tmp *** + 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) + 300 regular_text: From: me@porcupine.org + 324 regular_text: To: you@porcupine.org + 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 454 regular_text: Subject: hey! + 469 padding: 0 + 472 pointer_record: 0 + 489 regular_text: + 491 regular_text: text + 497 pointer_record: 0 + 514 *** HEADER EXTRACTED test-queue-file13c.tmp *** + 516 original_recipient: you@porcupine.org + 535 recipient: you@porcupine.org + 554 pointer_record: 0 + 571 *** MESSAGE FILE END test-queue-file13c.tmp *** diff --git a/src/cleanup/cleanup_milter.ref13d b/src/cleanup/cleanup_milter.ref13d new file mode 100644 index 0000000..df9f1dc --- /dev/null +++ b/src/cleanup/cleanup_milter.ref13d @@ -0,0 +1,39 @@ +*** ENVELOPE RECORDS test-queue-file13d.tmp *** + 0 message_size: 366 605 1 0 366 + 81 message_arrival_time: Mon Apr 27 20:41:30 2009 + 100 create_time: Mon Apr 27 20:41:41 2009 + 124 named_attribute: rewrite_context=local + 147 pointer_record: 975 + 975 pointer_record: 1009 + 1009 sender: n@porcupine.org + 1026 pointer_record: 992 + 992 pointer_record: 164 + 164 named_attribute: log_client_name=localhost + 191 named_attribute: log_client_address=127.0.0.1 + 221 named_attribute: log_client_port=51286 + 244 named_attribute: log_message_origin=localhost[127.0.0.1] + 285 named_attribute: log_helo_name=localhost + 310 named_attribute: log_protocol_name=SMTP + 334 named_attribute: client_name=localhost + 357 named_attribute: reverse_client_name=localhost + 388 named_attribute: client_address=127.0.0.1 + 414 named_attribute: client_port=51286 + 433 named_attribute: helo_name=localhost + 454 named_attribute: protocol_name=SMTP + 474 named_attribute: client_address_type=2 + 497 named_attribute: dsn_orig_rcpt=rfc822;wietse@localhost + 536 original_recipient: wietse@localhost + 554 recipient: wietse@localhost.porcupine.org + 586 pointer_record: 0 + 603 *** MESSAGE CONTENTS test-queue-file13d.tmp *** + 605 regular_text: Received: from localhost (localhost [127.0.0.1]) + 655 regular_text: by hades.porcupine.org (Postfix) with SMTP id 382B12B3292 + 715 regular_text: for ; Mon, 27 Apr 2009 20:41:30 -0400 (EDT) + 779 regular_text: Message-Id: <20090428004141.382B12B3292@hades.porcupine.org> + 841 regular_text: Date: Mon, 27 Apr 2009 20:41:30 -0400 (EDT) + 886 regular_text: From: MAILER-DAEMON + 907 regular_text: To: undisclosed-recipients:; + 937 pointer_record: 0 + 954 pointer_record: 0 + 971 *** HEADER EXTRACTED test-queue-file13d.tmp *** + 973 *** MESSAGE FILE END test-queue-file13d.tmp *** diff --git a/src/cleanup/cleanup_milter.ref13e b/src/cleanup/cleanup_milter.ref13e new file mode 100644 index 0000000..495c7fa --- /dev/null +++ b/src/cleanup/cleanup_milter.ref13e @@ -0,0 +1,30 @@ +*** ENVELOPE RECORDS test-queue-file13e.tmp *** + 0 message_size: 332 182 1 0 332 + 81 message_arrival_time: Sun Jan 21 13:32:59 2007 + 100 create_time: Sun Jan 21 13:33:08 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 pointer_record: 573 + 573 sender: m@porcupine.org + 590 named_attribute: notify_flags=1 + 606 original_recipient: a@porcupine.org + 623 recipient: a@porcupine.org + 640 pointer_record: 180 + 180 *** MESSAGE CONTENTS test-queue-file13e.tmp *** + 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) + 300 regular_text: From: me@porcupine.org + 324 regular_text: To: you@porcupine.org + 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 454 regular_text: Subject: hey! + 469 padding: 0 + 472 pointer_record: 0 + 489 regular_text: + 491 regular_text: text + 497 pointer_record: 0 + 514 *** HEADER EXTRACTED test-queue-file13e.tmp *** + 516 original_recipient: you@porcupine.org + 535 recipient: you@porcupine.org + 554 pointer_record: 0 + 571 *** MESSAGE FILE END test-queue-file13e.tmp *** diff --git a/src/cleanup/cleanup_milter.ref13f b/src/cleanup/cleanup_milter.ref13f new file mode 100644 index 0000000..dcc563a --- /dev/null +++ b/src/cleanup/cleanup_milter.ref13f @@ -0,0 +1,32 @@ +*** ENVELOPE RECORDS test-queue-file13f.tmp *** + 0 message_size: 332 182 1 0 332 + 81 message_arrival_time: Sun Jan 21 13:32:59 2007 + 100 create_time: Sun Jan 21 13:33:08 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 pointer_record: 573 + 573 pointer_record: 657 + 657 sender: n@porcupine.org + 674 pointer_record: 590 + 590 named_attribute: notify_flags=1 + 606 original_recipient: a@porcupine.org + 623 recipient: a@porcupine.org + 640 pointer_record: 180 + 180 *** MESSAGE CONTENTS test-queue-file13f.tmp *** + 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) + 300 regular_text: From: me@porcupine.org + 324 regular_text: To: you@porcupine.org + 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 454 regular_text: Subject: hey! + 469 padding: 0 + 472 pointer_record: 0 + 489 regular_text: + 491 regular_text: text + 497 pointer_record: 0 + 514 *** HEADER EXTRACTED test-queue-file13f.tmp *** + 516 original_recipient: you@porcupine.org + 535 recipient: you@porcupine.org + 554 pointer_record: 0 + 571 *** MESSAGE FILE END test-queue-file13f.tmp *** diff --git a/src/cleanup/cleanup_milter.ref13g b/src/cleanup/cleanup_milter.ref13g new file mode 100644 index 0000000..acadf22 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref13g @@ -0,0 +1,34 @@ +*** ENVELOPE RECORDS test-queue-file13g.tmp *** + 0 message_size: 332 182 1 0 332 + 81 message_arrival_time: Sun Jan 21 13:32:59 2007 + 100 create_time: Sun Jan 21 13:33:08 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 pointer_record: 573 + 573 pointer_record: 657 + 657 pointer_record: 691 + 691 sender: o@porcupine.org + 708 pointer_record: 674 + 674 pointer_record: 590 + 590 named_attribute: notify_flags=1 + 606 original_recipient: a@porcupine.org + 623 recipient: a@porcupine.org + 640 pointer_record: 180 + 180 *** MESSAGE CONTENTS test-queue-file13g.tmp *** + 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) + 300 regular_text: From: me@porcupine.org + 324 regular_text: To: you@porcupine.org + 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 454 regular_text: Subject: hey! + 469 padding: 0 + 472 pointer_record: 0 + 489 regular_text: + 491 regular_text: text + 497 pointer_record: 0 + 514 *** HEADER EXTRACTED test-queue-file13g.tmp *** + 516 original_recipient: you@porcupine.org + 535 recipient: you@porcupine.org + 554 pointer_record: 0 + 571 *** MESSAGE FILE END test-queue-file13g.tmp *** diff --git a/src/cleanup/cleanup_milter.ref13h b/src/cleanup/cleanup_milter.ref13h new file mode 100644 index 0000000..c3a8fe9 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref13h @@ -0,0 +1,29 @@ +*** ENVELOPE RECORDS test-queue-file13h.tmp *** + 0 message_size: 332 182 1 0 332 + 81 message_arrival_time: Sun Jan 21 13:32:59 2007 + 100 create_time: Sun Jan 21 13:33:08 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 pointer_record: 573 + 573 named_attribute: envelope_id=env-for-m + 596 named_attribute: ret_flags=2 + 609 sender: m@porcupine.org + 626 pointer_record: 180 + 180 *** MESSAGE CONTENTS test-queue-file13h.tmp *** + 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) + 300 regular_text: From: me@porcupine.org + 324 regular_text: To: you@porcupine.org + 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 454 regular_text: Subject: hey! + 469 padding: 0 + 472 pointer_record: 0 + 489 regular_text: + 491 regular_text: text + 497 pointer_record: 0 + 514 *** HEADER EXTRACTED test-queue-file13h.tmp *** + 516 original_recipient: you@porcupine.org + 535 recipient: you@porcupine.org + 554 pointer_record: 0 + 571 *** MESSAGE FILE END test-queue-file13h.tmp *** diff --git a/src/cleanup/cleanup_milter.ref13i b/src/cleanup/cleanup_milter.ref13i new file mode 100644 index 0000000..006ef13 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref13i @@ -0,0 +1,33 @@ +*** ENVELOPE RECORDS test-queue-file13i.tmp *** + 0 message_size: 332 182 1 0 332 + 81 message_arrival_time: Sun Jan 21 13:32:59 2007 + 100 create_time: Sun Jan 21 13:33:08 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 pointer_record: 573 + 573 named_attribute: envelope_id=env-for-m + 596 named_attribute: ret_flags=2 + 609 pointer_record: 643 + 643 named_attribute: envelope_id=env-for-n + 666 named_attribute: ret_flags=1 + 679 sender: n@porcupine.org + 696 pointer_record: 626 + 626 pointer_record: 180 + 180 *** MESSAGE CONTENTS test-queue-file13i.tmp *** + 182 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 244 regular_text: id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) + 300 regular_text: From: me@porcupine.org + 324 regular_text: To: you@porcupine.org + 347 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 409 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 454 regular_text: Subject: hey! + 469 padding: 0 + 472 pointer_record: 0 + 489 regular_text: + 491 regular_text: text + 497 pointer_record: 0 + 514 *** HEADER EXTRACTED test-queue-file13i.tmp *** + 516 original_recipient: you@porcupine.org + 535 recipient: you@porcupine.org + 554 pointer_record: 0 + 571 *** MESSAGE FILE END test-queue-file13i.tmp *** diff --git a/src/cleanup/cleanup_milter.ref14a1 b/src/cleanup/cleanup_milter.ref14a1 new file mode 100644 index 0000000..9006f9a --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14a1 @@ -0,0 +1,2 @@ +./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: 5.7.1 message content rejected +./cleanup_milter: errs = message content rejected diff --git a/src/cleanup/cleanup_milter.ref14a2 b/src/cleanup/cleanup_milter.ref14a2 new file mode 100644 index 0000000..cb690d8 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14a2 @@ -0,0 +1,27 @@ +*** ENVELOPE RECORDS test-queue-file14a.tmp *** + 0 message_size: 365 256 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:05:19 2009 + 100 create_time: Fri Jun 5 14:05:19 2009 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: wietse@ahost.example.com + 188 pointer_record: 0 + 203 original_recipient: wietse + 211 recipient: wietse@ahost.example.com + 237 pointer_record: 0 + 254 *** MESSAGE CONTENTS test-queue-file14a.tmp *** + 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 372 regular_text: To: wietse@ahost.example.com + 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com> + 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 507 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 555 pointer_record: 642 + 642 regular_text: X-SPAM-FLAG: YES + 660 pointer_record: 572 + 572 regular_text: + 574 regular_text: Fri Jun 5 14:05:19 EDT 2009 + 604 pointer_record: 0 + 621 *** HEADER EXTRACTED test-queue-file14a.tmp *** + 623 pointer_record: 0 + 640 *** MESSAGE FILE END test-queue-file14a.tmp *** diff --git a/src/cleanup/cleanup_milter.ref14b1 b/src/cleanup/cleanup_milter.ref14b1 new file mode 100644 index 0000000..5608b81 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14b1 @@ -0,0 +1,2 @@ +./cleanup_milter: NOQUEUE: milter-header-filter: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: transport:nexthop:port +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref14b2 b/src/cleanup/cleanup_milter.ref14b2 new file mode 100644 index 0000000..209bf2b --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14b2 @@ -0,0 +1,29 @@ +*** ENVELOPE RECORDS test-queue-file14b.tmp *** + 0 message_size: 365 256 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:05:19 2009 + 100 create_time: Fri Jun 5 14:05:19 2009 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: wietse@ahost.example.com + 188 pointer_record: 0 + 203 original_recipient: wietse + 211 recipient: wietse@ahost.example.com + 237 pointer_record: 0 + 254 *** MESSAGE CONTENTS test-queue-file14b.tmp *** + 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 372 regular_text: To: wietse@ahost.example.com + 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com> + 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 507 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 555 pointer_record: 642 + 642 regular_text: X-SPAM-FLAG: YES + 660 pointer_record: 572 + 572 regular_text: + 574 regular_text: Fri Jun 5 14:05:19 EDT 2009 + 604 pointer_record: 0 + 621 *** HEADER EXTRACTED test-queue-file14b.tmp *** + 623 pointer_record: 677 + 677 content_filter: transport:nexthop:port + 701 pointer_record: 640 + 640 *** MESSAGE FILE END test-queue-file14b.tmp *** diff --git a/src/cleanup/cleanup_milter.ref14c1 b/src/cleanup/cleanup_milter.ref14c1 new file mode 100644 index 0000000..26b8ffd --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14c1 @@ -0,0 +1 @@ +./cleanup_milter: NOQUEUE: milter-header-redirect: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: foo@examle.com diff --git a/src/cleanup/cleanup_milter.ref14c2 b/src/cleanup/cleanup_milter.ref14c2 new file mode 100644 index 0000000..8d2be31 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14c2 @@ -0,0 +1,29 @@ +*** ENVELOPE RECORDS test-queue-file14c.tmp *** + 0 message_size: 365 256 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:05:19 2009 + 100 create_time: Fri Jun 5 14:05:19 2009 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: wietse@ahost.example.com + 188 pointer_record: 0 + 203 original_recipient: wietse + 211 recipient: wietse@ahost.example.com + 237 pointer_record: 0 + 254 *** MESSAGE CONTENTS test-queue-file14c.tmp *** + 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 372 regular_text: To: wietse@ahost.example.com + 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com> + 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 507 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 555 pointer_record: 642 + 642 regular_text: X-SPAM-FLAG: YES + 660 pointer_record: 572 + 572 regular_text: + 574 regular_text: Fri Jun 5 14:05:19 EDT 2009 + 604 pointer_record: 0 + 621 *** HEADER EXTRACTED test-queue-file14c.tmp *** + 623 pointer_record: 677 + 677 redirect_to: foo@examle.com + 693 pointer_record: 640 + 640 *** MESSAGE FILE END test-queue-file14c.tmp *** diff --git a/src/cleanup/cleanup_milter.ref14d1 b/src/cleanup/cleanup_milter.ref14d1 new file mode 100644 index 0000000..2cea8d2 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14d1 @@ -0,0 +1,2 @@ +./cleanup_milter: NOQUEUE: milter-header-discard: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: +./cleanup_milter: flags = discard_message diff --git a/src/cleanup/cleanup_milter.ref14d2 b/src/cleanup/cleanup_milter.ref14d2 new file mode 100644 index 0000000..dece3d5 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14d2 @@ -0,0 +1,25 @@ +*** ENVELOPE RECORDS test-queue-file14d.tmp *** + 0 message_size: 365 256 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:05:19 2009 + 100 create_time: Fri Jun 5 14:05:19 2009 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: wietse@ahost.example.com + 188 pointer_record: 0 + 203 original_recipient: wietse + 211 recipient: wietse@ahost.example.com + 237 pointer_record: 0 + 254 *** MESSAGE CONTENTS test-queue-file14d.tmp *** + 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 372 regular_text: To: wietse@ahost.example.com + 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com> + 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 507 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 555 pointer_record: 0 + 572 regular_text: + 574 regular_text: Fri Jun 5 14:05:19 EDT 2009 + 604 pointer_record: 0 + 621 *** HEADER EXTRACTED test-queue-file14d.tmp *** + 623 pointer_record: 0 + 640 *** MESSAGE FILE END test-queue-file14d.tmp *** diff --git a/src/cleanup/cleanup_milter.ref14e1 b/src/cleanup/cleanup_milter.ref14e1 new file mode 100644 index 0000000..d08c06f --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14e1 @@ -0,0 +1,2 @@ +./cleanup_milter: NOQUEUE: milter-header-hold: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: +./cleanup_milter: flags = enable_header_body_filter hold_message enable_milters diff --git a/src/cleanup/cleanup_milter.ref14e2 b/src/cleanup/cleanup_milter.ref14e2 new file mode 100644 index 0000000..e6e5cc0 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14e2 @@ -0,0 +1,27 @@ +*** ENVELOPE RECORDS test-queue-file14e.tmp *** + 0 message_size: 365 256 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:05:19 2009 + 100 create_time: Fri Jun 5 14:05:19 2009 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: wietse@ahost.example.com + 188 pointer_record: 0 + 203 original_recipient: wietse + 211 recipient: wietse@ahost.example.com + 237 pointer_record: 0 + 254 *** MESSAGE CONTENTS test-queue-file14e.tmp *** + 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 372 regular_text: To: wietse@ahost.example.com + 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com> + 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 507 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 555 pointer_record: 642 + 642 regular_text: X-SPAM-FLAG: YES + 660 pointer_record: 572 + 572 regular_text: + 574 regular_text: Fri Jun 5 14:05:19 EDT 2009 + 604 pointer_record: 0 + 621 *** HEADER EXTRACTED test-queue-file14e.tmp *** + 623 pointer_record: 0 + 640 *** MESSAGE FILE END test-queue-file14e.tmp *** diff --git a/src/cleanup/cleanup_milter.ref14f1 b/src/cleanup/cleanup_milter.ref14f1 new file mode 100644 index 0000000..9006f9a --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14f1 @@ -0,0 +1,2 @@ +./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: 5.7.1 message content rejected +./cleanup_milter: errs = message content rejected diff --git a/src/cleanup/cleanup_milter.ref14f2 b/src/cleanup/cleanup_milter.ref14f2 new file mode 100644 index 0000000..3fdbf23 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14f2 @@ -0,0 +1,28 @@ +*** ENVELOPE RECORDS test-queue-file14f.tmp *** + 0 message_size: 365 256 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:05:19 2009 + 100 create_time: Fri Jun 5 14:05:19 2009 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: wietse@ahost.example.com + 188 pointer_record: 0 + 203 original_recipient: wietse + 211 recipient: wietse@ahost.example.com + 237 pointer_record: 0 + 254 *** MESSAGE CONTENTS test-queue-file14f.tmp *** + 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 372 pointer_record: 642 + 642 regular_text: X-SPAM-FLAG: YES + 660 regular_text: To: wietse@ahost.example.com + 690 pointer_record: 402 + 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com> + 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 507 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 555 pointer_record: 0 + 572 regular_text: + 574 regular_text: Fri Jun 5 14:05:19 EDT 2009 + 604 pointer_record: 0 + 621 *** HEADER EXTRACTED test-queue-file14f.tmp *** + 623 pointer_record: 0 + 640 *** MESSAGE FILE END test-queue-file14f.tmp *** diff --git a/src/cleanup/cleanup_milter.ref14g1 b/src/cleanup/cleanup_milter.ref14g1 new file mode 100644 index 0000000..9006f9a --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14g1 @@ -0,0 +1,2 @@ +./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: 5.7.1 message content rejected +./cleanup_milter: errs = message content rejected diff --git a/src/cleanup/cleanup_milter.ref14g2 b/src/cleanup/cleanup_milter.ref14g2 new file mode 100644 index 0000000..1ee50e4 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref14g2 @@ -0,0 +1,27 @@ +*** ENVELOPE RECORDS test-queue-file14g.tmp *** + 0 message_size: 365 256 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:05:19 2009 + 100 create_time: Fri Jun 5 14:05:19 2009 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: wietse@ahost.example.com + 188 pointer_record: 0 + 203 original_recipient: wietse + 211 recipient: wietse@ahost.example.com + 237 pointer_record: 0 + 254 *** MESSAGE CONTENTS test-queue-file14g.tmp *** + 256 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 316 regular_text: id DA4892510C1; Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 372 regular_text: To: wietse@ahost.example.com + 402 regular_text: Message-Id: <20090605180519.DA4892510C1@ahost.example.com> + 462 regular_text: Date: Fri, 5 Jun 2009 14:05:19 -0400 (EDT) + 507 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 555 pointer_record: 642 + 642 regular_text: X-SPAM-FLAG: YES + 660 pointer_record: 572 + 572 regular_text: + 574 regular_text: Fri Jun 5 14:05:19 EDT 2009 + 604 pointer_record: 0 + 621 *** HEADER EXTRACTED test-queue-file14g.tmp *** + 623 pointer_record: 0 + 640 *** MESSAGE FILE END test-queue-file14g.tmp *** diff --git a/src/cleanup/cleanup_milter.ref15a1 b/src/cleanup/cleanup_milter.ref15a1 new file mode 100644 index 0000000..9006f9a --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15a1 @@ -0,0 +1,2 @@ +./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: 5.7.1 message content rejected +./cleanup_milter: errs = message content rejected diff --git a/src/cleanup/cleanup_milter.ref15a2 b/src/cleanup/cleanup_milter.ref15a2 new file mode 100644 index 0000000..56c5d3e --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15a2 @@ -0,0 +1,27 @@ +*** ENVELOPE RECORDS test-queue-file15a.tmp *** + 0 message_size: 365 221 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:06:34 2009 + 99 create_time: Fri Jun 5 14:06:34 2009 + 123 named_attribute: rewrite_context=local + 146 sender_fullname: Wietse Venema + 161 sender: wietse@ahost.example.com + 187 pointer_record: 0 + 202 pointer_record: 0 + 219 *** MESSAGE CONTENTS test-queue-file15a.tmp *** + 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 337 regular_text: To: wietse@ahost.example.com + 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com> + 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 472 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 520 pointer_record: 641 + 641 regular_text: X-SPAM-FLAG: YES + 659 pointer_record: 537 + 537 regular_text: + 539 regular_text: Fri Jun 5 14:06:34 EDT 2009 + 569 pointer_record: 0 + 586 *** HEADER EXTRACTED test-queue-file15a.tmp *** + 588 pointer_record: 0 + 605 original_recipient: wietse + 613 recipient: wietse@ahost.example.com + 639 *** MESSAGE FILE END test-queue-file15a.tmp *** diff --git a/src/cleanup/cleanup_milter.ref15b1 b/src/cleanup/cleanup_milter.ref15b1 new file mode 100644 index 0000000..5608b81 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15b1 @@ -0,0 +1,2 @@ +./cleanup_milter: NOQUEUE: milter-header-filter: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: transport:nexthop:port +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref15b2 b/src/cleanup/cleanup_milter.ref15b2 new file mode 100644 index 0000000..c38c0a3 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15b2 @@ -0,0 +1,29 @@ +*** ENVELOPE RECORDS test-queue-file15b.tmp *** + 0 message_size: 365 221 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:06:34 2009 + 99 create_time: Fri Jun 5 14:06:34 2009 + 123 named_attribute: rewrite_context=local + 146 sender_fullname: Wietse Venema + 161 sender: wietse@ahost.example.com + 187 pointer_record: 0 + 202 pointer_record: 0 + 219 *** MESSAGE CONTENTS test-queue-file15b.tmp *** + 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 337 regular_text: To: wietse@ahost.example.com + 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com> + 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 472 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 520 pointer_record: 641 + 641 regular_text: X-SPAM-FLAG: YES + 659 pointer_record: 537 + 537 regular_text: + 539 regular_text: Fri Jun 5 14:06:34 EDT 2009 + 569 pointer_record: 0 + 586 *** HEADER EXTRACTED test-queue-file15b.tmp *** + 588 pointer_record: 676 + 676 content_filter: transport:nexthop:port + 700 pointer_record: 605 + 605 original_recipient: wietse + 613 recipient: wietse@ahost.example.com + 639 *** MESSAGE FILE END test-queue-file15b.tmp *** diff --git a/src/cleanup/cleanup_milter.ref15c1 b/src/cleanup/cleanup_milter.ref15c1 new file mode 100644 index 0000000..26b8ffd --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15c1 @@ -0,0 +1 @@ +./cleanup_milter: NOQUEUE: milter-header-redirect: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: foo@examle.com diff --git a/src/cleanup/cleanup_milter.ref15c2 b/src/cleanup/cleanup_milter.ref15c2 new file mode 100644 index 0000000..7725f48 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15c2 @@ -0,0 +1,29 @@ +*** ENVELOPE RECORDS test-queue-file15c.tmp *** + 0 message_size: 365 221 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:06:34 2009 + 99 create_time: Fri Jun 5 14:06:34 2009 + 123 named_attribute: rewrite_context=local + 146 sender_fullname: Wietse Venema + 161 sender: wietse@ahost.example.com + 187 pointer_record: 0 + 202 pointer_record: 0 + 219 *** MESSAGE CONTENTS test-queue-file15c.tmp *** + 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 337 regular_text: To: wietse@ahost.example.com + 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com> + 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 472 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 520 pointer_record: 641 + 641 regular_text: X-SPAM-FLAG: YES + 659 pointer_record: 537 + 537 regular_text: + 539 regular_text: Fri Jun 5 14:06:34 EDT 2009 + 569 pointer_record: 0 + 586 *** HEADER EXTRACTED test-queue-file15c.tmp *** + 588 pointer_record: 676 + 676 redirect_to: foo@examle.com + 692 pointer_record: 605 + 605 original_recipient: wietse + 613 recipient: wietse@ahost.example.com + 639 *** MESSAGE FILE END test-queue-file15c.tmp *** diff --git a/src/cleanup/cleanup_milter.ref15d1 b/src/cleanup/cleanup_milter.ref15d1 new file mode 100644 index 0000000..2cea8d2 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15d1 @@ -0,0 +1,2 @@ +./cleanup_milter: NOQUEUE: milter-header-discard: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: +./cleanup_milter: flags = discard_message diff --git a/src/cleanup/cleanup_milter.ref15d2 b/src/cleanup/cleanup_milter.ref15d2 new file mode 100644 index 0000000..746a13b --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15d2 @@ -0,0 +1,25 @@ +*** ENVELOPE RECORDS test-queue-file15d.tmp *** + 0 message_size: 365 221 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:06:34 2009 + 99 create_time: Fri Jun 5 14:06:34 2009 + 123 named_attribute: rewrite_context=local + 146 sender_fullname: Wietse Venema + 161 sender: wietse@ahost.example.com + 187 pointer_record: 0 + 202 pointer_record: 0 + 219 *** MESSAGE CONTENTS test-queue-file15d.tmp *** + 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 337 regular_text: To: wietse@ahost.example.com + 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com> + 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 472 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 520 pointer_record: 0 + 537 regular_text: + 539 regular_text: Fri Jun 5 14:06:34 EDT 2009 + 569 pointer_record: 0 + 586 *** HEADER EXTRACTED test-queue-file15d.tmp *** + 588 pointer_record: 0 + 605 original_recipient: wietse + 613 recipient: wietse@ahost.example.com + 639 *** MESSAGE FILE END test-queue-file15d.tmp *** diff --git a/src/cleanup/cleanup_milter.ref15e1 b/src/cleanup/cleanup_milter.ref15e1 new file mode 100644 index 0000000..d08c06f --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15e1 @@ -0,0 +1,2 @@ +./cleanup_milter: NOQUEUE: milter-header-hold: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: +./cleanup_milter: flags = enable_header_body_filter hold_message enable_milters diff --git a/src/cleanup/cleanup_milter.ref15e2 b/src/cleanup/cleanup_milter.ref15e2 new file mode 100644 index 0000000..13f9cb4 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15e2 @@ -0,0 +1,27 @@ +*** ENVELOPE RECORDS test-queue-file15e.tmp *** + 0 message_size: 365 221 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:06:34 2009 + 99 create_time: Fri Jun 5 14:06:34 2009 + 123 named_attribute: rewrite_context=local + 146 sender_fullname: Wietse Venema + 161 sender: wietse@ahost.example.com + 187 pointer_record: 0 + 202 pointer_record: 0 + 219 *** MESSAGE CONTENTS test-queue-file15e.tmp *** + 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 337 regular_text: To: wietse@ahost.example.com + 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com> + 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 472 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 520 pointer_record: 641 + 641 regular_text: X-SPAM-FLAG: YES + 659 pointer_record: 537 + 537 regular_text: + 539 regular_text: Fri Jun 5 14:06:34 EDT 2009 + 569 pointer_record: 0 + 586 *** HEADER EXTRACTED test-queue-file15e.tmp *** + 588 pointer_record: 0 + 605 original_recipient: wietse + 613 recipient: wietse@ahost.example.com + 639 *** MESSAGE FILE END test-queue-file15e.tmp *** diff --git a/src/cleanup/cleanup_milter.ref15f1 b/src/cleanup/cleanup_milter.ref15f1 new file mode 100644 index 0000000..b9d6021 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15f1 @@ -0,0 +1 @@ +./cleanup_milter: NOQUEUE: milter-header-redirect: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: x@y.z diff --git a/src/cleanup/cleanup_milter.ref15f2 b/src/cleanup/cleanup_milter.ref15f2 new file mode 100644 index 0000000..45dca53 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15f2 @@ -0,0 +1,30 @@ +*** ENVELOPE RECORDS test-queue-file15f.tmp *** + 0 message_size: 365 221 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:06:34 2009 + 99 create_time: Fri Jun 5 14:06:34 2009 + 123 named_attribute: rewrite_context=local + 146 sender_fullname: Wietse Venema + 161 sender: wietse@ahost.example.com + 187 pointer_record: 0 + 202 pointer_record: 0 + 219 *** MESSAGE CONTENTS test-queue-file15f.tmp *** + 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 337 pointer_record: 641 + 641 regular_text: X-SPAM-FLAG: YES + 659 regular_text: To: wietse@ahost.example.com + 689 pointer_record: 367 + 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com> + 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 472 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 520 pointer_record: 0 + 537 regular_text: + 539 regular_text: Fri Jun 5 14:06:34 EDT 2009 + 569 pointer_record: 0 + 586 *** HEADER EXTRACTED test-queue-file15f.tmp *** + 588 pointer_record: 706 + 706 redirect_to: x@y.z + 713 pointer_record: 605 + 605 original_recipient: wietse + 613 recipient: wietse@ahost.example.com + 639 *** MESSAGE FILE END test-queue-file15f.tmp *** diff --git a/src/cleanup/cleanup_milter.ref15g1 b/src/cleanup/cleanup_milter.ref15g1 new file mode 100644 index 0000000..295690a --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15g1 @@ -0,0 +1,2 @@ +./cleanup_milter: NOQUEUE: milter-header-filter: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: x:y:z +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref15g2 b/src/cleanup/cleanup_milter.ref15g2 new file mode 100644 index 0000000..fc67e56 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15g2 @@ -0,0 +1,30 @@ +*** ENVELOPE RECORDS test-queue-file15g.tmp *** + 0 message_size: 365 221 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:06:34 2009 + 99 create_time: Fri Jun 5 14:06:34 2009 + 123 named_attribute: rewrite_context=local + 146 sender_fullname: Wietse Venema + 161 sender: wietse@ahost.example.com + 187 pointer_record: 0 + 202 pointer_record: 0 + 219 *** MESSAGE CONTENTS test-queue-file15g.tmp *** + 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 337 pointer_record: 641 + 641 regular_text: X-SPAM-FLAG: YES + 659 regular_text: To: wietse@ahost.example.com + 689 pointer_record: 367 + 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com> + 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 472 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 520 pointer_record: 0 + 537 regular_text: + 539 regular_text: Fri Jun 5 14:06:34 EDT 2009 + 569 pointer_record: 0 + 586 *** HEADER EXTRACTED test-queue-file15g.tmp *** + 588 pointer_record: 706 + 706 content_filter: x:y:z + 713 pointer_record: 605 + 605 original_recipient: wietse + 613 recipient: wietse@ahost.example.com + 639 *** MESSAGE FILE END test-queue-file15g.tmp *** diff --git a/src/cleanup/cleanup_milter.ref15h1 b/src/cleanup/cleanup_milter.ref15h1 new file mode 100644 index 0000000..bb51e0e --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15h1 @@ -0,0 +1,3 @@ +./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: 5.7.1 whatever +./cleanup_milter: ignoring: add_header X-SPAM-FLAG NO +./cleanup_milter: errs = message content rejected diff --git a/src/cleanup/cleanup_milter.ref15h2 b/src/cleanup/cleanup_milter.ref15h2 new file mode 100644 index 0000000..936f022 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15h2 @@ -0,0 +1,27 @@ +*** ENVELOPE RECORDS test-queue-file15h.tmp *** + 0 message_size: 365 221 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:06:34 2009 + 99 create_time: Fri Jun 5 14:06:34 2009 + 123 named_attribute: rewrite_context=local + 146 sender_fullname: Wietse Venema + 161 sender: wietse@ahost.example.com + 187 pointer_record: 0 + 202 pointer_record: 0 + 219 *** MESSAGE CONTENTS test-queue-file15h.tmp *** + 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 337 regular_text: To: wietse@ahost.example.com + 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com> + 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 472 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 520 pointer_record: 641 + 641 regular_text: X-SPAM-FLAG: YES + 659 pointer_record: 537 + 537 regular_text: + 539 regular_text: Fri Jun 5 14:06:34 EDT 2009 + 569 pointer_record: 0 + 586 *** HEADER EXTRACTED test-queue-file15h.tmp *** + 588 pointer_record: 0 + 605 original_recipient: wietse + 613 recipient: wietse@ahost.example.com + 639 *** MESSAGE FILE END test-queue-file15h.tmp *** diff --git a/src/cleanup/cleanup_milter.ref15i1 b/src/cleanup/cleanup_milter.ref15i1 new file mode 100644 index 0000000..b561728 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15i1 @@ -0,0 +1,3 @@ +./cleanup_milter: NOQUEUE: milter-header-filter: header X-SPAM-FLAG: NO from client_name[client_addr]; from= to=: x:y:z +./cleanup_milter: NOQUEUE: milter-header-reject: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: 5.7.1 whatever +./cleanup_milter: errs = message content rejected diff --git a/src/cleanup/cleanup_milter.ref15i2 b/src/cleanup/cleanup_milter.ref15i2 new file mode 100644 index 0000000..531ac4c --- /dev/null +++ b/src/cleanup/cleanup_milter.ref15i2 @@ -0,0 +1,29 @@ +*** ENVELOPE RECORDS test-queue-file15i.tmp *** + 0 message_size: 365 221 1 0 365 + 81 message_arrival_time: Fri Jun 5 14:06:34 2009 + 99 create_time: Fri Jun 5 14:06:34 2009 + 123 named_attribute: rewrite_context=local + 146 sender_fullname: Wietse Venema + 161 sender: wietse@ahost.example.com + 187 pointer_record: 0 + 202 pointer_record: 0 + 219 *** MESSAGE CONTENTS test-queue-file15i.tmp *** + 221 regular_text: Received: by ahost.example.com (Postfix, from userid 1001) + 281 regular_text: id 06F8B2510C2; Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 337 regular_text: To: wietse@ahost.example.com + 367 regular_text: Message-Id: <20090605180634.06F8B2510C2@ahost.example.com> + 427 regular_text: Date: Fri, 5 Jun 2009 14:06:34 -0400 (EDT) + 472 regular_text: From: wietse@ahost.example.com (Wietse Venema) + 520 pointer_record: 641 + 641 regular_text: X-SPAM-FLAG: NO + 658 pointer_record: 675 + 675 regular_text: X-SPAM-FLAG: YES + 693 pointer_record: 537 + 537 regular_text: + 539 regular_text: Fri Jun 5 14:06:34 EDT 2009 + 569 pointer_record: 0 + 586 *** HEADER EXTRACTED test-queue-file15i.tmp *** + 588 pointer_record: 0 + 605 original_recipient: wietse + 613 recipient: wietse@ahost.example.com + 639 *** MESSAGE FILE END test-queue-file15i.tmp *** diff --git a/src/cleanup/cleanup_milter.ref16a1 b/src/cleanup/cleanup_milter.ref16a1 new file mode 100644 index 0000000..a1d30b0 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref16a1 @@ -0,0 +1,3 @@ +./cleanup_milter: NOQUEUE: milter-header-bcc: header X-SPAM-FLAG: NO from client_name[client_addr]; from= to=: bar@example.com +./cleanup_milter: NOQUEUE: milter-header-bcc: header X-SPAM-FLAG: YES from client_name[client_addr]; from= to=: foo@example.com +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref16a2 b/src/cleanup/cleanup_milter.ref16a2 new file mode 100644 index 0000000..3b0edd2 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref16a2 @@ -0,0 +1,37 @@ +*** ENVELOPE RECORDS test-queue-file16a.tmp *** + 0 message_size: 343 215 1 0 343 0 + 97 message_arrival_time: Tue Nov 18 16:43:29 2014 + 116 create_time: Tue Nov 18 16:43:29 2014 + 140 named_attribute: rewrite_context=local + 163 sender_fullname: Wietse Venema + 178 sender: user@example.com + 196 pointer_record: 654 + 654 original_recipient: bar@example.com + 671 recipient: bar@example.com + 688 pointer_record: 739 + 739 original_recipient: foo@example.com + 756 recipient: foo@example.com + 773 pointer_record: 213 + 213 *** MESSAGE CONTENTS test-queue-file16a.tmp *** + 215 regular_text: Received: by host.example.com (Postfix, from userid 1001) + 274 regular_text: id 663E22172797; Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 331 regular_text: To: user@example.com + 353 regular_text: Subject: test + 368 padding: 0 + 371 regular_text: Message-Id: <20141118214329.663E22172797@host.example.com> + 431 regular_text: Date: Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 476 regular_text: From: user@example.com (Wietse Venema) + 516 pointer_record: 705 + 705 regular_text: X-SPAM-FLAG: NO + 722 pointer_record: 790 + 790 regular_text: X-SPAM-FLAG: YES + 808 pointer_record: 533 + 533 regular_text: + 535 regular_text: test + 541 pointer_record: 0 + 558 *** HEADER EXTRACTED test-queue-file16a.tmp *** + 560 pointer_record: 0 + 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com + 616 original_recipient: user@example.com + 634 recipient: user@example.com + 652 *** MESSAGE FILE END test-queue-file16a.tmp *** diff --git a/src/cleanup/cleanup_milter.ref16b1 b/src/cleanup/cleanup_milter.ref16b1 new file mode 100644 index 0000000..eab5a83 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref16b1 @@ -0,0 +1 @@ +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref16b2 b/src/cleanup/cleanup_milter.ref16b2 new file mode 100644 index 0000000..2ae8719 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref16b2 @@ -0,0 +1,42 @@ +*** ENVELOPE RECORDS test-queue-file16b.tmp *** + 0 message_size: 343 215 1 0 343 0 + 97 message_arrival_time: Tue Nov 18 16:43:29 2014 + 116 create_time: Tue Nov 18 16:43:29 2014 + 140 named_attribute: rewrite_context=local + 163 sender_fullname: Wietse Venema + 178 sender: user@example.com + 196 pointer_record: 654 + 654 named_attribute: notify_flags=1 + 670 original_recipient: foo@example.com + 687 recipient: foo@example.com + 704 pointer_record: 721 + 721 original_recipient: bar@example.com + 738 recipient: bar@example.com + 755 pointer_record: 772 + 772 named_attribute: dsn_orig_rcpt=rfc822;orig-bar@example.com + 815 original_recipient: bar@example.com + 832 recipient: bar@example.com + 849 pointer_record: 866 + 866 named_attribute: notify_flags=8 + 882 original_recipient: bar@example.com + 899 recipient: bar@example.com + 916 pointer_record: 213 + 213 *** MESSAGE CONTENTS test-queue-file16b.tmp *** + 215 regular_text: Received: by host.example.com (Postfix, from userid 1001) + 274 regular_text: id 663E22172797; Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 331 regular_text: To: user@example.com + 353 regular_text: Subject: test + 368 padding: 0 + 371 regular_text: Message-Id: <20141118214329.663E22172797@host.example.com> + 431 regular_text: Date: Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 476 regular_text: From: user@example.com (Wietse Venema) + 516 pointer_record: 0 + 533 regular_text: + 535 regular_text: test + 541 pointer_record: 0 + 558 *** HEADER EXTRACTED test-queue-file16b.tmp *** + 560 pointer_record: 0 + 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com + 616 original_recipient: user@example.com + 634 recipient: user@example.com + 652 *** MESSAGE FILE END test-queue-file16b.tmp *** diff --git a/src/cleanup/cleanup_milter.ref17a1 b/src/cleanup/cleanup_milter.ref17a1 new file mode 100644 index 0000000..eab5a83 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17a1 @@ -0,0 +1 @@ +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref17a2 b/src/cleanup/cleanup_milter.ref17a2 new file mode 100644 index 0000000..18037a8 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17a2 @@ -0,0 +1,30 @@ +*** ENVELOPE RECORDS test-queue-file17a.tmp *** + 0 message_size: 343 215 1 0 343 0 + 97 message_arrival_time: Tue Nov 18 16:43:29 2014 + 116 create_time: Tue Nov 18 16:43:29 2014 + 140 named_attribute: rewrite_context=local + 163 sender_fullname: Wietse Venema + 178 sender: user@example.com + 196 pointer_record: 654 + 654 original_recipient: user@example.com + 672 recipient: user@example.com + 690 pointer_record: 213 + 213 *** MESSAGE CONTENTS test-queue-file17a.tmp *** + 215 regular_text: Received: by host.example.com (Postfix, from userid 1001) + 274 regular_text: id 663E22172797; Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 331 regular_text: To: user@example.com + 353 regular_text: Subject: test + 368 padding: 0 + 371 regular_text: Message-Id: <20141118214329.663E22172797@host.example.com> + 431 regular_text: Date: Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 476 regular_text: From: user@example.com (Wietse Venema) + 516 pointer_record: 0 + 533 regular_text: + 535 regular_text: test + 541 pointer_record: 0 + 558 *** HEADER EXTRACTED test-queue-file17a.tmp *** + 560 pointer_record: 0 + 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com + 616 original_recipient: user@example.com + 634 canceled_recipient: user@example.com + 652 *** MESSAGE FILE END test-queue-file17a.tmp *** diff --git a/src/cleanup/cleanup_milter.ref17b1 b/src/cleanup/cleanup_milter.ref17b1 new file mode 100644 index 0000000..eab5a83 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17b1 @@ -0,0 +1 @@ +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref17b2 b/src/cleanup/cleanup_milter.ref17b2 new file mode 100644 index 0000000..4df043d --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17b2 @@ -0,0 +1,31 @@ +*** ENVELOPE RECORDS test-queue-file17b.tmp *** + 0 message_size: 343 215 1 0 343 0 + 97 message_arrival_time: Tue Nov 18 16:43:29 2014 + 116 create_time: Tue Nov 18 16:43:29 2014 + 140 named_attribute: rewrite_context=local + 163 sender_fullname: Wietse Venema + 178 sender: user@example.com + 196 pointer_record: 654 + 654 named_attribute: notify_flags=1 + 670 original_recipient: user@example.com + 688 recipient: user@example.com + 706 pointer_record: 213 + 213 *** MESSAGE CONTENTS test-queue-file17b.tmp *** + 215 regular_text: Received: by host.example.com (Postfix, from userid 1001) + 274 regular_text: id 663E22172797; Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 331 regular_text: To: user@example.com + 353 regular_text: Subject: test + 368 padding: 0 + 371 regular_text: Message-Id: <20141118214329.663E22172797@host.example.com> + 431 regular_text: Date: Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 476 regular_text: From: user@example.com (Wietse Venema) + 516 pointer_record: 0 + 533 regular_text: + 535 regular_text: test + 541 pointer_record: 0 + 558 *** HEADER EXTRACTED test-queue-file17b.tmp *** + 560 pointer_record: 0 + 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com + 616 original_recipient: user@example.com + 634 canceled_recipient: user@example.com + 652 *** MESSAGE FILE END test-queue-file17b.tmp *** diff --git a/src/cleanup/cleanup_milter.ref17c1 b/src/cleanup/cleanup_milter.ref17c1 new file mode 100644 index 0000000..eab5a83 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17c1 @@ -0,0 +1 @@ +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref17c2 b/src/cleanup/cleanup_milter.ref17c2 new file mode 100644 index 0000000..509af41 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17c2 @@ -0,0 +1,31 @@ +*** ENVELOPE RECORDS test-queue-file17c.tmp *** + 0 message_size: 343 215 1 0 343 0 + 97 message_arrival_time: Tue Nov 18 16:43:29 2014 + 116 create_time: Tue Nov 18 16:43:29 2014 + 140 named_attribute: rewrite_context=local + 163 sender_fullname: Wietse Venema + 178 sender: user@example.com + 196 pointer_record: 654 + 654 named_attribute: notify_flags=1 + 670 original_recipient: user@example.com + 688 recipient: user@example.com + 706 pointer_record: 213 + 213 *** MESSAGE CONTENTS test-queue-file17c.tmp *** + 215 regular_text: Received: by host.example.com (Postfix, from userid 1001) + 274 regular_text: id 663E22172797; Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 331 regular_text: To: user@example.com + 353 regular_text: Subject: test + 368 padding: 0 + 371 regular_text: Message-Id: <20141118214329.663E22172797@host.example.com> + 431 regular_text: Date: Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 476 regular_text: From: user@example.com (Wietse Venema) + 516 pointer_record: 0 + 533 regular_text: + 535 regular_text: test + 541 pointer_record: 0 + 558 *** HEADER EXTRACTED test-queue-file17c.tmp *** + 560 pointer_record: 0 + 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com + 616 original_recipient: user@example.com + 634 canceled_recipient: user@example.com + 652 *** MESSAGE FILE END test-queue-file17c.tmp *** diff --git a/src/cleanup/cleanup_milter.ref17d1 b/src/cleanup/cleanup_milter.ref17d1 new file mode 100644 index 0000000..eab5a83 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17d1 @@ -0,0 +1 @@ +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref17d2 b/src/cleanup/cleanup_milter.ref17d2 new file mode 100644 index 0000000..846ac33 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17d2 @@ -0,0 +1,35 @@ +*** ENVELOPE RECORDS test-queue-file17d.tmp *** + 0 message_size: 343 215 1 0 343 0 + 97 message_arrival_time: Tue Nov 18 16:43:29 2014 + 116 create_time: Tue Nov 18 16:43:29 2014 + 140 named_attribute: rewrite_context=local + 163 sender_fullname: Wietse Venema + 178 sender: user@example.com + 196 pointer_record: 654 + 654 named_attribute: notify_flags=1 + 670 original_recipient: user@example.com + 688 canceled_recipient: user@example.com + 706 pointer_record: 723 + 723 named_attribute: notify_flags=1 + 739 original_recipient: user@example.com + 757 recipient: user@example.com + 775 pointer_record: 213 + 213 *** MESSAGE CONTENTS test-queue-file17d.tmp *** + 215 regular_text: Received: by host.example.com (Postfix, from userid 1001) + 274 regular_text: id 663E22172797; Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 331 regular_text: To: user@example.com + 353 regular_text: Subject: test + 368 padding: 0 + 371 regular_text: Message-Id: <20141118214329.663E22172797@host.example.com> + 431 regular_text: Date: Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 476 regular_text: From: user@example.com (Wietse Venema) + 516 pointer_record: 0 + 533 regular_text: + 535 regular_text: test + 541 pointer_record: 0 + 558 *** HEADER EXTRACTED test-queue-file17d.tmp *** + 560 pointer_record: 0 + 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com + 616 original_recipient: user@example.com + 634 canceled_recipient: user@example.com + 652 *** MESSAGE FILE END test-queue-file17d.tmp *** diff --git a/src/cleanup/cleanup_milter.ref17e1 b/src/cleanup/cleanup_milter.ref17e1 new file mode 100644 index 0000000..b78b1a2 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17e1 @@ -0,0 +1,2 @@ +./cleanup_milter: enable_original_recipient: 0 +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref17e2 b/src/cleanup/cleanup_milter.ref17e2 new file mode 100644 index 0000000..7e523f6 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17e2 @@ -0,0 +1,30 @@ +*** ENVELOPE RECORDS test-queue-file17e.tmp *** + 0 message_size: 343 215 1 0 343 0 + 97 message_arrival_time: Tue Nov 18 16:43:29 2014 + 116 create_time: Tue Nov 18 16:43:29 2014 + 140 named_attribute: rewrite_context=local + 163 sender_fullname: Wietse Venema + 178 sender: user@example.com + 196 pointer_record: 654 + 654 original_recipient: user@example.com + 672 recipient: user@example.com + 690 pointer_record: 213 + 213 *** MESSAGE CONTENTS test-queue-file17e.tmp *** + 215 regular_text: Received: by host.example.com (Postfix, from userid 1001) + 274 regular_text: id 663E22172797; Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 331 regular_text: To: user@example.com + 353 regular_text: Subject: test + 368 padding: 0 + 371 regular_text: Message-Id: <20141118214329.663E22172797@host.example.com> + 431 regular_text: Date: Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 476 regular_text: From: user@example.com (Wietse Venema) + 516 pointer_record: 0 + 533 regular_text: + 535 regular_text: test + 541 pointer_record: 0 + 558 *** HEADER EXTRACTED test-queue-file17e.tmp *** + 560 pointer_record: 0 + 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com + 616 original_recipient: user@example.com + 634 canceled_recipient: user@example.com + 652 *** MESSAGE FILE END test-queue-file17e.tmp *** diff --git a/src/cleanup/cleanup_milter.ref17f1 b/src/cleanup/cleanup_milter.ref17f1 new file mode 100644 index 0000000..b78b1a2 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17f1 @@ -0,0 +1,2 @@ +./cleanup_milter: enable_original_recipient: 0 +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref17f2 b/src/cleanup/cleanup_milter.ref17f2 new file mode 100644 index 0000000..ce18591 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17f2 @@ -0,0 +1,30 @@ +*** ENVELOPE RECORDS test-queue-file17f.tmp *** + 0 message_size: 343 215 1 0 343 0 + 97 message_arrival_time: Tue Nov 18 16:43:29 2014 + 116 create_time: Tue Nov 18 16:43:29 2014 + 140 named_attribute: rewrite_context=local + 163 sender_fullname: Wietse Venema + 178 sender: user@example.com + 196 pointer_record: 654 + 654 original_recipient: user@example.com + 672 recipient: user@example.com + 690 pointer_record: 213 + 213 *** MESSAGE CONTENTS test-queue-file17f.tmp *** + 215 regular_text: Received: by host.example.com (Postfix, from userid 1001) + 274 regular_text: id 663E22172797; Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 331 regular_text: To: user@example.com + 353 regular_text: Subject: test + 368 padding: 0 + 371 regular_text: Message-Id: <20141118214329.663E22172797@host.example.com> + 431 regular_text: Date: Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 476 regular_text: From: user@example.com (Wietse Venema) + 516 pointer_record: 0 + 533 regular_text: + 535 regular_text: test + 541 pointer_record: 0 + 558 *** HEADER EXTRACTED test-queue-file17f.tmp *** + 560 pointer_record: 0 + 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com + 616 original_recipient: user@example.com + 634 canceled_recipient: user@example.com + 652 *** MESSAGE FILE END test-queue-file17f.tmp *** diff --git a/src/cleanup/cleanup_milter.ref17g1 b/src/cleanup/cleanup_milter.ref17g1 new file mode 100644 index 0000000..b78b1a2 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17g1 @@ -0,0 +1,2 @@ +./cleanup_milter: enable_original_recipient: 0 +./cleanup_milter: flags = enable_header_body_filter enable_milters diff --git a/src/cleanup/cleanup_milter.ref17g2 b/src/cleanup/cleanup_milter.ref17g2 new file mode 100644 index 0000000..938eeed --- /dev/null +++ b/src/cleanup/cleanup_milter.ref17g2 @@ -0,0 +1,33 @@ +*** ENVELOPE RECORDS test-queue-file17g.tmp *** + 0 message_size: 343 215 1 0 343 0 + 97 message_arrival_time: Tue Nov 18 16:43:29 2014 + 116 create_time: Tue Nov 18 16:43:29 2014 + 140 named_attribute: rewrite_context=local + 163 sender_fullname: Wietse Venema + 178 sender: user@example.com + 196 pointer_record: 654 + 654 original_recipient: user@example.com + 672 canceled_recipient: user@example.com + 690 pointer_record: 707 + 707 original_recipient: user@example.com + 725 recipient: user@example.com + 743 pointer_record: 213 + 213 *** MESSAGE CONTENTS test-queue-file17g.tmp *** + 215 regular_text: Received: by host.example.com (Postfix, from userid 1001) + 274 regular_text: id 663E22172797; Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 331 regular_text: To: user@example.com + 353 regular_text: Subject: test + 368 padding: 0 + 371 regular_text: Message-Id: <20141118214329.663E22172797@host.example.com> + 431 regular_text: Date: Tue, 18 Nov 2014 16:43:29 -0500 (EST) + 476 regular_text: From: user@example.com (Wietse Venema) + 516 pointer_record: 0 + 533 regular_text: + 535 regular_text: test + 541 pointer_record: 0 + 558 *** HEADER EXTRACTED test-queue-file17g.tmp *** + 560 pointer_record: 0 + 577 named_attribute: dsn_orig_rcpt=rfc822;user@example.com + 616 original_recipient: user@example.com + 634 canceled_recipient: user@example.com + 652 *** MESSAGE FILE END test-queue-file17g.tmp *** diff --git a/src/cleanup/cleanup_milter.ref2 b/src/cleanup/cleanup_milter.ref2 new file mode 100644 index 0000000..bdfc994 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref2 @@ -0,0 +1,36 @@ +*** ENVELOPE RECORDS test-queue-file2.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file2.tmp *** + 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 regular_text: From: me@porcupine.org + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 pointer_record: 573 + 573 pointer_record: 608 + 608 pointer_record: 643 + 643 regular_text: Subject: hey! + 658 padding: 0 + 661 pointer_record: 489 + 489 pointer_record: 678 + 678 pointer_record: 712 + 712 pointer_record: 746 + 746 pointer_record: 780 + 780 regular_text: foo: foobar + 793 padding: 0 + 797 pointer_record: 695 + 695 pointer_record: 506 + 506 regular_text: + 508 regular_text: text + 514 pointer_record: 0 + 531 *** HEADER EXTRACTED test-queue-file2.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file2.tmp *** diff --git a/src/cleanup/cleanup_milter.ref3 b/src/cleanup/cleanup_milter.ref3 new file mode 100644 index 0000000..656b561 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref3 @@ -0,0 +1,50 @@ +*** ENVELOPE RECORDS test-queue-file3.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 573 + 573 original_recipient: me@porcupine.org + 591 canceled_recipient: me@porcupine.org + 609 pointer_record: 1397 + 1397 original_recipient: em@porcupine.org + 1415 canceled_recipient: em@porcupine.org + 1433 pointer_record: 197 + 197 *** MESSAGE CONTENTS test-queue-file3.tmp *** + 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 pointer_record: 842 + 842 pointer_record: 1315 + 1315 pointer_record: 1356 + 1356 pointer_record: 1450 + 1450 pointer_record: 1707 + 1707 pointer_record: 1748 + 1748 regular_text: From: me@porcupine.org + 1772 pointer_record: 1491 + 1491 pointer_record: 1789 + 1789 pointer_record: 1829 + 1829 regular_text: To: you@porcupine.org + 1852 pointer_record: 1531 + 1531 pointer_record: 1869 + 1869 pointer_record: 1948 + 1948 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 2010 pointer_record: 1610 + 1610 pointer_record: 2027 + 2027 pointer_record: 2089 + 2089 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 2134 pointer_record: 1672 + 1672 pointer_record: 2151 + 2151 pointer_record: 2186 + 2186 regular_text: Subject: hey! + 2201 padding: 0 + 2204 pointer_record: 489 + 489 pointer_record: 0 + 506 regular_text: + 508 regular_text: text + 514 pointer_record: 0 + 531 *** HEADER EXTRACTED test-queue-file3.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file3.tmp *** diff --git a/src/cleanup/cleanup_milter.ref4 b/src/cleanup/cleanup_milter.ref4 new file mode 100644 index 0000000..4c3be60 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref4 @@ -0,0 +1,59 @@ +*** ENVELOPE RECORDS test-queue-file4.tmp *** + 0 message_size: 441 813 3 0 441 + 81 message_arrival_time: Sat Jan 20 19:52:41 2007 + 100 create_time: Sat Jan 20 19:52:47 2007 + 124 named_attribute: rewrite_context=local + 147 sender: wietse@porcupine.org + 169 named_attribute: log_client_name=hades.porcupine.org + 206 named_attribute: log_client_address=168.100.189.10 + 241 named_attribute: log_message_origin=hades.porcupine.org[168.100.189.10] + 297 named_attribute: log_helo_name=hades.porcupine.org + 332 named_attribute: log_protocol_name=SMTP + 356 named_attribute: client_name=hades.porcupine.org + 389 named_attribute: reverse_client_name=hades.porcupine.org + 430 named_attribute: client_address=168.100.189.10 + 461 named_attribute: helo_name=hades.porcupine.org + 492 named_attribute: client_address_type=2 + 515 named_attribute: dsn_orig_rcpt=rfc822;wietse@porcupine.org + 558 original_recipient: wietse@porcupine.org + 580 recipient: wietse@porcupine.org + 602 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org + 650 original_recipient: alias@hades.porcupine.org + 677 recipient: wietse@porcupine.org + 699 named_attribute: dsn_orig_rcpt=rfc822;alias@hades.porcupine.org + 747 original_recipient: alias@hades.porcupine.org + 774 recipient: root@porcupine.org + 794 pointer_record: 1258 + 1258 original_recipient: 01 + 1262 canceled_recipient: 01 + 1266 pointer_record: 1283 + 1283 original_recipient: 02 + 1287 canceled_recipient: 02 + 1291 pointer_record: 1308 + 1308 original_recipient: 03 + 1312 canceled_recipient: 03 + 1316 pointer_record: 811 + 811 *** MESSAGE CONTENTS test-queue-file4.tmp *** + 813 regular_text: Received: from hades.porcupine.org (hades.porcupine.org [168.100.189.10]) + 888 regular_text: by hades.porcupine.org (Postfix) with SMTP id 38132290405; + 949 regular_text: Sat, 20 Jan 2007 19:52:41 -0500 (EST) + 989 regular_text: X: 1 + 995 padding: 0 + 1006 regular_text: 2 + 1010 regular_text: 3 + 1014 regular_text: 4 + 1018 regular_text: 5 + 1022 regular_text: 6 + 1026 regular_text: 7 + 1030 regular_text: Y: 1234567 + 1042 padding: 0 + 1047 regular_text: Message-Id: <20070121005247.38132290405@hades.porcupine.org> + 1109 regular_text: Date: Sat, 20 Jan 2007 19:52:41 -0500 (EST) + 1154 regular_text: From: wietse@porcupine.org + 1182 regular_text: To: undisclosed-recipients:; + 1212 pointer_record: 0 + 1229 regular_text: + 1231 regular_text: text + 1237 pointer_record: 0 + 1254 *** HEADER EXTRACTED test-queue-file4.tmp *** + 1256 *** MESSAGE FILE END test-queue-file4.tmp *** diff --git a/src/cleanup/cleanup_milter.ref5 b/src/cleanup/cleanup_milter.ref5 new file mode 100644 index 0000000..b5862a8 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref5 @@ -0,0 +1,29 @@ +*** ENVELOPE RECORDS test-queue-file5.tmp *** + 0 message_size: 376 237 1 0 376 + 81 message_arrival_time: Sun Jan 21 11:26:46 2007 + 100 create_time: Sun Jan 21 11:26:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 original_recipient: you@porcupine.org + 199 recipient: you@porcupine.org + 218 pointer_record: 0 + 235 *** MESSAGE CONTENTS test-queue-file5.tmp *** + 237 regular_text: Received: by hades.porcupine.org (Postfix, from userid 0) + 296 regular_text: id 38FA9290404; Sun, 21 Jan 2007 11:26:59 -0500 (EST) + 352 pointer_record: 707 + 707 regular_text: Subject: hya + 721 padding: 0 + 724 pointer_record: 662 + 662 regular_text: X: whatevershebringswesing + 690 pointer_record: 394 + 394 regular_text: Message-Id: <20070121162659.38FA9290404@hades.porcupine.org> + 456 regular_text: Date: Sun, 21 Jan 2007 11:26:46 -0500 (EST) + 501 regular_text: From: me@porcupine.org (Wietse Venema) + 541 regular_text: To: undisclosed-recipients:; + 571 pointer_record: 0 + 588 regular_text: + 590 regular_text: text + 596 pointer_record: 0 + 613 *** HEADER EXTRACTED test-queue-file5.tmp *** + 615 *** MESSAGE FILE END test-queue-file5.tmp *** diff --git a/src/cleanup/cleanup_milter.ref6a b/src/cleanup/cleanup_milter.ref6a new file mode 100644 index 0000000..193960d --- /dev/null +++ b/src/cleanup/cleanup_milter.ref6a @@ -0,0 +1,28 @@ +*** ENVELOPE RECORDS test-queue-file6.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file6.tmp *** + 199 pointer_record: 573 + 573 regular_text: X-Virus-Scanned: hya + 595 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 657 pointer_record: 261 + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 regular_text: From: me@porcupine.org + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 regular_text: Subject: hey! + 486 padding: 0 + 489 pointer_record: 0 + 506 regular_text: + 508 regular_text: text + 514 pointer_record: 0 + 531 *** HEADER EXTRACTED test-queue-file6.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file6.tmp *** diff --git a/src/cleanup/cleanup_milter.ref6b b/src/cleanup/cleanup_milter.ref6b new file mode 100644 index 0000000..fdcb38e --- /dev/null +++ b/src/cleanup/cleanup_milter.ref6b @@ -0,0 +1,31 @@ +*** ENVELOPE RECORDS test-queue-file6.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file6.tmp *** + 199 pointer_record: 573 + 573 regular_text: X-Virus-Scanned: hya + 595 pointer_record: 674 + 674 regular_text: Domainkey-Signature: hya + 700 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 762 pointer_record: 657 + 657 pointer_record: 261 + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 regular_text: From: me@porcupine.org + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 regular_text: Subject: hey! + 486 padding: 0 + 489 pointer_record: 0 + 506 regular_text: + 508 regular_text: text + 514 pointer_record: 0 + 531 *** HEADER EXTRACTED test-queue-file6.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file6.tmp *** diff --git a/src/cleanup/cleanup_milter.ref6c b/src/cleanup/cleanup_milter.ref6c new file mode 100644 index 0000000..9d58b6d --- /dev/null +++ b/src/cleanup/cleanup_milter.ref6c @@ -0,0 +1,33 @@ +*** ENVELOPE RECORDS test-queue-file6.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file6.tmp *** + 199 pointer_record: 573 + 573 regular_text: X-Virus-Scanned: hya + 595 pointer_record: 779 + 779 regular_text: DKIM-Signature: hya + 800 pointer_record: 674 + 674 regular_text: Domainkey-Signature: hya + 700 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 762 pointer_record: 657 + 657 pointer_record: 261 + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 regular_text: From: me@porcupine.org + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 regular_text: Subject: hey! + 486 padding: 0 + 489 pointer_record: 0 + 506 regular_text: + 508 regular_text: text + 514 pointer_record: 0 + 531 *** HEADER EXTRACTED test-queue-file6.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file6.tmp *** diff --git a/src/cleanup/cleanup_milter.ref7 b/src/cleanup/cleanup_milter.ref7 new file mode 100644 index 0000000..ffc63a3 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref7 @@ -0,0 +1,32 @@ +*** ENVELOPE RECORDS test-queue-file7.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file7.tmp *** + 199 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 pointer_record: 679 + 679 regular_text: DKIM-Signature: hya + 700 pointer_record: 636 + 636 regular_text: Domainkey-Signature: hya + 662 pointer_record: 573 + 573 regular_text: X-Virus-Scanned: hya + 595 regular_text: From: me@porcupine.org + 619 pointer_record: 341 + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 regular_text: Subject: hey! + 486 padding: 0 + 489 pointer_record: 0 + 506 regular_text: + 508 regular_text: text + 514 pointer_record: 0 + 531 *** HEADER EXTRACTED test-queue-file7.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file7.tmp *** diff --git a/src/cleanup/cleanup_milter.ref8 b/src/cleanup/cleanup_milter.ref8 new file mode 100644 index 0000000..5aadfd4 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref8 @@ -0,0 +1,34 @@ +*** ENVELOPE RECORDS test-queue-file8.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file8.tmp *** + 199 pointer_record: 573 + 573 regular_text: inserted-at-1: hya + 593 pointer_record: 672 + 672 regular_text: inserted-at-2: hya + 692 pointer_record: 771 + 771 regular_text: inserted-at-3: hya + 791 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 853 pointer_record: 754 + 754 pointer_record: 655 + 655 pointer_record: 261 + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 regular_text: From: me@porcupine.org + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 regular_text: Subject: hey! + 486 padding: 0 + 489 pointer_record: 0 + 506 regular_text: + 508 regular_text: text + 514 pointer_record: 0 + 531 *** HEADER EXTRACTED test-queue-file8.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file8.tmp *** diff --git a/src/cleanup/cleanup_milter.ref9 b/src/cleanup/cleanup_milter.ref9 new file mode 100644 index 0000000..9cfd626 --- /dev/null +++ b/src/cleanup/cleanup_milter.ref9 @@ -0,0 +1,33 @@ +*** ENVELOPE RECORDS test-queue-file9.tmp *** + 0 message_size: 332 199 1 0 332 + 81 message_arrival_time: Sat Jan 20 20:53:54 2007 + 100 create_time: Sat Jan 20 20:53:59 2007 + 124 named_attribute: rewrite_context=local + 147 sender_fullname: Wietse Venema + 162 sender: me@porcupine.org + 180 pointer_record: 0 + 197 *** MESSAGE CONTENTS test-queue-file9.tmp *** + 199 pointer_record: 573 + 573 regular_text: inserted-at-1: hya + 593 regular_text: Received: by hades.porcupine.org (Postfix, from userid 1001) + 655 pointer_record: 261 + 261 regular_text: id B85F1290407; Sat, 20 Jan 2007 20:53:59 -0500 (EST) + 317 pointer_record: 672 + 672 regular_text: inserted-at-3: hya + 692 regular_text: From: me@porcupine.org + 716 pointer_record: 733 + 733 regular_text: inserted-at-5: hya + 753 pointer_record: 341 + 341 regular_text: To: you@porcupine.org + 364 regular_text: Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> + 426 regular_text: Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) + 471 regular_text: Subject: hey! + 486 padding: 0 + 489 pointer_record: 0 + 506 regular_text: + 508 regular_text: text + 514 pointer_record: 0 + 531 *** HEADER EXTRACTED test-queue-file9.tmp *** + 533 original_recipient: you@porcupine.org + 552 recipient: you@porcupine.org + 571 *** MESSAGE FILE END test-queue-file9.tmp *** diff --git a/src/cleanup/cleanup_milter.reg14a b/src/cleanup/cleanup_milter.reg14a new file mode 100644 index 0000000..1a9ccf1 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg14a @@ -0,0 +1 @@ +/./ reject diff --git a/src/cleanup/cleanup_milter.reg14b b/src/cleanup/cleanup_milter.reg14b new file mode 100644 index 0000000..332ba69 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg14b @@ -0,0 +1 @@ +/./ FILTER transport:nexthop:port diff --git a/src/cleanup/cleanup_milter.reg14c b/src/cleanup/cleanup_milter.reg14c new file mode 100644 index 0000000..0421976 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg14c @@ -0,0 +1 @@ +/./ REDIRECT foo@examle.com diff --git a/src/cleanup/cleanup_milter.reg14d b/src/cleanup/cleanup_milter.reg14d new file mode 100644 index 0000000..78647a3 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg14d @@ -0,0 +1 @@ +/./ DISCARD diff --git a/src/cleanup/cleanup_milter.reg14e b/src/cleanup/cleanup_milter.reg14e new file mode 100644 index 0000000..c88ee2c --- /dev/null +++ b/src/cleanup/cleanup_milter.reg14e @@ -0,0 +1 @@ +/./ HOLD diff --git a/src/cleanup/cleanup_milter.reg14f b/src/cleanup/cleanup_milter.reg14f new file mode 100644 index 0000000..1a9ccf1 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg14f @@ -0,0 +1 @@ +/./ reject diff --git a/src/cleanup/cleanup_milter.reg14g b/src/cleanup/cleanup_milter.reg14g new file mode 100644 index 0000000..1a9ccf1 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg14g @@ -0,0 +1 @@ +/./ reject diff --git a/src/cleanup/cleanup_milter.reg15a b/src/cleanup/cleanup_milter.reg15a new file mode 100644 index 0000000..1a9ccf1 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg15a @@ -0,0 +1 @@ +/./ reject diff --git a/src/cleanup/cleanup_milter.reg15b b/src/cleanup/cleanup_milter.reg15b new file mode 100644 index 0000000..332ba69 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg15b @@ -0,0 +1 @@ +/./ FILTER transport:nexthop:port diff --git a/src/cleanup/cleanup_milter.reg15c b/src/cleanup/cleanup_milter.reg15c new file mode 100644 index 0000000..0421976 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg15c @@ -0,0 +1 @@ +/./ REDIRECT foo@examle.com diff --git a/src/cleanup/cleanup_milter.reg15d b/src/cleanup/cleanup_milter.reg15d new file mode 100644 index 0000000..78647a3 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg15d @@ -0,0 +1 @@ +/./ DISCARD diff --git a/src/cleanup/cleanup_milter.reg15e b/src/cleanup/cleanup_milter.reg15e new file mode 100644 index 0000000..c88ee2c --- /dev/null +++ b/src/cleanup/cleanup_milter.reg15e @@ -0,0 +1 @@ +/./ HOLD diff --git a/src/cleanup/cleanup_milter.reg15f b/src/cleanup/cleanup_milter.reg15f new file mode 100644 index 0000000..a629bf2 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg15f @@ -0,0 +1 @@ +/./ redirect x@y.z diff --git a/src/cleanup/cleanup_milter.reg15g b/src/cleanup/cleanup_milter.reg15g new file mode 100644 index 0000000..0a25be3 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg15g @@ -0,0 +1 @@ +/./ filter x:y:z diff --git a/src/cleanup/cleanup_milter.reg15h b/src/cleanup/cleanup_milter.reg15h new file mode 100644 index 0000000..8c97639 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg15h @@ -0,0 +1,2 @@ +/YES/ reject whatever +/NO/ filter x:y:z diff --git a/src/cleanup/cleanup_milter.reg15i b/src/cleanup/cleanup_milter.reg15i new file mode 100644 index 0000000..8c97639 --- /dev/null +++ b/src/cleanup/cleanup_milter.reg15i @@ -0,0 +1,2 @@ +/YES/ reject whatever +/NO/ filter x:y:z diff --git a/src/cleanup/cleanup_milter.reg16a b/src/cleanup/cleanup_milter.reg16a new file mode 100644 index 0000000..674aa5d --- /dev/null +++ b/src/cleanup/cleanup_milter.reg16a @@ -0,0 +1,2 @@ +/YES/ bcc foo@example.com +/NO/ bcc bar@example.com diff --git a/src/cleanup/cleanup_out.c b/src/cleanup/cleanup_out.c new file mode 100644 index 0000000..5f8ed0a --- /dev/null +++ b/src/cleanup/cleanup_out.c @@ -0,0 +1,233 @@ +/*++ +/* NAME +/* cleanup_out 3 +/* SUMMARY +/* record output support +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* int CLEANUP_OUT_OK(state) +/* CLEANUP_STATE *state; +/* +/* void cleanup_out(state, type, data, len) +/* CLEANUP_STATE *state; +/* int type; +/* const char *data; +/* ssize_t len; +/* +/* void cleanup_out_string(state, type, str) +/* CLEANUP_STATE *state; +/* int type; +/* const char *str; +/* +/* void CLEANUP_OUT_BUF(state, type, buf) +/* CLEANUP_STATE *state; +/* int type; +/* VSTRING *buf; +/* +/* void cleanup_out_format(state, type, format, ...) +/* CLEANUP_STATE *state; +/* int type; +/* const char *format; +/* +/* void cleanup_out_header(state, buf) +/* CLEANUP_STATE *state; +/* VSTRING *buf; +/* DESCRIPTION +/* This module writes records to the output stream. +/* +/* CLEANUP_OUT_OK() is a macro that evaluates to non-zero +/* as long as it makes sense to produce output. All output +/* routines below check for this condition. +/* +/* cleanup_out() is the main record output routine. It writes +/* one record of the specified type, with the specified data +/* and length to the output stream. +/* +/* cleanup_out_string() outputs one string as a record. +/* +/* CLEANUP_OUT_BUF() is an unsafe macro that outputs +/* one string buffer as a record. +/* +/* cleanup_out_format() formats its arguments and writes +/* the result as a record. +/* +/* cleanup_out_header() outputs a multi-line header as records +/* of the specified type. The input is expected to be newline +/* separated (not newline terminated), and is modified. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include /* 44BSD stdarg.h uses abort() */ +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +#define STR vstring_str + +/* cleanup_out - output one single record */ + +void cleanup_out(CLEANUP_STATE *state, int type, const char *string, ssize_t len) +{ + int err = 0; + + /* + * Long message header lines have to be read and written as multiple + * records. Other header/body content, and envelope data, is copied one + * record at a time. Be sure to not skip a zero-length request. + * + * XXX We don't know if we're writing a message header or not, but that is + * not a problem. A REC_TYPE_NORM or REC_TYPE_CONT record can always be + * chopped up into an equivalent set of REC_TYPE_CONT plus REC_TYPE_NORM + * records. + */ + if (CLEANUP_OUT_OK(state) == 0) + return; + +#define TEXT_RECORD(t) ((t) == REC_TYPE_NORM || (t) == REC_TYPE_CONT) + + if (msg_verbose && !TEXT_RECORD(type)) + msg_info("cleanup_out: %c %.*s", type, (int) len, string); + + if (var_line_limit <= 0) + msg_panic("cleanup_out: bad line length limit: %d", var_line_limit); + do { + if (len > var_line_limit && TEXT_RECORD(type)) { + err = rec_put(state->dst, REC_TYPE_CONT, string, var_line_limit); + string += var_line_limit; + len -= var_line_limit; + } else { + err = rec_put(state->dst, type, string, len); + break; + } + } while (len > 0 && err >= 0); + + if (err < 0) { + if (errno == EFBIG) { + msg_warn("%s: queue file size limit exceeded", + state->queue_id); + state->errs |= CLEANUP_STAT_SIZE; + } else { + msg_warn("%s: write queue file: %m", state->queue_id); + state->errs |= CLEANUP_STAT_WRITE; + } + } +} + +/* cleanup_out_string - output string to one single record */ + +void cleanup_out_string(CLEANUP_STATE *state, int type, const char *string) +{ + cleanup_out(state, type, string, strlen(string)); +} + +/* cleanup_out_format - output one formatted record */ + +void cleanup_out_format(CLEANUP_STATE *state, int type, const char *fmt,...) +{ + static VSTRING *vp; + va_list ap; + + if (vp == 0) + vp = vstring_alloc(100); + va_start(ap, fmt); + vstring_vsprintf(vp, fmt, ap); + va_end(ap); + CLEANUP_OUT_BUF(state, type, vp); +} + +/* cleanup_out_header - output one multi-line header as a bunch of records */ + +void cleanup_out_header(CLEANUP_STATE *state, VSTRING *header_buf) +{ + char *start = vstring_str(header_buf); + char *line; + char *next_line; + ssize_t line_len; + + /* + * Fix 20140711: Auto-detect the presence of a non-ASCII header. + */ + if (var_smtputf8_enable && *STR(header_buf) && !allascii(STR(header_buf))) { + state->smtputf8 |= SMTPUTF8_FLAG_HEADER; + /* Fix 20140713: request SMTPUTF8 support selectively. */ + if (state->flags & CLEANUP_FLAG_AUTOUTF8) + state->smtputf8 |= SMTPUTF8_FLAG_REQUESTED; + } + + /* + * Prepend a tab to continued header lines that went through the address + * rewriting machinery. See cleanup_fold_header(state) below for the form + * of such header lines. NB: This code destroys the header. We could try + * to avoid clobbering it, but we're not going to use the data any + * further. + * + * XXX We prefer to truncate a header at the last line boundary before the + * header size limit. If this would undershoot the limit by more than + * 10%, we truncate between line boundaries to avoid losing too much + * text. This "unkind cut" may result in syntax errors and may trigger + * warnings from down-stream MTAs. + * + * If Milter is enabled, pad a short header record with a dummy record so + * that a header record can safely be overwritten by a pointer record. + * This simplifies header modification enormously. + */ + for (line = start; line; line = next_line) { + next_line = split_at(line, '\n'); + line_len = next_line ? next_line - 1 - line : strlen(line); + if (line + line_len > start + var_header_limit) { + if (line - start > 0.9 * var_header_limit) /* nice cut */ + break; + start[var_header_limit] = 0; /* unkind cut */ + next_line = 0; + } + if (line == start) { + cleanup_out_string(state, REC_TYPE_NORM, line); + if ((state->milters || cleanup_milters) + && line_len < REC_TYPE_PTR_PAYL_SIZE) + rec_pad(state->dst, REC_TYPE_DTXT, + REC_TYPE_PTR_PAYL_SIZE - line_len); + } else if (IS_SPACE_TAB(*line)) { + cleanup_out_string(state, REC_TYPE_NORM, line); + } else { + cleanup_out_format(state, REC_TYPE_NORM, "\t%s", line); + } + } +} diff --git a/src/cleanup/cleanup_out_recipient.c b/src/cleanup/cleanup_out_recipient.c new file mode 100644 index 0000000..5e965fa --- /dev/null +++ b/src/cleanup/cleanup_out_recipient.c @@ -0,0 +1,266 @@ +/*++ +/* NAME +/* cleanup_out_recipient 3 +/* SUMMARY +/* envelope recipient output filter +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* void cleanup_out_recipient(state, dsn_orig_recipient, +/* dsn_notify, orig_recipient, +/* recipient) +/* CLEANUP_STATE *state; +/* const char *dsn_orig_recipient; +/* const char *dsn_notify; +/* const char *orig_recipient; +/* const char *recipient; +/* DESCRIPTION +/* This module implements an envelope recipient output filter. +/* +/* cleanup_out_recipient() performs virtual table expansion +/* and recipient duplicate filtering, and appends the +/* resulting recipients to the output stream. It also +/* generates DSN SUCCESS notifications. +/* +/* Arguments: +/* .IP state +/* Cleanup server state. +/* .IP dsn_orig_recipient +/* DSN original recipient information. +/* .IP dsn_notify +/* DSN notify flags. +/* .IP orig_recipient +/* Envelope recipient as received by Postfix. +/* .IP recipient +/* Envelope recipient as rewritten by Postfix. +/* CONFIGURATION +/* .ad +/* .fi +/* .IP enable_original_recipient +/* Enable orig_recipient support. +/* .IP local_duplicate_filter_limit +/* Upper bound to the size of the recipient duplicate filter. +/* Zero means no limit; this may cause the mail system to +/* become stuck. +/* .IP virtual_alias_maps +/* list of virtual address lookup tables. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* cleanup_trace_path */ +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +/* cleanup_trace_append - update trace logfile */ + +static void cleanup_trace_append(CLEANUP_STATE *state, RECIPIENT *rcpt, + DSN *dsn) +{ + MSG_STATS stats; + + if (cleanup_trace_path == 0) { + cleanup_trace_path = vstring_alloc(10); + mail_queue_path(cleanup_trace_path, MAIL_QUEUE_TRACE, + state->queue_id); + } + if (trace_append(BOUNCE_FLAG_CLEAN, state->queue_id, + CLEANUP_MSG_STATS(&stats, state), + rcpt, "none", dsn) != 0) { + msg_warn("%s: trace logfile update error", state->queue_id); + state->errs |= CLEANUP_STAT_WRITE; + } +} + +/* cleanup_verify_append - update verify daemon */ + +static void cleanup_verify_append(CLEANUP_STATE *state, RECIPIENT *rcpt, + DSN *dsn, int verify_status) +{ + MSG_STATS stats; + + if (verify_append(state->queue_id, CLEANUP_MSG_STATS(&stats, state), + rcpt, "none", dsn, verify_status) != 0) { + msg_warn("%s: verify service update error", state->queue_id); + state->errs |= CLEANUP_STAT_WRITE; + } +} + +/* cleanup_out_recipient - envelope recipient output filter */ + +void cleanup_out_recipient(CLEANUP_STATE *state, + const char *dsn_orcpt, + int dsn_notify, + const char *orcpt, + const char *recip) +{ + ARGV *argv; + char **cpp; + + /* + * XXX Not elegant, but eliminates complexity in the record reading loop. + */ + if (dsn_orcpt == 0) + dsn_orcpt = ""; + + /* + * Distinguish between different original recipient addresses that map + * onto the same mailbox. The recipient will use our original recipient + * message header to figure things out. + * + * Postfix 2.2 compatibility: when ignoring differences in Postfix original + * recipient information, also ignore differences in DSN attributes. We + * do, however, keep the DSN attributes of the recipient that survives + * duplicate elimination. + */ +#define STREQ(x, y) (strcmp((x), (y)) == 0) + + if ((state->flags & CLEANUP_FLAG_MAP_OK) == 0 + || cleanup_virt_alias_maps == 0) { + /* Matches been_here_drop{,_fixed}() calls cleanup_del_rcpt(). */ + if ((var_enable_orcpt ? + been_here(state->dups, "%s\n%d\n%s\n%s", + dsn_orcpt, dsn_notify, orcpt, recip) : + been_here_fixed(state->dups, recip)) == 0) { + if (dsn_notify) + cleanup_out_format(state, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_NOTIFY, dsn_notify); + if (*dsn_orcpt) + cleanup_out_format(state, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_DSN_ORCPT, dsn_orcpt); + cleanup_out_string(state, REC_TYPE_ORCP, orcpt); + cleanup_out_string(state, REC_TYPE_RCPT, recip); + state->rcpt_count++; + } + } + + /* + * XXX DSN. RFC 3461 gives us three options for multi-recipient aliases + * (we're treating single recipient aliases as a special case of + * multi-recipient aliases, one argument being that it is none of the + * sender's business). + * + * (a) Don't propagate ENVID, NOTIFY, RET, or ORCPT. If NOTIFY specified + * SUCCESS, send a "relayed" DSN. + * + * (b) Propagate ENVID, (NOTIFY minus SUCCESS), RET, and ORCPT. If NOTIFY + * specified SUCCESS, send an "expanded" DSN. + * + * (c) Propagate ENVID, NOTIFY, RET, and ORCPT to one recipient only. Send + * no DSN. + * + * In all three cases we are modifying at least one NOTIFY value. Either we + * have to record explicit dsn_notify records, or we must not allow the + * use of a per-message non-default NOTIFY value that applies to all + * recipient records. + * + * Alternatives (a) and (c) require that we store explicit per-recipient RET + * and ENVID records, at least for the recipients that are excluded from + * RET and ENVID propagation. This means storing explicit ENVID records + * to indicate that the information does not exist. All this makes + * alternative (b) more and more attractive. It is no surprise that we + * use (b) here and in the local delivery agent. + * + * In order to generate a SUCCESS notification from the cleanup server we + * have to write the trace logfile record now. We're NOT going to flush + * the trace file from the cleanup server; if we need to write bounce + * logfile records, and the bounce service fails, we must be able to + * cancel the entire cleanup request including any success or failure + * notifications. The queue manager will flush the trace (and bounce) + * logfile, possibly after it has generated its own success or failure + * notification records. + * + * Postfix 2.2 compatibility: when ignoring differences in Postfix original + * recipient information, also ignore differences in DSN attributes. We + * do, however, keep the DSN attributes of the recipient that survives + * duplicate elimination. + * + * In the case of a verify(8) request for a one-to-many alias, declare the + * alias address as "deliverable". Do not verify the individual addresses + * in the expansion because that results in multiple verify(8) updates + * for one verify(8) request. + * + * Multiple verify(8) updates for one verify(8) request would overwrite + * each other's status, and if the last status update is "undeliverable", + * then the whole alias is flagged as undeliverable. + */ + else { + RECIPIENT rcpt; + DSN dsn; + + argv = cleanup_map1n_internal(state, recip, cleanup_virt_alias_maps, + cleanup_ext_prop_mask & EXT_PROP_VIRTUAL); + if (argv->argc > 1 && (state->tflags & DEL_REQ_FLAG_MTA_VRFY)) { + (void) DSN_SIMPLE(&dsn, "2.0.0", "aliased to multiple recipients"); + dsn.action = "deliverable"; + RECIPIENT_ASSIGN(&rcpt, 0, dsn_orcpt, dsn_notify, orcpt, recip); + cleanup_verify_append(state, &rcpt, &dsn, DEL_RCPT_STAT_OK); + argv_free(argv); + return; + } + if ((dsn_notify & DSN_NOTIFY_SUCCESS) + && (argv->argc > 1 || strcmp(recip, argv->argv[0]) != 0)) { + (void) DSN_SIMPLE(&dsn, "2.0.0", "alias expanded"); + dsn.action = "expanded"; + RECIPIENT_ASSIGN(&rcpt, 0, dsn_orcpt, dsn_notify, orcpt, recip); + cleanup_trace_append(state, &rcpt, &dsn); + dsn_notify = (dsn_notify == DSN_NOTIFY_SUCCESS ? DSN_NOTIFY_NEVER : + dsn_notify & ~DSN_NOTIFY_SUCCESS); + } + for (cpp = argv->argv; *cpp; cpp++) { + if ((var_enable_orcpt ? + been_here(state->dups, "%s\n%d\n%s\n%s", + dsn_orcpt, dsn_notify, orcpt, *cpp) : + been_here_fixed(state->dups, *cpp)) == 0) { + if (dsn_notify) + cleanup_out_format(state, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_NOTIFY, dsn_notify); + if (*dsn_orcpt) + cleanup_out_format(state, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_DSN_ORCPT, dsn_orcpt); + cleanup_out_string(state, REC_TYPE_ORCP, orcpt); + cleanup_out_string(state, REC_TYPE_RCPT, *cpp); + state->rcpt_count++; + } + } + argv_free(argv); + } +} diff --git a/src/cleanup/cleanup_region.c b/src/cleanup/cleanup_region.c new file mode 100644 index 0000000..b2acbee --- /dev/null +++ b/src/cleanup/cleanup_region.c @@ -0,0 +1,232 @@ +/*++ +/* NAME +/* cleanup_region 3 +/* SUMMARY +/* queue file region manager +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* void cleanup_region_init(state) +/* CLEANUP_STATE *state; +/* +/* CLEANUP_REGION *cleanup_region_open(state, space_needed) +/* CLEANUP_STATE *state; +/* ssize_t space_needed; +/* +/* int cleanup_region_close(state, rp) +/* CLEANUP_STATE *state; +/* CLEANUP_REGION *rp; +/* +/* CLEANUP_REGION *cleanup_region_return(state, rp) +/* CLEANUP_STATE *state; +/* CLEANUP_REGION *rp; +/* +/* void cleanup_region_done(state) +/* CLEANUP_STATE *state; +/* DESCRIPTION +/* This module maintains queue file regions. Regions are created +/* on-the-fly and can be reused multiple times. Each region +/* structure consists of a file offset, a length (0 for an +/* open-ended region at the end of the file), a write offset +/* (maintained by the caller), and list linkage. Region +/* boundaries are not enforced by this module. It is up to the +/* caller to ensure that they stay within bounds. +/* +/* cleanup_region_init() performs mandatory initialization and +/* overlays an initial region structure over an already existing +/* queue file. This function must not be called before the +/* queue file is complete. +/* +/* cleanup_region_open() opens an existing region or creates +/* a new region that can accommodate at least the specified +/* amount of space. A new region is an open-ended region at +/* the end of the file; it must be closed (see next) before +/* unrelated data can be appended to the same file. +/* +/* cleanup_region_close() indicates that a region will not be +/* updated further. With an open-ended region, the region's +/* end is frozen just before the caller-maintained write offset. +/* With a close-ended region, unused space (beginning at the +/* caller-maintained write offset) may be returned to the free +/* pool. +/* +/* cleanup_region_return() returns a list of regions to the +/* free pool, and returns a null pointer. To avoid fragmentation, +/* adjacent free regions may be coalesced together. +/* +/* cleanup_region_done() destroys all in-memory information +/* that was allocated for administering queue file regions. +/* +/* Arguments: +/* .IP state +/* Queue file and message processing state. This state is +/* updated as records are processed and as errors happen. +/* .IP space_needed +/* The minimum region size needed. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include + +/* cleanup_region_alloc - create queue file region */ + +static CLEANUP_REGION *cleanup_region_alloc(off_t start, off_t len) +{ + CLEANUP_REGION *rp; + + rp = (CLEANUP_REGION *) mymalloc(sizeof(*rp)); + rp->write_offs = rp->start = start; + rp->len = len; + rp->next = 0; + + return (rp); +} + +/* cleanup_region_free - destroy region list */ + +static CLEANUP_REGION *cleanup_region_free(CLEANUP_REGION *regions) +{ + CLEANUP_REGION *rp; + CLEANUP_REGION *next; + + for (rp = regions; rp != 0; rp = next) { + next = rp->next; + myfree((void *) rp); + } + return (0); +} + +/* cleanup_region_init - create initial region overlay */ + +void cleanup_region_init(CLEANUP_STATE *state) +{ + const char *myname = "cleanup_region_init"; + + /* + * Sanity check. + */ + if (state->free_regions != 0 || state->body_regions != 0) + msg_panic("%s: repeated call", myname); + + /* + * Craft the first regions on the fly, from circumstantial evidence. + */ + state->body_regions = + cleanup_region_alloc(state->append_hdr_pt_target, + state->xtra_offset - state->append_hdr_pt_target); + if (msg_verbose) + msg_info("%s: body start %ld len %ld", + myname, (long) state->body_regions->start, (long) state->body_regions->len); +} + +/* cleanup_region_open - open existing region or create new region */ + +CLEANUP_REGION *cleanup_region_open(CLEANUP_STATE *state, ssize_t len) +{ + const char *myname = "cleanup_region_open"; + CLEANUP_REGION **rpp; + CLEANUP_REGION *rp; + struct stat st; + + /* + * Find the first region that is large enough, or create a new region. + */ + for (rpp = &state->free_regions; /* see below */ ; rpp = &(rp->next)) { + + /* + * Create an open-ended region at the end of the queue file. We + * freeze the region size after we stop writing to it. XXX Assume + * that fstat() returns a file size that is never less than the file + * append offset. It is not a problem if fstat() returns a larger + * result; we would just waste some space. + */ + if ((rp = *rpp) == 0) { + if (fstat(vstream_fileno(state->dst), &st) < 0) + msg_fatal("%s: fstat file %s: %m", myname, cleanup_path); + rp = cleanup_region_alloc(st.st_size, 0); + break; + } + + /* + * Reuse an existing region. + */ + if (rp->len >= len) { + (*rpp) = rp->next; + rp->next = 0; + rp->write_offs = rp->start; + break; + } + + /* + * Skip a too small region. + */ + if (msg_verbose) + msg_info("%s: skip start %ld len %ld < %ld", + myname, (long) rp->start, (long) rp->len, (long) len); + } + if (msg_verbose) + msg_info("%s: done start %ld len %ld", + myname, (long) rp->start, (long) rp->len); + return (rp); +} + +/* cleanup_region_close - freeze queue file region size */ + +void cleanup_region_close(CLEANUP_STATE *unused_state, CLEANUP_REGION *rp) +{ + const char *myname = "cleanup_region_close"; + + /* + * If this region is still open ended, freeze the size. If this region is + * closed, some future version of this routine may shrink the size and + * return the unused portion to the free pool. + */ + if (rp->len == 0) + rp->len = rp->write_offs - rp->start; + if (msg_verbose) + msg_info("%s: freeze start %ld len %ld", + myname, (long) rp->start, (long) rp->len); +} + +/* cleanup_region_return - return region list to free pool */ + +CLEANUP_REGION *cleanup_region_return(CLEANUP_STATE *state, CLEANUP_REGION *rp) +{ + CLEANUP_REGION **rpp; + + for (rpp = &state->free_regions; (*rpp) != 0; rpp = &(*rpp)->next) + /* void */ ; + *rpp = rp; + return (0); +} + +/* cleanup_region_done - destroy region metadata */ + +void cleanup_region_done(CLEANUP_STATE *state) +{ + if (state->free_regions != 0) + state->free_regions = cleanup_region_free(state->free_regions); + if (state->body_regions != 0) + state->body_regions = cleanup_region_free(state->body_regions); +} diff --git a/src/cleanup/cleanup_rewrite.c b/src/cleanup/cleanup_rewrite.c new file mode 100644 index 0000000..3c81e7b --- /dev/null +++ b/src/cleanup/cleanup_rewrite.c @@ -0,0 +1,123 @@ +/*++ +/* NAME +/* cleanup_rewrite 3 +/* SUMMARY +/* address canonicalization +/* SYNOPSIS +/* #include +/* +/* int cleanup_rewrite_external(context_name, result, addr) +/* const char *context; +/* VSTRING *result; +/* const char *addr; +/* +/* int cleanup_rewrite_internal(context_name, result, addr) +/* const char *context; +/* VSTRING *result; +/* const char *addr; +/* +/* int cleanup_rewrite_tree(context_name, tree) +/* const char *context; +/* TOK822 *tree; +/* DESCRIPTION +/* This module rewrites addresses to canonical form, adding missing +/* domains and stripping source routes etc., and performs +/* \fIcanonical\fR map lookups to map addresses to official form. +/* These functions return non-zero when the address was changed. +/* +/* cleanup_rewrite_init() performs one-time initialization. +/* +/* cleanup_rewrite_external() rewrites the external (quoted) string +/* form of an address. +/* +/* cleanup_rewrite_internal() is a wrapper around the +/* cleanup_rewrite_external() routine that transforms from +/* internal (quoted) string form to external form and back. +/* +/* cleanup_rewrite_tree() is a wrapper around the +/* cleanup_rewrite_external() routine that transforms from +/* internal parse tree form to external form and back. +/* +/* Arguments: +/* .IP context_name +/* The name of an address rewriting context that supplies +/* the equivalents of myorigin and mydomain. +/* .IP result +/* Result buffer. +/* .IP addr +/* Input buffer. +/* DIAGNOSTICS +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include "cleanup.h" + +#define STR vstring_str + +/* cleanup_rewrite_external - rewrite address external form */ + +int cleanup_rewrite_external(const char *context_name, VSTRING *result, + const char *addr) +{ + rewrite_clnt(context_name, addr, result); + return (strcmp(STR(result), addr) != 0); +} + +/* cleanup_rewrite_tree - rewrite address node */ + +int cleanup_rewrite_tree(const char *context_name, TOK822 *tree) +{ + VSTRING *dst = vstring_alloc(100); + VSTRING *src = vstring_alloc(100); + int did_rewrite; + + tok822_externalize(src, tree->head, TOK822_STR_DEFL); + did_rewrite = cleanup_rewrite_external(context_name, dst, STR(src)); + tok822_free_tree(tree->head); + tree->head = tok822_scan(STR(dst), &tree->tail); + vstring_free(dst); + vstring_free(src); + return (did_rewrite); +} + +/* cleanup_rewrite_internal - rewrite address internal form */ + +int cleanup_rewrite_internal(const char *context_name, + VSTRING *result, const char *addr) +{ + VSTRING *dst = vstring_alloc(100); + VSTRING *src = vstring_alloc(100); + int did_rewrite; + + quote_822_local(src, addr); + did_rewrite = cleanup_rewrite_external(context_name, dst, STR(src)); + unquote_822_local(result, STR(dst)); + vstring_free(dst); + vstring_free(src); + return (did_rewrite); +} diff --git a/src/cleanup/cleanup_state.c b/src/cleanup/cleanup_state.c new file mode 100644 index 0000000..99adf84 --- /dev/null +++ b/src/cleanup/cleanup_state.c @@ -0,0 +1,200 @@ +/*++ +/* NAME +/* cleanup_state 3 +/* SUMMARY +/* per-message state variables +/* SYNOPSIS +/* #include "cleanup.h" +/* +/* CLEANUP_STATE *cleanup_state_alloc(src) +/* VSTREAM *src; +/* +/* void cleanup_state_free(state) +/* CLEANUP_STATE *state; +/* DESCRIPTION +/* This module maintains about two dozen state variables +/* that are used by many routines in the course of processing one +/* message. +/* +/* cleanup_state_alloc() initializes the per-message state variables. +/* +/* cleanup_state_free() cleans up. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Milter library. */ + +#include + +/* Application-specific. */ + +#include "cleanup.h" + +/* cleanup_state_alloc - initialize global state */ + +CLEANUP_STATE *cleanup_state_alloc(VSTREAM *src) +{ + CLEANUP_STATE *state = (CLEANUP_STATE *) mymalloc(sizeof(*state)); + + state->attr_buf = vstring_alloc(10); + state->temp1 = vstring_alloc(10); + state->temp2 = vstring_alloc(10); + if (cleanup_strip_chars) + state->stripped_buf = vstring_alloc(10); + state->src = src; + state->dst = 0; + state->handle = 0; + state->queue_name = 0; + state->queue_id = 0; + state->arrival_time.tv_sec = state->arrival_time.tv_usec = 0; + state->fullname = 0; + state->sender = 0; + state->recip = 0; + state->orig_rcpt = 0; + state->return_receipt = 0; + state->errors_to = 0; + state->auto_hdrs = argv_alloc(1); + state->hbc_rcpt = 0; + state->flags = 0; + state->tflags = 0; + state->qmgr_opts = 0; + state->errs = 0; + state->err_mask = 0; + state->headers_seen = 0; + state->hop_count = 0; + state->resent = ""; + state->dups = been_here_init(var_dup_filter_limit, BH_FLAG_FOLD); + state->action = cleanup_envelope; + state->data_offset = -1; + state->body_offset = -1; + state->xtra_offset = -1; + state->cont_length = 0; + state->sender_pt_offset = -1; + state->sender_pt_target = -1; + state->append_rcpt_pt_offset = -1; + state->append_rcpt_pt_target = -1; + state->append_hdr_pt_offset = -1; + state->append_hdr_pt_target = -1; + state->append_meta_pt_offset = -1; + state->append_meta_pt_target = -1; + state->rcpt_count = 0; + state->reason = 0; + state->smtp_reply = 0; + state->attr = nvtable_create(10); + nvtable_update(state->attr, MAIL_ATTR_LOG_ORIGIN, MAIL_ATTR_ORG_LOCAL); + state->mime_state = 0; + state->mime_errs = 0; + state->hdr_rewrite_context = MAIL_ATTR_RWR_LOCAL; + state->filter = 0; + state->redirect = 0; + state->dsn_envid = 0; + state->dsn_ret = 0; + state->dsn_notify = 0; + state->dsn_orcpt = 0; + state->verp_delims = 0; + state->milters = 0; + state->client_name = 0; + state->reverse_name = 0; + state->client_addr = 0; + state->client_af = 0; + state->client_port = 0; + state->server_addr = 0; + state->server_port = 0; + state->milter_ext_from = 0; + state->milter_ext_rcpt = 0; + state->milter_err_text = 0; + state->milter_dsn_buf = 0; + state->free_regions = state->body_regions = state->curr_body_region = 0; + state->smtputf8 = 0; + return (state); +} + +/* cleanup_state_free - destroy global state */ + +void cleanup_state_free(CLEANUP_STATE *state) +{ + vstring_free(state->attr_buf); + vstring_free(state->temp1); + vstring_free(state->temp2); + if (cleanup_strip_chars) + vstring_free(state->stripped_buf); + if (state->fullname) + myfree(state->fullname); + if (state->sender) + myfree(state->sender); + if (state->recip) + myfree(state->recip); + if (state->orig_rcpt) + myfree(state->orig_rcpt); + if (state->return_receipt) + myfree(state->return_receipt); + if (state->errors_to) + myfree(state->errors_to); + argv_free(state->auto_hdrs); + if (state->hbc_rcpt) + argv_free(state->hbc_rcpt); + if (state->queue_name) + myfree(state->queue_name); + if (state->queue_id) + myfree(state->queue_id); + been_here_free(state->dups); + if (state->reason) + myfree(state->reason); + if (state->smtp_reply) + myfree(state->smtp_reply); + nvtable_free(state->attr); + if (state->mime_state) + mime_state_free(state->mime_state); + if (state->filter) + myfree(state->filter); + if (state->redirect) + myfree(state->redirect); + if (state->dsn_envid) + myfree(state->dsn_envid); + if (state->dsn_orcpt) + myfree(state->dsn_orcpt); + if (state->verp_delims) + myfree(state->verp_delims); + if (state->milters) + milter_free(state->milters); + if (state->milter_ext_from) + vstring_free(state->milter_ext_from); + if (state->milter_ext_rcpt) + vstring_free(state->milter_ext_rcpt); + if (state->milter_err_text) + vstring_free(state->milter_err_text); + if (state->milter_dsn_buf) + vstring_free(state->milter_dsn_buf); + cleanup_region_done(state); + myfree((void *) state); +} diff --git a/src/cleanup/loremipsum b/src/cleanup/loremipsum new file mode 100644 index 0000000..774f86d --- /dev/null +++ b/src/cleanup/loremipsum @@ -0,0 +1,28 @@ +Sed ut perspiciatis unde omnis iste natus error sit voluptatem +accusantium doloremque laudantium, totam rem aperiam, eaque ipsa +quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit +aspernatur aut odit aut fugit, sed quia consequuntur magni dolores +eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam +est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci +velit, sed quia non numquam eius modi tempora incidunt ut labore +et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima +veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, +nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure +reprehenderit qui in ea voluptate velit esse quam nihil molestiae +consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla +pariatur? + +At vero eos et accusamus et iusto odio dignissimos ducimus qui +blanditiis praesentium voluptatum deleniti atque corrupti quos +dolores et quas molestias excepturi sint occaecati cupiditate non +provident, similique sunt in culpa qui officia deserunt mollitia +animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis +est et expedita distinctio. Nam libero tempore, cum soluta nobis +est eligendi optio cumque nihil impedit quo minus id quod maxime +placeat facere possimus, omnis voluptas assumenda est, omnis dolor +repellendus. Temporibus autem quibusdam et aut officiis debitis aut +rerum necessitatibus saepe eveniet ut et voluptates repudiandae +sint et molestiae non recusandae. Itaque earum rerum hic tenetur a +sapiente delectus, ut aut reiciendis voluptatibus maiores alias +consequatur aut perferendis doloribus asperiores repellat. diff --git a/src/cleanup/loremipsum2 b/src/cleanup/loremipsum2 new file mode 100644 index 0000000..93cfa67 --- /dev/null +++ b/src/cleanup/loremipsum2 @@ -0,0 +1,57 @@ +Sed ut perspiciatis unde omnis iste natus error sit voluptatem +accusantium doloremque laudantium, totam rem aperiam, eaque ipsa +quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit +aspernatur aut odit aut fugit, sed quia consequuntur magni dolores +eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam +est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci +velit, sed quia non numquam eius modi tempora incidunt ut labore +et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima +veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, +nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure +reprehenderit qui in ea voluptate velit esse quam nihil molestiae +consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla +pariatur? + +At vero eos et accusamus et iusto odio dignissimos ducimus qui +blanditiis praesentium voluptatum deleniti atque corrupti quos +dolores et quas molestias excepturi sint occaecati cupiditate non +provident, similique sunt in culpa qui officia deserunt mollitia +animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis +est et expedita distinctio. Nam libero tempore, cum soluta nobis +est eligendi optio cumque nihil impedit quo minus id quod maxime +placeat facere possimus, omnis voluptas assumenda est, omnis dolor +repellendus. Temporibus autem quibusdam et aut officiis debitis aut +rerum necessitatibus saepe eveniet ut et voluptates repudiandae +sint et molestiae non recusandae. Itaque earum rerum hic tenetur a +sapiente delectus, ut aut reiciendis voluptatibus maiores alias +consequatur aut perferendis doloribus asperiores repellat. + +Sed ut perspiciatis unde omnis iste natus error sit voluptatem +accusantium doloremque laudantium, totam rem aperiam, eaque ipsa +quae ab illo inventore veritatis et quasi architecto beatae vitae +dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit +aspernatur aut odit aut fugit, sed quia consequuntur magni dolores +eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam +est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci +velit, sed quia non numquam eius modi tempora incidunt ut labore +et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima +veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, +nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure +reprehenderit qui in ea voluptate velit esse quam nihil molestiae +consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla +pariatur? + +At vero eos et accusamus et iusto odio dignissimos ducimus qui +blanditiis praesentium voluptatum deleniti atque corrupti quos +dolores et quas molestias excepturi sint occaecati cupiditate non +provident, similique sunt in culpa qui officia deserunt mollitia +animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis +est et expedita distinctio. Nam libero tempore, cum soluta nobis +est eligendi optio cumque nihil impedit quo minus id quod maxime +placeat facere possimus, omnis voluptas assumenda est, omnis dolor +repellendus. Temporibus autem quibusdam et aut officiis debitis aut +rerum necessitatibus saepe eveniet ut et voluptates repudiandae +sint et molestiae non recusandae. Itaque earum rerum hic tenetur a +sapiente delectus, ut aut reiciendis voluptatibus maiores alias +consequatur aut perferendis doloribus asperiores repellat. diff --git a/src/cleanup/test-queue-file b/src/cleanup/test-queue-file new file mode 100644 index 0000000..8412ae3 Binary files /dev/null and b/src/cleanup/test-queue-file differ diff --git a/src/cleanup/test-queue-file10 b/src/cleanup/test-queue-file10 new file mode 100644 index 0000000..27a9ec7 Binary files /dev/null and b/src/cleanup/test-queue-file10 differ diff --git a/src/cleanup/test-queue-file11 b/src/cleanup/test-queue-file11 new file mode 100644 index 0000000..745dc90 Binary files /dev/null and b/src/cleanup/test-queue-file11 differ diff --git a/src/cleanup/test-queue-file12 b/src/cleanup/test-queue-file12 new file mode 100644 index 0000000..4979c1d Binary files /dev/null and b/src/cleanup/test-queue-file12 differ diff --git a/src/cleanup/test-queue-file13a b/src/cleanup/test-queue-file13a new file mode 100644 index 0000000..4979c1d Binary files /dev/null and b/src/cleanup/test-queue-file13a differ diff --git a/src/cleanup/test-queue-file13b b/src/cleanup/test-queue-file13b new file mode 100644 index 0000000..4979c1d Binary files /dev/null and b/src/cleanup/test-queue-file13b differ diff --git a/src/cleanup/test-queue-file13c b/src/cleanup/test-queue-file13c new file mode 100644 index 0000000..4979c1d Binary files /dev/null and b/src/cleanup/test-queue-file13c differ diff --git a/src/cleanup/test-queue-file13d b/src/cleanup/test-queue-file13d new file mode 100644 index 0000000..745dc90 Binary files /dev/null and b/src/cleanup/test-queue-file13d differ diff --git a/src/cleanup/test-queue-file13e b/src/cleanup/test-queue-file13e new file mode 100644 index 0000000..4979c1d Binary files /dev/null and b/src/cleanup/test-queue-file13e differ diff --git a/src/cleanup/test-queue-file13f b/src/cleanup/test-queue-file13f new file mode 100644 index 0000000..4979c1d Binary files /dev/null and b/src/cleanup/test-queue-file13f differ diff --git a/src/cleanup/test-queue-file13g b/src/cleanup/test-queue-file13g new file mode 100644 index 0000000..4979c1d Binary files /dev/null and b/src/cleanup/test-queue-file13g differ diff --git a/src/cleanup/test-queue-file13h b/src/cleanup/test-queue-file13h new file mode 100644 index 0000000..4979c1d Binary files /dev/null and b/src/cleanup/test-queue-file13h differ diff --git a/src/cleanup/test-queue-file13i b/src/cleanup/test-queue-file13i new file mode 100644 index 0000000..4979c1d Binary files /dev/null and b/src/cleanup/test-queue-file13i differ diff --git a/src/cleanup/test-queue-file14 b/src/cleanup/test-queue-file14 new file mode 100644 index 0000000..e9b388a Binary files /dev/null and b/src/cleanup/test-queue-file14 differ diff --git a/src/cleanup/test-queue-file15 b/src/cleanup/test-queue-file15 new file mode 100644 index 0000000..088bdfd Binary files /dev/null and b/src/cleanup/test-queue-file15 differ diff --git a/src/cleanup/test-queue-file16 b/src/cleanup/test-queue-file16 new file mode 100644 index 0000000..3f7c5f3 Binary files /dev/null and b/src/cleanup/test-queue-file16 differ diff --git a/src/cleanup/test-queue-file17 b/src/cleanup/test-queue-file17 new file mode 100644 index 0000000..3f7c5f3 Binary files /dev/null and b/src/cleanup/test-queue-file17 differ diff --git a/src/cleanup/test-queue-file2 b/src/cleanup/test-queue-file2 new file mode 100644 index 0000000..27a9ec7 Binary files /dev/null and b/src/cleanup/test-queue-file2 differ diff --git a/src/cleanup/test-queue-file3 b/src/cleanup/test-queue-file3 new file mode 100644 index 0000000..27a9ec7 Binary files /dev/null and b/src/cleanup/test-queue-file3 differ diff --git a/src/cleanup/test-queue-file4 b/src/cleanup/test-queue-file4 new file mode 100644 index 0000000..8412ae3 Binary files /dev/null and b/src/cleanup/test-queue-file4 differ diff --git a/src/cleanup/test-queue-file5 b/src/cleanup/test-queue-file5 new file mode 100644 index 0000000..d7adfb4 Binary files /dev/null and b/src/cleanup/test-queue-file5 differ diff --git a/src/cleanup/test-queue-file6 b/src/cleanup/test-queue-file6 new file mode 100644 index 0000000..27a9ec7 Binary files /dev/null and b/src/cleanup/test-queue-file6 differ diff --git a/src/cleanup/test-queue-file7 b/src/cleanup/test-queue-file7 new file mode 100644 index 0000000..27a9ec7 Binary files /dev/null and b/src/cleanup/test-queue-file7 differ diff --git a/src/cleanup/test-queue-file8 b/src/cleanup/test-queue-file8 new file mode 100644 index 0000000..27a9ec7 Binary files /dev/null and b/src/cleanup/test-queue-file8 differ diff --git a/src/cleanup/test-queue-file9 b/src/cleanup/test-queue-file9 new file mode 100644 index 0000000..27a9ec7 Binary files /dev/null and b/src/cleanup/test-queue-file9 differ diff --git a/src/discard/.indent.pro b/src/discard/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/discard/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/discard/.printfck b/src/discard/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/discard/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/discard/Makefile.in b/src/discard/Makefile.in new file mode 100644 index 0000000..f8c3b65 --- /dev/null +++ b/src/discard/Makefile.in @@ -0,0 +1,86 @@ +SHELL = /bin/sh +SRCS = discard.c +OBJS = discard.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = discard +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +discard.o: ../../include/attr.h +discard.o: ../../include/bounce.h +discard.o: ../../include/check_arg.h +discard.o: ../../include/deliver_completed.h +discard.o: ../../include/deliver_request.h +discard.o: ../../include/dsn.h +discard.o: ../../include/dsn_buf.h +discard.o: ../../include/dsn_util.h +discard.o: ../../include/flush_clnt.h +discard.o: ../../include/htable.h +discard.o: ../../include/mail_conf.h +discard.o: ../../include/mail_queue.h +discard.o: ../../include/mail_server.h +discard.o: ../../include/mail_version.h +discard.o: ../../include/msg.h +discard.o: ../../include/msg_stats.h +discard.o: ../../include/mymalloc.h +discard.o: ../../include/nvtable.h +discard.o: ../../include/recipient_list.h +discard.o: ../../include/sent.h +discard.o: ../../include/sys_defs.h +discard.o: ../../include/vbuf.h +discard.o: ../../include/vstream.h +discard.o: ../../include/vstring.h +discard.o: discard.c diff --git a/src/discard/discard.c b/src/discard/discard.c new file mode 100644 index 0000000..331f96f --- /dev/null +++ b/src/discard/discard.c @@ -0,0 +1,253 @@ +/*++ +/* NAME +/* discard 8 +/* SUMMARY +/* Postfix discard mail delivery agent +/* SYNOPSIS +/* \fBdiscard\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The Postfix \fBdiscard\fR(8) delivery agent processes +/* delivery requests from +/* the queue manager. Each request specifies a queue file, a sender +/* address, a next-hop destination that is treated as the reason for +/* discarding the mail, and recipient information. +/* The reason may be prefixed with an RFC 3463-compatible detail code. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* The \fBdiscard\fR(8) delivery agent pretends to deliver all recipients +/* in the delivery request, logs the "next-hop" destination +/* as the reason for discarding the mail, updates the +/* queue file, and either marks recipients as finished or informs the +/* queue manager that delivery should be tried again at a later time. +/* +/* Delivery status reports are sent to the \fBtrace\fR(8) +/* daemon as appropriate. +/* SECURITY +/* .ad +/* .fi +/* The \fBdiscard\fR(8) mailer is not security-sensitive. It does not talk +/* to the network, and can be run chrooted at fixed low privilege. +/* STANDARDS +/* RFC 3463 (Enhanced Status Codes) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces and of other trouble. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically as \fBdiscard\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBdouble_bounce_sender (double-bounce)\fR" +/* The sender address of postmaster notifications that are generated +/* by the mail system. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* qmgr(8), queue manager +/* bounce(8), delivery status reports +/* error(8), Postfix error delivery agent +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* This service was introduced with Postfix version 2.2. +/* AUTHOR(S) +/* Victor Duchovni +/* Morgan Stanley +/* +/* Based on code by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single server skeleton. */ + +#include + +/* deliver_message - deliver message with extreme prejudice */ + +static int deliver_message(DELIVER_REQUEST *request) +{ + const char *myname = "deliver_message"; + VSTREAM *src; + int result = 0; + int status; + RECIPIENT *rcpt; + int nrcpt; + DSN_SPLIT dp; + DSN dsn; + + if (msg_verbose) + msg_info("deliver_message: from %s", request->sender); + + /* + * Sanity checks. + */ + if (request->nexthop[0] == 0) + msg_fatal("empty nexthop hostname"); + if (request->rcpt_list.len <= 0) + msg_fatal("recipient count: %d", request->rcpt_list.len); + + /* + * Open the queue file. Opening the file can fail for a variety of + * reasons, such as the system running out of resources. Instead of + * throwing away mail, we're raising a fatal error which forces the mail + * system to back off, and retry later. + */ + src = mail_queue_open(request->queue_name, request->queue_id, + O_RDWR, 0); + if (src == 0) + msg_fatal("%s: open %s %s: %m", myname, + request->queue_name, request->queue_id); + if (msg_verbose) + msg_info("%s: file %s", myname, VSTREAM_PATH(src)); + + /* + * Discard all recipients. + */ +#define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS(request->flags) + + dsn_split(&dp, "2.0.0", request->nexthop); + (void) DSN_SIMPLE(&dsn, DSN_STATUS(dp.dsn), dp.text); + for (nrcpt = 0; nrcpt < request->rcpt_list.len; nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + status = sent(BOUNCE_FLAGS(request), request->queue_id, + &request->msg_stats, rcpt, "none", &dsn); + if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) + deliver_completed(src, rcpt->offset); + result |= status; + } + + /* + * Clean up. + */ + if (vstream_fclose(src)) + msg_warn("close %s %s: %m", request->queue_name, request->queue_id); + + return (result); +} + +/* discard_service - perform service for client */ + +static void discard_service(VSTREAM *client_stream, char *unused_service, char **argv) +{ + DELIVER_REQUEST *request; + int status; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * dedicated to the discard mailer. What we see below is a little + * protocol to (1) tell the queue manager that we are ready, (2) read a + * request from the queue manager, and (3) report the completion status + * of that request. All connection-management stuff is handled by the + * common code in single_server.c. + */ + if ((request = deliver_request_read(client_stream)) != 0) { + status = deliver_message(request); + deliver_request_done(client_stream, request, status); + } +} + +/* pre_init - pre-jail initialization */ + +static void pre_init(char *unused_name, char **unused_argv) +{ + flush_init(); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, discard_service, + CA_MAIL_SERVER_PRE_INIT(pre_init), + 0); +} diff --git a/src/dns/.indent.pro b/src/dns/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/dns/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/dns/.printfck b/src/dns/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/dns/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/dns/Makefile.in b/src/dns/Makefile.in new file mode 100644 index 0000000..795f9ba --- /dev/null +++ b/src/dns/Makefile.in @@ -0,0 +1,408 @@ +SHELL = /bin/sh +SRCS = dns_lookup.c dns_rr.c dns_strerror.c dns_strtype.c dns_rr_to_pa.c \ + dns_sa_to_rr.c dns_rr_eq_sa.c dns_rr_to_sa.c dns_strrecord.c \ + dns_rr_filter.c dns_str_resflags.c dns_sec.c +OBJS = dns_lookup.o dns_rr.o dns_strerror.o dns_strtype.o dns_rr_to_pa.o \ + dns_sa_to_rr.o dns_rr_eq_sa.o dns_rr_to_sa.o dns_strrecord.o \ + dns_rr_filter.o dns_str_resflags.o dns_sec.o +HDRS = dns.h +TESTSRC = test_dns_lookup.c test_alias_token.c +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +INCL = +LIB = lib$(LIB_PREFIX)dns$(LIB_SUFFIX) +TESTPROG= test_dns_lookup dns_rr_to_pa dns_rr_to_sa dns_sa_to_rr dns_rr_eq_sa +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) +LIB_DIR = ../../lib +INC_DIR = ../../include + +.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c + +all: $(LIB) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: test dns_rr_to_pa_test dns_rr_to_sa_test dns_sa_to_rr_test \ + dns_rr_eq_sa_test no-a-test no-aaaa-test no-mx-test \ + error-filter-test nullmx_test nxdomain_test mxonly_test \ + dnsbl_tests + +dnsbl_tests: \ + dnsbl_ttl_127.0.0.2_bind_plain_test \ + dnsbl_ttl_127.0.0.2_bind_ncache_test \ + dnsbl_ttl_127.0.0.2_priv_plain_test \ + dnsbl_ttl_127.0.0.2_priv_ncache_test \ + dnsbl_ttl_127.0.0.1_bind_plain_test \ + dnsbl_ttl_127.0.0.1_bind_ncache_test \ + dnsbl_ttl_127.0.0.1_priv_plain_test \ + dnsbl_ttl_127.0.0.1_priv_ncache_test + +DNSBL_NEXIST_REPLY_FIX = \ + sed -e 's/ [0-9][0-9]* IN SOA / TTL IN SOA /' \ + -e 's/len=[0-9][0-9]* /len=LEN /' \ + -e 's/nscount=[1-9][0-9]*/nscount=N/' \ + -e 's/ [0-9]* [0-9]* [0-9]* [0-9]* [0-9]*/ D D D D D/' + +DNSBL_EXIST_REPLY_FIX = \ + sed -e 's/ [0-9][0-9]* IN A / TTL IN A /' \ + -e 's/len=[0-9][0-9]* /len=LEN /' \ + -e 's/ancount=[1-9][0-9]*/ancount=N/' \ + -e 's/nscount=[1-9][0-9]*/nscount=N/' \ + -e 's/ [0-9]* [0-9]* [0-9]* [0-9]* [0-9]*/ D D D D D/' \ + -e 's/127.0.0.[0-9]*$$/127.0.0.D/' \ + | uniq + +root_tests: + +$(LIB): $(OBJS) + $(AR) $(ARFL) $(LIB) $? + $(RANLIB) $(LIB) + $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS) + +$(LIB_DIR)/$(LIB): $(LIB) + cp $(LIB) $(LIB_DIR) + $(RANLIB) $(LIB_DIR)/$(LIB) + +update: $(LIB_DIR)/$(LIB) $(HDRS) + -for i in $(HDRS); \ + do \ + cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \ + done + cd $(INC_DIR); chmod 644 $(HDRS) + +test_dns_lookup: test_dns_lookup.c all $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +dns_rr_to_pa: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +dns_rr_to_sa: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +dns_sa_to_rr: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +dns_rr_eq_sa: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +dns_rr_to_pa_test: dns_rr_to_pa dns_rr_to_pa.in dns_rr_to_pa.ref + $(SHLIB_ENV) ./dns_rr_to_pa `cat dns_rr_to_pa.in` >dns_rr_to_pa.tmp + diff dns_rr_to_pa.ref dns_rr_to_pa.tmp + rm -f dns_rr_to_pa.tmp + +dns_rr_to_sa_test: dns_rr_to_sa dns_rr_to_sa.in dns_rr_to_sa.ref + $(SHLIB_ENV) ./dns_rr_to_sa `cat dns_rr_to_sa.in` >dns_rr_to_sa.tmp + diff dns_rr_to_sa.ref dns_rr_to_sa.tmp + rm -f dns_rr_to_sa.tmp + +dns_sa_to_rr_test: dns_sa_to_rr dns_sa_to_rr.in dns_sa_to_rr.ref + $(SHLIB_ENV) ./dns_sa_to_rr `cat dns_sa_to_rr.in` >dns_sa_to_rr.tmp + diff dns_sa_to_rr.ref dns_sa_to_rr.tmp + rm -f dns_sa_to_rr.tmp + +dns_rr_eq_sa_test: dns_rr_eq_sa dns_rr_eq_sa.in dns_rr_eq_sa.ref + $(SHLIB_ENV) ./dns_rr_eq_sa `cat dns_rr_eq_sa.in` >dns_rr_eq_sa.tmp + diff dns_rr_eq_sa.ref dns_rr_eq_sa.tmp + rm -f dns_rr_eq_sa.tmp + +no-a-test: no-a.reg test_dns_lookup no-a.ref + $(SHLIB_ENV) ./test_dns_lookup -f regexp:no-a.reg a,aaaa spike.porcupine.org >test_dns_lookup.tmp 2>&1 + diff no-a.ref test_dns_lookup.tmp + rm -f test_dns_lookup.tmp + +no-aaaa-test: no-aaaa.reg test_dns_lookup no-aaaa.ref + $(SHLIB_ENV) ./test_dns_lookup -f regexp:no-aaaa.reg a,aaaa spike.porcupine.org >test_dns_lookup.tmp 2>&1 + diff no-aaaa.ref test_dns_lookup.tmp + rm -f test_dns_lookup.tmp + +no-mx-test: no-mx.reg test_dns_lookup no-mx.ref + set -e; $(SHLIB_ENV) ./test_dns_lookup -f regexp:no-mx.reg mx porcupine.org 2>&1 | sort >test_dns_lookup.tmp || true + diff no-mx.ref test_dns_lookup.tmp + rm -f test_dns_lookup.tmp + +error-filter-test: error.reg test_dns_lookup error.ref + set -e; $(SHLIB_ENV) ./test_dns_lookup -f regexp:error.reg a,aaaa spike.porcupine.org >test_dns_lookup.tmp 2>&1 || true + diff error.ref test_dns_lookup.tmp + rm -f test_dns_lookup.tmp + +nullmx_test: test_dns_lookup nullmx_test.ref + (set -e; \ + $(SHLIB_ENV) ./test_dns_lookup mx,a nullmx.porcupine.org; \ + ) >nullmx_test.tmp 2>&1 || exit 0 + diff nullmx_test.ref nullmx_test.tmp + rm -f nullmx_test.tmp + +nxdomain_test: test_dns_lookup nxdomain_test.ref + (set -e; \ + $(SHLIB_ENV) ./test_dns_lookup mx,a nxdomain.porcupine.org; \ + ) >nxdomain_test.tmp 2>&1 || exit 0 + diff nxdomain_test.ref nxdomain_test.tmp + rm -f nxdomain_test.tmp + +mxonly_test: test_dns_lookup mxonly_test.ref + (set -e; \ + $(SHLIB_ENV) ./test_dns_lookup mx,a porcupine.org | sort; \ + ) >mxonly_test.tmp 2>&1 || exit 0 + diff mxonly_test.ref mxonly_test.tmp + rm -f mxonly_test.tmp + +# Non-existent record, libbind API, RFC 2308 disabled. + +dnsbl_ttl_127.0.0.1_bind_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_plain.ref + (set -e; \ + $(SHLIB_ENV) ./test_dns_lookup a 1.0.0.127.zen.spamhaus.org; \ + $(SHLIB_ENV) ./test_dns_lookup a 1.0.0.127.bl.spamcop.net; \ + ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_bind_plain.tmp + diff dnsbl_ttl_127.0.0.1_bind_plain.ref dnsbl_ttl_127.0.0.1_bind_plain.tmp + rm -f dnsbl_ttl_127.0.0.1_bind_plain.tmp + +# Non-existent record, private API, RFC 2308 disabled. + +dnsbl_ttl_127.0.0.1_priv_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_plain.ref + (set -e; \ + $(SHLIB_ENV) ./test_dns_lookup -p a 1.0.0.127.zen.spamhaus.org; \ + $(SHLIB_ENV) ./test_dns_lookup -p a 1.0.0.127.bl.spamcop.net; \ + ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_priv_plain.tmp + diff dnsbl_ttl_127.0.0.1_bind_plain.ref dnsbl_ttl_127.0.0.1_priv_plain.tmp + rm -f dnsbl_ttl_127.0.0.1_priv_plain.tmp + +# Non-existent record, libbind API, RFC 2308 enabled. + +dnsbl_ttl_127.0.0.1_bind_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_ncache.ref + (set -e; \ + $(SHLIB_ENV) ./test_dns_lookup -n a 1.0.0.127.zen.spamhaus.org; \ + $(SHLIB_ENV) ./test_dns_lookup -n a 1.0.0.127.bl.spamcop.net; \ + ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_bind_ncache.tmp + diff dnsbl_ttl_127.0.0.1_bind_ncache.ref dnsbl_ttl_127.0.0.1_bind_ncache.tmp + rm -f dnsbl_ttl_127.0.0.1_bind_ncache.tmp + +# Non-existent record, private API, RFC 2308 enabled. + +dnsbl_ttl_127.0.0.1_priv_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.1_bind_ncache.ref + (set -e; \ + $(SHLIB_ENV) ./test_dns_lookup -n -p a 1.0.0.127.zen.spamhaus.org; \ + $(SHLIB_ENV) ./test_dns_lookup -n -p a 1.0.0.127.bl.spamcop.net; \ + ) 2>&1 | $(DNSBL_NEXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.1_priv_ncache.tmp + diff dnsbl_ttl_127.0.0.1_bind_ncache.ref dnsbl_ttl_127.0.0.1_priv_ncache.tmp + rm -f dnsbl_ttl_127.0.0.1_priv_ncache.tmp + +# Existing record, libbind API, RFC 2308 disabled. + +dnsbl_ttl_127.0.0.2_bind_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref + (set -e; \ + $(SHLIB_ENV) ./test_dns_lookup a 2.0.0.127.zen.spamhaus.org; \ + $(SHLIB_ENV) ./test_dns_lookup a 2.0.0.127.bl.spamcop.net; \ + ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_bind_plain.tmp + diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_bind_plain.tmp + rm -f dnsbl_ttl_127.0.0.2_bind_plain.tmp + +# Existing record, private API, RFC 2308 disabled. + +dnsbl_ttl_127.0.0.2_priv_plain_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref + (set -e; \ + $(SHLIB_ENV) ./test_dns_lookup -p a 2.0.0.127.zen.spamhaus.org; \ + $(SHLIB_ENV) ./test_dns_lookup -p a 2.0.0.127.bl.spamcop.net; \ + ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_priv_plain.tmp + diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_priv_plain.tmp + rm -f dnsbl_ttl_127.0.0.2_priv_plain.tmp + +# Existing record, libbind API, RFC 2308 enabled. + +dnsbl_ttl_127.0.0.2_bind_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref + (set -e; \ + $(SHLIB_ENV) ./test_dns_lookup -n a 2.0.0.127.zen.spamhaus.org; \ + $(SHLIB_ENV) ./test_dns_lookup -n a 2.0.0.127.bl.spamcop.net; \ + ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_bind_ncache.tmp + diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_bind_ncache.tmp + rm -f dnsbl_ttl_127.0.0.2_bind_ncache.tmp + +# Existing record, private API, RFC 2308 enabled. + +dnsbl_ttl_127.0.0.2_priv_ncache_test: test_dns_lookup dnsbl_ttl_127.0.0.2_bind_plain.ref + (set -e; \ + $(SHLIB_ENV) ./test_dns_lookup -n -p a 2.0.0.127.zen.spamhaus.org; \ + $(SHLIB_ENV) ./test_dns_lookup -n -p a 2.0.0.127.bl.spamcop.net; \ + ) 2>&1 | $(DNSBL_EXIST_REPLY_FIX) >dnsbl_ttl_127.0.0.2_priv_ncache.tmp + diff dnsbl_ttl_127.0.0.2_bind_plain.ref dnsbl_ttl_127.0.0.2_priv_ncache.tmp + rm -f dnsbl_ttl_127.0.0.2_priv_ncache.tmp + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o $(LIB) *core $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +dns_lookup.o: ../../include/argv.h +dns_lookup.o: ../../include/check_arg.h +dns_lookup.o: ../../include/dict.h +dns_lookup.o: ../../include/mail_params.h +dns_lookup.o: ../../include/maps.h +dns_lookup.o: ../../include/msg.h +dns_lookup.o: ../../include/myaddrinfo.h +dns_lookup.o: ../../include/myflock.h +dns_lookup.o: ../../include/mymalloc.h +dns_lookup.o: ../../include/sock_addr.h +dns_lookup.o: ../../include/stringops.h +dns_lookup.o: ../../include/sys_defs.h +dns_lookup.o: ../../include/valid_hostname.h +dns_lookup.o: ../../include/vbuf.h +dns_lookup.o: ../../include/vstream.h +dns_lookup.o: ../../include/vstring.h +dns_lookup.o: dns.h +dns_lookup.o: dns_lookup.c +dns_rr.o: ../../include/check_arg.h +dns_rr.o: ../../include/msg.h +dns_rr.o: ../../include/myaddrinfo.h +dns_rr.o: ../../include/mymalloc.h +dns_rr.o: ../../include/myrand.h +dns_rr.o: ../../include/sock_addr.h +dns_rr.o: ../../include/sys_defs.h +dns_rr.o: ../../include/vbuf.h +dns_rr.o: ../../include/vstring.h +dns_rr.o: dns.h +dns_rr.o: dns_rr.c +dns_rr_eq_sa.o: ../../include/check_arg.h +dns_rr_eq_sa.o: ../../include/msg.h +dns_rr_eq_sa.o: ../../include/myaddrinfo.h +dns_rr_eq_sa.o: ../../include/sock_addr.h +dns_rr_eq_sa.o: ../../include/sys_defs.h +dns_rr_eq_sa.o: ../../include/vbuf.h +dns_rr_eq_sa.o: ../../include/vstring.h +dns_rr_eq_sa.o: dns.h +dns_rr_eq_sa.o: dns_rr_eq_sa.c +dns_rr_filter.o: ../../include/argv.h +dns_rr_filter.o: ../../include/check_arg.h +dns_rr_filter.o: ../../include/dict.h +dns_rr_filter.o: ../../include/maps.h +dns_rr_filter.o: ../../include/msg.h +dns_rr_filter.o: ../../include/myaddrinfo.h +dns_rr_filter.o: ../../include/myflock.h +dns_rr_filter.o: ../../include/sock_addr.h +dns_rr_filter.o: ../../include/sys_defs.h +dns_rr_filter.o: ../../include/vbuf.h +dns_rr_filter.o: ../../include/vstream.h +dns_rr_filter.o: ../../include/vstring.h +dns_rr_filter.o: dns.h +dns_rr_filter.o: dns_rr_filter.c +dns_rr_to_pa.o: ../../include/check_arg.h +dns_rr_to_pa.o: ../../include/msg.h +dns_rr_to_pa.o: ../../include/myaddrinfo.h +dns_rr_to_pa.o: ../../include/sock_addr.h +dns_rr_to_pa.o: ../../include/sys_defs.h +dns_rr_to_pa.o: ../../include/vbuf.h +dns_rr_to_pa.o: ../../include/vstring.h +dns_rr_to_pa.o: dns.h +dns_rr_to_pa.o: dns_rr_to_pa.c +dns_rr_to_sa.o: ../../include/check_arg.h +dns_rr_to_sa.o: ../../include/msg.h +dns_rr_to_sa.o: ../../include/myaddrinfo.h +dns_rr_to_sa.o: ../../include/sock_addr.h +dns_rr_to_sa.o: ../../include/sys_defs.h +dns_rr_to_sa.o: ../../include/vbuf.h +dns_rr_to_sa.o: ../../include/vstring.h +dns_rr_to_sa.o: dns.h +dns_rr_to_sa.o: dns_rr_to_sa.c +dns_sa_to_rr.o: ../../include/check_arg.h +dns_sa_to_rr.o: ../../include/msg.h +dns_sa_to_rr.o: ../../include/myaddrinfo.h +dns_sa_to_rr.o: ../../include/sock_addr.h +dns_sa_to_rr.o: ../../include/sys_defs.h +dns_sa_to_rr.o: ../../include/vbuf.h +dns_sa_to_rr.o: ../../include/vstring.h +dns_sa_to_rr.o: dns.h +dns_sa_to_rr.o: dns_sa_to_rr.c +dns_sec.o: ../../include/check_arg.h +dns_sec.o: ../../include/mail_params.h +dns_sec.o: ../../include/msg.h +dns_sec.o: ../../include/myaddrinfo.h +dns_sec.o: ../../include/mymalloc.h +dns_sec.o: ../../include/sock_addr.h +dns_sec.o: ../../include/split_at.h +dns_sec.o: ../../include/sys_defs.h +dns_sec.o: ../../include/vbuf.h +dns_sec.o: ../../include/vstring.h +dns_sec.o: dns.h +dns_sec.o: dns_sec.c +dns_str_resflags.o: ../../include/check_arg.h +dns_str_resflags.o: ../../include/myaddrinfo.h +dns_str_resflags.o: ../../include/name_mask.h +dns_str_resflags.o: ../../include/sock_addr.h +dns_str_resflags.o: ../../include/sys_defs.h +dns_str_resflags.o: ../../include/vbuf.h +dns_str_resflags.o: ../../include/vstring.h +dns_str_resflags.o: dns.h +dns_str_resflags.o: dns_str_resflags.c +dns_strerror.o: ../../include/check_arg.h +dns_strerror.o: ../../include/myaddrinfo.h +dns_strerror.o: ../../include/sock_addr.h +dns_strerror.o: ../../include/sys_defs.h +dns_strerror.o: ../../include/vbuf.h +dns_strerror.o: ../../include/vstring.h +dns_strerror.o: dns.h +dns_strerror.o: dns_strerror.c +dns_strrecord.o: ../../include/check_arg.h +dns_strrecord.o: ../../include/msg.h +dns_strrecord.o: ../../include/myaddrinfo.h +dns_strrecord.o: ../../include/sock_addr.h +dns_strrecord.o: ../../include/sys_defs.h +dns_strrecord.o: ../../include/vbuf.h +dns_strrecord.o: ../../include/vstring.h +dns_strrecord.o: dns.h +dns_strrecord.o: dns_strrecord.c +dns_strtype.o: ../../include/check_arg.h +dns_strtype.o: ../../include/myaddrinfo.h +dns_strtype.o: ../../include/sock_addr.h +dns_strtype.o: ../../include/sys_defs.h +dns_strtype.o: ../../include/vbuf.h +dns_strtype.o: ../../include/vstring.h +dns_strtype.o: dns.h +dns_strtype.o: dns_strtype.c +test_dns_lookup.o: ../../include/argv.h +test_dns_lookup.o: ../../include/check_arg.h +test_dns_lookup.o: ../../include/mail_params.h +test_dns_lookup.o: ../../include/msg.h +test_dns_lookup.o: ../../include/msg_vstream.h +test_dns_lookup.o: ../../include/myaddrinfo.h +test_dns_lookup.o: ../../include/mymalloc.h +test_dns_lookup.o: ../../include/sock_addr.h +test_dns_lookup.o: ../../include/sys_defs.h +test_dns_lookup.o: ../../include/vbuf.h +test_dns_lookup.o: ../../include/vstream.h +test_dns_lookup.o: ../../include/vstring.h +test_dns_lookup.o: dns.h +test_dns_lookup.o: test_dns_lookup.c diff --git a/src/dns/dns.h b/src/dns/dns.h new file mode 100644 index 0000000..5f53dbc --- /dev/null +++ b/src/dns/dns.h @@ -0,0 +1,357 @@ +#ifndef _DNS_H_INCLUDED_ +#define _DNS_H_INCLUDED_ + +/*++ +/* NAME +/* dns 3h +/* SUMMARY +/* domain name service lookup +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include +#ifdef RESOLVE_H_NEEDS_STDIO_H +#include +#endif +#ifdef RESOLVE_H_NEEDS_NAMESER8_COMPAT_H +#include +#endif +#ifdef RESOLVE_H_NEEDS_ARPA_NAMESER_COMPAT_H +#include +#endif +#include + + /* + * Name server compatibility. These undocumented macros appear in the file + * , but since they are undocumented we should not count on + * their presence, and so they are included here just in case. + */ +#ifndef GETSHORT + +#define GETSHORT(s, cp) { \ + unsigned char *t_cp = (u_char *)(cp); \ + (s) = ((unsigned)t_cp[0] << 8) \ + | ((unsigned)t_cp[1]) \ + ; \ + (cp) += 2; \ +} + +#define GETLONG(l, cp) { \ + unsigned char *t_cp = (u_char *)(cp); \ + (l) = ((unsigned)t_cp[0] << 24) \ + | ((unsigned)t_cp[1] << 16) \ + | ((unsigned)t_cp[2] << 8) \ + | ((unsigned)t_cp[3]) \ + ; \ + (cp) += 4; \ +} + +#endif + + /* + * Provide API compatibility for systems without res_nxxx() API. Also + * require calling dns_get_h_errno() instead of directly accessing the + * global h_errno variable. We should not count on that being updated. + */ +#if !defined(NO_RES_NCALLS) && defined(__RES) && (__RES >= 19991006) +#define USE_RES_NCALLS +#undef h_errno +#define h_errno use_dns_get_h_errno_instead_of_h_errno +#endif + +/* + * Disable DNSSEC at compile-time even if RES_USE_DNSSEC is available + */ +#ifdef NO_DNSSEC +#undef RES_USE_DNSSEC +#undef RES_TRUSTAD +#endif + + /* + * Compatibility with systems that lack RES_USE_DNSSEC and RES_USE_EDNS0 + */ +#ifndef RES_USE_DNSSEC +#define RES_USE_DNSSEC 0 +#endif +#ifndef RES_USE_EDNS0 +#define RES_USE_EDNS0 0 +#endif +#ifndef RES_TRUSTAD +#define RES_TRUSTAD 0 +#endif + + /*- + * TLSA: https://tools.ietf.org/html/rfc6698#section-7.1 + * RRSIG: http://tools.ietf.org/html/rfc4034#section-3 + * + * We don't request RRSIG, but we get it "for free" when we send the DO-bit. + */ +#ifndef T_TLSA +#define T_TLSA 52 +#endif +#ifndef T_RRSIG +#define T_RRSIG 46 /* Avoid unknown RR in logs */ +#endif +#ifndef T_DNAME +#define T_DNAME 39 /* [RFC6672] */ +#endif + + /* + * https://tools.ietf.org/html/rfc6698#section-7.2 + */ +#define DNS_TLSA_USAGE_CA_CONSTRAINT 0 +#define DNS_TLSA_USAGE_SERVICE_CERTIFICATE_CONSTRAINT 1 +#define DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION 2 +#define DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE 3 + + /* + * https://tools.ietf.org/html/rfc6698#section-7.3 + */ +#define DNS_TLSA_SELECTOR_FULL_CERTIFICATE 0 +#define DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO 1 + + /* + * https://tools.ietf.org/html/rfc6698#section-7.4 + */ +#define DNS_TLSA_MATCHING_TYPE_NO_HASH_USED 0 +#define DNS_TLSA_MATCHING_TYPE_SHA256 1 +#define DNS_TLSA_MATCHING_TYPE_SHA512 2 + + /* + * SunOS 4 needs this. + */ +#ifndef T_TXT +#define T_TXT 16 +#endif + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Structure for fixed resource record data. + */ +typedef struct DNS_FIXED { + unsigned short type; /* T_A, T_CNAME, etc. */ + unsigned short class; /* C_IN, etc. */ + unsigned int ttl; /* always */ + unsigned length; /* record length */ +} DNS_FIXED; + + /* + * Structure of a DNS resource record after expansion. The components are + * named after the things one can expect to find in a DNS resource record. + */ +typedef struct DNS_RR { + char *qname; /* query name, mystrdup()ed */ + char *rname; /* reply name, mystrdup()ed */ + unsigned short type; /* T_A, T_CNAME, etc. */ + unsigned short class; /* C_IN, etc. */ + unsigned int ttl; /* always */ + unsigned int dnssec_valid; /* DNSSEC validated */ + unsigned short pref; /* T_MX only */ + struct DNS_RR *next; /* linkage */ + size_t data_len; /* actual data size */ + char data[1]; /* actually a bunch of data */ +} DNS_RR; + + /* + * dns_strerror.c + */ +extern const char *dns_strerror(unsigned); + + /* + * dns_strtype.c + */ +extern const char *dns_strtype(unsigned); +extern unsigned dns_type(const char *); + + /* + * dns_strrecord.c + */ +extern char *dns_strrecord(VSTRING *, DNS_RR *); + + /* + * dns_rr.c + */ +extern DNS_RR *dns_rr_create(const char *, const char *, + ushort, ushort, + unsigned, unsigned, + const char *, size_t); +extern void dns_rr_free(DNS_RR *); +extern DNS_RR *dns_rr_copy(DNS_RR *); +extern DNS_RR *dns_rr_append(DNS_RR *, DNS_RR *); +extern DNS_RR *dns_rr_sort(DNS_RR *, int (*) (DNS_RR *, DNS_RR *)); +extern int dns_rr_compare_pref_ipv6(DNS_RR *, DNS_RR *); +extern int dns_rr_compare_pref_ipv4(DNS_RR *, DNS_RR *); +extern int dns_rr_compare_pref_any(DNS_RR *, DNS_RR *); +extern int dns_rr_compare_pref(DNS_RR *, DNS_RR *); +extern DNS_RR *dns_rr_shuffle(DNS_RR *); +extern DNS_RR *dns_rr_remove(DNS_RR *, DNS_RR *); + + /* + * dns_rr_to_pa.c + */ +extern const char *dns_rr_to_pa(DNS_RR *, MAI_HOSTADDR_STR *); + + /* + * dns_sa_to_rr.c + */ +extern DNS_RR *dns_sa_to_rr(const char *, unsigned, struct sockaddr *); + + /* + * dns_rr_to_sa.c + */ +extern int dns_rr_to_sa(DNS_RR *, unsigned, struct sockaddr *, SOCKADDR_SIZE *); + + /* + * dns_rr_eq_sa.c + */ +extern int dns_rr_eq_sa(DNS_RR *, struct sockaddr *); + +#ifdef HAS_IPV6 +#define DNS_RR_EQ_SA(rr, sa) \ + ((SOCK_ADDR_IN_FAMILY(sa) == AF_INET && (rr)->type == T_A \ + && SOCK_ADDR_IN_ADDR(sa).s_addr == IN_ADDR((rr)->data).s_addr) \ + || (SOCK_ADDR_IN_FAMILY(sa) == AF_INET6 && (rr)->type == T_AAAA \ + && memcmp((char *) &(SOCK_ADDR_IN6_ADDR(sa)), \ + (rr)->data, (rr)->data_len) == 0)) +#else +#define DNS_RR_EQ_SA(rr, sa) \ + (SOCK_ADDR_IN_FAMILY(sa) == AF_INET && (rr)->type == T_A \ + && SOCK_ADDR_IN_ADDR(sa).s_addr == IN_ADDR((rr)->data).s_addr) +#endif + + /* + * dns_lookup.c + */ +extern int dns_lookup_x(const char *, unsigned, unsigned, DNS_RR **, + VSTRING *, VSTRING *, int *, unsigned); +extern int dns_lookup_rl(const char *, unsigned, DNS_RR **, VSTRING *, + VSTRING *, int *, int,...); +extern int dns_lookup_rv(const char *, unsigned, DNS_RR **, VSTRING *, + VSTRING *, int *, int, unsigned *); +extern int dns_get_h_errno(void); + +#define dns_lookup(name, type, rflags, list, fqdn, why) \ + dns_lookup_x((name), (type), (rflags), (list), (fqdn), (why), (int *) 0, \ + (unsigned) 0) +#define dns_lookup_r(name, type, rflags, list, fqdn, why, rcode) \ + dns_lookup_x((name), (type), (rflags), (list), (fqdn), (why), (rcode), \ + (unsigned) 0) +#define dns_lookup_l(name, rflags, list, fqdn, why, lflags, ...) \ + dns_lookup_rl((name), (rflags), (list), (fqdn), (why), (int *) 0, \ + (lflags), __VA_ARGS__) +#define dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype) \ + dns_lookup_rv((name), (rflags), (list), (fqdn), (why), (int *) 0, \ + (lflags), (ltype)) + + /* + * The dns_lookup() rflag that requests DNSSEC validation. + */ +#define DNS_WANT_DNSSEC_VALIDATION(rflags) ((rflags) & RES_USE_DNSSEC) + + /* + * lflags. + */ +#define DNS_REQ_FLAG_STOP_OK (1<<0) +#define DNS_REQ_FLAG_STOP_INVAL (1<<1) +#define DNS_REQ_FLAG_STOP_NULLMX (1<<2) +#define DNS_REQ_FLAG_STOP_MX_POLICY (1<<3) +#define DNS_REQ_FLAG_NCACHE_TTL (1<<4) +#define DNS_REQ_FLAG_NONE (0) + + /* + * Status codes. Failures must have negative codes so they will not collide + * with valid counts of answer records etc. + * + * When a function queries multiple record types for one name, it issues one + * query for each query record type. Each query returns a (status, rcode, + * text). Only one of these (status, rcode, text) will be returned to the + * caller. The selection is based on the status code precedence. + * + * - Return DNS_OK (and the corresponding rcode) as long as any query returned + * DNS_OK. If this is changed, then code needs to be added to prevent memory + * leaks. + * + * - Return DNS_RETRY (and the corresponding rcode and text) instead of any + * hard negative result. + * + * - Return DNS_NOTFOUND (and the corresponding rcode and text) only when all + * queries returned DNS_NOTFOUND. + * + * DNS_POLICY ranks higher than DNS_RETRY because there was a DNS_OK result, + * but the reply filter dropped it. This is a very soft error. + * + * Below is the precedence order. The order between DNS_RETRY and DNS_NOTFOUND + * is arbitrary. + */ +#define DNS_RECURSE (-7) /* internal only: recursion needed */ +#define DNS_NOTFOUND (-6) /* query ok, data not found */ +#define DNS_NULLMX (-5) /* query ok, service unavailable */ +#define DNS_FAIL (-4) /* query failed, don't retry */ +#define DNS_INVAL (-3) /* query ok, malformed reply */ +#define DNS_RETRY (-2) /* query failed, try again */ +#define DNS_POLICY (-1) /* query ok, all records dropped */ +#define DNS_OK 0 /* query succeeded */ + + /* + * How long can a DNS name or single text value be? + */ +#define DNS_NAME_LEN 1024 + + /* + * dns_rr_filter.c. + */ +extern void dns_rr_filter_compile(const char *, const char *); + +#ifdef LIBDNS_INTERNAL +#include +extern MAPS *dns_rr_filter_maps; +extern int dns_rr_filter_execute(DNS_RR **); + +#endif + + /* + * dns_str_resflags.c + */ +const char *dns_str_resflags(unsigned long); + + /* + * dns_sec.c. + */ +#define DNS_SEC_FLAG_AVAILABLE (1<<0) /* got some DNSSEC validated reply */ +#define DNS_SEC_FLAG_DONT_PROBE (1<<1) /* probe already sent, or disabled */ + +#define DNS_SEC_STATS_SET(flags) (dns_sec_stats |= (flags)) +#define DNS_SEC_STATS_TEST(flags) (dns_sec_stats & (flags)) + +extern int dns_sec_stats; /* See DNS_SEC_FLAG_XXX above */ +extern void dns_sec_probe(int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/dns/dns_lookup.c b/src/dns/dns_lookup.c new file mode 100644 index 0000000..615902d --- /dev/null +++ b/src/dns/dns_lookup.c @@ -0,0 +1,1272 @@ +/*++ +/* NAME +/* dns_lookup 3 +/* SUMMARY +/* domain name service lookup +/* SYNOPSIS +/* #include +/* +/* int dns_lookup(name, type, rflags, list, fqdn, why) +/* const char *name; +/* unsigned type; +/* unsigned rflags; +/* DNS_RR **list; +/* VSTRING *fqdn; +/* VSTRING *why; +/* +/* int dns_lookup_l(name, rflags, list, fqdn, why, lflags, ltype, ...) +/* const char *name; +/* unsigned rflags; +/* DNS_RR **list; +/* VSTRING *fqdn; +/* VSTRING *why; +/* int lflags; +/* unsigned ltype; +/* +/* int dns_lookup_v(name, rflags, list, fqdn, why, lflags, ltype) +/* const char *name; +/* unsigned rflags; +/* DNS_RR **list; +/* VSTRING *fqdn; +/* VSTRING *why; +/* int lflags; +/* unsigned *ltype; +/* +/* int dns_get_h_errno() +/* AUXILIARY FUNCTIONS +/* extern int var_dns_ncache_ttl_fix; +/* +/* int dns_lookup_r(name, type, rflags, list, fqdn, why, rcode) +/* const char *name; +/* unsigned type; +/* unsigned rflags; +/* DNS_RR **list; +/* VSTRING *fqdn; +/* VSTRING *why; +/* int *rcode; +/* +/* int dns_lookup_rl(name, rflags, list, fqdn, why, rcode, lflags, +/* ltype, ...) +/* const char *name; +/* unsigned rflags; +/* DNS_RR **list; +/* VSTRING *fqdn; +/* VSTRING *why; +/* int *rcode; +/* int lflags; +/* unsigned ltype; +/* +/* int dns_lookup_rv(name, rflags, list, fqdn, why, rcode, lflags, +/* ltype) +/* const char *name; +/* unsigned rflags; +/* DNS_RR **list; +/* VSTRING *fqdn; +/* VSTRING *why; +/* int *rcode; +/* int lflags; +/* unsigned *ltype; +/* +/* int dns_lookup_x(name, type, rflags, list, fqdn, why, rcode, lflags) +/* const char *name; +/* unsigned type; +/* unsigned rflags; +/* DNS_RR **list; +/* VSTRING *fqdn; +/* VSTRING *why; +/* int *rcode; +/* unsigned lflags; +/* DESCRIPTION +/* dns_lookup() looks up DNS resource records. When requested to +/* look up data other than type CNAME, it will follow a limited +/* number of CNAME indirections. All result names (including +/* null terminator) will fit a buffer of size DNS_NAME_LEN. +/* All name results are validated by \fIvalid_hostname\fR(); +/* an invalid name is reported as a DNS_INVAL result, while +/* malformed replies are reported as transient errors. +/* +/* dns_get_h_errno() returns the last error. This deprecates +/* usage of the global h_errno variable. We should not rely +/* on that being updated. +/* +/* dns_lookup_l() and dns_lookup_v() allow the user to specify +/* a list of resource types. +/* +/* dns_lookup_x, dns_lookup_r(), dns_lookup_rl() and dns_lookup_rv() +/* accept or return additional information. +/* +/* The var_dns_ncache_ttl_fix variable controls a workaround +/* for res_search(3) implementations that break the +/* DNS_REQ_FLAG_NCACHE_TTL feature. The workaround does not +/* support EDNS0 or DNSSEC, but it should be sufficient for +/* DNSBL/DNSWL lookups. +/* INPUTS +/* .ad +/* .fi +/* .IP name +/* The name to be looked up in the domain name system. +/* This name must pass the valid_hostname() test; it +/* must not be an IP address. +/* .IP type +/* The resource record type to be looked up (T_A, T_MX etc.). +/* .IP rflags +/* Resolver flags. These are a bitwise OR of: +/* .RS +/* .IP RES_DEBUG +/* Print debugging information. +/* .IP RES_DNSRCH +/* Search local domain and parent domains. +/* .IP RES_DEFNAMES +/* Append local domain to unqualified names. +/* .IP RES_USE_DNSSEC +/* Request DNSSEC validation. This flag is silently ignored +/* when the system stub resolver API, resolver(3), does not +/* implement DNSSEC. +/* Automatically turns on the RES_TRUSTAD flag on systems that +/* support this flag (this behavior will be more configurable +/* in a later release). +/* .RE +/* .IP lflags +/* Flags that control the operation of the dns_lookup*() +/* functions. DNS_REQ_FLAG_NONE requests no special processing. +/* Otherwise, specify one or more of the following: +/* .RS +/* .IP DNS_REQ_FLAG_STOP_INVAL +/* This flag is used by dns_lookup_l() and dns_lookup_v(). +/* Invoke dns_lookup() for the resource types in the order as +/* specified, and return when dns_lookup() returns DNS_INVAL. +/* .IP DNS_REQ_FLAG_STOP_NULLMX +/* This flag is used by dns_lookup_l() and dns_lookup_v(). +/* Invoke dns_lookup() for the resource types in the order as +/* specified, and return when dns_lookup() returns DNS_NULLMX. +/* .IP DNS_REQ_FLAG_STOP_MX_POLICY +/* This flag is used by dns_lookup_l() and dns_lookup_v(). +/* Invoke dns_lookup() for the resource types in the order as +/* specified, and return when dns_lookup() returns DNS_POLICY +/* for an MX query. +/* .IP DNS_REQ_FLAG_STOP_OK +/* This flag is used by dns_lookup_l() and dns_lookup_v(). +/* Invoke dns_lookup() for the resource types in the order as +/* specified, and return when dns_lookup() returns DNS_OK. +/* .IP DNS_REQ_FLAG_NCACHE_TTL +/* When the lookup result status is DNS_NOTFOUND, return the +/* SOA record(s) from the authority section in the reply, if +/* available. The per-record reply TTL specifies how long the +/* DNS_NOTFOUND answer is valid. The caller should pass the +/* record(s) to dns_rr_free(). +/* Logs a warning if the RES_DNSRCH or RES_DEFNAMES resolver +/* flags are set, and disables those flags. +/* .RE +/* .IP ltype +/* The resource record types to be looked up. In the case of +/* dns_lookup_l(), this is a null-terminated argument list. +/* In the case of dns_lookup_v(), this is a null-terminated +/* integer array. +/* OUTPUTS +/* .ad +/* .fi +/* .IP list +/* A null pointer, or a pointer to a variable that receives a +/* list of requested resource records. +/* .IP fqdn +/* A null pointer, or storage for the fully-qualified domain +/* name found for \fIname\fR. +/* .IP why +/* A null pointer, or storage for the reason for failure. +/* .IP rcode +/* Pointer to storage for the reply RCODE value. This gives +/* more detailed information than DNS_FAIL, DNS_RETRY, etc. +/* DIAGNOSTICS +/* If DNSSEC validation is requested but the response is not +/* DNSSEC validated, dns_lookup() will send a one-time probe +/* query as configured with the \fBdnssec_probe\fR configuration +/* parameter, and will log a warning when the probe response +/* was not DNSSEC validated. +/* .PP +/* dns_lookup() returns one of the following codes and sets the +/* \fIwhy\fR argument accordingly: +/* .IP DNS_OK +/* The DNS query succeeded. +/* .IP DNS_POLICY +/* The DNS query succeeded, but the answer did not pass the +/* policy filter. +/* .IP DNS_NOTFOUND +/* The DNS query succeeded; the requested information was not found. +/* .IP DNS_NULLMX +/* The DNS query succeeded; the requested service is unavailable. +/* This is returned when the list argument is not a null +/* pointer, and an MX lookup result contains a null server +/* name (so-called "nullmx" record). +/* .IP DNS_INVAL +/* The DNS query succeeded; the result failed the valid_hostname() test. +/* +/* NOTE: the valid_hostname() test is skipped for results that +/* the caller suppresses explicitly. For example, when the +/* caller requests MX record lookup but specifies a null +/* resource record list argument, no syntax check will be done +/* for MX server names. +/* .IP DNS_RETRY +/* The query failed, or the reply was malformed. +/* The problem is considered transient. +/* .IP DNS_FAIL +/* The query failed. +/* BUGS +/* dns_lookup() implements a subset of all possible resource types: +/* CNAME, MX, A, and some records with similar formatting requirements. +/* It is unwise to specify the T_ANY wildcard resource type. +/* +/* It takes a surprising amount of code to accomplish what appears +/* to be a simple task. Later versions of the mail system may implement +/* their own DNS client software. +/* SEE ALSO +/* dns_rr(3) resource record memory and list management +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* DNS library. */ + +#define LIBDNS_INTERNAL +#include "dns.h" + +/* Local stuff. */ + + /* + * Structure to keep track of things while decoding a name server reply. + */ +#define DEF_DNS_REPLY_SIZE 4096 /* in case we're using TCP */ +#define MAX_DNS_REPLY_SIZE 65536 /* in case we're using TCP */ +#define MAX_DNS_QUERY_SIZE 2048 /* XXX */ + +typedef struct DNS_REPLY { + unsigned char *buf; /* raw reply data */ + size_t buf_len; /* reply buffer length */ + int rcode; /* unfiltered reply code */ + int dnssec_ad; /* DNSSEC AD bit */ + int query_count; /* number of queries */ + int answer_count; /* number of answers */ + int auth_count; /* number of authority records */ + unsigned char *query_start; /* start of query data */ + unsigned char *answer_start; /* start of answer data */ + unsigned char *end; /* first byte past reply */ +} DNS_REPLY; + + /* + * Test/set primitives to determine if the reply buffer contains a server + * response. We use this when the caller requests DNS_REQ_FLAG_NCACHE_TTL, + * and the DNS server replies that the requested record does not exist. + */ +#define TEST_HAVE_DNS_REPLY_PACKET(r) ((r)->end > (r)->buf) +#define SET_HAVE_DNS_REPLY_PACKET(r, l) ((r)->end = (r)->buf + (l)) +#define SET_NO_DNS_REPLY_PACKET(r) ((r)->end = (r)->buf) + +#define INET_ADDR_LEN 4 /* XXX */ +#define INET6_ADDR_LEN 16 /* XXX */ + + /* + * Use the threadsafe resolver API if available, not because it is theadsafe, + * but because it has more functionality. + */ +#ifdef USE_RES_NCALLS +static struct __res_state dns_res_state; + +#define DNS_RES_NINIT res_ninit +#define DNS_RES_NMKQUERY res_nmkquery +#define DNS_RES_NSEARCH res_nsearch +#define DNS_RES_NSEND res_nsend +#define DNS_GET_H_ERRNO(statp) ((statp)->res_h_errno) + + /* + * Alias new resolver API calls to the legacy resolver API which stores + * resolver and error state in global variables. + */ +#else +#define dns_res_state _res +#define DNS_RES_NINIT(statp) res_init() +#define DNS_RES_NMKQUERY(statp, op, dname, class, type, data, datalen, \ + newrr, buf, buflen) \ + res_mkquery((op), (dname), (class), (type), (data), (datalen), \ + (newrr), (buf), (buflen)) +#define DNS_RES_NSEARCH(statp, dname, class, type, answer, anslen) \ + res_search((dname), (class), (type), (answer), (anslen)) +#define DNS_RES_NSEND(statp, msg, msglen, answer, anslen) \ + res_send((msg), (msglen), (answer), (anslen)) +#define DNS_GET_H_ERRNO(statp) (h_errno) +#endif + +#ifdef USE_SET_H_ERRNO +#define DNS_SET_H_ERRNO(statp, err) (set_h_errno(err)) +#else +#define DNS_SET_H_ERRNO(statp, err) (DNS_GET_H_ERRNO(statp) = (err)) +#endif + + /* + * To improve postscreen's allowlisting support, we need to know how long a + * DNSBL "not found" answer is valid. The 2010 implementation assumed it was + * valid for 3600 seconds. That is too long by 2015 standards. + * + * Instead of guessing, Postfix 3.1 and later implement RFC 2308 (DNS NCACHE), + * where a DNS server provides the TTL of a "not found" response as the TTL + * of an SOA record in the authority section. + * + * Unfortunately, the res_search() and res_query() API gets in the way. These + * functions overload their result value, the server reply length, and + * return -1 when the requested record does not exist. With libbind-based + * implementations, the server response is still available in an application + * buffer, thanks to the promise that res_query() and res_search() invoke + * res_send(), which returns the full server response even if the requested + * record does not exist. + * + * If this promise is broken (for example, res_search() does not call + * res_send(), but some non-libbind implementation that updates the + * application buffer only when the requested record exists), then we have a + * way out by setting the var_dns_ncache_ttl_fix variable. This enables a + * limited res_query() clone that should be sufficient for DNSBL / DNSWL + * lookups. + * + * The libunbound API does not comingle the reply length and reply status + * information, but that will have to wait until it is safe to make + * libunbound a mandatory dependency for Postfix. + */ +#ifdef HAVE_RES_SEND + +/* dns_neg_query - a res_query() clone that can return negative replies */ + +static int dns_neg_query(const char *name, int class, int type, + unsigned char *answer, int anslen) +{ + unsigned char msg_buf[MAX_DNS_QUERY_SIZE]; + HEADER *reply_header = (HEADER *) answer; + int len; + + /* + * Differences with res_query() from libbind: + * + * - This function returns a positive server reply length not only in case + * of success, but in all cases where a server reply is available that + * passes the preliminary checks in res_send(). + * + * - This function clears h_errno in case of success. The caller must use + * h_errno instead of the return value to decide if the lookup was + * successful. + * + * - No support for EDNS0 and DNSSEC (including turning off EDNS0 after + * error). That should be sufficient for DNS reputation lookups where the + * reply contains a small number of IP addresses. TXT records are out of + * scope for this workaround. + */ + reply_header->rcode = NOERROR; + +#define NO_MKQUERY_DATA_BUF ((unsigned char *) 0) +#define NO_MKQUERY_DATA_LEN ((int) 0) +#define NO_MKQUERY_NEWRR ((unsigned char *) 0) + + if ((len = DNS_RES_NMKQUERY(&dns_res_state, + QUERY, name, class, type, NO_MKQUERY_DATA_BUF, + NO_MKQUERY_DATA_LEN, NO_MKQUERY_NEWRR, + msg_buf, sizeof(msg_buf))) < 0) { + DNS_SET_H_ERRNO(&dns_res_state, NO_RECOVERY); + if (msg_verbose) + msg_info("res_nmkquery() failed"); + return (len); + } else if ((len = DNS_RES_NSEND(&dns_res_state, + msg_buf, len, answer, anslen)) < 0) { + DNS_SET_H_ERRNO(&dns_res_state, TRY_AGAIN); + if (msg_verbose) + msg_info("res_nsend() failed"); + return (len); + } else { + switch (reply_header->rcode) { + case NXDOMAIN: + DNS_SET_H_ERRNO(&dns_res_state, HOST_NOT_FOUND); + break; + case NOERROR: + if (reply_header->ancount != 0) + DNS_SET_H_ERRNO(&dns_res_state, 0); + else + DNS_SET_H_ERRNO(&dns_res_state, NO_DATA); + break; + case SERVFAIL: + DNS_SET_H_ERRNO(&dns_res_state, TRY_AGAIN); + break; + default: + DNS_SET_H_ERRNO(&dns_res_state, NO_RECOVERY); + break; + } + return (len); + } +} + +#endif + +/* dns_neg_search - res_search() that can return negative replies */ + +static int dns_neg_search(const char *name, int class, int type, + unsigned char *answer, int anslen, int keep_notfound) +{ + int len; + + /* + * Differences with res_search() from libbind: + * + * - With a non-zero keep_notfound argument, this function returns a + * positive server reply length not only in case of success, but also in + * case of a "notfound" reply status. The keep_notfound argument is + * usually zero, which allows us to avoid an unnecessary memset() call in + * the most common use case. + * + * - This function clears h_errno in case of success. The caller must use + * h_errno instead of the return value to decide if a lookup was + * successful. + */ +#define NOT_FOUND_H_ERRNO(he) ((he) == HOST_NOT_FOUND || (he) == NO_DATA) + + if (keep_notfound) + /* Prepare for returning a null-padded server reply. */ + memset(answer, 0, anslen); + len = DNS_RES_NSEARCH(&dns_res_state, name, class, type, answer, anslen); + /* Begin API creep workaround. */ + if (len < 0 && DNS_GET_H_ERRNO(&dns_res_state) == 0) { + DNS_SET_H_ERRNO(&dns_res_state, TRY_AGAIN); + msg_warn("res_nsearch(state, \"%s\", %d, %d, %p, %d) returns %d" + " with h_errno==0 -- setting h_errno=TRY_AGAIN", + name, class, type, answer, anslen, len); + } + /* End API creep workaround. */ + if (len > 0) { + DNS_SET_H_ERRNO(&dns_res_state, 0); + } else if (keep_notfound + && NOT_FOUND_H_ERRNO(DNS_GET_H_ERRNO(&dns_res_state))) { + /* Expect to return a null-padded server reply. */ + len = anslen; + } + return (len); +} + +/* dns_query - query name server and pre-parse the reply */ + +static int dns_query(const char *name, int type, unsigned flags, + DNS_REPLY *reply, VSTRING *why, unsigned lflags) +{ + HEADER *reply_header; + int len; + unsigned long saved_options; + int keep_notfound = (lflags & DNS_REQ_FLAG_NCACHE_TTL); + + /* + * Initialize the reply buffer. + */ + if (reply->buf == 0) { + reply->buf = (unsigned char *) mymalloc(DEF_DNS_REPLY_SIZE); + reply->buf_len = DEF_DNS_REPLY_SIZE; + } + + /* + * Initialize the name service. + */ + if ((dns_res_state.options & RES_INIT) == 0 + && DNS_RES_NINIT(&dns_res_state) < 0) { + if (why) + vstring_strcpy(why, "Name service initialization failure"); + return (DNS_FAIL); + } + + /* + * Set search options: debugging, parent domain search, append local + * domain. Do not allow the user to control other features. + */ +#define USER_FLAGS (RES_DEBUG | RES_DNSRCH | RES_DEFNAMES | RES_USE_DNSSEC) + + if ((flags & USER_FLAGS) != flags) + msg_panic("dns_query: bad flags: %d", flags); + + /* + * Set extra options that aren't exposed to the application. + */ +#define XTRA_FLAGS (RES_USE_EDNS0 | RES_TRUSTAD) + + if (DNS_WANT_DNSSEC_VALIDATION(flags)) + flags |= (RES_USE_EDNS0 | RES_TRUSTAD); + + /* + * Can't append domains: we need the right SOA TTL. + */ +#define APPEND_DOMAIN_FLAGS (RES_DNSRCH | RES_DEFNAMES) + + if (keep_notfound && (flags & APPEND_DOMAIN_FLAGS)) { + msg_warn("negative caching disables RES_DEFNAMES and RES_DNSRCH"); + flags &= ~APPEND_DOMAIN_FLAGS; + } + + /* + * Save and restore resolver options that we overwrite, to avoid + * surprising behavior in other code that also invokes the resolver. + */ +#define SAVE_FLAGS (USER_FLAGS | XTRA_FLAGS) + + saved_options = (dns_res_state.options & SAVE_FLAGS); + + /* + * Perform the lookup. Claim that the information cannot be found if and + * only if the name server told us so. + */ + for (;;) { + dns_res_state.options &= ~saved_options; + dns_res_state.options |= flags; + if (keep_notfound && var_dns_ncache_ttl_fix) { +#ifdef HAVE_RES_SEND + len = dns_neg_query((char *) name, C_IN, type, reply->buf, + reply->buf_len); +#else + var_dns_ncache_ttl_fix = 0; + msg_warn("system library does not support %s=yes" + " -- ignoring this setting", VAR_DNS_NCACHE_TTL_FIX); + len = dns_neg_search((char *) name, C_IN, type, reply->buf, + reply->buf_len, keep_notfound); +#endif + } else { + len = dns_neg_search((char *) name, C_IN, type, reply->buf, + reply->buf_len, keep_notfound); + } + dns_res_state.options &= ~flags; + dns_res_state.options |= saved_options; + reply_header = (HEADER *) reply->buf; + reply->rcode = reply_header->rcode; + if ((reply->dnssec_ad = !!reply_header->ad) != 0) + DNS_SEC_STATS_SET(DNS_SEC_FLAG_AVAILABLE); + if (DNS_GET_H_ERRNO(&dns_res_state) != 0) { + if (why) + vstring_sprintf(why, "Host or domain name not found. " + "Name service error for name=%s type=%s: %s", + name, dns_strtype(type), + dns_strerror(DNS_GET_H_ERRNO(&dns_res_state))); + if (msg_verbose) + msg_info("dns_query: %s (%s): %s", + name, dns_strtype(type), + dns_strerror(DNS_GET_H_ERRNO(&dns_res_state))); + switch (DNS_GET_H_ERRNO(&dns_res_state)) { + case NO_RECOVERY: + return (DNS_FAIL); + case HOST_NOT_FOUND: + case NO_DATA: + if (keep_notfound) + break; + SET_NO_DNS_REPLY_PACKET(reply); + return (DNS_NOTFOUND); + default: + return (DNS_RETRY); + } + } else { + if (msg_verbose) + msg_info("dns_query: %s (%s): OK", name, dns_strtype(type)); + } + + if (reply_header->tc == 0 || reply->buf_len >= MAX_DNS_REPLY_SIZE) + break; + reply->buf = (unsigned char *) + myrealloc((void *) reply->buf, 2 * reply->buf_len); + reply->buf_len *= 2; + } + + /* + * Future proofing. If this reaches the panic call, then some code change + * introduced a bug. + */ + if (len < 0) + msg_panic("dns_query: bad length %d (h_errno=%s)", + len, dns_strerror(DNS_GET_H_ERRNO(&dns_res_state))); + + /* + * Paranoia. + */ + if (len > reply->buf_len) { + msg_warn("reply length %d > buffer length %d for name=%s type=%s", + len, (int) reply->buf_len, name, dns_strtype(type)); + len = reply->buf_len; + } + + /* + * Initialize the reply structure. Some structure members are filled on + * the fly while the reply is being parsed. + */ + SET_HAVE_DNS_REPLY_PACKET(reply, len); + reply->query_start = reply->buf + sizeof(HEADER); + reply->answer_start = 0; + reply->query_count = ntohs(reply_header->qdcount); + reply->answer_count = ntohs(reply_header->ancount); + reply->auth_count = ntohs(reply_header->nscount); + if (msg_verbose > 1) + msg_info("dns_query: reply len=%d ancount=%d nscount=%d", + len, reply->answer_count, reply->auth_count); + + /* + * Future proofing. If this reaches the panic call, then some code change + * introduced a bug. + */ + if (DNS_GET_H_ERRNO(&dns_res_state) == 0) { + return (DNS_OK); + } else if (keep_notfound) { + return (DNS_NOTFOUND); + } else { + msg_panic("dns_query: unexpected reply status: %s", + dns_strerror(DNS_GET_H_ERRNO(&dns_res_state))); + } +} + +/* dns_skip_query - skip query data in name server reply */ + +static int dns_skip_query(DNS_REPLY *reply) +{ + int query_count = reply->query_count; + unsigned char *pos = reply->query_start; + int len; + + /* + * For each query, skip over the domain name and over the fixed query + * data. + */ + while (query_count-- > 0) { + if (pos >= reply->end) + return DNS_RETRY; + len = dn_skipname(pos, reply->end); + if (len < 0) + return (DNS_RETRY); + pos += len + QFIXEDSZ; + } + reply->answer_start = pos; + return (DNS_OK); +} + +/* dns_get_fixed - extract fixed data from resource record */ + +static int dns_get_fixed(unsigned char *pos, DNS_FIXED *fixed) +{ + GETSHORT(fixed->type, pos); + GETSHORT(fixed->class, pos); + GETLONG(fixed->ttl, pos); + GETSHORT(fixed->length, pos); + + if (fixed->class != C_IN) { + msg_warn("dns_get_fixed: bad class: %u", fixed->class); + return (DNS_RETRY); + } + return (DNS_OK); +} + +/* valid_rr_name - validate hostname in resource record */ + +static int valid_rr_name(const char *name, const char *location, + unsigned type, DNS_REPLY *reply) +{ + char temp[DNS_NAME_LEN]; + char *query_name; + int len; + char *gripe; + int result; + + /* + * People aren't supposed to specify numeric names where domain names are + * required, but it "works" with some mailers anyway, so people complain + * when software doesn't bend over backwards. + */ +#define PASS_NAME 1 +#define REJECT_NAME 0 + + if (valid_hostaddr(name, DONT_GRIPE)) { + result = PASS_NAME; + gripe = "numeric domain name"; + } else if (!valid_hostname(name, DO_GRIPE | DO_WILDCARD)) { + result = REJECT_NAME; + gripe = "malformed domain name"; + } else { + result = PASS_NAME; + gripe = 0; + } + + /* + * If we have a gripe, show some context, including the name used in the + * query and the type of reply that we're looking at. + */ + if (gripe) { + len = dn_expand(reply->buf, reply->end, reply->query_start, + temp, DNS_NAME_LEN); + query_name = (len < 0 ? "*unparsable*" : temp); + msg_warn("%s in %s of %s record for %s: %.100s", + gripe, location, dns_strtype(type), query_name, name); + } + return (result); +} + +/* dns_get_rr - extract resource record from name server reply */ + +static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply, + unsigned char *pos, char *rr_name, + DNS_FIXED *fixed) +{ + char temp[DNS_NAME_LEN]; + char *tempbuf = temp; + UINT32_TYPE soa_buf[5]; + int comp_len; + ssize_t data_len; + unsigned pref = 0; + unsigned char *src; + unsigned char *dst; + int ch; + +#define MIN2(a, b) ((unsigned)(a) < (unsigned)(b) ? (a) : (b)) + + *list = 0; + + switch (fixed->type) { + default: + msg_panic("dns_get_rr: don't know how to extract resource type %s", + dns_strtype(fixed->type)); + case T_CNAME: + case T_DNAME: + case T_MB: + case T_MG: + case T_MR: + case T_NS: + case T_PTR: + if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0) + return (DNS_RETRY); + if (!valid_rr_name(temp, "resource data", fixed->type, reply)) + return (DNS_INVAL); + data_len = strlen(temp) + 1; + break; + case T_MX: + GETSHORT(pref, pos); + if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0) + return (DNS_RETRY); + /* Don't even think of returning an invalid hostname to the caller. */ + if (*temp == 0) + return (DNS_NULLMX); /* TODO: descriptive text */ + if (!valid_rr_name(temp, "resource data", fixed->type, reply)) + return (DNS_INVAL); + data_len = strlen(temp) + 1; + break; + case T_A: + if (fixed->length != INET_ADDR_LEN) { + msg_warn("extract_answer: bad address length: %d", fixed->length); + return (DNS_RETRY); + } + if (fixed->length > sizeof(temp)) + msg_panic("dns_get_rr: length %d > DNS_NAME_LEN", + fixed->length); + memcpy(temp, pos, fixed->length); + data_len = fixed->length; + break; +#ifdef T_AAAA + case T_AAAA: + if (fixed->length != INET6_ADDR_LEN) { + msg_warn("extract_answer: bad address length: %d", fixed->length); + return (DNS_RETRY); + } + if (fixed->length > sizeof(temp)) + msg_panic("dns_get_rr: length %d > DNS_NAME_LEN", + fixed->length); + memcpy(temp, pos, fixed->length); + data_len = fixed->length; + break; +#endif + + /* + * We impose the same length limit here as for DNS names. However, + * see T_TLSA discussion below. + */ + case T_TXT: + data_len = MIN2(pos[0] + 1, MIN2(fixed->length + 1, sizeof(temp))); + for (src = pos + 1, dst = (unsigned char *) (temp); + dst < (unsigned char *) (temp) + data_len - 1; /* */ ) { + ch = *src++; + *dst++ = (ISPRINT(ch) ? ch : ' '); + } + *dst = 0; + break; + + /* + * For a full certificate, fixed->length may be longer than + * sizeof(tmpbuf) == DNS_NAME_LEN. Since we don't need a decode + * buffer, just copy the raw data into the rr. + * + * XXX Reject replies with bogus length < 3. + * + * XXX What about enforcing a sane upper bound? The RFC 1035 hard + * protocol limit is the RRDATA length limit of 65535. + */ + case T_TLSA: + data_len = fixed->length; + tempbuf = (char *) pos; + break; + + /* + * We use the SOA record TTL to determine the negative reply TTL. We + * save the time fields in the SOA record for debugging, but for now + * we don't bother saving the source host and mailbox information, as + * that would require changes to the DNS_RR structure and APIs. See + * also code in dns_strrecord(). + */ + case T_SOA: + comp_len = dn_skipname(pos, reply->end); + if (comp_len < 0) + return (DNS_RETRY); + pos += comp_len; + comp_len = dn_skipname(pos, reply->end); + if (comp_len < 0) + return (DNS_RETRY); + pos += comp_len; + if (reply->end - pos < sizeof(soa_buf)) { + msg_warn("extract_answer: bad SOA length: %d", fixed->length); + return (DNS_RETRY); + } + GETLONG(soa_buf[0], pos); /* Serial */ + GETLONG(soa_buf[1], pos); /* Refresh */ + GETLONG(soa_buf[2], pos); /* Retry */ + GETLONG(soa_buf[3], pos); /* Expire */ + GETLONG(soa_buf[4], pos); /* Ncache TTL */ + tempbuf = (char *) soa_buf; + data_len = sizeof(soa_buf); + break; + } + *list = dns_rr_create(orig_name, rr_name, fixed->type, fixed->class, + fixed->ttl, pref, tempbuf, data_len); + return (DNS_OK); +} + +/* dns_get_alias - extract CNAME from name server reply */ + +static int dns_get_alias(DNS_REPLY *reply, unsigned char *pos, + DNS_FIXED *fixed, char *cname, int c_len) +{ + if (fixed->type != T_CNAME) + msg_panic("dns_get_alias: bad type %s", dns_strtype(fixed->type)); + if (dn_expand(reply->buf, reply->end, pos, cname, c_len) < 0) + return (DNS_RETRY); + if (!valid_rr_name(cname, "resource data", fixed->type, reply)) + return (DNS_INVAL); + return (DNS_OK); +} + +/* dns_get_answer - extract answers from name server reply */ + +static int dns_get_answer(const char *orig_name, DNS_REPLY *reply, int type, + DNS_RR **rrlist, VSTRING *fqdn, char *cname, int c_len, + int *maybe_secure) +{ + char rr_name[DNS_NAME_LEN]; + unsigned char *pos; + int answer_count = reply->answer_count; + int len; + DNS_FIXED fixed; + DNS_RR *rr; + int resource_found = 0; + int cname_found = 0; + int not_found_status = DNS_NOTFOUND; /* can't happen */ + int status; + + /* + * Initialize. Skip over the name server query if we haven't yet. + */ + if (reply->answer_start == 0) + if ((status = dns_skip_query(reply)) < 0) + return (status); + pos = reply->answer_start; + + /* + * Either this, or use a GOTO for emergency exits. The purpose is to + * prevent incomplete answers from being passed back to the caller. + */ +#define CORRUPT(status) { \ + if (rrlist && *rrlist) { \ + dns_rr_free(*rrlist); \ + *rrlist = 0; \ + } \ + return (status); \ + } + + /* + * Iterate over all answers. + */ + while (answer_count-- > 0) { + + /* + * Optionally extract the fully-qualified domain name. + */ + if (pos >= reply->end) + CORRUPT(DNS_RETRY); + len = dn_expand(reply->buf, reply->end, pos, rr_name, DNS_NAME_LEN); + if (len < 0) + CORRUPT(DNS_RETRY); + pos += len; + + /* + * Extract the fixed reply data: type, class, ttl, length. + */ + if (pos + RRFIXEDSZ > reply->end) + CORRUPT(DNS_RETRY); + if ((status = dns_get_fixed(pos, &fixed)) != DNS_OK) + CORRUPT(status); + if (strcmp(orig_name, ".") == 0 && *rr_name == 0) + /* Allow empty response name for root queries. */ ; + else if (!valid_rr_name(rr_name, "resource name", fixed.type, reply)) + CORRUPT(DNS_INVAL); + if (fqdn) + vstring_strcpy(fqdn, rr_name); + if (msg_verbose) + msg_info("dns_get_answer: type %s for %s", + dns_strtype(fixed.type), rr_name); + pos += RRFIXEDSZ; + + /* + * Optionally extract the requested resource or CNAME data. + */ + if (pos + fixed.length > reply->end) + CORRUPT(DNS_RETRY); + if (type == fixed.type || type == T_ANY) { /* requested type */ + if (rrlist) { + if ((status = dns_get_rr(&rr, orig_name, reply, pos, rr_name, + &fixed)) == DNS_OK) { + resource_found++; + rr->dnssec_valid = *maybe_secure ? reply->dnssec_ad : 0; + *rrlist = dns_rr_append(*rrlist, rr); + } else if (status == DNS_NULLMX) { + CORRUPT(status); /* TODO: use better name */ + } else if (not_found_status != DNS_RETRY) + not_found_status = status; + } else + resource_found++; + } else if (fixed.type == T_CNAME) { /* cname resource */ + cname_found++; + if (cname && c_len > 0) + if ((status = dns_get_alias(reply, pos, &fixed, cname, c_len)) != DNS_OK) + CORRUPT(status); + if (!reply->dnssec_ad) + *maybe_secure = 0; + } + pos += fixed.length; + } + + /* + * See what answer we came up with. Report success when the requested + * information was found. Otherwise, when a CNAME was found, report that + * more recursion is needed. Otherwise report failure. + */ + if (resource_found) + return (DNS_OK); + if (cname_found) + return (DNS_RECURSE); + return (not_found_status); +} + +/* dns_lookup_x - DNS lookup user interface */ + +int dns_lookup_x(const char *name, unsigned type, unsigned flags, + DNS_RR **rrlist, VSTRING *fqdn, VSTRING *why, + int *rcode, unsigned lflags) +{ + char cname[DNS_NAME_LEN]; + int c_len = sizeof(cname); + static DNS_REPLY reply; + int count; + int status; + int maybe_secure = 1; /* Query name presumed secure */ + const char *orig_name = name; + + /* + * Reset results early. DNS_OK is not the only status that returns + * resource records; DNS_NOTFOUND will do that too, if requested. + */ + if (rrlist) + *rrlist = 0; + + /* + * DJBDNS produces a bogus A record when given a numerical hostname. + */ + if (valid_hostaddr(name, DONT_GRIPE)) { + if (why) + vstring_sprintf(why, + "Name service error for %s: invalid host or domain name", + name); + if (rcode) + *rcode = NXDOMAIN; + DNS_SET_H_ERRNO(&dns_res_state, HOST_NOT_FOUND); + return (DNS_NOTFOUND); + } + + /* + * The Linux resolver misbehaves when given an invalid domain name. + */ + if (strcmp(name, ".") && !valid_hostname(name, DONT_GRIPE | DO_WILDCARD)) { + if (why) + vstring_sprintf(why, + "Name service error for %s: invalid host or domain name", + name); + if (rcode) + *rcode = NXDOMAIN; + DNS_SET_H_ERRNO(&dns_res_state, HOST_NOT_FOUND); + return (DNS_NOTFOUND); + } + + /* + * Perform the lookup. Follow CNAME chains, but only up to a + * pre-determined maximum. + */ + for (count = 0; count < 10; count++) { + + /* + * Perform the DNS lookup, and pre-parse the name server reply. + */ + status = dns_query(name, type, flags, &reply, why, lflags); + if (rcode) + *rcode = reply.rcode; + if (status != DNS_OK) { + + /* + * If the record does not exist, and we have a copy of the server + * response, try to extract the negative caching TTL for the SOA + * record in the authority section. DO NOT return an error if an + * SOA record is malformed. + */ + if (status == DNS_NOTFOUND && TEST_HAVE_DNS_REPLY_PACKET(&reply) + && reply.auth_count > 0) { + reply.answer_count = reply.auth_count; /* XXX TODO: Fix API */ + (void) dns_get_answer(orig_name, &reply, T_SOA, rrlist, fqdn, + cname, c_len, &maybe_secure); + } + if (DNS_WANT_DNSSEC_VALIDATION(flags) + && !DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE | \ + DNS_SEC_FLAG_DONT_PROBE)) + dns_sec_probe(flags); /* XXX Clobbers 'reply' */ + return (status); + } + + /* + * Extract resource records of the requested type. Pick up CNAME + * information just in case the requested data is not found. + */ + status = dns_get_answer(orig_name, &reply, type, rrlist, fqdn, + cname, c_len, &maybe_secure); + if (DNS_WANT_DNSSEC_VALIDATION(flags) + && !DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE | \ + DNS_SEC_FLAG_DONT_PROBE)) + dns_sec_probe(flags); /* XXX Clobbers 'reply' */ + switch (status) { + default: + if (why) + vstring_sprintf(why, "Name service error for name=%s type=%s: " + "Malformed or unexpected name server reply", + name, dns_strtype(type)); + return (status); + case DNS_NULLMX: + if (why) + vstring_sprintf(why, "Domain %s does not accept mail (nullMX)", + name); + DNS_SET_H_ERRNO(&dns_res_state, NO_DATA); + return (status); + case DNS_OK: + if (rrlist && dns_rr_filter_maps) { + if (dns_rr_filter_execute(rrlist) < 0) { + if (why) + vstring_sprintf(why, + "Error looking up name=%s type=%s: " + "Invalid DNS reply filter syntax", + name, dns_strtype(type)); + dns_rr_free(*rrlist); + *rrlist = 0; + status = DNS_RETRY; + } else if (*rrlist == 0) { + if (why) + vstring_sprintf(why, + "Error looking up name=%s type=%s: " + "DNS reply filter drops all results", + name, dns_strtype(type)); + status = DNS_POLICY; + } + } + return (status); + case DNS_RECURSE: + if (msg_verbose) + msg_info("dns_lookup: %s aliased to %s", name, cname); +#if RES_USE_DNSSEC + + /* + * Once an intermediate CNAME reply is not validated, all + * consequent RRs are deemed not validated, so we don't ask for + * further DNSSEC replies. + */ + if (maybe_secure == 0) + flags &= ~RES_USE_DNSSEC; +#endif + name = cname; + } + } + if (why) + vstring_sprintf(why, "Name server loop for %s", name); + msg_warn("dns_lookup: Name server loop for %s", name); + return (DNS_NOTFOUND); +} + +/* dns_lookup_rl - DNS lookup interface with types list */ + +int dns_lookup_rl(const char *name, unsigned flags, DNS_RR **rrlist, + VSTRING *fqdn, VSTRING *why, int *rcode, + int lflags,...) +{ + va_list ap; + unsigned type, next; + int status = DNS_NOTFOUND; + int hpref_status = INT_MIN; + VSTRING *hpref_rtext = 0; + int hpref_rcode; + int hpref_h_errno; + DNS_RR *rr; + + /* Save intermediate highest-priority result. */ +#define SAVE_HPREF_STATUS() do { \ + hpref_status = status; \ + if (rcode) \ + hpref_rcode = *rcode; \ + if (why && status != DNS_OK) \ + vstring_strcpy(hpref_rtext ? hpref_rtext : \ + (hpref_rtext = vstring_alloc(VSTRING_LEN(why))), \ + vstring_str(why)); \ + hpref_h_errno = DNS_GET_H_ERRNO(&dns_res_state); \ + } while (0) + + /* Restore intermediate highest-priority result. */ +#define RESTORE_HPREF_STATUS() do { \ + status = hpref_status; \ + if (rcode) \ + *rcode = hpref_rcode; \ + if (why && status != DNS_OK) \ + vstring_strcpy(why, vstring_str(hpref_rtext)); \ + DNS_SET_H_ERRNO(&dns_res_state, hpref_h_errno); \ + } while (0) + + if (rrlist) + *rrlist = 0; + va_start(ap, lflags); + for (type = va_arg(ap, unsigned); type != 0; type = next) { + next = va_arg(ap, unsigned); + if (msg_verbose) + msg_info("lookup %s type %s flags %s", + name, dns_strtype(type), dns_str_resflags(flags)); + status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0, + fqdn, why, rcode, lflags); + if (rrlist && rr) + *rrlist = dns_rr_append(*rrlist, rr); + if (status == DNS_OK) { + if (lflags & DNS_REQ_FLAG_STOP_OK) + break; + } else if (status == DNS_INVAL) { + if (lflags & DNS_REQ_FLAG_STOP_INVAL) + break; + } else if (status == DNS_POLICY) { + if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY)) + break; + } else if (status == DNS_NULLMX) { + if (lflags & DNS_REQ_FLAG_STOP_NULLMX) + break; + } + /* XXX Stop after NXDOMAIN error. */ + if (next == 0) + break; + if (status >= hpref_status) + SAVE_HPREF_STATUS(); /* save last info */ + } + va_end(ap); + if (status < hpref_status) + RESTORE_HPREF_STATUS(); /* else report last info */ + if (hpref_rtext) + vstring_free(hpref_rtext); + return (status); +} + +/* dns_lookup_rv - DNS lookup interface with types vector */ + +int dns_lookup_rv(const char *name, unsigned flags, DNS_RR **rrlist, + VSTRING *fqdn, VSTRING *why, int *rcode, + int lflags, unsigned *types) +{ + unsigned type, next; + int status = DNS_NOTFOUND; + int hpref_status = INT_MIN; + VSTRING *hpref_rtext = 0; + int hpref_rcode; + int hpref_h_errno; + DNS_RR *rr; + + if (rrlist) + *rrlist = 0; + for (type = *types++; type != 0; type = next) { + next = *types++; + if (msg_verbose) + msg_info("lookup %s type %s flags %s", + name, dns_strtype(type), dns_str_resflags(flags)); + status = dns_lookup_x(name, type, flags, rrlist ? &rr : (DNS_RR **) 0, + fqdn, why, rcode, lflags); + if (rrlist && rr) + *rrlist = dns_rr_append(*rrlist, rr); + if (status == DNS_OK) { + if (lflags & DNS_REQ_FLAG_STOP_OK) + break; + } else if (status == DNS_INVAL) { + if (lflags & DNS_REQ_FLAG_STOP_INVAL) + break; + } else if (status == DNS_POLICY) { + if (type == T_MX && (lflags & DNS_REQ_FLAG_STOP_MX_POLICY)) + break; + } else if (status == DNS_NULLMX) { + if (lflags & DNS_REQ_FLAG_STOP_NULLMX) + break; + } + /* XXX Stop after NXDOMAIN error. */ + if (next == 0) + break; + if (status >= hpref_status) + SAVE_HPREF_STATUS(); /* save last info */ + } + if (status < hpref_status) + RESTORE_HPREF_STATUS(); /* else report last info */ + if (hpref_rtext) + vstring_free(hpref_rtext); + return (status); +} + +/* dns_get_h_errno - get the last lookup status */ + +int dns_get_h_errno(void) +{ + return (DNS_GET_H_ERRNO(&dns_res_state)); +} diff --git a/src/dns/dns_rr.c b/src/dns/dns_rr.c new file mode 100644 index 0000000..b550788 --- /dev/null +++ b/src/dns/dns_rr.c @@ -0,0 +1,347 @@ +/*++ +/* NAME +/* dns_rr 3 +/* SUMMARY +/* resource record memory and list management +/* SYNOPSIS +/* #include +/* +/* DNS_RR *dns_rr_create(qname, rname, type, class, ttl, preference, +/* data, data_len) +/* const char *qname; +/* const char *rname; +/* unsigned short type; +/* unsigned short class; +/* unsigned int ttl; +/* unsigned preference; +/* const char *data; +/* size_t data_len; +/* +/* void dns_rr_free(list) +/* DNS_RR *list; +/* +/* DNS_RR *dns_rr_copy(record) +/* DNS_RR *record; +/* +/* DNS_RR *dns_rr_append(list, record) +/* DNS_RR *list; +/* DNS_RR *record; +/* +/* DNS_RR *dns_rr_sort(list, compar) +/* DNS_RR *list +/* int (*compar)(DNS_RR *, DNS_RR *); +/* +/* int dns_rr_compare_pref_ipv6(DNS_RR *a, DNS_RR *b) +/* DNS_RR *list +/* DNS_RR *list +/* +/* int dns_rr_compare_pref_ipv4(DNS_RR *a, DNS_RR *b) +/* DNS_RR *list +/* DNS_RR *list +/* +/* int dns_rr_compare_pref_any(DNS_RR *a, DNS_RR *b) +/* DNS_RR *list +/* DNS_RR *list +/* +/* DNS_RR *dns_rr_shuffle(list) +/* DNS_RR *list; +/* +/* DNS_RR *dns_rr_remove(list, record) +/* DNS_RR *list; +/* DNS_RR *record; +/* DESCRIPTION +/* The routines in this module maintain memory for DNS resource record +/* information, and maintain lists of DNS resource records. +/* +/* dns_rr_create() creates and initializes one resource record. +/* The \fIqname\fR field specifies the query name. +/* The \fIrname\fR field specifies the reply name. +/* \fIpreference\fR is used for MX records; \fIdata\fR is a null +/* pointer or specifies optional resource-specific data; +/* \fIdata_len\fR is the amount of resource-specific data. +/* +/* dns_rr_free() releases the resource used by of zero or more +/* resource records. +/* +/* dns_rr_copy() makes a copy of a resource record. +/* +/* dns_rr_append() appends a resource record to a (list of) resource +/* record(s). +/* A null input list is explicitly allowed. +/* +/* dns_rr_sort() sorts a list of resource records into ascending +/* order according to a user-specified criterion. The result is the +/* sorted list. +/* +/* dns_rr_compare_pref_XXX() are dns_rr_sort() helpers to sort +/* records by their MX preference and by their address family. +/* +/* dns_rr_shuffle() randomly permutes a list of resource records. +/* +/* dns_rr_remove() removes the specified record from the specified list. +/* The updated list is the result value. +/* The record MUST be a list member. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* DNS library. */ + +#include "dns.h" + +/* dns_rr_create - fill in resource record structure */ + +DNS_RR *dns_rr_create(const char *qname, const char *rname, + ushort type, ushort class, + unsigned int ttl, unsigned pref, + const char *data, size_t data_len) +{ + DNS_RR *rr; + + rr = (DNS_RR *) mymalloc(sizeof(*rr) + data_len - 1); + rr->qname = mystrdup(qname); + rr->rname = mystrdup(rname); + rr->type = type; + rr->class = class; + rr->ttl = ttl; + rr->dnssec_valid = 0; + rr->pref = pref; + if (data && data_len > 0) + memcpy(rr->data, data, data_len); + rr->data_len = data_len; + rr->next = 0; + return (rr); +} + +/* dns_rr_free - destroy resource record structure */ + +void dns_rr_free(DNS_RR *rr) +{ + if (rr) { + if (rr->next) + dns_rr_free(rr->next); + myfree(rr->qname); + myfree(rr->rname); + myfree((void *) rr); + } +} + +/* dns_rr_copy - copy resource record */ + +DNS_RR *dns_rr_copy(DNS_RR *src) +{ + ssize_t len = sizeof(*src) + src->data_len - 1; + DNS_RR *dst; + + /* + * Combine struct assignment and data copy in one block copy operation. + */ + dst = (DNS_RR *) mymalloc(len); + memcpy((void *) dst, (void *) src, len); + dst->qname = mystrdup(src->qname); + dst->rname = mystrdup(src->rname); + dst->next = 0; + return (dst); +} + +/* dns_rr_append - append resource record to list */ + +DNS_RR *dns_rr_append(DNS_RR *list, DNS_RR *rr) +{ + if (list == 0) { + list = rr; + } else { + list->next = dns_rr_append(list->next, rr); + } + return (list); +} + +/* dns_rr_compare_pref_ipv6 - compare records by preference, ipv6 preferred */ + +int dns_rr_compare_pref_ipv6(DNS_RR *a, DNS_RR *b) +{ + if (a->pref != b->pref) + return (a->pref - b->pref); +#ifdef HAS_IPV6 + if (a->type == b->type) /* 200412 */ + return 0; + if (a->type == T_AAAA) + return (-1); + if (b->type == T_AAAA) + return (+1); +#endif + return 0; +} + +/* dns_rr_compare_pref_ipv4 - compare records by preference, ipv4 preferred */ + +int dns_rr_compare_pref_ipv4(DNS_RR *a, DNS_RR *b) +{ + if (a->pref != b->pref) + return (a->pref - b->pref); +#ifdef HAS_IPV6 + if (a->type == b->type) + return 0; + if (a->type == T_AAAA) + return (+1); + if (b->type == T_AAAA) + return (-1); +#endif + return 0; +} + +/* dns_rr_compare_pref_any - compare records by preference, protocol-neutral */ + +int dns_rr_compare_pref_any(DNS_RR *a, DNS_RR *b) +{ + if (a->pref != b->pref) + return (a->pref - b->pref); + return 0; +} + +/* dns_rr_compare_pref - binary compatibility helper after name change */ + +int dns_rr_compare_pref(DNS_RR *a, DNS_RR *b) +{ + return (dns_rr_compare_pref_ipv6(a, b)); +} + +/* dns_rr_sort_callback - glue function */ + +static int (*dns_rr_sort_user) (DNS_RR *, DNS_RR *); + +static int dns_rr_sort_callback(const void *a, const void *b) +{ + DNS_RR *aa = *(DNS_RR **) a; + DNS_RR *bb = *(DNS_RR **) b; + + return (dns_rr_sort_user(aa, bb)); +} + +/* dns_rr_sort - sort resource record list */ + +DNS_RR *dns_rr_sort(DNS_RR *list, int (*compar) (DNS_RR *, DNS_RR *)) +{ + int (*saved_user) (DNS_RR *, DNS_RR *); + DNS_RR **rr_array; + DNS_RR *rr; + int len; + int i; + + /* + * Save state and initialize. + */ + saved_user = dns_rr_sort_user; + dns_rr_sort_user = compar; + + /* + * Build linear array with pointers to each list element. + */ + for (len = 0, rr = list; rr != 0; len++, rr = rr->next) + /* void */ ; + rr_array = (DNS_RR **) mymalloc(len * sizeof(*rr_array)); + for (len = 0, rr = list; rr != 0; len++, rr = rr->next) + rr_array[len] = rr; + + /* + * Sort by user-specified criterion. + */ + qsort((void *) rr_array, len, sizeof(*rr_array), dns_rr_sort_callback); + + /* + * Fix the links. + */ + for (i = 0; i < len - 1; i++) + rr_array[i]->next = rr_array[i + 1]; + rr_array[i]->next = 0; + list = rr_array[0]; + + /* + * Cleanup. + */ + myfree((void *) rr_array); + dns_rr_sort_user = saved_user; + return (list); +} + +/* dns_rr_shuffle - shuffle resource record list */ + +DNS_RR *dns_rr_shuffle(DNS_RR *list) +{ + DNS_RR **rr_array; + DNS_RR *rr; + int len; + int i; + int r; + + /* + * Build linear array with pointers to each list element. + */ + for (len = 0, rr = list; rr != 0; len++, rr = rr->next) + /* void */ ; + rr_array = (DNS_RR **) mymalloc(len * sizeof(*rr_array)); + for (len = 0, rr = list; rr != 0; len++, rr = rr->next) + rr_array[len] = rr; + + /* + * Shuffle resource records. Every element has an equal chance of landing + * in slot 0. After that every remaining element has an equal chance of + * landing in slot 1, ... This is exactly n! states for n! permutations. + */ + for (i = 0; i < len - 1; i++) { + r = i + (myrand() % (len - i)); /* Victor&Son */ + rr = rr_array[i]; + rr_array[i] = rr_array[r]; + rr_array[r] = rr; + } + + /* + * Fix the links. + */ + for (i = 0; i < len - 1; i++) + rr_array[i]->next = rr_array[i + 1]; + rr_array[i]->next = 0; + list = rr_array[0]; + + /* + * Cleanup. + */ + myfree((void *) rr_array); + return (list); +} + +/* dns_rr_remove - remove record from list, return new list */ + +DNS_RR *dns_rr_remove(DNS_RR *list, DNS_RR *record) +{ + if (list == 0) + msg_panic("dns_rr_remove: record not found"); + + if (list == record) { + list = record->next; + record->next = 0; + dns_rr_free(record); + } else { + list->next = dns_rr_remove(list->next, record); + } + return (list); +} diff --git a/src/dns/dns_rr_eq_sa.c b/src/dns/dns_rr_eq_sa.c new file mode 100644 index 0000000..8113d6b --- /dev/null +++ b/src/dns/dns_rr_eq_sa.c @@ -0,0 +1,157 @@ +/*++ +/* NAME +/* dns_rr_eq_sa 3 +/* SUMMARY +/* compare resource record with socket address +/* SYNOPSIS +/* #include +/* +/* int dns_rr_eq_sa(DNS_RR *rr, struct sockaddr *sa) +/* DNS_RR *rr; +/* struct sockaddr *sa; +/* +/* int DNS_RR_EQ_SA(DNS_RR *rr, struct sockaddr *sa) +/* DNS_RR *rr; +/* struct sockaddr *sa; +/* DESCRIPTION +/* dns_rr_eq_sa() compares a DNS resource record with a socket +/* address. The result is non-zero when the resource type +/* matches the socket address family, and when the network +/* address information is identical. +/* +/* DNS_RR_EQ_SA() is an unsafe macro version for those who live fast. +/* +/* Arguments: +/* .IP rr +/* DNS resource record pointer. +/* .IP sa +/* Binary address pointer. +/* DIAGNOSTICS +/* Panic: unknown socket address family. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include + +/* Utility library. */ + +#include +#include + +/* DNS library. */ + +#include + +/* dns_rr_eq_sa - compare resource record with socket address */ + +int dns_rr_eq_sa(DNS_RR *rr, struct sockaddr *sa) +{ + const char *myname = "dns_rr_eq_sa"; + + if (sa->sa_family == AF_INET) { + return (rr->type == T_A + && SOCK_ADDR_IN_ADDR(sa).s_addr == IN_ADDR(rr->data).s_addr); +#ifdef HAS_IPV6 + } else if (sa->sa_family == AF_INET6) { + return (rr->type == T_AAAA + && memcmp((void *) &SOCK_ADDR_IN6_ADDR(sa), + rr->data, rr->data_len) == 0); +#endif + } else { + msg_panic("%s: unsupported socket address family type: %d", + myname, sa->sa_family); + } +} + + /* + * Stand-alone test program. + */ +#ifdef TEST +#include +#include +#include +#include +#include + +static const char *myname; + +static NORETURN usage(void) +{ + msg_fatal("usage: %s hostname address", myname); +} + +static int compare_family(const void *a, const void *b) +{ + struct addrinfo *resa = *(struct addrinfo **) a; + struct addrinfo *resb = *(struct addrinfo **) b; + + return (resa->ai_family - resb->ai_family); +} + +int main(int argc, char **argv) +{ + MAI_HOSTADDR_STR hostaddr; + DNS_RR *rr; + struct addrinfo *res0; + struct addrinfo *res1; + struct addrinfo *res; + struct addrinfo **resv; + size_t len, n; + int aierr; + + myname = argv[0]; + + if (argc < 3) + usage(); + + inet_proto_init(argv[0], INET_PROTO_NAME_ALL); + + while (*++argv) { + if (argv[1] == 0) + usage(); + + if ((aierr = hostaddr_to_sockaddr(argv[1], (char *) 0, 0, &res1)) != 0) + msg_fatal("host address %s: %s", argv[1], MAI_STRERROR(aierr)); + if ((rr = dns_sa_to_rr(argv[1], 0, res1->ai_addr)) == 0) + msg_fatal("dns_sa_to_rr: %m"); + freeaddrinfo(res1); + + if ((aierr = hostname_to_sockaddr(argv[0], (char *) 0, 0, &res0)) != 0) + msg_fatal("host name %s: %s", argv[0], MAI_STRERROR(aierr)); + for (len = 0, res = res0; res != 0; res = res->ai_next) + len += 1; + resv = (struct addrinfo **) mymalloc(len * sizeof(*resv)); + for (len = 0, res = res0; res != 0; res = res->ai_next) + resv[len++] = res; + qsort((void *) resv, len, sizeof(*resv), compare_family); + for (n = 0; n < len; n++) { + SOCKADDR_TO_HOSTADDR(resv[n]->ai_addr, resv[n]->ai_addrlen, + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + vstream_printf("%s =?= %s\n", hostaddr.buf, argv[1]); + vstream_printf("tested by function: %s\n", + dns_rr_eq_sa(rr, resv[n]->ai_addr) ? + "yes" : "no"); + vstream_printf("tested by macro: %s\n", + DNS_RR_EQ_SA(rr, resv[n]->ai_addr) ? + "yes" : "no"); + } + dns_rr_free(rr); + freeaddrinfo(res0); + myfree((void *) resv); + vstream_fflush(VSTREAM_OUT); + argv += 1; + } + return (0); +} + +#endif diff --git a/src/dns/dns_rr_eq_sa.in b/src/dns/dns_rr_eq_sa.in new file mode 100644 index 0000000..e8b6f83 --- /dev/null +++ b/src/dns/dns_rr_eq_sa.in @@ -0,0 +1,4 @@ +spike.porcupine.org 168.100.3.2 +spike.porcupine.org 168.100.3.3 +spike.porcupine.org 2604:8d00:189::2 +spike.porcupine.org 2604:8d00:189::3 diff --git a/src/dns/dns_rr_eq_sa.ref b/src/dns/dns_rr_eq_sa.ref new file mode 100644 index 0000000..45e6b78 --- /dev/null +++ b/src/dns/dns_rr_eq_sa.ref @@ -0,0 +1,24 @@ +168.100.3.2 =?= 168.100.3.2 +tested by function: yes +tested by macro: yes +2604:8d00:189::2 =?= 168.100.3.2 +tested by function: no +tested by macro: no +168.100.3.2 =?= 168.100.3.3 +tested by function: no +tested by macro: no +2604:8d00:189::2 =?= 168.100.3.3 +tested by function: no +tested by macro: no +168.100.3.2 =?= 2604:8d00:189::2 +tested by function: no +tested by macro: no +2604:8d00:189::2 =?= 2604:8d00:189::2 +tested by function: yes +tested by macro: yes +168.100.3.2 =?= 2604:8d00:189::3 +tested by function: no +tested by macro: no +2604:8d00:189::2 =?= 2604:8d00:189::3 +tested by function: no +tested by macro: no diff --git a/src/dns/dns_rr_filter.c b/src/dns/dns_rr_filter.c new file mode 100644 index 0000000..a02d3de --- /dev/null +++ b/src/dns/dns_rr_filter.c @@ -0,0 +1,150 @@ +/*++ +/* NAME +/* dns_rr_filter 3 +/* SUMMARY +/* DNS resource record filter +/* SYNOPSIS +/* #include +/* +/* void dns_rr_filter_compile(title, map_names) +/* const char *title; +/* const char *map_names; +/* INTERNAL INTERFACES +/* int dns_rr_filter_execute(rrlist) +/* DNS_RR **rrlist; +/* +/* MAPS *dns_rr_filter_maps; +/* DESCRIPTION +/* This module implements a simple filter for dns_lookup*() +/* results. +/* +/* dns_rr_filter_compile() initializes a result filter. The +/* title and map_names arguments are as with maps_create(). +/* This function may be invoked more than once; only the last +/* filter takes effect. +/* +/* dns_rr_filter_execute() converts each resource record in the +/* specified list with dns_strrecord to ASCII form and matches +/* that against the specified maps. If a match is found it +/* executes the corresponding action. Currently, only the +/* "ignore" action is implemented. This removes the matched +/* record from the list. The result is 0 in case of success, +/* -1 in case of error. +/* +/* dns_rr_filter_maps is updated by dns_rr_filter_compile(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include + + /* + * DNS library. + */ +#define LIBDNS_INTERNAL +#include + + /* + * Application-specific. + */ +MAPS *dns_rr_filter_maps; + +static DNS_RR dns_rr_filter_error[1]; + +#define STR vstring_str + +/* dns_rr_filter_compile - compile dns result filter */ + +void dns_rr_filter_compile(const char *title, const char *map_names) +{ + if (dns_rr_filter_maps != 0) + maps_free(dns_rr_filter_maps); + dns_rr_filter_maps = maps_create(title, map_names, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); +} + +/* dns_rr_action - execute action from filter map */ + +static DNS_RR *dns_rr_action(const char *cmd, DNS_RR *rr, const char *rr_text) +{ + const char *cmd_args = cmd + strcspn(cmd, " \t"); + int cmd_len = cmd_args - cmd; + + while (*cmd_args && ISSPACE(*cmd_args)) + cmd_args++; + +#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0) + + if (STREQUAL(cmd, "IGNORE", cmd_len)) { + msg_info("ignoring DNS RR: %s", rr_text); + return (0); + } else { + msg_warn("%s: unknown DNS filter action: \"%s\"", + dns_rr_filter_maps->title, cmd); + return (dns_rr_filter_error); + } + return (rr); +} + +/* dns_rr_filter_execute - filter DNS lookup result */ + +int dns_rr_filter_execute(DNS_RR **rrlist) +{ + static VSTRING *buf = 0; + DNS_RR **rrp; + DNS_RR *rr; + const char *map_res; + DNS_RR *act_res; + + /* + * Convert the resource record to string form, then search the maps for a + * matching action. + */ + if (buf == 0) + buf = vstring_alloc(100); + for (rrp = rrlist; (rr = *rrp) != 0; /* see below */ ) { + map_res = maps_find(dns_rr_filter_maps, dns_strrecord(buf, rr), + DICT_FLAG_NONE); + if (map_res != 0) { + if ((act_res = dns_rr_action(map_res, rr, STR(buf))) == 0) { + *rrp = rr->next; /* do not advance in the list */ + rr->next = 0; + dns_rr_free(rr); + continue; + } else if (act_res == dns_rr_filter_error) { + return (-1); + } + } else if (dns_rr_filter_maps->error) { + return (-1); + } + rrp = &(rr->next); /* do advance in the list */ + } + return (0); +} diff --git a/src/dns/dns_rr_to_pa.c b/src/dns/dns_rr_to_pa.c new file mode 100644 index 0000000..bfd93a0 --- /dev/null +++ b/src/dns/dns_rr_to_pa.c @@ -0,0 +1,113 @@ +/*++ +/* NAME +/* dns_rr_to_pa 3 +/* SUMMARY +/* resource record to printable address +/* SYNOPSIS +/* #include +/* +/* const char *dns_rr_to_pa(rr, hostaddr) +/* DNS_RR *rr; +/* MAI_HOSTADDR_STR *hostaddr; +/* DESCRIPTION +/* dns_rr_to_pa() converts the address in a DNS resource record +/* into printable form and returns a pointer to the result. +/* +/* Arguments: +/* .IP rr +/* The DNS resource record. +/* .IP hostaddr +/* Storage for the printable address. +/* DIAGNOSTICS +/* The result is null in case of problems, with errno set +/* to indicate the nature of the problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include + +/* DNS library. */ + +#include + +/* dns_rr_to_pa - resource record to printable address */ + +const char *dns_rr_to_pa(DNS_RR *rr, MAI_HOSTADDR_STR *hostaddr) +{ + if (rr->type == T_A) { + return (inet_ntop(AF_INET, rr->data, hostaddr->buf, + sizeof(hostaddr->buf))); +#ifdef HAS_IPV6 + } else if (rr->type == T_AAAA) { + return (inet_ntop(AF_INET6, rr->data, hostaddr->buf, + sizeof(hostaddr->buf))); +#endif + } else { + errno = EAFNOSUPPORT; + return (0); + } +} + + /* + * Stand-alone test program. + */ +#ifdef TEST +#include +#include + +static const char *myname; + +static NORETURN usage(void) +{ + msg_fatal("usage: %s dnsaddrtype hostname", myname); +} + +int main(int argc, char **argv) +{ + DNS_RR *rr; + MAI_HOSTADDR_STR hostaddr; + VSTRING *why; + int type; + + myname = argv[0]; + if (argc < 3) + usage(); + why = vstring_alloc(1); + + while (*++argv) { + if (argv[1] == 0) + usage(); + if ((type = dns_type(argv[0])) == 0) + usage(); + if (dns_lookup(argv[1], type, 0, &rr, (VSTRING *) 0, why) != DNS_OK) + msg_fatal("%s: %s", argv[1], vstring_str(why)); + if (dns_rr_to_pa(rr, &hostaddr) == 0) + msg_fatal("dns_rr_to_sa: %m"); + vstream_printf("%s -> %s\n", argv[1], hostaddr.buf); + vstream_fflush(VSTREAM_OUT); + argv += 1; + dns_rr_free(rr); + } + vstring_free(why); + return (0); +} + +#endif diff --git a/src/dns/dns_rr_to_pa.in b/src/dns/dns_rr_to_pa.in new file mode 100644 index 0000000..28d0e77 --- /dev/null +++ b/src/dns/dns_rr_to_pa.in @@ -0,0 +1,2 @@ +a spike.porcupine.org +aaaa spike.porcupine.org diff --git a/src/dns/dns_rr_to_pa.ref b/src/dns/dns_rr_to_pa.ref new file mode 100644 index 0000000..db1c7af --- /dev/null +++ b/src/dns/dns_rr_to_pa.ref @@ -0,0 +1,2 @@ +spike.porcupine.org -> 168.100.3.2 +spike.porcupine.org -> 2604:8d00:189::2 diff --git a/src/dns/dns_rr_to_sa.c b/src/dns/dns_rr_to_sa.c new file mode 100644 index 0000000..f264260 --- /dev/null +++ b/src/dns/dns_rr_to_sa.c @@ -0,0 +1,163 @@ +/*++ +/* NAME +/* dns_rr_to_sa 3 +/* SUMMARY +/* resource record to socket address +/* SYNOPSIS +/* #include +/* +/* int dns_rr_to_sa(rr, port, sa, sa_length) +/* DNS_RR *rr; +/* unsigned port; +/* struct sockaddr *sa; +/* SOCKADDR_SIZE *sa_length; +/* DESCRIPTION +/* dns_rr_to_sa() converts the address in a DNS resource record into +/* a socket address of the corresponding type. +/* +/* Arguments: +/* .IP rr +/* DNS resource record pointer. +/* .IP port +/* TCP or UDP port, network byte order. +/* .IP sa +/* Socket address pointer. +/* .IP sa_length +/* On input, the available socket address storage space. +/* On output, the amount of space actually used. +/* DIAGNOSTICS +/* The result is non-zero in case of problems, with the +/* error type returned via the errno variable. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include + +/* Utility library. */ + +#include + +/* DNS library. */ + +#include + +/* dns_rr_to_sa - resource record to socket address */ + +int dns_rr_to_sa(DNS_RR *rr, unsigned port, struct sockaddr *sa, + SOCKADDR_SIZE *sa_length) +{ + SOCKADDR_SIZE sock_addr_len; + + if (rr->type == T_A) { + if (rr->data_len != sizeof(SOCK_ADDR_IN_ADDR(sa))) { + errno = EINVAL; + return (-1); + } else if ((sock_addr_len = sizeof(*SOCK_ADDR_IN_PTR(sa))) > *sa_length) { + errno = ENOSPC; + return (-1); + } else { + memset((void *) SOCK_ADDR_IN_PTR(sa), 0, sock_addr_len); + SOCK_ADDR_IN_FAMILY(sa) = AF_INET; + SOCK_ADDR_IN_PORT(sa) = port; + SOCK_ADDR_IN_ADDR(sa) = IN_ADDR(rr->data); +#ifdef HAS_SA_LEN + sa->sa_len = sock_addr_len; +#endif + *sa_length = sock_addr_len; + return (0); + } +#ifdef HAS_IPV6 + } else if (rr->type == T_AAAA) { + if (rr->data_len != sizeof(SOCK_ADDR_IN6_ADDR(sa))) { + errno = EINVAL; + return (-1); + } else if ((sock_addr_len = sizeof(*SOCK_ADDR_IN6_PTR(sa))) > *sa_length) { + errno = ENOSPC; + return (-1); + } else { + memset((void *) SOCK_ADDR_IN6_PTR(sa), 0, sock_addr_len); + SOCK_ADDR_IN6_FAMILY(sa) = AF_INET6; + SOCK_ADDR_IN6_PORT(sa) = port; + SOCK_ADDR_IN6_ADDR(sa) = IN6_ADDR(rr->data); +#ifdef HAS_SA_LEN + sa->sa_len = sock_addr_len; +#endif + *sa_length = sock_addr_len; + return (0); + } +#endif + } else { + errno = EAFNOSUPPORT; + return (-1); + } +} + + /* + * Stand-alone test program. + */ +#ifdef TEST +#include + +#include +#include +#include + +static const char *myname; + +static NORETURN usage(void) +{ + msg_fatal("usage: %s dnsaddrtype hostname portnumber", myname); +} + +int main(int argc, char **argv) +{ + DNS_RR *rr; + MAI_HOSTADDR_STR hostaddr; + MAI_SERVPORT_STR portnum; + struct sockaddr_storage ss; + struct sockaddr *sa = (struct sockaddr *) &ss; + SOCKADDR_SIZE sa_length = sizeof(ss); + VSTRING *why; + int type; + int port; + + myname = argv[0]; + if (argc < 4) + usage(); + why = vstring_alloc(1); + + while (*++argv) { + if (argv[1] == 0 || argv[2] == 0) + usage(); + if ((type = dns_type(argv[0])) == 0) + usage(); + if (!alldig(argv[2]) || (port = atoi(argv[2])) > 65535) + usage(); + if (dns_lookup(argv[1], type, 0, &rr, (VSTRING *) 0, why) != DNS_OK) + msg_fatal("%s: %s", argv[1], vstring_str(why)); + sa_length = sizeof(ss); + if (dns_rr_to_sa(rr, htons(port), sa, &sa_length) != 0) + msg_fatal("dns_rr_to_sa: %m"); + SOCKADDR_TO_HOSTADDR(sa, sa_length, &hostaddr, &portnum, 0); + vstream_printf("%s %s -> %s %s\n", + argv[1], argv[2], hostaddr.buf, portnum.buf); + vstream_fflush(VSTREAM_OUT); + argv += 2; + dns_rr_free(rr); + } + vstring_free(why); + return (0); +} + +#endif diff --git a/src/dns/dns_rr_to_sa.in b/src/dns/dns_rr_to_sa.in new file mode 100644 index 0000000..1fff6c0 --- /dev/null +++ b/src/dns/dns_rr_to_sa.in @@ -0,0 +1,2 @@ +a spike.porcupine.org 25 +aaaa spike.porcupine.org 25 diff --git a/src/dns/dns_rr_to_sa.ref b/src/dns/dns_rr_to_sa.ref new file mode 100644 index 0000000..8a114b5 --- /dev/null +++ b/src/dns/dns_rr_to_sa.ref @@ -0,0 +1,2 @@ +spike.porcupine.org 25 -> 168.100.3.2 25 +spike.porcupine.org 25 -> 2604:8d00:189::2 25 diff --git a/src/dns/dns_sa_to_rr.c b/src/dns/dns_sa_to_rr.c new file mode 100644 index 0000000..6b9efcc --- /dev/null +++ b/src/dns/dns_sa_to_rr.c @@ -0,0 +1,138 @@ +/*++ +/* NAME +/* dns_sa_to_rr 3 +/* SUMMARY +/* socket address to resource record +/* SYNOPSIS +/* #include +/* +/* DNS_RR *dns_sa_to_rr(hostname, pref, sa) +/* const char *hostname; +/* unsigned pref; +/* struct sockaddr *sa; +/* DESCRIPTION +/* dns_sa_to_rr() converts a socket address into a DNS resource record. +/* +/* Arguments: +/* .IP hostname +/* The resource record host name. This will be both the qname +/* and the rname in the synthetic DNS resource record. +/* .IP pref +/* The resource record MX host preference, if applicable. +/* .IP sa +/* Binary address. +/* DIAGNOSTICS +/* The result is a null pointer in case of problems, with the +/* errno variable set to indicate the problem type. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include + +/* Utility library. */ + +#include + +/* DNS library. */ + +#include + +/* dns_sa_to_rr - socket address to resource record */ + +DNS_RR *dns_sa_to_rr(const char *hostname, unsigned pref, struct sockaddr *sa) +{ +#define DUMMY_TTL 0 + + if (sa->sa_family == AF_INET) { + return (dns_rr_create(hostname, hostname, T_A, C_IN, DUMMY_TTL, pref, + (char *) &SOCK_ADDR_IN_ADDR(sa), + sizeof(SOCK_ADDR_IN_ADDR(sa)))); +#ifdef HAS_IPV6 + } else if (sa->sa_family == AF_INET6) { + return (dns_rr_create(hostname, hostname, T_AAAA, C_IN, DUMMY_TTL, pref, + (char *) &SOCK_ADDR_IN6_ADDR(sa), + sizeof(SOCK_ADDR_IN6_ADDR(sa)))); +#endif + } else { + errno = EAFNOSUPPORT; + return (0); + } +} + + /* + * Stand-alone test program. + */ +#ifdef TEST +#include +#include +#include +#include +#include + +static const char *myname; + +static NORETURN usage(void) +{ + msg_fatal("usage: %s hostname", myname); +} + +static int compare_family(const void *a, const void *b) +{ + struct addrinfo *resa = *(struct addrinfo **) a; + struct addrinfo *resb = *(struct addrinfo **) b; + + return (resa->ai_family - resb->ai_family); +} + +int main(int argc, char **argv) +{ + MAI_HOSTADDR_STR hostaddr; + struct addrinfo *res0; + struct addrinfo *res; + struct addrinfo **resv; + size_t len, n; + DNS_RR *rr; + int aierr; + + myname = argv[0]; + if (argc < 2) + usage(); + + inet_proto_init(argv[0], INET_PROTO_NAME_ALL); + + while (*++argv) { + if ((aierr = hostname_to_sockaddr(argv[0], (char *) 0, 0, &res0)) != 0) + msg_fatal("%s: %s", argv[0], MAI_STRERROR(aierr)); + for (len = 0, res = res0; res != 0; res = res->ai_next) + len += 1; + resv = (struct addrinfo **) mymalloc(len * sizeof(*resv)); + for (len = 0, res = res0; res != 0; res = res->ai_next) + resv[len++] = res; + qsort((void *) resv, len, sizeof(*resv), compare_family); + for (n = 0; n < len; n++) { + if ((rr = dns_sa_to_rr(argv[0], 0, resv[n]->ai_addr)) == 0) + msg_fatal("dns_sa_to_rr: %m"); + if (dns_rr_to_pa(rr, &hostaddr) == 0) + msg_fatal("dns_rr_to_pa: %m"); + vstream_printf("%s -> %s\n", argv[0], hostaddr.buf); + vstream_fflush(VSTREAM_OUT); + dns_rr_free(rr); + } + freeaddrinfo(res0); + myfree((void *) resv); + } + return (0); +} + +#endif diff --git a/src/dns/dns_sa_to_rr.in b/src/dns/dns_sa_to_rr.in new file mode 100644 index 0000000..4f83a7d --- /dev/null +++ b/src/dns/dns_sa_to_rr.in @@ -0,0 +1 @@ +spike.porcupine.org diff --git a/src/dns/dns_sa_to_rr.ref b/src/dns/dns_sa_to_rr.ref new file mode 100644 index 0000000..db1c7af --- /dev/null +++ b/src/dns/dns_sa_to_rr.ref @@ -0,0 +1,2 @@ +spike.porcupine.org -> 168.100.3.2 +spike.porcupine.org -> 2604:8d00:189::2 diff --git a/src/dns/dns_sec.c b/src/dns/dns_sec.c new file mode 100644 index 0000000..849627e --- /dev/null +++ b/src/dns/dns_sec.c @@ -0,0 +1,144 @@ +/*++ +/* NAME +/* dns_sec 3 +/* SUMMARY +/* DNSSEC validation availability +/* SYNOPSIS +/* #include +/* +/* DNS_SEC_STATS_SET( +/* int flags) +/* +/* DNS_SEC_STATS_TEST( +/* int flags) +/* +/* void dns_sec_probe( +/* int rflags) +/* DESCRIPTION +/* This module maintains information about the availability of +/* DNSSEC validation, in global flags that summarize +/* process-lifetime history. +/* .IP DNS_SEC_FLAG_AVAILABLE +/* The process has received at least one DNSSEC validated +/* response to a query that requested DNSSEC validation. +/* .IP DNS_SEC_FLAG_DONT_PROBE +/* The process has sent a DNSSEC probe (see below), or DNSSEC +/* probing is disabled by configuration. +/* .PP +/* DNS_SEC_STATS_SET() sets one or more DNS_SEC_FLAG_* flags, +/* and DNS_SEC_STATS_TEST() returns non-zero if any of the +/* specified flags is set. +/* +/* dns_sec_probe() generates a query to the target specified +/* with the \fBdnssec_probe\fR configuration parameter. It +/* sets the DNS_SEC_FLAG_DONT_PROBE flag, and it calls +/* dns_lookup() which sets DNS_SEC_FLAG_AVAILABLE if it receives +/* a DNSSEC validated response. Preconditions: +/* .IP \(bu +/* The rflags argument must request DNSSEC validation (in the +/* same manner as dns_lookup() rflags argument). +/* .IP \(bu +/* The DNS_SEC_FLAG_AVAILABLE and DNS_SEC_FLAG_DONT_PROBE +/* flags must be false. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#include + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * Global library. + */ +#include + + /* + * DNS library. + */ +#include + +int dns_sec_stats; + +/* dns_sec_probe - send a probe to establish DNSSEC viability */ + +void dns_sec_probe(int rflags) +{ + const char myname[] = "dns_sec_probe"; + char *saved_dnssec_probe; + char *qname; + int qtype; + DNS_RR *rrlist = 0; + int dns_status; + VSTRING *why; + + /* + * Sanity checks. + */ + if (!DNS_WANT_DNSSEC_VALIDATION(rflags)) + msg_panic("%s: DNSSEC is not requested", myname); + if (DNS_SEC_STATS_TEST(DNS_SEC_FLAG_DONT_PROBE)) + msg_panic("%s: DNSSEC probe was already sent, or probing is disabled", + myname); + if (DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE)) + msg_panic("%s: already have validated DNS response", myname); + + /* + * Don't recurse. + */ + DNS_SEC_STATS_SET(DNS_SEC_FLAG_DONT_PROBE); + + /* + * Don't probe. + */ + if (*var_dnssec_probe == 0) + return; + + /* + * Parse the probe spec. Format is type:resource. + */ + saved_dnssec_probe = mystrdup(var_dnssec_probe); + if ((qname = split_at(saved_dnssec_probe, ':')) == 0 || *qname == 0 + || (qtype = dns_type(saved_dnssec_probe)) == 0) + msg_fatal("malformed %s value: %s format is qtype:qname", + VAR_DNSSEC_PROBE, var_dnssec_probe); + + why = vstring_alloc(100); + dns_status = dns_lookup(qname, qtype, rflags, &rrlist, (VSTRING *) 0, why); + if (!DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE)) + msg_warn("DNSSEC validation may be unavailable"); + else if (msg_verbose) + msg_info(VAR_DNSSEC_PROBE + " '%s' received a response that is DNSSEC validated", + var_dnssec_probe); + switch (dns_status) { + default: + if (!DNS_SEC_STATS_TEST(DNS_SEC_FLAG_AVAILABLE)) + msg_warn("reason: " VAR_DNSSEC_PROBE + " '%s' received a response that is not DNSSEC validated", + var_dnssec_probe); + if (rrlist) + dns_rr_free(rrlist); + break; + case DNS_RETRY: + case DNS_FAIL: + msg_warn("reason: " VAR_DNSSEC_PROBE " '%s' received no response: %s", + var_dnssec_probe, vstring_str(why)); + break; + } + myfree(saved_dnssec_probe); + vstring_free(why); +} diff --git a/src/dns/dns_str_resflags.c b/src/dns/dns_str_resflags.c new file mode 100644 index 0000000..472394c --- /dev/null +++ b/src/dns/dns_str_resflags.c @@ -0,0 +1,128 @@ +/*++ +/* NAME +/* dns_str_resflags 3 +/* SUMMARY +/* convert resolver flags to printable form +/* SYNOPSIS +/* #include +/* +/* const char *dns_str_resflags(mask) +/* unsigned long mask; +/* DESCRIPTION +/* dns_str_resflags() converts RES_* resolver(5) flags from internal +/* form to printable string. Individual flag names are separated +/* with '|'. The result is overwritten with each call. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include +#include + + /* + * Utility library. + */ +#include + + /* + * DNS library. + */ +#include + + /* + * Application-specific. + */ + + /* + * This list overlaps with dns_res_opt_masks[] in smtp.c, but there we + * permit only a small subset of all possible flags. + */ +static const LONG_NAME_MASK resflag_table[] = { + "RES_INIT", RES_INIT, + "RES_DEBUG", RES_DEBUG, +#ifdef RES_AAONLY + "RES_AAONLY", RES_AAONLY, +#endif + "RES_USEVC", RES_USEVC, +#ifdef RES_PRIMARY + "RES_PRIMARY", RES_PRIMARY, +#endif + "RES_IGNTC", RES_IGNTC, + "RES_RECURSE", RES_RECURSE, + "RES_DEFNAMES", RES_DEFNAMES, + "RES_STAYOPEN", RES_STAYOPEN, + "RES_DNSRCH", RES_DNSRCH, +#ifdef RES_INSECURE1 + "RES_INSECURE1", RES_INSECURE1, +#endif +#ifdef RES_INSECURE2 + "RES_INSECURE2", RES_INSECURE2, +#endif + "RES_NOALIASES", RES_NOALIASES, +#ifdef RES_USE_INET6 + "RES_USE_INET6", RES_USE_INET6, +#endif +#ifdef RES_ROTATE + "RES_ROTATE", RES_ROTATE, +#endif +#ifdef RES_NOCHECKNAME + "RES_NOCHECKNAME", RES_NOCHECKNAME, +#endif + "RES_USE_EDNS0", RES_USE_EDNS0, + "RES_USE_DNSSEC", RES_USE_DNSSEC, +#ifdef RES_KEEPTSIG + "RES_KEEPTSIG", RES_KEEPTSIG, +#endif +#ifdef RES_BLAST + "RES_BLAST", RES_BLAST, +#endif +#ifdef RES_USEBSTRING + "RES_USEBSTRING", RES_USEBSTRING, +#endif +#ifdef RES_NSID + "RES_NSID", RES_NSID, +#endif +#ifdef RES_NOIP6DOTINT + "RES_NOIP6DOTINT", RES_NOIP6DOTINT, +#endif +#ifdef RES_USE_DNAME + "RES_USE_DNAME", RES_USE_DNAME, +#endif +#ifdef RES_NO_NIBBLE2 + "RES_NO_NIBBLE2", RES_NO_NIBBLE2, +#endif +#ifdef RES_SNGLKUP + "RES_SNGLKUP", RES_SNGLKUP, +#endif +#ifdef RES_SNGLKUPREOP + "RES_SNGLKUPREOP", RES_SNGLKUPREOP, +#endif +#ifdef RES_NOTLDQUERY + "RES_NOTLDQUERY", RES_NOTLDQUERY, +#endif + 0, +}; + +/* dns_str_resflags - convert RES_* resolver flags to printable form */ + +const char *dns_str_resflags(unsigned long mask) +{ + static VSTRING *buf; + + if (buf == 0) + buf = vstring_alloc(20); + return (str_long_name_mask_opt(buf, "dsns_str_resflags", resflag_table, + mask, NAME_MASK_NUMBER | NAME_MASK_PIPE)); +} diff --git a/src/dns/dns_strerror.c b/src/dns/dns_strerror.c new file mode 100644 index 0000000..9e56d3b --- /dev/null +++ b/src/dns/dns_strerror.c @@ -0,0 +1,69 @@ +/*++ +/* NAME +/* dns_strerror 3 +/* SUMMARY +/* name service lookup error code to string +/* SYNOPSIS +/* #include +/* +/* const char *dns_strerror(code) +/* int code; +/* DESCRIPTION +/* dns_strerror() maps a name service lookup error to printable string. +/* The result is for read-only purposes, and unknown codes share a +/* common string buffer. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include + +/* DNS library. */ + +#include "dns.h" + + /* + * Mapping from error code to printable string. The herror() routine does + * something similar, but has output only to the stderr stream. + */ +struct dns_error_map { + unsigned error; + const char *text; +}; + +static struct dns_error_map dns_error_map[] = { + HOST_NOT_FOUND, "Host not found", + TRY_AGAIN, "Host not found, try again", + NO_RECOVERY, "Non-recoverable error", + NO_DATA, "Host found but no data record of requested type", +}; + +/* dns_strerror - map resolver error code to printable string */ + +const char *dns_strerror(unsigned error) +{ + static VSTRING *unknown = 0; + unsigned i; + + for (i = 0; i < sizeof(dns_error_map) / sizeof(dns_error_map[0]); i++) + if (dns_error_map[i].error == error) + return (dns_error_map[i].text); + if (unknown == 0) + unknown = vstring_alloc(sizeof("Unknown error XXXXXX")); + vstring_sprintf(unknown, "Unknown error %u", error); + return (vstring_str(unknown)); +} diff --git a/src/dns/dns_strrecord.c b/src/dns/dns_strrecord.c new file mode 100644 index 0000000..6b8e989 --- /dev/null +++ b/src/dns/dns_strrecord.c @@ -0,0 +1,117 @@ +/*++ +/* NAME +/* dns_strrecord 3 +/* SUMMARY +/* name service resource record printable forms +/* SYNOPSIS +/* #include +/* +/* char *dns_strrecord(buf, record) +/* VSTRING *buf; +/* DNS_RR *record; +/* DESCRIPTION +/* dns_strrecord() formats a DNS resource record as "name ttl +/* class type preference value", where the class field is +/* always "IN", the preference field exists only for MX records, +/* and all names end in ".". The result value is the payload +/* of the buffer argument. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include /* memcpy */ + +/* Utility library. */ + +#include +#include + +/* DNS library. */ + +#include + +/* dns_strrecord - format resource record as generic string */ + +char *dns_strrecord(VSTRING *buf, DNS_RR *rr) +{ + const char myname[] = "dns_strrecord"; + MAI_HOSTADDR_STR host; + UINT32_TYPE soa_buf[5]; + + vstring_sprintf(buf, "%s. %u IN %s ", + rr->rname, rr->ttl, dns_strtype(rr->type)); + switch (rr->type) { + case T_A: +#ifdef T_AAAA + case T_AAAA: +#endif + if (dns_rr_to_pa(rr, &host) == 0) + msg_fatal("%s: conversion error for resource record type %s: %m", + myname, dns_strtype(rr->type)); + vstring_sprintf_append(buf, "%s", host.buf); + break; + case T_CNAME: + case T_DNAME: + case T_MB: + case T_MG: + case T_MR: + case T_NS: + case T_PTR: + vstring_sprintf_append(buf, "%s.", rr->data); + break; + case T_TXT: + vstring_sprintf_append(buf, "%s", rr->data); + break; + case T_MX: + vstring_sprintf_append(buf, "%u %s.", rr->pref, rr->data); + break; + case T_TLSA: + if (rr->data_len >= 3) { + uint8_t *ip = (uint8_t *) rr->data; + uint8_t usage = *ip++; + uint8_t selector = *ip++; + uint8_t mtype = *ip++; + unsigned i; + + /* /\.example\. \d+ IN TLSA \d+ \d+ \d+ [\da-f]*$/ IGNORE */ + vstring_sprintf_append(buf, "%d %d %d ", usage, selector, mtype); + for (i = 3; i < rr->data_len; ++i) + vstring_sprintf_append(buf, "%02x", *ip++); + } else { + vstring_sprintf_append(buf, "[truncated record]"); + } + + /* + * We use the SOA record TTL to determine the negative reply TTL. We + * save the time fields in the SOA record for debugging, but for now + * we don't bother saving the source host and mailbox information, as + * that would require changes to the DNS_RR structure. See also code + * in dns_get_rr(). + */ + case T_SOA: + memcpy(soa_buf, rr->data, sizeof(soa_buf)); + vstring_sprintf_append(buf, "- - %u %u %u %u %u", + soa_buf[0], soa_buf[1], soa_buf[2], + soa_buf[3], soa_buf[4]); + break; + default: + msg_fatal("%s: don't know how to print type %s", + myname, dns_strtype(rr->type)); + } + return (vstring_str(buf)); +} diff --git a/src/dns/dns_strtype.c b/src/dns/dns_strtype.c new file mode 100644 index 0000000..70e59ac --- /dev/null +++ b/src/dns/dns_strtype.c @@ -0,0 +1,211 @@ +/*++ +/* NAME +/* dns_strtype 3 +/* SUMMARY +/* name service lookup type codes and printable forms +/* SYNOPSIS +/* #include +/* +/* const char *dns_strtype(code) +/* int code; +/* +/* int dns_type(strval) +/* const char *strval; +/* DESCRIPTION +/* dns_strtype() maps a name service lookup type to printable string. +/* The result is for read-only purposes, and unknown codes share a +/* common string buffer. +/* +/* dns_type() converts a name service lookup string value to a numeric +/* code. A null result means the code was not found. The input can be +/* in lower case, upper case or mixed case. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include + +/* DNS library. */ + +#include "dns.h" + + /* + * Mapping from type code to printable string. Some names are possibly not + * defined on every platform, so I have #ifdef-ed them all just to be safe. + */ +struct dns_type_map { + unsigned type; + const char *text; +}; + +static struct dns_type_map dns_type_map[] = { +#ifdef T_A + T_A, "A", +#endif +#ifdef T_AAAA + T_AAAA, "AAAA", +#endif +#ifdef T_NS + T_NS, "NS", +#endif +#ifdef T_MD + T_MD, "MD", +#endif +#ifdef T_MF + T_MF, "MF", +#endif +#ifdef T_CNAME + T_CNAME, "CNAME", +#endif +#ifdef T_SOA + T_SOA, "SOA", +#endif +#ifdef T_MB + T_MB, "MB", +#endif +#ifdef T_MG + T_MG, "MG", +#endif +#ifdef T_MR + T_MR, "MR", +#endif +#ifdef T_NULL + T_NULL, "NULL", +#endif +#ifdef T_WKS + T_WKS, "WKS", +#endif +#ifdef T_PTR + T_PTR, "PTR", +#endif +#ifdef T_HINFO + T_HINFO, "HINFO", +#endif +#ifdef T_MINFO + T_MINFO, "MINFO", +#endif +#ifdef T_MX + T_MX, "MX", +#endif +#ifdef T_TXT + T_TXT, "TXT", +#endif +#ifdef T_RP + T_RP, "RP", +#endif +#ifdef T_AFSDB + T_AFSDB, "AFSDB", +#endif +#ifdef T_X25 + T_X25, "X25", +#endif +#ifdef T_ISDN + T_ISDN, "ISDN", +#endif +#ifdef T_RT + T_RT, "RT", +#endif +#ifdef T_NSAP + T_NSAP, "NSAP", +#endif +#ifdef T_NSAP_PTR + T_NSAP_PTR, "NSAP_PTR", +#endif +#ifdef T_SIG + T_SIG, "SIG", +#endif +#ifdef T_KEY + T_KEY, "KEY", +#endif +#ifdef T_PX + T_PX, "PX", +#endif +#ifdef T_GPOS + T_GPOS, "GPOS", +#endif +#ifdef T_AAAA + T_AAAA, "AAAA", +#endif +#ifdef T_LOC + T_LOC, "LOC", +#endif +#ifdef T_UINFO + T_UINFO, "UINFO", +#endif +#ifdef T_UID + T_UID, "UID", +#endif +#ifdef T_GID + T_GID, "GID", +#endif +#ifdef T_UNSPEC + T_UNSPEC, "UNSPEC", +#endif +#ifdef T_AXFR + T_AXFR, "AXFR", +#endif +#ifdef T_MAILB + T_MAILB, "MAILB", +#endif +#ifdef T_MAILA + T_MAILA, "MAILA", +#endif +#ifdef T_TLSA + T_TLSA, "TLSA", +#endif +#ifdef T_RRSIG + T_RRSIG, "RRSIG", +#endif +#ifdef T_DNAME + T_DNAME, "DNAME", +#endif +#ifdef T_ANY + T_ANY, "ANY", +#endif +}; + +/* dns_strtype - translate DNS query type to string */ + +const char *dns_strtype(unsigned type) +{ + static VSTRING *unknown = 0; + unsigned i; + + for (i = 0; i < sizeof(dns_type_map) / sizeof(dns_type_map[0]); i++) + if (dns_type_map[i].type == type) + return (dns_type_map[i].text); + if (unknown == 0) + unknown = vstring_alloc(sizeof("Unknown type XXXXXX")); + vstring_sprintf(unknown, "Unknown type %u", type); + return (vstring_str(unknown)); +} + +/* dns_type - translate string to DNS query type */ + +unsigned dns_type(const char *text) +{ + unsigned i; + + for (i = 0; i < sizeof(dns_type_map) / sizeof(dns_type_map[0]); i++) + if (strcasecmp(dns_type_map[i].text, text) == 0) + return (dns_type_map[i].type); + return (0); +} diff --git a/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref b/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref new file mode 100644 index 0000000..af7b6bc --- /dev/null +++ b/src/dns/dnsbl_ttl_127.0.0.1_bind_ncache.ref @@ -0,0 +1,12 @@ +./test_dns_lookup: lookup 1.0.0.127.zen.spamhaus.org type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: 1.0.0.127.zen.spamhaus.org (A): Host not found +./test_dns_lookup: dns_get_answer: type SOA for zen.spamhaus.org +./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.zen.spamhaus.org type=A: Host not found (rcode=3) +1.0.0.127.zen.spamhaus.org: fqdn: zen.spamhaus.org +ad: 0, rr: zen.spamhaus.org. TTL IN SOA - - D D D D D +./test_dns_lookup: lookup 1.0.0.127.bl.spamcop.net type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: 1.0.0.127.bl.spamcop.net (A): Host not found +./test_dns_lookup: dns_get_answer: type SOA for bl.spamcop.net +./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.bl.spamcop.net type=A: Host not found (rcode=3) +1.0.0.127.bl.spamcop.net: fqdn: bl.spamcop.net +ad: 0, rr: bl.spamcop.net. TTL IN SOA - - D D D D D diff --git a/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref b/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref new file mode 100644 index 0000000..b84554a --- /dev/null +++ b/src/dns/dnsbl_ttl_127.0.0.1_bind_plain.ref @@ -0,0 +1,6 @@ +./test_dns_lookup: lookup 1.0.0.127.zen.spamhaus.org type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: 1.0.0.127.zen.spamhaus.org (A): Host not found +./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.zen.spamhaus.org type=A: Host not found (rcode=3) +./test_dns_lookup: lookup 1.0.0.127.bl.spamcop.net type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: 1.0.0.127.bl.spamcop.net (A): Host not found +./test_dns_lookup: warning: Host or domain name not found. Name service error for name=1.0.0.127.bl.spamcop.net type=A: Host not found (rcode=3) diff --git a/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref b/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref new file mode 100644 index 0000000..81dd845 --- /dev/null +++ b/src/dns/dnsbl_ttl_127.0.0.2_bind_plain.ref @@ -0,0 +1,10 @@ +./test_dns_lookup: lookup 2.0.0.127.zen.spamhaus.org type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: 2.0.0.127.zen.spamhaus.org (A): OK +./test_dns_lookup: dns_get_answer: type A for 2.0.0.127.zen.spamhaus.org +2.0.0.127.zen.spamhaus.org: fqdn: 2.0.0.127.zen.spamhaus.org +ad: 0, rr: 2.0.0.127.zen.spamhaus.org. TTL IN A 127.0.0.D +./test_dns_lookup: lookup 2.0.0.127.bl.spamcop.net type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: 2.0.0.127.bl.spamcop.net (A): OK +./test_dns_lookup: dns_get_answer: type A for 2.0.0.127.bl.spamcop.net +2.0.0.127.bl.spamcop.net: fqdn: 2.0.0.127.bl.spamcop.net +ad: 0, rr: 2.0.0.127.bl.spamcop.net. TTL IN A 127.0.0.D diff --git a/src/dns/error.ref b/src/dns/error.ref new file mode 100644 index 0000000..c535153 --- /dev/null +++ b/src/dns/error.ref @@ -0,0 +1,13 @@ +./test_dns_lookup: lookup spike.porcupine.org type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: spike.porcupine.org (A): OK +./test_dns_lookup: dns_get_answer: type A for spike.porcupine.org +./test_dns_lookup: dict_regexp_lookup: error.reg: spike.porcupine.org. 3600 IN A 168.100.3.2 +./test_dns_lookup: maps_find: DNS reply filter: regexp:error.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN A 168.100.3.2 = oops +./test_dns_lookup: warning: DNS reply filter: unknown DNS filter action: "oops" +./test_dns_lookup: lookup spike.porcupine.org type AAAA flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: spike.porcupine.org (AAAA): OK +./test_dns_lookup: dns_get_answer: type AAAA for spike.porcupine.org +./test_dns_lookup: dict_regexp_lookup: error.reg: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 +./test_dns_lookup: maps_find: DNS reply filter: regexp:error.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 = oops +./test_dns_lookup: warning: DNS reply filter: unknown DNS filter action: "oops" +./test_dns_lookup: warning: Error looking up name=spike.porcupine.org type=AAAA: Invalid DNS reply filter syntax (rcode=0) diff --git a/src/dns/error.reg b/src/dns/error.reg new file mode 100644 index 0000000..4e553e8 --- /dev/null +++ b/src/dns/error.reg @@ -0,0 +1 @@ +/./ oops diff --git a/src/dns/mxonly_test.ref b/src/dns/mxonly_test.ref new file mode 100644 index 0000000..44f22d6 --- /dev/null +++ b/src/dns/mxonly_test.ref @@ -0,0 +1,11 @@ +./test_dns_lookup: lookup porcupine.org type MX flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: porcupine.org (MX): OK +./test_dns_lookup: dns_get_answer: type MX for porcupine.org +./test_dns_lookup: dns_get_answer: type MX for porcupine.org +./test_dns_lookup: dns_get_answer: type MX for porcupine.org +./test_dns_lookup: lookup porcupine.org type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: porcupine.org (A): Host found but no data record of requested type +ad: 0, rr: porcupine.org. 3600 IN MX 10 spike.porcupine.org. +ad: 0, rr: porcupine.org. 3600 IN MX 30 m1.porcupine.org. +ad: 0, rr: porcupine.org. 3600 IN MX 30 vz.porcupine.org. +porcupine.org: fqdn: porcupine.org diff --git a/src/dns/no-a.ref b/src/dns/no-a.ref new file mode 100644 index 0000000..5dff824 --- /dev/null +++ b/src/dns/no-a.ref @@ -0,0 +1,13 @@ +./test_dns_lookup: lookup spike.porcupine.org type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: spike.porcupine.org (A): OK +./test_dns_lookup: dns_get_answer: type A for spike.porcupine.org +./test_dns_lookup: dict_regexp_lookup: no-a.reg: spike.porcupine.org. 3600 IN A 168.100.3.2 +./test_dns_lookup: maps_find: DNS reply filter: regexp:no-a.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN A 168.100.3.2 = ignore +./test_dns_lookup: ignoring DNS RR: spike.porcupine.org. 3600 IN A 168.100.3.2 +./test_dns_lookup: lookup spike.porcupine.org type AAAA flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: spike.porcupine.org (AAAA): OK +./test_dns_lookup: dns_get_answer: type AAAA for spike.porcupine.org +./test_dns_lookup: dict_regexp_lookup: no-a.reg: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 +./test_dns_lookup: maps_find: DNS reply filter: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2: not found +spike.porcupine.org: fqdn: spike.porcupine.org +ad: 0, rr: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 diff --git a/src/dns/no-a.reg b/src/dns/no-a.reg new file mode 100644 index 0000000..69e05e5 --- /dev/null +++ b/src/dns/no-a.reg @@ -0,0 +1 @@ +/ +a +/ ignore diff --git a/src/dns/no-aaaa.ref b/src/dns/no-aaaa.ref new file mode 100644 index 0000000..657c69b --- /dev/null +++ b/src/dns/no-aaaa.ref @@ -0,0 +1,13 @@ +./test_dns_lookup: lookup spike.porcupine.org type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: spike.porcupine.org (A): OK +./test_dns_lookup: dns_get_answer: type A for spike.porcupine.org +./test_dns_lookup: dict_regexp_lookup: no-aaaa.reg: spike.porcupine.org. 3600 IN A 168.100.3.2 +./test_dns_lookup: maps_find: DNS reply filter: spike.porcupine.org. 3600 IN A 168.100.3.2: not found +./test_dns_lookup: lookup spike.porcupine.org type AAAA flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: spike.porcupine.org (AAAA): OK +./test_dns_lookup: dns_get_answer: type AAAA for spike.porcupine.org +./test_dns_lookup: dict_regexp_lookup: no-aaaa.reg: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 +./test_dns_lookup: maps_find: DNS reply filter: regexp:no-aaaa.reg(0,lock|fold_fix): spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 = ignore +./test_dns_lookup: ignoring DNS RR: spike.porcupine.org. 3600 IN AAAA 2604:8d00:189::2 +spike.porcupine.org: fqdn: spike.porcupine.org +ad: 0, rr: spike.porcupine.org. 3600 IN A 168.100.3.2 diff --git a/src/dns/no-aaaa.reg b/src/dns/no-aaaa.reg new file mode 100644 index 0000000..962adda --- /dev/null +++ b/src/dns/no-aaaa.reg @@ -0,0 +1 @@ +/ +aaaa +/ ignore diff --git a/src/dns/no-mx.ref b/src/dns/no-mx.ref new file mode 100644 index 0000000..5adc7bf --- /dev/null +++ b/src/dns/no-mx.ref @@ -0,0 +1,15 @@ +./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 10 spike.porcupine.org. +./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 30 m1.porcupine.org. +./test_dns_lookup: dict_regexp_lookup: no-mx.reg: porcupine.org. 3600 IN MX 30 vz.porcupine.org. +./test_dns_lookup: dns_get_answer: type MX for porcupine.org +./test_dns_lookup: dns_get_answer: type MX for porcupine.org +./test_dns_lookup: dns_get_answer: type MX for porcupine.org +./test_dns_lookup: dns_query: porcupine.org (MX): OK +./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 10 spike.porcupine.org. +./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 30 m1.porcupine.org. +./test_dns_lookup: ignoring DNS RR: porcupine.org. 3600 IN MX 30 vz.porcupine.org. +./test_dns_lookup: lookup porcupine.org type MX flags RES_USE_DNSSEC +./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 10 spike.porcupine.org. = ignore +./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 30 m1.porcupine.org. = ignore +./test_dns_lookup: maps_find: DNS reply filter: regexp:no-mx.reg(0,lock|fold_fix): porcupine.org. 3600 IN MX 30 vz.porcupine.org. = ignore +./test_dns_lookup: warning: Error looking up name=porcupine.org type=MX: DNS reply filter drops all results (rcode=0) diff --git a/src/dns/no-mx.reg b/src/dns/no-mx.reg new file mode 100644 index 0000000..69cf05d --- /dev/null +++ b/src/dns/no-mx.reg @@ -0,0 +1 @@ +/ +mx +/ ignore diff --git a/src/dns/no-txt.reg b/src/dns/no-txt.reg new file mode 100644 index 0000000..175600b --- /dev/null +++ b/src/dns/no-txt.reg @@ -0,0 +1 @@ +/ +txt +/ ignore diff --git a/src/dns/nullmx_test.ref b/src/dns/nullmx_test.ref new file mode 100644 index 0000000..1a9cab2 --- /dev/null +++ b/src/dns/nullmx_test.ref @@ -0,0 +1,8 @@ +./test_dns_lookup: lookup nullmx.porcupine.org type MX flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: nullmx.porcupine.org (MX): OK +./test_dns_lookup: dns_get_answer: type MX for nullmx.porcupine.org +./test_dns_lookup: lookup nullmx.porcupine.org type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: nullmx.porcupine.org (A): OK +./test_dns_lookup: dns_get_answer: type A for nullmx.porcupine.org +nullmx.porcupine.org: fqdn: nullmx.porcupine.org +ad: 0, rr: nullmx.porcupine.org. 3600 IN A 168.100.3.13 diff --git a/src/dns/nxdomain_test.ref b/src/dns/nxdomain_test.ref new file mode 100644 index 0000000..15be203 --- /dev/null +++ b/src/dns/nxdomain_test.ref @@ -0,0 +1,5 @@ +./test_dns_lookup: lookup nxdomain.porcupine.org type MX flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: nxdomain.porcupine.org (MX): Host not found +./test_dns_lookup: lookup nxdomain.porcupine.org type A flags RES_USE_DNSSEC +./test_dns_lookup: dns_query: nxdomain.porcupine.org (A): Host not found +./test_dns_lookup: warning: Host or domain name not found. Name service error for name=nxdomain.porcupine.org type=A: Host not found (rcode=3) diff --git a/src/dns/test_dns_lookup.c b/src/dns/test_dns_lookup.c new file mode 100644 index 0000000..e25f523 --- /dev/null +++ b/src/dns/test_dns_lookup.c @@ -0,0 +1,131 @@ +/*++ +/* NAME +/* test_dns_lookup 1 +/* SUMMARY +/* DNS lookup test program +/* SYNOPSIS +/* test_dns_lookup query-type domain-name +/* DESCRIPTION +/* test_dns_lookup performs a DNS query of the specified resource +/* type for the specified resource name. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include "dns.h" + +static void print_rr(VSTRING *buf, DNS_RR *rr) +{ + while (rr) { + vstream_printf("ad: %u, rr: %s\n", + rr->dnssec_valid, dns_strrecord(buf, rr)); + rr = rr->next; + } +} + +static NORETURN usage(char **argv) +{ + msg_fatal("usage: %s [-npv] [-f filter] types name", argv[0]); +} + +int main(int argc, char **argv) +{ + ARGV *types_argv; + unsigned *types; + char *name; + VSTRING *fqdn = vstring_alloc(100); + VSTRING *why = vstring_alloc(100); + VSTRING *buf; + int rcode; + DNS_RR *rr; + int i; + int ch; + int lflags = DNS_REQ_FLAG_NONE; + + var_dnssec_probe = ""; + + msg_vstream_init(argv[0], VSTREAM_ERR); + while ((ch = GETOPT(argc, argv, "f:npvs")) > 0) { + switch (ch) { + case 'v': + msg_verbose++; + break; + case 'f': + dns_rr_filter_compile("DNS reply filter", optarg); + break; + case 'n': + lflags |= DNS_REQ_FLAG_NCACHE_TTL; + break; + case 'p': + var_dns_ncache_ttl_fix = 1; + break; + case 's': + var_dnssec_probe = DEF_DNSSEC_PROBE; + break; + default: + usage(argv); + } + } + if (argc != optind + 2) + usage(argv); + types_argv = argv_split(argv[optind], CHARS_COMMA_SP); + types = (unsigned *) mymalloc(sizeof(*types) * (types_argv->argc + 1)); + for (i = 0; i < types_argv->argc; i++) + if ((types[i] = dns_type(types_argv->argv[i])) == 0) + msg_fatal("invalid query type: %s", types_argv->argv[i]); + types[i] = 0; + argv_free(types_argv); + name = argv[optind + 1]; + msg_verbose = 1; + switch (dns_lookup_rv(name, RES_USE_DNSSEC, &rr, fqdn, why, + &rcode, lflags, types)) { + default: + msg_warn("%s (rcode=%d)", vstring_str(why), rcode); + case DNS_OK: + if (rr) { + vstream_printf("%s: fqdn: %s\n", name, vstring_str(fqdn)); + buf = vstring_alloc(100); + print_rr(buf, rr); + dns_rr_free(rr); + vstring_free(buf); + vstream_fflush(VSTREAM_OUT); + } + } + myfree((void *) types); + exit(0); +} diff --git a/src/dnsblog/.indent.pro b/src/dnsblog/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/dnsblog/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/dnsblog/Makefile.in b/src/dnsblog/Makefile.in new file mode 100644 index 0000000..c2ed848 --- /dev/null +++ b/src/dnsblog/Makefile.in @@ -0,0 +1,84 @@ +SHELL = /bin/sh +SRCS = dnsblog.c +OBJS = dnsblog.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = dnsblog +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: test + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +dnsblog.o: ../../include/argv.h +dnsblog.o: ../../include/attr.h +dnsblog.o: ../../include/check_arg.h +dnsblog.o: ../../include/dns.h +dnsblog.o: ../../include/htable.h +dnsblog.o: ../../include/iostuff.h +dnsblog.o: ../../include/mail_conf.h +dnsblog.o: ../../include/mail_params.h +dnsblog.o: ../../include/mail_proto.h +dnsblog.o: ../../include/mail_server.h +dnsblog.o: ../../include/mail_version.h +dnsblog.o: ../../include/msg.h +dnsblog.o: ../../include/myaddrinfo.h +dnsblog.o: ../../include/mymalloc.h +dnsblog.o: ../../include/nvtable.h +dnsblog.o: ../../include/sock_addr.h +dnsblog.o: ../../include/sys_defs.h +dnsblog.o: ../../include/valid_hostname.h +dnsblog.o: ../../include/vbuf.h +dnsblog.o: ../../include/vstream.h +dnsblog.o: ../../include/vstring.h +dnsblog.o: dnsblog.c diff --git a/src/dnsblog/dnsblog.c b/src/dnsblog/dnsblog.c new file mode 100644 index 0000000..bc87c4b --- /dev/null +++ b/src/dnsblog/dnsblog.c @@ -0,0 +1,319 @@ +/*++ +/* NAME +/* dnsblog 8 +/* SUMMARY +/* Postfix DNS allow/denylist logger +/* SYNOPSIS +/* \fBdnsblog\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBdnsblog\fR(8) server implements an ad-hoc DNS +/* allow/denylist lookup service. This may eventually be +/* replaced by an UDP client that is built directly into the +/* \fBpostscreen\fR(8) server. +/* PROTOCOL +/* .ad +/* .fi +/* With each connection, the \fBdnsblog\fR(8) server receives +/* a DNS allow/denylist domain name, an IP address, and an ID. +/* If the IP address is listed under the DNS allow/denylist, the +/* \fBdnsblog\fR(8) server logs the match and replies with the +/* query arguments plus an address list with the resulting IP +/* addresses, separated by whitespace, and the reply TTL. +/* Otherwise it replies with the query arguments plus an empty +/* address list and the reply TTL; the reply TTL is -1 if there +/* is no reply, or a negative reply that contains no SOA record. +/* Finally, the \fBdnsblog\fR(8) server closes the connection. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically, as +/* \fBdnsblog\fR(8) processes run for only a limited amount +/* of time. Use the command "\fBpostfix reload\fR" to speed +/* up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBpostscreen_dnsbl_sites (empty)\fR" +/* Optional list of DNS allow/denylist domains, filters and weight +/* factors. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* smtpd(8), Postfix SMTP server +/* postconf(5), configuration parameters +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* This service was introduced with Postfix version 2.8. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* DNS library. */ + +#include + +/* Server skeleton. */ + +#include + +/* Application-specific. */ + + /* + * Tunable parameters. + */ +int var_dnsblog_delay; + + /* + * Static so we don't allocate and free on every request. + */ +static VSTRING *rbl_domain; +static VSTRING *addr; +static VSTRING *query; +static VSTRING *why; +static VSTRING *result; + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* static void dnsblog_query - query DNSBL for client address */ + +static VSTRING *dnsblog_query(VSTRING *result, int *result_ttl, + const char *dnsbl_domain, + const char *addr) +{ + const char *myname = "dnsblog_query"; + ARGV *octets; + int i; + struct addrinfo *res; + unsigned char *ipv6_addr; + int dns_status; + DNS_RR *addr_list; + DNS_RR *rr; + MAI_HOSTADDR_STR hostaddr; + + if (msg_verbose) + msg_info("%s: addr %s dnsbl_domain %s", + myname, addr, dnsbl_domain); + + VSTRING_RESET(query); + + /* + * Reverse the client IPV6 address, represented as 32 hexadecimal + * nibbles. We use the binary address to avoid tricky code. Asking for an + * AAAA record makes no sense here. Just like with IPv4 we use the lookup + * result as a bit mask, not as an IP address. + */ +#ifdef HAS_IPV6 + if (valid_ipv6_hostaddr(addr, DONT_GRIPE)) { + if (hostaddr_to_sockaddr(addr, (char *) 0, 0, &res) != 0 + || res->ai_family != PF_INET6) + msg_fatal("%s: unable to convert address %s", myname, addr); + ipv6_addr = (unsigned char *) &SOCK_ADDR_IN6_ADDR(res->ai_addr); + for (i = sizeof(SOCK_ADDR_IN6_ADDR(res->ai_addr)) - 1; i >= 0; i--) + vstring_sprintf_append(query, "%x.%x.", + ipv6_addr[i] & 0xf, ipv6_addr[i] >> 4); + freeaddrinfo(res); + } else +#endif + + /* + * Reverse the client IPV4 address, represented as four decimal octet + * values. We use the textual address for convenience. + */ + { + octets = argv_split(addr, "."); + for (i = octets->argc - 1; i >= 0; i--) { + vstring_strcat(query, octets->argv[i]); + vstring_strcat(query, "."); + } + argv_free(octets); + } + + /* + * Tack on the RBL domain name and query the DNS for an A record. + */ + vstring_strcat(query, dnsbl_domain); + dns_status = dns_lookup_x(STR(query), T_A, 0, &addr_list, (VSTRING *) 0, + why, (int *) 0, DNS_REQ_FLAG_NCACHE_TTL); + + /* + * We return the lowest TTL in the response from the A record(s) if + * found, or from the SOA record(s) if available. If the reply specifies + * no TTL, or if the query fails, we return a TTL of -1. + */ + VSTRING_RESET(result); + *result_ttl = -1; + if (dns_status == DNS_OK) { + for (rr = addr_list; rr != 0; rr = rr->next) { + if (dns_rr_to_pa(rr, &hostaddr) == 0) { + msg_warn("%s: skipping reply record type %s for query %s: %m", + myname, dns_strtype(rr->type), STR(query)); + } else { + msg_info("addr %s listed by domain %s as %s", + addr, dnsbl_domain, hostaddr.buf); + if (LEN(result) > 0) + vstring_strcat(result, " "); + vstring_strcat(result, hostaddr.buf); + /* Grab the positive reply TTL. */ + if (*result_ttl < 0 || *result_ttl > rr->ttl) + *result_ttl = rr->ttl; + } + } + dns_rr_free(addr_list); + } else if (dns_status == DNS_NOTFOUND) { + if (msg_verbose) + msg_info("%s: addr %s not listed by domain %s", + myname, addr, dnsbl_domain); + /* Grab the negative reply TTL. */ + for (rr = addr_list; rr != 0; rr = rr->next) { + if (rr->type == T_SOA && (*result_ttl < 0 || *result_ttl > rr->ttl)) + *result_ttl = rr->ttl; + } + dns_rr_free(addr_list); + } else { + msg_warn("%s: lookup error for DNS query %s: %s", + myname, STR(query), STR(why)); + } + VSTRING_TERMINATE(result); + return (result); +} + +/* dnsblog_service - perform service for client */ + +static void dnsblog_service(VSTREAM *client_stream, char *unused_service, + char **argv) +{ + int request_id; + int result_ttl; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the socket dedicated + * to the dnsblog service. All connection-management stuff is handled by + * the common code in single_server.c. + */ + if (attr_scan(client_stream, + ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, rbl_domain), + RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, addr), + RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id), + ATTR_TYPE_END) == 3) { + (void) dnsblog_query(result, &result_ttl, STR(rbl_domain), STR(addr)); + if (var_dnsblog_delay > 0) + sleep(var_dnsblog_delay); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, STR(rbl_domain)), + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, STR(addr)), + SEND_ATTR_INT(MAIL_ATTR_LABEL, request_id), + SEND_ATTR_STR(MAIL_ATTR_RBL_ADDR, STR(result)), + SEND_ATTR_INT(MAIL_ATTR_TTL, result_ttl), + ATTR_TYPE_END); + vstream_fflush(client_stream); + } +} + +/* post_jail_init - post-jail initialization */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + rbl_domain = vstring_alloc(100); + addr = vstring_alloc(100); + query = vstring_alloc(100); + why = vstring_alloc(100); + result = vstring_alloc(100); + var_use_limit = 0; +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the multi-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_DNSBLOG_DELAY, DEF_DNSBLOG_DELAY, &var_dnsblog_delay, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, dnsblog_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_UNLIMITED, + CA_MAIL_SERVER_RETIRE_ME, + 0); +} diff --git a/src/error/.indent.pro b/src/error/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/error/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/error/.printfck b/src/error/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/error/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/error/Makefile.in b/src/error/Makefile.in new file mode 100644 index 0000000..c98a267 --- /dev/null +++ b/src/error/Makefile.in @@ -0,0 +1,89 @@ +SHELL = /bin/sh +SRCS = error.c +OBJS = error.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = error +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +error.o: ../../include/attr.h +error.o: ../../include/bounce.h +error.o: ../../include/check_arg.h +error.o: ../../include/defer.h +error.o: ../../include/deliver_completed.h +error.o: ../../include/deliver_request.h +error.o: ../../include/dsn.h +error.o: ../../include/dsn_buf.h +error.o: ../../include/dsn_util.h +error.o: ../../include/flush_clnt.h +error.o: ../../include/htable.h +error.o: ../../include/iostuff.h +error.o: ../../include/mail_conf.h +error.o: ../../include/mail_proto.h +error.o: ../../include/mail_queue.h +error.o: ../../include/mail_server.h +error.o: ../../include/mail_version.h +error.o: ../../include/msg.h +error.o: ../../include/msg_stats.h +error.o: ../../include/mymalloc.h +error.o: ../../include/nvtable.h +error.o: ../../include/recipient_list.h +error.o: ../../include/sys_defs.h +error.o: ../../include/sys_exits.h +error.o: ../../include/vbuf.h +error.o: ../../include/vstream.h +error.o: ../../include/vstring.h +error.o: error.c diff --git a/src/error/error.c b/src/error/error.c new file mode 100644 index 0000000..61e805b --- /dev/null +++ b/src/error/error.c @@ -0,0 +1,266 @@ +/*++ +/* NAME +/* error 8 +/* SUMMARY +/* Postfix error/retry mail delivery agent +/* SYNOPSIS +/* \fBerror\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The Postfix \fBerror\fR(8) delivery agent processes delivery +/* requests from +/* the queue manager. Each request specifies a queue file, a sender +/* address, the reason for non-delivery (specified as the +/* next-hop destination), and recipient information. +/* The reason may be prefixed with an RFC 3463-compatible detail code; +/* if none is specified a default 4.0.0 or 5.0.0 code is used instead. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* Depending on the service name in master.cf, \fBerror\fR +/* or \fBretry\fR, the server bounces or defers all recipients +/* in the delivery request using the "next-hop" information +/* as the reason for non-delivery. The \fBretry\fR service name is +/* supported as of Postfix 2.4. +/* +/* Delivery status reports are sent to the \fBbounce\fR(8), +/* \fBdefer\fR(8) or \fBtrace\fR(8) daemon as appropriate. +/* SECURITY +/* .ad +/* .fi +/* The \fBerror\fR(8) mailer is not security-sensitive. It does not talk +/* to the network, and can be run chrooted at fixed low privilege. +/* STANDARDS +/* RFC 3463 (Enhanced Status Codes) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces and of other trouble. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically as \fBerror\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fB2bounce_notice_recipient (postmaster)\fR" +/* The recipient of undeliverable mail that cannot be returned to +/* the sender. +/* .IP "\fBbounce_notice_recipient (postmaster)\fR" +/* The recipient of postmaster notifications with the message headers +/* of mail that Postfix did not deliver and of SMTP conversation +/* transcripts of mail that Postfix did not receive. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBdouble_bounce_sender (double-bounce)\fR" +/* The sender address of postmaster notifications that are generated +/* by the mail system. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBnotify_classes (resource, software)\fR" +/* The list of error classes that are reported to the postmaster. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* qmgr(8), queue manager +/* bounce(8), delivery status reports +/* discard(8), Postfix discard delivery agent +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single server skeleton. */ + +#include + +/* deliver_message - deliver message with extreme prejudice */ + +static int deliver_message(DELIVER_REQUEST *request, const char *def_dsn, + int (*append) (int, const char *, MSG_STATS *, RECIPIENT *, + const char *, DSN *)) +{ + const char *myname = "deliver_message"; + VSTREAM *src; + int result = 0; + int status; + RECIPIENT *rcpt; + int nrcpt; + DSN_SPLIT dp; + DSN dsn; + + if (msg_verbose) + msg_info("deliver_message: from %s", request->sender); + + /* + * Sanity checks. + */ + if (request->nexthop[0] == 0) + msg_fatal("empty nexthop hostname"); + if (request->rcpt_list.len <= 0) + msg_fatal("recipient count: %d", request->rcpt_list.len); + + /* + * Open the queue file. Opening the file can fail for a variety of + * reasons, such as the system running out of resources. Instead of + * throwing away mail, we're raising a fatal error which forces the mail + * system to back off, and retry later. + */ + src = mail_queue_open(request->queue_name, request->queue_id, + O_RDWR, 0); + if (src == 0) + msg_fatal("%s: open %s %s: %m", myname, + request->queue_name, request->queue_id); + if (msg_verbose) + msg_info("%s: file %s", myname, VSTREAM_PATH(src)); + + /* + * Bounce/defer/whatever all recipients. + */ +#define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS(request->flags) + + dsn_split(&dp, def_dsn, request->nexthop); + (void) DSN_SIMPLE(&dsn, DSN_STATUS(dp.dsn), dp.text); + for (nrcpt = 0; nrcpt < request->rcpt_list.len; nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + status = append(BOUNCE_FLAGS(request), request->queue_id, + &request->msg_stats, rcpt, "none", &dsn); + if (status == 0) + deliver_completed(src, rcpt->offset); + result |= status; + } + + /* + * Clean up. + */ + if (vstream_fclose(src)) + msg_warn("close %s %s: %m", request->queue_name, request->queue_id); + + return (result); +} + +/* error_service - perform service for client */ + +static void error_service(VSTREAM *client_stream, char *service, char **argv) +{ + DELIVER_REQUEST *request; + int status; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * dedicated to the error mailer. What we see below is a little protocol + * to (1) tell the queue manager that we are ready, (2) read a request + * from the queue manager, and (3) report the completion status of that + * request. All connection-management stuff is handled by the common code + * in single_server.c. + */ + if ((request = deliver_request_read(client_stream)) != 0) { + if (strcmp(service, MAIL_SERVICE_ERROR) == 0) + status = deliver_message(request, "5.0.0", bounce_append); + else if (strcmp(service, MAIL_SERVICE_RETRY) == 0) + status = deliver_message(request, "4.0.0", defer_append); + else + msg_fatal("bad error service name: %s", service); + deliver_request_done(client_stream, request, status); + } +} + +/* pre_init - pre-jail initialization */ + +static void pre_init(char *unused_name, char **unused_argv) +{ + flush_init(); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, error_service, + CA_MAIL_SERVER_PRE_INIT(pre_init), + 0); +} diff --git a/src/flush/.indent.pro b/src/flush/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/flush/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/flush/.printfck b/src/flush/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/flush/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/flush/Makefile.in b/src/flush/Makefile.in new file mode 100644 index 0000000..8d490bf --- /dev/null +++ b/src/flush/Makefile.in @@ -0,0 +1,96 @@ +SHELL = /bin/sh +SRCS = flush.c +OBJS = flush.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = flush +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +flush.o: ../../include/argv.h +flush.o: ../../include/attr.h +flush.o: ../../include/check_arg.h +flush.o: ../../include/dict.h +flush.o: ../../include/domain_list.h +flush.o: ../../include/events.h +flush.o: ../../include/flush_clnt.h +flush.o: ../../include/htable.h +flush.o: ../../include/iostuff.h +flush.o: ../../include/mail_conf.h +flush.o: ../../include/mail_flush.h +flush.o: ../../include/mail_params.h +flush.o: ../../include/mail_proto.h +flush.o: ../../include/mail_queue.h +flush.o: ../../include/mail_scan_dir.h +flush.o: ../../include/mail_server.h +flush.o: ../../include/mail_version.h +flush.o: ../../include/maps.h +flush.o: ../../include/match_list.h +flush.o: ../../include/match_parent_style.h +flush.o: ../../include/midna_domain.h +flush.o: ../../include/msg.h +flush.o: ../../include/myflock.h +flush.o: ../../include/mymalloc.h +flush.o: ../../include/nvtable.h +flush.o: ../../include/safe_open.h +flush.o: ../../include/scan_dir.h +flush.o: ../../include/stringops.h +flush.o: ../../include/sys_defs.h +flush.o: ../../include/vbuf.h +flush.o: ../../include/vstream.h +flush.o: ../../include/vstring.h +flush.o: ../../include/vstring_vstream.h +flush.o: ../../include/warn_stat.h +flush.o: flush.c diff --git a/src/flush/flush.c b/src/flush/flush.c new file mode 100644 index 0000000..b8fae77 --- /dev/null +++ b/src/flush/flush.c @@ -0,0 +1,865 @@ +/*++ +/* NAME +/* flush 8 +/* SUMMARY +/* Postfix fast flush server +/* SYNOPSIS +/* \fBflush\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBflush\fR(8) server maintains a record of deferred +/* mail by destination. +/* This information is used to improve the performance of the SMTP +/* \fBETRN\fR request, and of its command-line equivalent, +/* "\fBsendmail -qR\fR" or "\fBpostqueue -f\fR". +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* The record is implemented as a per-destination logfile with +/* as contents the queue IDs of deferred mail. A logfile is +/* append-only, and is truncated when delivery is requested +/* for the corresponding destination. A destination is the +/* part on the right-hand side of the right-most \fB@\fR in +/* an email address. +/* +/* Per-destination logfiles of deferred mail are maintained only for +/* eligible destinations. The list of eligible destinations is +/* specified with the \fBfast_flush_domains\fR configuration parameter, +/* which defaults to \fB$relay_domains\fR. +/* +/* This server implements the following requests: +/* .IP "\fBadd\fI sitename queueid\fR" +/* Inform the \fBflush\fR(8) server that the message with the specified +/* queue ID is queued for the specified destination. +/* .IP "\fBsend_site\fI sitename\fR" +/* Request delivery of mail that is queued for the specified +/* destination. +/* .IP "\fBsend_file\fI queueid\fR" +/* Request delivery of the specified deferred message. +/* .IP \fBrefresh\fR +/* Refresh non-empty per-destination logfiles that were not read in +/* \fB$fast_flush_refresh_time\fR hours, by simulating +/* send requests (see above) for the corresponding destinations. +/* .sp +/* Delete empty per-destination logfiles that were not updated in +/* \fB$fast_flush_purge_time\fR days. +/* .sp +/* This request completes in the background. +/* .IP \fBpurge\fR +/* Do a \fBrefresh\fR for all per-destination logfiles. +/* SECURITY +/* .ad +/* .fi +/* The \fBflush\fR(8) server is not security-sensitive. It does not +/* talk to the network, and it does not talk to local users. +/* The fast flush server can run chrooted at fixed low privilege. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* Fast flush logfiles are truncated only after a "send" +/* request, not when mail is actually delivered, and therefore can +/* accumulate outdated or redundant data. In order to maintain sanity, +/* "refresh" must be executed periodically. This can +/* be automated with a suitable wakeup timer setting in the +/* \fBmaster.cf\fR configuration file. +/* +/* Upon receipt of a request to deliver mail for an eligible +/* destination, the \fBflush\fR(8) server requests delivery of all messages +/* that are listed in that destination's logfile, regardless of the +/* recipients of those messages. This is not an issue for mail +/* that is sent to a \fBrelay_domains\fR destination because +/* such mail typically only has recipients in one domain. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically as \fBflush\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBfast_flush_domains ($relay_domains)\fR" +/* Optional list of destinations that are eligible for per-destination +/* logfiles with mail that is queued to those destinations. +/* .IP "\fBfast_flush_refresh_time (12h)\fR" +/* The time after which a non-empty but unread per-destination "fast +/* flush" logfile needs to be refreshed. +/* .IP "\fBfast_flush_purge_time (7d)\fR" +/* The time after which an empty per-destination "fast flush" logfile +/* is deleted. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR" +/* A list of Postfix features where the pattern "example.com" also +/* matches subdomains of example.com, +/* instead of requiring an explicit ".example.com" pattern. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* FILES +/* /var/spool/postfix/flush, "fast flush" logfiles. +/* SEE ALSO +/* smtpd(8), SMTP server +/* qmgr(8), queue manager +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* ETRN_README, Postfix ETRN howto +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* This service was introduced with Postfix version 1.0. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single server skeleton. */ + +#include + +/* Application-specific. */ + + /* + * Tunable parameters. The fast_flush_domains parameter is not defined here, + * because it is also used by the global library, and therefore is owned by + * the library. + */ +int var_fflush_refresh; +int var_fflush_purge; + + /* + * Flush policy stuff. + */ +static DOMAIN_LIST *flush_domains; + + /* + * Some hard-wired policy: how many queue IDs we remember while we're + * flushing a logfile (duplicate elimination). Sites with 1000+ emails + * queued should arrange for permanent connectivity. + */ +#define FLUSH_DUP_FILTER_SIZE 10000 /* graceful degradation */ + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define STREQ(x,y) (STRREF(x) == STRREF(y) || strcmp(x,y) == 0) + + /* + * Forward declarations resulting from breaking up routines according to + * name space: domain names versus safe-to-use pathnames. + */ +static int flush_add_path(const char *, const char *); +static int flush_send_path(const char *, int); + + /* + * Do we only refresh the per-destination logfile, or do we really request + * mail delivery as if someone sent ETRN? If the latter, we must override + * information about unavailable hosts or unavailable transports. + * + * When selectively flushing deferred mail, we need to override the queue + * manager's "dead destination" information and unthrottle transports and + * queues. There are two options: + * + * - Unthrottle all transports and queues before we move mail to the incoming + * queue. This is less accurate, but has the advantage when flushing lots of + * mail, because Postfix can skip delivery of flushed messages after it + * discovers that a destination is (still) unavailable. + * + * - Unthrottle some transports and queues after the queue manager moves mail + * to the active queue. This is more accurate, but has the disadvantage when + * flushing lots of mail, because Postfix cannot skip delivery of flushed + * messages after it discovers that a destination is (still) unavailable. + */ +#define REFRESH_ONLY 0 +#define UNTHROTTLE_BEFORE (1<<0) +#define UNTHROTTLE_AFTER (1<<1) + +/* flush_site_to_path - convert domain or [addr] to harmless string */ + +static VSTRING *flush_site_to_path(VSTRING *path, const char *site) +{ + const char *ptr; + int ch; + + /* + * Convert the name to ASCII, so that we don't to end up with non-ASCII + * names in the file system. The IDNA library functions fold case. + */ +#ifndef NO_EAI + if ((site = midna_domain_to_ascii(site)) == 0) + return (0); +#endif + + /* + * Allocate buffer on the fly; caller still needs to clean up. + */ + if (path == 0) + path = vstring_alloc(10); + + /* + * Mask characters that could upset the name-to-queue-file mapping code. + */ + for (ptr = site; (ch = *(unsigned const char *) ptr) != 0; ptr++) + if (ISALNUM(ch)) + VSTRING_ADDCH(path, tolower(ch)); + else + VSTRING_ADDCH(path, '_'); + VSTRING_TERMINATE(path); + + if (msg_verbose) + msg_info("site %s to path %s", site, STR(path)); + + return (path); +} + +/* flush_add_service - append queue ID to per-site fast flush logfile */ + +static int flush_add_service(const char *site, const char *queue_id) +{ + const char *myname = "flush_add_service"; + VSTRING *site_path; + int status; + + if (msg_verbose) + msg_info("%s: site %s queue_id %s", myname, site, queue_id); + + /* + * If this site is not eligible for logging, deny the request. + */ + if (domain_list_match(flush_domains, site) == 0) + return (flush_domains->error ? FLUSH_STAT_FAIL : FLUSH_STAT_DENY); + + /* + * Map site to path and update log. + */ + if ((site_path = flush_site_to_path((VSTRING *) 0, site)) == 0) + return (FLUSH_STAT_DENY); + status = flush_add_path(STR(site_path), queue_id); + vstring_free(site_path); + + return (status); +} + +/* flush_add_path - add record to log */ + +static int flush_add_path(const char *path, const char *queue_id) +{ + const char *myname = "flush_add_path"; + VSTREAM *log; + + /* + * Sanity check. + */ + if (!mail_queue_id_ok(path)) + return (FLUSH_STAT_BAD); + + /* + * Open the logfile or bust. + */ + if ((log = mail_queue_open(MAIL_QUEUE_FLUSH, path, + O_CREAT | O_APPEND | O_WRONLY, 0600)) == 0) + msg_fatal("%s: open fast flush logfile %s: %m", myname, path); + + /* + * We must lock the logfile, so that we don't lose information due to + * concurrent access. If the lock takes too long, the Postfix watchdog + * will eventually take care of the problem, but it will take a while. + */ + if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock fast flush logfile %s: %m", myname, path); + + /* + * Append the queue ID. With 15 bits of microsecond time, a queue ID is + * not recycled often enough for false hits to be a problem. If it does, + * then we could add other signature information, such as the file size + * in bytes. + */ + vstream_fprintf(log, "%s\n", queue_id); + if (vstream_fflush(log)) + msg_warn("write fast flush logfile %s: %m", path); + + /* + * Clean up. + */ + if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock fast flush logfile %s: %m", myname, path); + if (vstream_fclose(log) != 0) + msg_warn("write fast flush logfile %s: %m", path); + + return (FLUSH_STAT_OK); +} + +/* flush_send_service - flush mail queued for site */ + +static int flush_send_service(const char *site, int how) +{ + const char *myname = "flush_send_service"; + VSTRING *site_path; + int status; + + if (msg_verbose) + msg_info("%s: site %s", myname, site); + + /* + * If this site is not eligible for logging, deny the request. + */ + if (domain_list_match(flush_domains, site) == 0) + return (flush_domains->error ? FLUSH_STAT_FAIL : FLUSH_STAT_DENY); + + /* + * Map site name to path name and flush the log. + */ + if ((site_path = flush_site_to_path((VSTRING *) 0, site)) == 0) + return (FLUSH_STAT_DENY); + status = flush_send_path(STR(site_path), how); + vstring_free(site_path); + + return (status); +} + +/* flush_one_file - move one queue file to incoming queue */ + +static int flush_one_file(const char *queue_id, VSTRING *queue_file, + struct utimbuf * tbuf, int how) +{ + const char *myname = "flush_one_file"; + const char *queue_name; + const char *path; + + /* + * Some other instance of this program may flush some logfile and may + * just have moved this queue file to the incoming queue. + */ + for (queue_name = MAIL_QUEUE_DEFERRED; /* see below */ ; + queue_name = MAIL_QUEUE_INCOMING) { + path = mail_queue_path(queue_file, queue_name, queue_id); + if (utime(path, tbuf) == 0) + break; + if (errno != ENOENT) + msg_warn("%s: update %s time stamps: %m", myname, path); + if (STREQ(queue_name, MAIL_QUEUE_INCOMING)) + return (0); + } + + /* + * With the UNTHROTTLE_AFTER strategy, we leave it up to the queue + * manager to unthrottle transports and queues as it reads recipients + * from a queue file. We request this unthrottle operation by setting the + * group read permission bit. + * + * Note: we must avoid using chmod(). It is not only slower than fchmod() + * but it is also less secure. With chmod(), an attacker could repeatedly + * send requests to the flush server and trick it into changing + * permissions of non-queue files, by exploiting a race condition. + * + * We use safe_open() because we don't validate the file content before + * modifying the file status. + */ + if (how & UNTHROTTLE_AFTER) { + VSTRING *why; + struct stat st; + VSTREAM *fp; + + for (why = vstring_alloc(1); /* see below */ ; + queue_name = MAIL_QUEUE_INCOMING, + path = mail_queue_path(queue_file, queue_name, queue_id)) { + if ((fp = safe_open(path, O_RDWR, 0, &st, -1, -1, why)) != 0) + break; + if (errno != ENOENT) + msg_warn("%s: open %s: %s", myname, path, STR(why)); + if (errno != ENOENT || STREQ(queue_name, MAIL_QUEUE_INCOMING)) { + vstring_free(why); + return (0); + } + } + vstring_free(why); + if ((st.st_mode & MAIL_QUEUE_STAT_READY) != MAIL_QUEUE_STAT_READY) { + (void) vstream_fclose(fp); + return (0); + } + if (fchmod(vstream_fileno(fp), st.st_mode | MAIL_QUEUE_STAT_UNTHROTTLE) < 0) + msg_warn("%s: fchmod %s: %m", myname, path); + (void) vstream_fclose(fp); + } + + /* + * Move the file to the incoming queue, if it isn't already there. + */ + if (STREQ(queue_name, MAIL_QUEUE_INCOMING) == 0 + && mail_queue_rename(queue_id, queue_name, MAIL_QUEUE_INCOMING) < 0 + && errno != ENOENT) + msg_warn("%s: rename from %s to %s: %m", + path, queue_name, MAIL_QUEUE_INCOMING); + + /* + * If we got here, we achieved something, so let's claim success. + */ + return (1); +} + +/* flush_send_path - flush logfile file */ + +static int flush_send_path(const char *path, int how) +{ + const char *myname = "flush_send_path"; + VSTRING *queue_id; + VSTRING *queue_file; + VSTREAM *log; + struct utimbuf tbuf; + static char qmgr_flush_trigger[] = { + QMGR_REQ_FLUSH_DEAD, /* flush dead site/transport cache */ + }; + static char qmgr_scan_trigger[] = { + QMGR_REQ_SCAN_INCOMING, /* scan incoming queue */ + }; + HTABLE *dup_filter; + int count; + + /* + * Sanity check. + */ + if (!mail_queue_id_ok(path)) + return (FLUSH_STAT_BAD); + + /* + * Open the logfile. If the file does not exist, then there is no queued + * mail for this destination. + */ + if ((log = mail_queue_open(MAIL_QUEUE_FLUSH, path, O_RDWR, 0600)) == 0) { + if (errno != ENOENT) + msg_fatal("%s: open fast flush logfile %s: %m", myname, path); + return (FLUSH_STAT_OK); + } + + /* + * We must lock the logfile, so that we don't lose information when it is + * truncated. Unfortunately, this means that the file can be locked for a + * significant amount of time. If things really get stuck the Postfix + * watchdog will take care of it. + */ + if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock fast flush logfile %s: %m", myname, path); + + /* + * With the UNTHROTTLE_BEFORE strategy, we ask the queue manager to + * unthrottle all transports and queues before we move a deferred queue + * file to the incoming queue. This minimizes a race condition where the + * queue manager seizes a queue file before it knows that we want to + * flush that message. + * + * This reduces the race condition time window to a very small amount (the + * flush server does not really know when the queue manager reads its + * command fifo). But there is a worse race, where the queue manager + * moves a deferred queue file to the active queue before we have a + * chance to expedite its delivery. + */ + if (how & UNTHROTTLE_BEFORE) + mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service, + qmgr_flush_trigger, sizeof(qmgr_flush_trigger)); + + /* + * This is the part that dominates running time: schedule the listed + * queue files for delivery by updating their file time stamps and by + * moving them from the deferred queue to the incoming queue. This should + * take no more than a couple seconds under normal conditions. Filter out + * duplicate queue file names to avoid hammering the file system, with + * some finite limit on the amount of memory that we are willing to + * sacrifice for duplicate filtering. Graceful degradation. + * + * By moving selected queue files from the deferred queue to the incoming + * queue we optimize for the case where most deferred mail is for other + * sites. If that assumption does not hold, i.e. all deferred mail is for + * the same site, then doing a "fast flush" will cost more disk I/O than + * a "slow flush" that delivers the entire deferred queue. This penalty + * is only temporary - it will go away after we unite the active queue + * and the incoming queue. + */ + queue_id = vstring_alloc(10); + queue_file = vstring_alloc(10); + dup_filter = htable_create(10); + tbuf.actime = tbuf.modtime = event_time(); + for (count = 0; vstring_get_nonl(queue_id, log) != VSTREAM_EOF; count++) { + if (!mail_queue_id_ok(STR(queue_id))) { + msg_warn("bad queue id \"%.30s...\" in fast flush logfile %s", + STR(queue_id), path); + continue; + } + if (dup_filter->used >= FLUSH_DUP_FILTER_SIZE + || htable_find(dup_filter, STR(queue_id)) == 0) { + if (msg_verbose) + msg_info("%s: logfile %s: update queue file %s time stamps", + myname, path, STR(queue_id)); + if (dup_filter->used <= FLUSH_DUP_FILTER_SIZE) + htable_enter(dup_filter, STR(queue_id), 0); + count += flush_one_file(STR(queue_id), queue_file, &tbuf, how); + } else { + if (msg_verbose) + msg_info("%s: logfile %s: skip queue file %s as duplicate", + myname, path, STR(queue_file)); + } + } + htable_free(dup_filter, (void (*) (void *)) 0); + vstring_free(queue_file); + vstring_free(queue_id); + + /* + * Truncate the fast flush log. + */ + if (count > 0 && ftruncate(vstream_fileno(log), (off_t) 0) < 0) + msg_fatal("%s: truncate fast flush logfile %s: %m", myname, path); + + /* + * Workaround for noatime mounts. Use futimes() if available. + */ + (void) utimes(VSTREAM_PATH(log), (struct timeval *) 0); + + /* + * Request delivery and clean up. + */ + if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock fast flush logfile %s: %m", myname, path); + if (vstream_fclose(log) != 0) + msg_warn("%s: read fast flush logfile %s: %m", myname, path); + if (count > 0) { + if (msg_verbose) + msg_info("%s: requesting delivery for logfile %s", myname, path); + mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service, + qmgr_scan_trigger, sizeof(qmgr_scan_trigger)); + } + return (FLUSH_STAT_OK); +} + +/* flush_send_file_service - flush one queue file */ + +static int flush_send_file_service(const char *queue_id) +{ + const char *myname = "flush_send_file_service"; + VSTRING *queue_file; + struct utimbuf tbuf; + static char qmgr_scan_trigger[] = { + QMGR_REQ_SCAN_INCOMING, /* scan incoming queue */ + }; + + /* + * Sanity check. + */ + if (!mail_queue_id_ok(queue_id)) + return (FLUSH_STAT_BAD); + + if (msg_verbose) + msg_info("%s: requesting delivery for queue_id %s", myname, queue_id); + + queue_file = vstring_alloc(30); + tbuf.actime = tbuf.modtime = event_time(); + if (flush_one_file(queue_id, queue_file, &tbuf, UNTHROTTLE_AFTER) > 0) + mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service, + qmgr_scan_trigger, sizeof(qmgr_scan_trigger)); + vstring_free(queue_file); + + return (FLUSH_STAT_OK); +} + +/* flush_refresh_service - refresh logfiles beyond some age */ + +static int flush_refresh_service(int max_age) +{ + const char *myname = "flush_refresh_service"; + SCAN_DIR *scan; + char *site_path; + struct stat st; + VSTRING *path = vstring_alloc(10); + + scan = scan_dir_open(MAIL_QUEUE_FLUSH); + while ((site_path = mail_scan_dir_next(scan)) != 0) { + if (!mail_queue_id_ok(site_path)) + continue; /* XXX grumble. */ + mail_queue_path(path, MAIL_QUEUE_FLUSH, site_path); + if (stat(STR(path), &st) < 0) { + if (errno != ENOENT) + msg_warn("%s: stat %s: %m", myname, STR(path)); + else if (msg_verbose) + msg_info("%s: %s: %m", myname, STR(path)); + continue; + } + if (st.st_size == 0) { + if (st.st_mtime + var_fflush_purge < event_time()) { + if (unlink(STR(path)) < 0) + msg_warn("remove logfile %s: %m", STR(path)); + else if (msg_verbose) + msg_info("%s: unlink %s, empty and unchanged for %d days", + myname, STR(path), var_fflush_purge / 86400); + } else if (msg_verbose) + msg_info("%s: skip logfile %s - empty log", myname, site_path); + } else if (st.st_atime + max_age < event_time()) { + if (msg_verbose) + msg_info("%s: flush logfile %s", myname, site_path); + flush_send_path(site_path, REFRESH_ONLY); + } else { + if (msg_verbose) + msg_info("%s: skip logfile %s, unread for <%d hours(s) ", + myname, site_path, max_age / 3600); + } + } + scan_dir_close(scan); + vstring_free(path); + + return (FLUSH_STAT_OK); +} + +/* flush_request_receive - receive request */ + +static int flush_request_receive(VSTREAM *client_stream, VSTRING *request) +{ + int count; + + /* + * Announce the protocol. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_FLUSH), + ATTR_TYPE_END); + (void) vstream_fflush(client_stream); + + /* + * Kluge: choose the protocol depending on the request size. + */ + if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) { + msg_warn("timeout while waiting for data from %s", + VSTREAM_PATH(client_stream)); + return (-1); + } + if ((count = peekfd(vstream_fileno(client_stream))) < 0) { + msg_warn("cannot examine read buffer of %s: %m", + VSTREAM_PATH(client_stream)); + return (-1); + } + + /* + * Short request: master trigger. Use the string+null protocol. + */ + if (count <= 2) { + if (vstring_get_null(request, client_stream) == VSTREAM_EOF) { + msg_warn("end-of-input while reading request from %s: %m", + VSTREAM_PATH(client_stream)); + return (-1); + } + } + + /* + * Long request: real flush client. Use the attribute list protocol. + */ + else { + if (attr_scan(client_stream, + ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_REQ, request), + ATTR_TYPE_END) != 1) { + return (-1); + } + } + return (0); +} + +/* flush_service - perform service for client */ + +static void flush_service(VSTREAM *client_stream, char *unused_service, + char **argv) +{ + VSTRING *request = vstring_alloc(10); + VSTRING *site = 0; + VSTRING *queue_id = 0; + static char wakeup[] = { /* master wakeup request */ + TRIGGER_REQ_WAKEUP, + 0, + }; + int status = FLUSH_STAT_BAD; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * dedicated to the fast flush service. What we see below is a little + * protocol to (1) read a request from the client (the name of the site) + * and (2) acknowledge that we have received the request. + * + * All connection-management stuff is handled by the common code in + * single_server.c. + */ + if (flush_request_receive(client_stream, request) == 0) { + if (STREQ(STR(request), FLUSH_REQ_ADD)) { + site = vstring_alloc(10); + queue_id = vstring_alloc(10); + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_SITE, site), + RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), + ATTR_TYPE_END) == 2 + && mail_queue_id_ok(STR(queue_id))) + status = flush_add_service(STR(site), STR(queue_id)); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + ATTR_TYPE_END); + } else if (STREQ(STR(request), FLUSH_REQ_SEND_SITE)) { + site = vstring_alloc(10); + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_SITE, site), + ATTR_TYPE_END) == 1) + status = flush_send_service(STR(site), UNTHROTTLE_BEFORE); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + ATTR_TYPE_END); + } else if (STREQ(STR(request), FLUSH_REQ_SEND_FILE)) { + queue_id = vstring_alloc(10); + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), + ATTR_TYPE_END) == 1) + status = flush_send_file_service(STR(queue_id)); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + ATTR_TYPE_END); + } else if (STREQ(STR(request), FLUSH_REQ_REFRESH) + || STREQ(STR(request), wakeup)) { + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK), + ATTR_TYPE_END); + vstream_fflush(client_stream); + (void) flush_refresh_service(var_fflush_refresh); + } else if (STREQ(STR(request), FLUSH_REQ_PURGE)) { + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK), + ATTR_TYPE_END); + vstream_fflush(client_stream); + (void) flush_refresh_service(0); + } + } else + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + ATTR_TYPE_END); + vstring_free(request); + if (site) + vstring_free(site); + if (queue_id) + vstring_free(queue_id); +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + flush_domains = domain_list_init(VAR_FFLUSH_DOMAINS, MATCH_FLAG_RETURN + | match_parent_style(VAR_FFLUSH_DOMAINS), + var_fflush_domains); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_FFLUSH_REFRESH, DEF_FFLUSH_REFRESH, &var_fflush_refresh, 1, 0, + VAR_FFLUSH_PURGE, DEF_FFLUSH_PURGE, &var_fflush_purge, 1, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, flush_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_UNLIMITED, + 0); +} diff --git a/src/fsstone/.indent.pro b/src/fsstone/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/fsstone/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/fsstone/.printfck b/src/fsstone/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/fsstone/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/fsstone/Makefile.in b/src/fsstone/Makefile.in new file mode 100644 index 0000000..d34b0a2 --- /dev/null +++ b/src/fsstone/Makefile.in @@ -0,0 +1,69 @@ +SHELL = /bin/sh +SRCS = fsstone.c +OBJS = fsstone.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = fsstone +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +all: $(PROG) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +fsstone: fsstone.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ fsstone.o $(LIBS) $(SYSLIBS) + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/fsstone + +../../libexec/fsstone: fsstone + cp $? $@ + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +fsstone.o: ../../include/check_arg.h +fsstone.o: ../../include/mail_version.h +fsstone.o: ../../include/msg.h +fsstone.o: ../../include/msg_vstream.h +fsstone.o: ../../include/sys_defs.h +fsstone.o: ../../include/vbuf.h +fsstone.o: ../../include/vstream.h +fsstone.o: fsstone.c diff --git a/src/fsstone/fsstone.c b/src/fsstone/fsstone.c new file mode 100644 index 0000000..217cb04 --- /dev/null +++ b/src/fsstone/fsstone.c @@ -0,0 +1,242 @@ +/*++ +/* NAME +/* fsstone 1 +/* SUMMARY +/* measure directory operation overhead +/* SYNOPSIS +/* .fi +/* \fBfsstone\fR [\fB-cr\fR] [\fB-s \fIsize\fR] +/* \fImsg_count files_per_dir\fR +/* DESCRIPTION +/* The \fBfsstone\fR command measures the cost of creating, renaming +/* and deleting queue files versus appending messages to existing +/* files and truncating them after use. +/* +/* The program simulates the arrival of \fImsg_count\fR short messages, +/* and arranges for at most \fIfiles_per_dir\fR simultaneous files +/* in the same directory. +/* +/* Options: +/* .IP \fB-c\fR +/* Create and delete files. +/* .IP \fB-r\fR +/* Rename files twice (requires \fB-c\fR). +/* .IP \fB-s \fIsize\fR +/* Specify the file size in kbytes. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* BUGS +/* The \fB-r\fR option renames files within the same directory. +/* For a more realistic simulation, the program should rename files +/* between directories, and should also have an option to use +/* hashed directories as implemented with, for example, the +/* \fBdir_forest\fR(3) module. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* Global directory. */ + +#include + +/* rename_file - rename a file */ + +static void rename_file(int old, int new) +{ + char new_path[BUFSIZ]; + char old_path[BUFSIZ]; + + sprintf(new_path, "%06d", new); + sprintf(old_path, "%06d", old); + if (rename(old_path, new_path)) + msg_fatal("rename %s to %s: %m", old_path, new_path); +} + +/* make_file - create a little file and use it */ + +static void make_file(int seqno, int size) +{ + char path[BUFSIZ]; + char buf[1024]; + FILE *fp; + int i; + + sprintf(path, "%06d", seqno); + if ((fp = fopen(path, "w")) == 0) + msg_fatal("open %s: %m", path); + memset(buf, 'x', sizeof(buf)); + for (i = 0; i < size; i++) + if (fwrite(buf, 1, sizeof(buf), fp) != sizeof(buf)) + msg_fatal("fwrite: %m"); + if (fsync(fileno(fp))) + msg_fatal("fsync: %m"); + if (fclose(fp)) + msg_fatal("fclose: %m"); + if ((fp = fopen(path, "r")) == 0) + msg_fatal("open %s: %m", path); + while (fgets(path, sizeof(path), fp)) + /* void */ ; + if (fclose(fp)) + msg_fatal("fclose: %m"); +} + +/* use_file - use existing file */ + +static void use_file(int seqno) +{ + char path[BUFSIZ]; + FILE *fp; + int i; + + sprintf(path, "%06d", seqno); + if ((fp = fopen(path, "w")) == 0) + msg_fatal("open %s: %m", path); + for (i = 0; i < 400; i++) + fprintf(fp, "hello"); + if (fsync(fileno(fp))) + msg_fatal("fsync: %m"); + if (fclose(fp)) + msg_fatal("fclose: %m"); + if ((fp = fopen(path, "r+")) == 0) + msg_fatal("open %s: %m", path); + while (fgets(path, sizeof(path), fp)) + /* void */ ; + if (ftruncate(fileno(fp), (off_t) 0)) + msg_fatal("ftruncate: %m");; + if (fclose(fp)) + msg_fatal("fclose: %m"); +} + +/* remove_file - delete specified file */ + +static void remove_file(int seq) +{ + char path[BUFSIZ]; + + sprintf(path, "%06d", seq); + if (remove(path)) + msg_fatal("remove %s: %m", path); +} + +/* remove_silent - delete specified file, silently */ + +static void remove_silent(int seq) +{ + char path[BUFSIZ]; + + sprintf(path, "%06d", seq); + (void) remove(path); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s [-cr] [-s size] messages directory_entries", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + int op_count; + int max_file; + struct timeval start, end; + int do_rename = 0; + int do_create = 0; + int seq; + int ch; + int size = 2; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + msg_vstream_init(argv[0], VSTREAM_ERR); + while ((ch = GETOPT(argc, argv, "crs:")) != EOF) { + switch (ch) { + case 'c': + do_create++; + break; + case 'r': + do_rename++; + break; + case 's': + if ((size = atoi(optarg)) <= 0) + usage(argv[0]); + break; + default: + usage(argv[0]); + } + } + + if (argc - optind != 2 || (do_rename && !do_create)) + usage(argv[0]); + if ((op_count = atoi(argv[optind])) <= 0) + usage(argv[0]); + if ((max_file = atoi(argv[optind + 1])) <= 0) + usage(argv[0]); + + /* + * Populate the directory with little files. + */ + for (seq = 0; seq < max_file; seq++) + make_file(seq, size); + + /* + * Simulate arrival and delivery of mail messages. + */ + GETTIMEOFDAY(&start); + while (op_count > 0) { + seq %= max_file; + if (do_create) { + remove_file(seq); + make_file(seq, size); + if (do_rename) { + rename_file(seq, seq + max_file); + rename_file(seq + max_file, seq); + } + } else { + use_file(seq); + } + seq++; + op_count--; + } + GETTIMEOFDAY(&end); + if (end.tv_usec < start.tv_usec) { + end.tv_sec--; + end.tv_usec += 1000000; + } + printf("elapsed time: %ld.%06ld\n", + (long) (end.tv_sec - start.tv_sec), + (long) (end.tv_usec - start.tv_usec)); + + /* + * Clean up directory fillers. + */ + for (seq = 0; seq < max_file; seq++) + remove_silent(seq); + return (0); +} diff --git a/src/global/.indent.pro b/src/global/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/global/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/global/.printfck b/src/global/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/global/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/global/Makefile.in b/src/global/Makefile.in new file mode 100644 index 0000000..0944a75 --- /dev/null +++ b/src/global/Makefile.in @@ -0,0 +1,3086 @@ +SHELL = /bin/sh +SRCS = abounce.c anvil_clnt.c been_here.c bounce.c bounce_log.c \ + canon_addr.c cfg_parser.c cleanup_strerror.c cleanup_strflags.c \ + clnt_stream.c conv_time.c db_common.c debug_peer.c debug_process.c \ + defer.c deliver_completed.c deliver_flock.c deliver_pass.c \ + deliver_request.c dict_ldap.c dict_mysql.c dict_pgsql.c \ + dict_proxy.c dict_sqlite.c domain_list.c dot_lockfile.c dot_lockfile_as.c \ + dsb_scan.c dsn.c dsn_buf.c dsn_mask.c dsn_print.c dsn_util.c \ + ehlo_mask.c ext_prop.c file_id.c flush_clnt.c header_opts.c \ + header_token.c input_transp.c int_filt.c is_header.c log_adhoc.c \ + mail_addr.c mail_addr_crunch.c mail_addr_find.c mail_addr_map.c \ + mail_command_client.c mail_command_server.c mail_conf.c \ + mail_conf_bool.c mail_conf_int.c mail_conf_long.c mail_conf_raw.c \ + mail_conf_str.c mail_conf_time.c mail_connect.c mail_copy.c \ + mail_date.c mail_dict.c mail_error.c mail_flush.c mail_open_ok.c \ + mail_params.c mail_pathname.c mail_queue.c mail_run.c \ + mail_scan_dir.c mail_stream.c mail_task.c mail_trigger.c maps.c \ + mark_corrupt.c match_parent_style.c mbox_conf.c mbox_open.c \ + mime_state.c mkmap_cdb.c mkmap_db.c mkmap_dbm.c mkmap_lmdb.c mkmap_open.c \ + mkmap_sdbm.c msg_stats_print.c msg_stats_scan.c mynetworks.c \ + mypwd.c namadr_list.c off_cvt.c opened.c own_inet_addr.c \ + pipe_command.c post_mail.c quote_821_local.c quote_822_local.c \ + rcpt_buf.c rcpt_print.c rec_attr_map.c rec_streamlf.c rec_type.c \ + recipient_list.c record.c remove.c resolve_clnt.c resolve_local.c \ + rewrite_clnt.c scache_clnt.c scache_multi.c scache_single.c \ + sent.c smtp_stream.c split_addr.c string_list.c strip_addr.c \ + sys_exits.c timed_ipc.c tok822_find.c tok822_node.c tok822_parse.c \ + tok822_resolve.c tok822_rewrite.c tok822_tree.c trace.c \ + user_acl.c valid_mailhost_addr.c verify.c verify_clnt.c \ + verp_sender.c wildcard_inet_addr.c xtext.c delivered_hdr.c \ + fold_addr.c header_body_checks.c mkmap_proxy.c data_redirect.c \ + match_service.c mail_conf_nint.c addr_match_list.c mail_conf_nbool.c \ + smtp_reply_footer.c safe_ultostr.c verify_sender_addr.c \ + dict_memcache.c mail_version.c memcache_proto.c server_acl.c \ + mkmap_fail.c haproxy_srvr.c dsn_filter.c dynamicmaps.c uxtext.c \ + smtputf8.c mail_conf_over.c mail_parm_split.c midna_adomain.c \ + mail_addr_form.c quote_flags.c maillog_client.c \ + normalize_mailhost_addr.c map_search.c reject_deliver_request.c \ + info_log_addr_form.c sasl_mech_filter.c login_sender_match.c \ + test_main.c compat_level.c config_known_tcp_ports.c \ + hfrom_format.c +OBJS = abounce.o anvil_clnt.o been_here.o bounce.o bounce_log.o \ + canon_addr.o cfg_parser.o cleanup_strerror.o cleanup_strflags.o \ + clnt_stream.o conv_time.o db_common.o debug_peer.o debug_process.o \ + defer.o deliver_completed.o deliver_flock.o deliver_pass.o \ + deliver_request.o \ + dict_proxy.o domain_list.o dot_lockfile.o dot_lockfile_as.o \ + dsb_scan.o dsn.o dsn_buf.o dsn_mask.o dsn_print.o dsn_util.o \ + ehlo_mask.o ext_prop.o file_id.o flush_clnt.o header_opts.o \ + header_token.o input_transp.o int_filt.o is_header.o log_adhoc.o \ + mail_addr.o mail_addr_crunch.o mail_addr_find.o mail_addr_map.o \ + mail_command_client.o mail_command_server.o mail_conf.o \ + mail_conf_bool.o mail_conf_int.o mail_conf_long.o mail_conf_raw.o \ + mail_conf_str.o mail_conf_time.o mail_connect.o mail_copy.o \ + mail_date.o mail_dict.o mail_error.o mail_flush.o mail_open_ok.o \ + mail_params.o mail_pathname.o mail_queue.o mail_run.o \ + mail_scan_dir.o mail_stream.o mail_task.o mail_trigger.o maps.o \ + mark_corrupt.o match_parent_style.o mbox_conf.o mbox_open.o \ + mime_state.o mkmap_db.o mkmap_dbm.o mkmap_open.o \ + msg_stats_print.o msg_stats_scan.o mynetworks.o \ + mypwd.o namadr_list.o off_cvt.o opened.o own_inet_addr.o \ + pipe_command.o post_mail.o quote_821_local.o quote_822_local.o \ + rcpt_buf.o rcpt_print.o rec_attr_map.o rec_streamlf.o rec_type.o \ + recipient_list.o record.o remove.o resolve_clnt.o resolve_local.o \ + rewrite_clnt.o scache_clnt.o scache_multi.o scache_single.o \ + sent.o smtp_stream.o split_addr.o string_list.o strip_addr.o \ + sys_exits.o timed_ipc.o tok822_find.o tok822_node.o tok822_parse.o \ + tok822_resolve.o tok822_rewrite.o tok822_tree.o trace.o \ + user_acl.o valid_mailhost_addr.o verify.o verify_clnt.o \ + verp_sender.o wildcard_inet_addr.o xtext.o delivered_hdr.o \ + fold_addr.o header_body_checks.o mkmap_proxy.o data_redirect.o \ + match_service.o mail_conf_nint.o addr_match_list.o mail_conf_nbool.o \ + smtp_reply_footer.o safe_ultostr.o verify_sender_addr.o \ + dict_memcache.o mail_version.o memcache_proto.o server_acl.o \ + mkmap_fail.o haproxy_srvr.o dsn_filter.o dynamicmaps.o uxtext.o \ + smtputf8.o attr_override.o mail_parm_split.o midna_adomain.o \ + $(NON_PLUGIN_MAP_OBJ) mail_addr_form.o quote_flags.o maillog_client.o \ + normalize_mailhost_addr.o map_search.o reject_deliver_request.o \ + info_log_addr_form.o sasl_mech_filter.o login_sender_match.o \ + test_main.o compat_level.o config_known_tcp_ports.o \ + hfrom_format.o +# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf. +# When hard-linking these maps, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ), +# otherwise it sets the PLUGIN_* macros. +MAP_OBJ = dict_ldap.o dict_mysql.o dict_pgsql.o dict_sqlite.o mkmap_cdb.o \ + mkmap_lmdb.o mkmap_sdbm.o +HDRS = abounce.h anvil_clnt.h been_here.h bounce.h bounce_log.h \ + canon_addr.h cfg_parser.h cleanup_user.h clnt_stream.h config.h \ + conv_time.h db_common.h debug_peer.h debug_process.h defer.h \ + deliver_completed.h deliver_flock.h deliver_pass.h deliver_request.h \ + dict_ldap.h dict_mysql.h dict_pgsql.h dict_proxy.h dict_sqlite.h domain_list.h \ + dot_lockfile.h dot_lockfile_as.h dsb_scan.h dsn.h dsn_buf.h \ + dsn_mask.h dsn_print.h dsn_util.h ehlo_mask.h ext_prop.h \ + file_id.h flush_clnt.h header_opts.h header_token.h input_transp.h \ + int_filt.h is_header.h lex_822.h log_adhoc.h mail_addr.h \ + mail_addr_crunch.h mail_addr_find.h mail_addr_map.h mail_conf.h \ + mail_copy.h mail_date.h mail_dict.h mail_error.h mail_flush.h \ + mail_open_ok.h mail_params.h mail_proto.h mail_queue.h mail_run.h \ + mail_scan_dir.h mail_stream.h mail_task.h mail_version.h maps.h \ + mark_corrupt.h match_parent_style.h mbox_conf.h mbox_open.h \ + mime_state.h mkmap.h msg_stats.h mynetworks.h mypwd.h namadr_list.h \ + off_cvt.h opened.h own_inet_addr.h pipe_command.h post_mail.h \ + qmgr_user.h qmqp_proto.h quote_821_local.h quote_822_local.h \ + quote_flags.h rcpt_buf.h rcpt_print.h rec_attr_map.h rec_streamlf.h \ + rec_type.h recipient_list.h record.h resolve_clnt.h resolve_local.h \ + rewrite_clnt.h scache.h sent.h smtp_stream.h split_addr.h \ + string_list.h strip_addr.h sys_exits.h timed_ipc.h tok822.h \ + trace.h user_acl.h valid_mailhost_addr.h verify.h verify_clnt.h \ + verp_sender.h wildcard_inet_addr.h xtext.h delivered_hdr.h \ + fold_addr.h header_body_checks.h data_redirect.h match_service.h \ + addr_match_list.h smtp_reply_footer.h safe_ultostr.h \ + verify_sender_addr.h dict_memcache.h memcache_proto.h server_acl.h \ + haproxy_srvr.h dsn_filter.h dynamicmaps.h uxtext.h smtputf8.h \ + attr_override.h mail_parm_split.h midna_adomain.h mail_addr_form.h \ + maillog_client.h normalize_mailhost_addr.h map_search.h \ + info_log_addr_form.h sasl_mech_filter.h login_sender_match.h \ + test_main.h compat_level.h config_known_tcp_ports.h \ + hfrom_format.h +TESTSRC = rec2stream.c stream2rec.c recdump.c +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +INCL = +LIB = lib$(LIB_PREFIX)global$(LIB_SUFFIX) +TESTPROG= domain_list dot_lockfile mail_addr_crunch mail_addr_find \ + mail_addr_map mail_date maps mynetworks mypwd namadr_list \ + off_cvt quote_822_local rec2stream recdump resolve_clnt \ + resolve_local rewrite_clnt stream2rec string_list tok822_parse \ + quote_821_local mail_conf_time mime_state strip_addr \ + verify_clnt xtext anvil_clnt scache ehlo_mask \ + valid_mailhost_addr own_inet_addr header_body_checks \ + data_redirect addr_match_list safe_ultostr verify_sender_addr \ + mail_version mail_dict server_acl uxtext mail_parm_split \ + fold_addr smtp_reply_footer mail_addr_map normalize_mailhost_addr \ + haproxy_srvr map_search delivered_hdr login_sender_match \ + compat_level config_known_tcp_ports hfrom_format + +LIBS = ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) +LIB_DIR = ../../lib +INC_DIR = ../../include +PLUGIN_MAP_SO = $(LIB_PREFIX)ldap$(LIB_SUFFIX) $(LIB_PREFIX)mysql$(LIB_SUFFIX) \ + $(LIB_PREFIX)pgsql$(LIB_SUFFIX) $(LIB_PREFIX)sqlite$(LIB_SUFFIX) \ + $(LIB_PREFIX)lmdb$(LIB_SUFFIX) $(LIB_PREFIX)cdb$(LIB_SUFFIX) \ + $(LIB_PREFIX)sdbm$(LIB_SUFFIX) +MAKES = + +.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c + +all: $(LIB) $(PLUGIN_MAP_SO_MAKE) $(PLUGIN_MAP_OBJ) + +$(OBJS) $(PLUGIN_MAP_OBJ): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +$(LIB): $(OBJS) + $(AR) $(ARFL) $(LIB) $? + $(RANLIB) $(LIB) + $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS) + +$(LIB_DIR)/$(LIB): $(LIB) + cp $(LIB) $(LIB_DIR) + $(RANLIB) $(LIB_DIR)/$(LIB) + +plugin_map_so_make: $(PLUGIN_MAP_SO) + +$(LIB_PREFIX)ldap$(LIB_SUFFIX): dict_ldap.o + $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_ldap.o $(AUXLIBS_LDAP) + +$(LIB_PREFIX)mysql$(LIB_SUFFIX): dict_mysql.o + $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_mysql.o $(AUXLIBS_MYSQL) + +$(LIB_PREFIX)pgsql$(LIB_SUFFIX): dict_pgsql.o + $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_pgsql.o $(AUXLIBS_PGSQL) + +$(LIB_PREFIX)sqlite$(LIB_SUFFIX): dict_sqlite.o + $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_sqlite.o $(AUXLIBS_SQLITE) + +$(LIB_PREFIX)cdb$(LIB_SUFFIX): mkmap_cdb.o $(LIB_DIR)/dict_cdb.o + $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ mkmap_cdb.o \ + $(LIB_DIR)/dict_cdb.o $(AUXLIBS_CDB) + +$(LIB_PREFIX)lmdb$(LIB_SUFFIX): mkmap_lmdb.o $(LIB_DIR)/dict_lmdb.o \ + $(LIB_DIR)/slmdb.o + $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ mkmap_lmdb.o $(LIB_DIR)/dict_lmdb.o \ + $(LIB_DIR)/slmdb.o $(AUXLIBS_LMDB) + +$(LIB_PREFIX)sdbm$(LIB_SUFFIX): mkmap_sdbm.o $(LIB_DIR)/dict_sdbm.o + $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ mkmap_sdbm.o \ + $(LIB_DIR)/dict_sdbm.o $(AUXLIBS_SDBM) + +update: $(LIB_DIR)/$(LIB) $(HDRS) $(PLUGIN_MAP_SO_UPDATE) + -for i in $(HDRS); \ + do \ + cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \ + done + cd $(INC_DIR); chmod 644 $(HDRS) + +plugin_map_so_update: $(PLUGIN_MAP_SO) + -for i in $(PLUGIN_MAP_SO); \ + do \ + for type in $(DEFINED_MAP_TYPES); do \ + case $$i in $(LIB_PREFIX)$$type$(LIB_SUFFIX)) \ + cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \ + continue 2;; \ + esac; \ + done; \ + rm -f $(LIB_DIR)/$$i; \ + done + +dot_lockfile: $(LIB) $(LIBS) + mv $@.o junk + $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +tok822_parse: $(LIB) $(LIBS) + mv $@.o junk + $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +rec2stream: rec2stream.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +stream2rec: stream2rec.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +recdump: recdump.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +namadr_list: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +domain_list: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +mynetworks: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +resolve_clnt: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +rewrite_clnt: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +quote_822_local: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +off_cvt: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +mail_addr_map: mail_addr_map.c $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +mail_addr_find: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +maps: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +mypwd: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +mail_date: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +resolve_local: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +mail_addr_crunch: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +string_list: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +local_transport: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +quote_821_local: quote_821_local.c $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS) + +mail_conf_time: $(LIB) $(LIBS) + mv $@.o junk + $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +mime_state: $(LIB) $(LIBS) + mv $@.o junk + $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +strip_addr: $(LIB) $(LIBS) + mv $@.o junk + $(CC) -DTEST $(CFLAGS) -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +uxtext: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +verify_clnt: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +xtext: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +anvil_clnt: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +scache: scache.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +ehlo_mask: ehlo_mask.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +valid_mailhost_addr: valid_mailhost_addr.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +own_inet_addr: own_inet_addr.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +header_body_checks: header_body_checks.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +data_redirect: data_redirect.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +addr_match_list: addr_match_list.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +safe_ultostr: safe_ultostr.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +verify_sender_addr: verify_sender_addr.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +mail_version: mail_version.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +mail_dict: mail_dict.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +server_acl: server_acl.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +mail_parm_split: mail_parm_split.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +fold_addr: fold_addr.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +smtp_reply_footer: smtp_reply_footer.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +normalize_mailhost_addr: normalize_mailhost_addr.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +haproxy_srvr: haproxy_srvr.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +map_search: map_search.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +delivered_hdr: delivered_hdr.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +login_sender_match: login_sender_match.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +compat_level: compat_level.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +hfrom_format: hfrom_format.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +config_known_tcp_ports: config_known_tcp_ports.c $(LIB) $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + +tests: tok822_test mime_tests strip_addr_test tok822_limit_test \ + xtext_test scache_multi_test ehlo_mask_test \ + namadr_list_test mail_conf_time_test header_body_checks_tests \ + mail_version_test server_acl_test resolve_local_test maps_test \ + safe_ultostr_test mail_parm_split_test fold_addr_test \ + smtp_reply_footer_test off_cvt_test mail_addr_crunch_test \ + mail_addr_find_test mail_addr_map_test quote_822_local_test \ + normalize_mailhost_addr_test haproxy_srvr_test map_search_test \ + delivered_hdr_test login_sender_match_test compat_level_test \ + config_known_tcp_ports_test hfrom_format_test + +mime_tests: mime_test mime_nest mime_8bit mime_dom mime_trunc mime_cvt \ + mime_cvt2 mime_cvt3 mime_garb1 mime_garb2 mime_garb3 mime_garb4 + +header_body_checks_tests: header_body_checks_null_test \ + header_body_checks_warn_test header_body_checks_prepend_test \ + header_body_checks_ignore_test header_body_checks_replace_test \ + header_body_checks_strip_test + +root_tests: rewrite_clnt_test resolve_clnt_test verify_sender_addr_test + +tok822_test: tok822_parse tok822_parse.in tok822_parse.ref + $(SHLIB_ENV) $(VALGRIND) ./tok822_parse tok822_parse.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 diff --git a/src/local/.indent.pro b/src/local/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/local/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/local/.printfck b/src/local/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/local/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/local/Makefile.in b/src/local/Makefile.in new file mode 100644 index 0000000..648ad51 --- /dev/null +++ b/src/local/Makefile.in @@ -0,0 +1,686 @@ +SHELL = /bin/sh +SRCS = alias.c command.c dotforward.c file.c forward.c \ + include.c indirect.c local.c mailbox.c recipient.c resolve.c token.c \ + deliver_attr.c maildir.c biff_notify.c unknown.c \ + local_expand.c bounce_workaround.c +OBJS = alias.o command.o dotforward.o file.o forward.o \ + include.o indirect.o local.o mailbox.o recipient.o resolve.o token.o \ + deliver_attr.o maildir.o biff_notify.o unknown.o \ + local_expand.o bounce_workaround.c +HDRS = local.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +PROG = local +TESTPROG= +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +alias.o: ../../include/argv.h +alias.o: ../../include/attr.h +alias.o: ../../include/been_here.h +alias.o: ../../include/bounce.h +alias.o: ../../include/canon_addr.h +alias.o: ../../include/check_arg.h +alias.o: ../../include/defer.h +alias.o: ../../include/deliver_request.h +alias.o: ../../include/delivered_hdr.h +alias.o: ../../include/dict.h +alias.o: ../../include/dsn.h +alias.o: ../../include/dsn_buf.h +alias.o: ../../include/dsn_mask.h +alias.o: ../../include/fold_addr.h +alias.o: ../../include/htable.h +alias.o: ../../include/mail_params.h +alias.o: ../../include/maps.h +alias.o: ../../include/mbox_conf.h +alias.o: ../../include/msg.h +alias.o: ../../include/msg_stats.h +alias.o: ../../include/myflock.h +alias.o: ../../include/mymalloc.h +alias.o: ../../include/mypwd.h +alias.o: ../../include/nvtable.h +alias.o: ../../include/recipient_list.h +alias.o: ../../include/resolve_clnt.h +alias.o: ../../include/sent.h +alias.o: ../../include/stringops.h +alias.o: ../../include/sys_defs.h +alias.o: ../../include/tok822.h +alias.o: ../../include/trace.h +alias.o: ../../include/vbuf.h +alias.o: ../../include/vstream.h +alias.o: ../../include/vstring.h +alias.o: alias.c +alias.o: local.h +biff_notify.o: ../../include/iostuff.h +biff_notify.o: ../../include/msg.h +biff_notify.o: ../../include/sys_defs.h +biff_notify.o: biff_notify.c +biff_notify.o: biff_notify.h +bounce_workaround.o: ../../include/argv.h +bounce_workaround.o: ../../include/attr.h +bounce_workaround.o: ../../include/been_here.h +bounce_workaround.o: ../../include/bounce.h +bounce_workaround.o: ../../include/canon_addr.h +bounce_workaround.o: ../../include/check_arg.h +bounce_workaround.o: ../../include/defer.h +bounce_workaround.o: ../../include/deliver_request.h +bounce_workaround.o: ../../include/delivered_hdr.h +bounce_workaround.o: ../../include/dict.h +bounce_workaround.o: ../../include/dsn.h +bounce_workaround.o: ../../include/dsn_buf.h +bounce_workaround.o: ../../include/fold_addr.h +bounce_workaround.o: ../../include/htable.h +bounce_workaround.o: ../../include/mail_params.h +bounce_workaround.o: ../../include/maps.h +bounce_workaround.o: ../../include/mbox_conf.h +bounce_workaround.o: ../../include/msg.h +bounce_workaround.o: ../../include/msg_stats.h +bounce_workaround.o: ../../include/myflock.h +bounce_workaround.o: ../../include/mymalloc.h +bounce_workaround.o: ../../include/nvtable.h +bounce_workaround.o: ../../include/recipient_list.h +bounce_workaround.o: ../../include/resolve_clnt.h +bounce_workaround.o: ../../include/split_addr.h +bounce_workaround.o: ../../include/split_at.h +bounce_workaround.o: ../../include/stringops.h +bounce_workaround.o: ../../include/strip_addr.h +bounce_workaround.o: ../../include/sys_defs.h +bounce_workaround.o: ../../include/tok822.h +bounce_workaround.o: ../../include/vbuf.h +bounce_workaround.o: ../../include/vstream.h +bounce_workaround.o: ../../include/vstring.h +bounce_workaround.o: bounce_workaround.c +bounce_workaround.o: local.h +command.o: ../../include/argv.h +command.o: ../../include/attr.h +command.o: ../../include/been_here.h +command.o: ../../include/bounce.h +command.o: ../../include/check_arg.h +command.o: ../../include/defer.h +command.o: ../../include/deliver_request.h +command.o: ../../include/delivered_hdr.h +command.o: ../../include/dict.h +command.o: ../../include/dsn.h +command.o: ../../include/dsn_buf.h +command.o: ../../include/dsn_util.h +command.o: ../../include/fold_addr.h +command.o: ../../include/htable.h +command.o: ../../include/mac_parse.h +command.o: ../../include/mail_copy.h +command.o: ../../include/mail_params.h +command.o: ../../include/mail_parm_split.h +command.o: ../../include/maps.h +command.o: ../../include/mbox_conf.h +command.o: ../../include/msg.h +command.o: ../../include/msg_stats.h +command.o: ../../include/myflock.h +command.o: ../../include/mymalloc.h +command.o: ../../include/nvtable.h +command.o: ../../include/pipe_command.h +command.o: ../../include/recipient_list.h +command.o: ../../include/resolve_clnt.h +command.o: ../../include/sent.h +command.o: ../../include/sys_defs.h +command.o: ../../include/tok822.h +command.o: ../../include/vbuf.h +command.o: ../../include/vstream.h +command.o: ../../include/vstring.h +command.o: command.c +command.o: local.h +deliver_attr.o: ../../include/argv.h +deliver_attr.o: ../../include/attr.h +deliver_attr.o: ../../include/been_here.h +deliver_attr.o: ../../include/check_arg.h +deliver_attr.o: ../../include/deliver_request.h +deliver_attr.o: ../../include/delivered_hdr.h +deliver_attr.o: ../../include/dict.h +deliver_attr.o: ../../include/dsn.h +deliver_attr.o: ../../include/dsn_buf.h +deliver_attr.o: ../../include/fold_addr.h +deliver_attr.o: ../../include/htable.h +deliver_attr.o: ../../include/maps.h +deliver_attr.o: ../../include/mbox_conf.h +deliver_attr.o: ../../include/msg.h +deliver_attr.o: ../../include/msg_stats.h +deliver_attr.o: ../../include/myflock.h +deliver_attr.o: ../../include/mymalloc.h +deliver_attr.o: ../../include/nvtable.h +deliver_attr.o: ../../include/recipient_list.h +deliver_attr.o: ../../include/resolve_clnt.h +deliver_attr.o: ../../include/sys_defs.h +deliver_attr.o: ../../include/tok822.h +deliver_attr.o: ../../include/vbuf.h +deliver_attr.o: ../../include/vstream.h +deliver_attr.o: ../../include/vstring.h +deliver_attr.o: deliver_attr.c +deliver_attr.o: local.h +dotforward.o: ../../include/argv.h +dotforward.o: ../../include/attr.h +dotforward.o: ../../include/been_here.h +dotforward.o: ../../include/bounce.h +dotforward.o: ../../include/check_arg.h +dotforward.o: ../../include/defer.h +dotforward.o: ../../include/deliver_request.h +dotforward.o: ../../include/delivered_hdr.h +dotforward.o: ../../include/dict.h +dotforward.o: ../../include/dsn.h +dotforward.o: ../../include/dsn_buf.h +dotforward.o: ../../include/dsn_mask.h +dotforward.o: ../../include/ext_prop.h +dotforward.o: ../../include/fold_addr.h +dotforward.o: ../../include/htable.h +dotforward.o: ../../include/iostuff.h +dotforward.o: ../../include/lstat_as.h +dotforward.o: ../../include/mac_expand.h +dotforward.o: ../../include/mac_parse.h +dotforward.o: ../../include/mail_conf.h +dotforward.o: ../../include/mail_params.h +dotforward.o: ../../include/maps.h +dotforward.o: ../../include/mbox_conf.h +dotforward.o: ../../include/msg.h +dotforward.o: ../../include/msg_stats.h +dotforward.o: ../../include/myflock.h +dotforward.o: ../../include/mymalloc.h +dotforward.o: ../../include/mypwd.h +dotforward.o: ../../include/nvtable.h +dotforward.o: ../../include/open_as.h +dotforward.o: ../../include/recipient_list.h +dotforward.o: ../../include/resolve_clnt.h +dotforward.o: ../../include/sent.h +dotforward.o: ../../include/stringops.h +dotforward.o: ../../include/sys_defs.h +dotforward.o: ../../include/tok822.h +dotforward.o: ../../include/trace.h +dotforward.o: ../../include/vbuf.h +dotforward.o: ../../include/vstream.h +dotforward.o: ../../include/vstring.h +dotforward.o: dotforward.c +dotforward.o: local.h +file.o: ../../include/argv.h +file.o: ../../include/attr.h +file.o: ../../include/been_here.h +file.o: ../../include/bounce.h +file.o: ../../include/check_arg.h +file.o: ../../include/defer.h +file.o: ../../include/deliver_flock.h +file.o: ../../include/deliver_request.h +file.o: ../../include/delivered_hdr.h +file.o: ../../include/dict.h +file.o: ../../include/dsn.h +file.o: ../../include/dsn_buf.h +file.o: ../../include/dsn_util.h +file.o: ../../include/fold_addr.h +file.o: ../../include/htable.h +file.o: ../../include/mail_copy.h +file.o: ../../include/mail_params.h +file.o: ../../include/maps.h +file.o: ../../include/mbox_conf.h +file.o: ../../include/mbox_open.h +file.o: ../../include/msg.h +file.o: ../../include/msg_stats.h +file.o: ../../include/myflock.h +file.o: ../../include/mymalloc.h +file.o: ../../include/nvtable.h +file.o: ../../include/recipient_list.h +file.o: ../../include/resolve_clnt.h +file.o: ../../include/safe_open.h +file.o: ../../include/sent.h +file.o: ../../include/set_eugid.h +file.o: ../../include/sys_defs.h +file.o: ../../include/tok822.h +file.o: ../../include/vbuf.h +file.o: ../../include/vstream.h +file.o: ../../include/vstring.h +file.o: file.c +file.o: local.h +forward.o: ../../include/argv.h +forward.o: ../../include/attr.h +forward.o: ../../include/been_here.h +forward.o: ../../include/bounce.h +forward.o: ../../include/check_arg.h +forward.o: ../../include/cleanup_user.h +forward.o: ../../include/deliver_request.h +forward.o: ../../include/delivered_hdr.h +forward.o: ../../include/dict.h +forward.o: ../../include/dsn.h +forward.o: ../../include/dsn_buf.h +forward.o: ../../include/dsn_mask.h +forward.o: ../../include/fold_addr.h +forward.o: ../../include/htable.h +forward.o: ../../include/iostuff.h +forward.o: ../../include/mail_date.h +forward.o: ../../include/mail_params.h +forward.o: ../../include/mail_proto.h +forward.o: ../../include/maps.h +forward.o: ../../include/mark_corrupt.h +forward.o: ../../include/mbox_conf.h +forward.o: ../../include/msg.h +forward.o: ../../include/msg_stats.h +forward.o: ../../include/myflock.h +forward.o: ../../include/mymalloc.h +forward.o: ../../include/nvtable.h +forward.o: ../../include/rec_type.h +forward.o: ../../include/recipient_list.h +forward.o: ../../include/record.h +forward.o: ../../include/resolve_clnt.h +forward.o: ../../include/sent.h +forward.o: ../../include/smtputf8.h +forward.o: ../../include/stringops.h +forward.o: ../../include/sys_defs.h +forward.o: ../../include/tok822.h +forward.o: ../../include/vbuf.h +forward.o: ../../include/vstream.h +forward.o: ../../include/vstring.h +forward.o: ../../include/vstring_vstream.h +forward.o: forward.c +forward.o: local.h +include.o: ../../include/argv.h +include.o: ../../include/attr.h +include.o: ../../include/been_here.h +include.o: ../../include/bounce.h +include.o: ../../include/check_arg.h +include.o: ../../include/defer.h +include.o: ../../include/deliver_request.h +include.o: ../../include/delivered_hdr.h +include.o: ../../include/dict.h +include.o: ../../include/dsn.h +include.o: ../../include/dsn_buf.h +include.o: ../../include/ext_prop.h +include.o: ../../include/fold_addr.h +include.o: ../../include/htable.h +include.o: ../../include/iostuff.h +include.o: ../../include/mail_params.h +include.o: ../../include/maps.h +include.o: ../../include/mbox_conf.h +include.o: ../../include/msg.h +include.o: ../../include/msg_stats.h +include.o: ../../include/myflock.h +include.o: ../../include/mymalloc.h +include.o: ../../include/mypwd.h +include.o: ../../include/nvtable.h +include.o: ../../include/open_as.h +include.o: ../../include/recipient_list.h +include.o: ../../include/resolve_clnt.h +include.o: ../../include/sent.h +include.o: ../../include/stat_as.h +include.o: ../../include/sys_defs.h +include.o: ../../include/tok822.h +include.o: ../../include/vbuf.h +include.o: ../../include/vstream.h +include.o: ../../include/vstring.h +include.o: include.c +include.o: local.h +indirect.o: ../../include/argv.h +indirect.o: ../../include/attr.h +indirect.o: ../../include/been_here.h +indirect.o: ../../include/bounce.h +indirect.o: ../../include/check_arg.h +indirect.o: ../../include/defer.h +indirect.o: ../../include/deliver_request.h +indirect.o: ../../include/delivered_hdr.h +indirect.o: ../../include/dict.h +indirect.o: ../../include/dsn.h +indirect.o: ../../include/dsn_buf.h +indirect.o: ../../include/fold_addr.h +indirect.o: ../../include/htable.h +indirect.o: ../../include/mail_params.h +indirect.o: ../../include/maps.h +indirect.o: ../../include/mbox_conf.h +indirect.o: ../../include/msg.h +indirect.o: ../../include/msg_stats.h +indirect.o: ../../include/myflock.h +indirect.o: ../../include/mymalloc.h +indirect.o: ../../include/nvtable.h +indirect.o: ../../include/recipient_list.h +indirect.o: ../../include/resolve_clnt.h +indirect.o: ../../include/sent.h +indirect.o: ../../include/sys_defs.h +indirect.o: ../../include/tok822.h +indirect.o: ../../include/vbuf.h +indirect.o: ../../include/vstream.h +indirect.o: ../../include/vstring.h +indirect.o: indirect.c +indirect.o: local.h +local.o: ../../include/argv.h +local.o: ../../include/attr.h +local.o: ../../include/been_here.h +local.o: ../../include/check_arg.h +local.o: ../../include/deliver_completed.h +local.o: ../../include/deliver_request.h +local.o: ../../include/delivered_hdr.h +local.o: ../../include/dict.h +local.o: ../../include/dsn.h +local.o: ../../include/dsn_buf.h +local.o: ../../include/ext_prop.h +local.o: ../../include/flush_clnt.h +local.o: ../../include/fold_addr.h +local.o: ../../include/htable.h +local.o: ../../include/iostuff.h +local.o: ../../include/mail_addr.h +local.o: ../../include/mail_conf.h +local.o: ../../include/mail_params.h +local.o: ../../include/mail_server.h +local.o: ../../include/mail_version.h +local.o: ../../include/maps.h +local.o: ../../include/mbox_conf.h +local.o: ../../include/msg.h +local.o: ../../include/msg_stats.h +local.o: ../../include/myflock.h +local.o: ../../include/mymalloc.h +local.o: ../../include/name_mask.h +local.o: ../../include/nvtable.h +local.o: ../../include/recipient_list.h +local.o: ../../include/resolve_clnt.h +local.o: ../../include/set_eugid.h +local.o: ../../include/sys_defs.h +local.o: ../../include/tok822.h +local.o: ../../include/vbuf.h +local.o: ../../include/vstream.h +local.o: ../../include/vstring.h +local.o: local.c +local.o: local.h +local_expand.o: ../../include/argv.h +local_expand.o: ../../include/attr.h +local_expand.o: ../../include/been_here.h +local_expand.o: ../../include/check_arg.h +local_expand.o: ../../include/deliver_request.h +local_expand.o: ../../include/delivered_hdr.h +local_expand.o: ../../include/dict.h +local_expand.o: ../../include/dsn.h +local_expand.o: ../../include/dsn_buf.h +local_expand.o: ../../include/fold_addr.h +local_expand.o: ../../include/htable.h +local_expand.o: ../../include/mac_expand.h +local_expand.o: ../../include/mac_parse.h +local_expand.o: ../../include/mail_params.h +local_expand.o: ../../include/maps.h +local_expand.o: ../../include/mbox_conf.h +local_expand.o: ../../include/msg_stats.h +local_expand.o: ../../include/myflock.h +local_expand.o: ../../include/mymalloc.h +local_expand.o: ../../include/nvtable.h +local_expand.o: ../../include/recipient_list.h +local_expand.o: ../../include/resolve_clnt.h +local_expand.o: ../../include/sys_defs.h +local_expand.o: ../../include/tok822.h +local_expand.o: ../../include/vbuf.h +local_expand.o: ../../include/vstream.h +local_expand.o: ../../include/vstring.h +local_expand.o: local.h +local_expand.o: local_expand.c +mailbox.o: ../../include/argv.h +mailbox.o: ../../include/attr.h +mailbox.o: ../../include/been_here.h +mailbox.o: ../../include/bounce.h +mailbox.o: ../../include/check_arg.h +mailbox.o: ../../include/defer.h +mailbox.o: ../../include/deliver_pass.h +mailbox.o: ../../include/deliver_request.h +mailbox.o: ../../include/delivered_hdr.h +mailbox.o: ../../include/dict.h +mailbox.o: ../../include/dsn.h +mailbox.o: ../../include/dsn_buf.h +mailbox.o: ../../include/dsn_util.h +mailbox.o: ../../include/fold_addr.h +mailbox.o: ../../include/htable.h +mailbox.o: ../../include/iostuff.h +mailbox.o: ../../include/mail_copy.h +mailbox.o: ../../include/mail_params.h +mailbox.o: ../../include/mail_proto.h +mailbox.o: ../../include/maps.h +mailbox.o: ../../include/mbox_conf.h +mailbox.o: ../../include/mbox_open.h +mailbox.o: ../../include/msg.h +mailbox.o: ../../include/msg_stats.h +mailbox.o: ../../include/myflock.h +mailbox.o: ../../include/mymalloc.h +mailbox.o: ../../include/mypwd.h +mailbox.o: ../../include/nvtable.h +mailbox.o: ../../include/recipient_list.h +mailbox.o: ../../include/resolve_clnt.h +mailbox.o: ../../include/safe_open.h +mailbox.o: ../../include/sent.h +mailbox.o: ../../include/set_eugid.h +mailbox.o: ../../include/stringops.h +mailbox.o: ../../include/sys_defs.h +mailbox.o: ../../include/tok822.h +mailbox.o: ../../include/vbuf.h +mailbox.o: ../../include/vstream.h +mailbox.o: ../../include/vstring.h +mailbox.o: ../../include/warn_stat.h +mailbox.o: biff_notify.h +mailbox.o: local.h +mailbox.o: mailbox.c +maildir.o: ../../include/argv.h +maildir.o: ../../include/attr.h +maildir.o: ../../include/been_here.h +maildir.o: ../../include/bounce.h +maildir.o: ../../include/check_arg.h +maildir.o: ../../include/defer.h +maildir.o: ../../include/deliver_request.h +maildir.o: ../../include/delivered_hdr.h +maildir.o: ../../include/dict.h +maildir.o: ../../include/dsn.h +maildir.o: ../../include/dsn_buf.h +maildir.o: ../../include/dsn_util.h +maildir.o: ../../include/fold_addr.h +maildir.o: ../../include/get_hostname.h +maildir.o: ../../include/htable.h +maildir.o: ../../include/mail_copy.h +maildir.o: ../../include/mail_params.h +maildir.o: ../../include/make_dirs.h +maildir.o: ../../include/maps.h +maildir.o: ../../include/mbox_conf.h +maildir.o: ../../include/mbox_open.h +maildir.o: ../../include/msg.h +maildir.o: ../../include/msg_stats.h +maildir.o: ../../include/myflock.h +maildir.o: ../../include/mymalloc.h +maildir.o: ../../include/nvtable.h +maildir.o: ../../include/recipient_list.h +maildir.o: ../../include/resolve_clnt.h +maildir.o: ../../include/safe_open.h +maildir.o: ../../include/sane_fsops.h +maildir.o: ../../include/sent.h +maildir.o: ../../include/set_eugid.h +maildir.o: ../../include/stringops.h +maildir.o: ../../include/sys_defs.h +maildir.o: ../../include/tok822.h +maildir.o: ../../include/vbuf.h +maildir.o: ../../include/vstream.h +maildir.o: ../../include/vstring.h +maildir.o: ../../include/warn_stat.h +maildir.o: local.h +maildir.o: maildir.c +recipient.o: ../../include/argv.h +recipient.o: ../../include/attr.h +recipient.o: ../../include/been_here.h +recipient.o: ../../include/bounce.h +recipient.o: ../../include/canon_addr.h +recipient.o: ../../include/check_arg.h +recipient.o: ../../include/defer.h +recipient.o: ../../include/deliver_request.h +recipient.o: ../../include/delivered_hdr.h +recipient.o: ../../include/dict.h +recipient.o: ../../include/dsn.h +recipient.o: ../../include/dsn_buf.h +recipient.o: ../../include/ext_prop.h +recipient.o: ../../include/fold_addr.h +recipient.o: ../../include/htable.h +recipient.o: ../../include/mail_params.h +recipient.o: ../../include/maps.h +recipient.o: ../../include/mbox_conf.h +recipient.o: ../../include/msg.h +recipient.o: ../../include/msg_stats.h +recipient.o: ../../include/myflock.h +recipient.o: ../../include/mymalloc.h +recipient.o: ../../include/mypwd.h +recipient.o: ../../include/nvtable.h +recipient.o: ../../include/recipient_list.h +recipient.o: ../../include/resolve_clnt.h +recipient.o: ../../include/split_addr.h +recipient.o: ../../include/split_at.h +recipient.o: ../../include/stat_as.h +recipient.o: ../../include/stringops.h +recipient.o: ../../include/strip_addr.h +recipient.o: ../../include/sys_defs.h +recipient.o: ../../include/tok822.h +recipient.o: ../../include/vbuf.h +recipient.o: ../../include/vstream.h +recipient.o: ../../include/vstring.h +recipient.o: local.h +recipient.o: recipient.c +resolve.o: ../../include/argv.h +resolve.o: ../../include/attr.h +resolve.o: ../../include/been_here.h +resolve.o: ../../include/bounce.h +resolve.o: ../../include/check_arg.h +resolve.o: ../../include/defer.h +resolve.o: ../../include/deliver_request.h +resolve.o: ../../include/delivered_hdr.h +resolve.o: ../../include/dict.h +resolve.o: ../../include/dsn.h +resolve.o: ../../include/dsn_buf.h +resolve.o: ../../include/fold_addr.h +resolve.o: ../../include/htable.h +resolve.o: ../../include/iostuff.h +resolve.o: ../../include/mail_params.h +resolve.o: ../../include/mail_proto.h +resolve.o: ../../include/maps.h +resolve.o: ../../include/mbox_conf.h +resolve.o: ../../include/msg.h +resolve.o: ../../include/msg_stats.h +resolve.o: ../../include/myflock.h +resolve.o: ../../include/mymalloc.h +resolve.o: ../../include/nvtable.h +resolve.o: ../../include/recipient_list.h +resolve.o: ../../include/resolve_clnt.h +resolve.o: ../../include/rewrite_clnt.h +resolve.o: ../../include/sys_defs.h +resolve.o: ../../include/tok822.h +resolve.o: ../../include/vbuf.h +resolve.o: ../../include/vstream.h +resolve.o: ../../include/vstring.h +resolve.o: local.h +resolve.o: resolve.c +token.o: ../../include/argv.h +token.o: ../../include/attr.h +token.o: ../../include/been_here.h +token.o: ../../include/bounce.h +token.o: ../../include/check_arg.h +token.o: ../../include/defer.h +token.o: ../../include/deliver_request.h +token.o: ../../include/delivered_hdr.h +token.o: ../../include/dict.h +token.o: ../../include/dsn.h +token.o: ../../include/dsn_buf.h +token.o: ../../include/fold_addr.h +token.o: ../../include/htable.h +token.o: ../../include/mail_params.h +token.o: ../../include/maps.h +token.o: ../../include/mbox_conf.h +token.o: ../../include/msg.h +token.o: ../../include/msg_stats.h +token.o: ../../include/myflock.h +token.o: ../../include/mymalloc.h +token.o: ../../include/nvtable.h +token.o: ../../include/readlline.h +token.o: ../../include/recipient_list.h +token.o: ../../include/resolve_clnt.h +token.o: ../../include/stringops.h +token.o: ../../include/sys_defs.h +token.o: ../../include/tok822.h +token.o: ../../include/vbuf.h +token.o: ../../include/vstream.h +token.o: ../../include/vstring.h +token.o: ../../include/vstring_vstream.h +token.o: local.h +token.o: token.c +unknown.o: ../../include/argv.h +unknown.o: ../../include/attr.h +unknown.o: ../../include/been_here.h +unknown.o: ../../include/bounce.h +unknown.o: ../../include/canon_addr.h +unknown.o: ../../include/check_arg.h +unknown.o: ../../include/defer.h +unknown.o: ../../include/deliver_pass.h +unknown.o: ../../include/deliver_request.h +unknown.o: ../../include/delivered_hdr.h +unknown.o: ../../include/dict.h +unknown.o: ../../include/dsn.h +unknown.o: ../../include/dsn_buf.h +unknown.o: ../../include/fold_addr.h +unknown.o: ../../include/htable.h +unknown.o: ../../include/iostuff.h +unknown.o: ../../include/mail_addr.h +unknown.o: ../../include/mail_params.h +unknown.o: ../../include/mail_proto.h +unknown.o: ../../include/maps.h +unknown.o: ../../include/mbox_conf.h +unknown.o: ../../include/msg.h +unknown.o: ../../include/msg_stats.h +unknown.o: ../../include/myflock.h +unknown.o: ../../include/mymalloc.h +unknown.o: ../../include/nvtable.h +unknown.o: ../../include/recipient_list.h +unknown.o: ../../include/resolve_clnt.h +unknown.o: ../../include/sent.h +unknown.o: ../../include/stringops.h +unknown.o: ../../include/sys_defs.h +unknown.o: ../../include/tok822.h +unknown.o: ../../include/vbuf.h +unknown.o: ../../include/vstream.h +unknown.o: ../../include/vstring.h +unknown.o: local.h +unknown.o: unknown.c diff --git a/src/local/Musings b/src/local/Musings new file mode 100644 index 0000000..6149a2e --- /dev/null +++ b/src/local/Musings @@ -0,0 +1,39 @@ +Local delivery models + +The "monolithic" model: recursively expand the complete initial +recipient list (via aliases, mailing lists, .forward files) to one +expanded recipient list (mail addresses, shell commands, files, +mailboxes). Sort/uniq the expanded recipient list, and deliver. + +The "forward as if sent by recipient" model: each level of recursion +(aliases, mailing lists, forward files) takes one entire iteration +through the mail system. Non-recursively expand one local recipient +(via alias, mailing list, the recipient's .forward file) to a list +of expanded recipients. Sort/uniq the list and deliver by re-injecting +messages into the mail system. Since recipient expansion uses a +non-recursive algorithm, the mailer might loop indefinitely, +re-injecting messages into itself. These local forwarding loops +must be broken by stamping a message when it reaches the local +delivery stage (e.g., by adding a Delivered-To: message header). + +The Postfix system uses a hybrid approach. It does recursive alias +expansion, but only one initial recipient at a time. It delivers +to expanded recipients by re-submitting the message into the mail +system, so it can keep track of the delivery status for each expanded +recipient. Because alias expansion does not look in .forward files, +it cannot prevent local forwarding loops. The Postfix system adds +Delivered: message headers to break local and external forwarding +loops. + +Delivery status management + +The "exact" model: maintain on file the delivery status of each +expanded recipient: remote recipients, shell commands and files, +including the privileges for delivery to shell commands and files. + +The "safe" model: maintain on file only the delivery status of +non-sensitive destinations (local or remote addresses). Deliver to +sensitive destinations first (commands, files), but do not keep a +record of their status on file (including privileges). This means +that the mail system will occasionally deliver the same message +more than once to a file or command. diff --git a/src/local/alias.c b/src/local/alias.c new file mode 100644 index 0000000..99e3dd6 --- /dev/null +++ b/src/local/alias.c @@ -0,0 +1,386 @@ +/*++ +/* NAME +/* alias 3 +/* SUMMARY +/* alias data base lookups +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_alias(state, usr_attr, name, statusp) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *name; +/* int *statusp; +/* DESCRIPTION +/* deliver_alias() looks up the expansion of the recipient in +/* the global alias database and delivers the message to the +/* listed destinations. The result is zero when no alias was found +/* or when the message should be delivered to the user instead. +/* +/* deliver_alias() has wired-in knowledge about a few reserved +/* recipient names. +/* .IP \(bu +/* When no alias is found for the local \fIpostmaster\fR or +/* \fImailer-daemon\fR a warning is issued and the message +/* is discarded. +/* .IP \(bu +/* When an alias exists for recipient \fIname\fR, and an alias +/* exists for \fIowner-name\fR, the sender address is changed +/* to \fIowner-name\fR, and the owner delivery attribute is +/* set accordingly. This feature is disabled with +/* "owner_request_special = no". +/* .PP +/* Arguments: +/* .IP state +/* Attributes that specify the message, recipient and more. +/* Expansion type (alias, include, .forward). +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* User attributes (rights, environment). +/* .IP name +/* The alias to be looked up. +/* .IP statusp +/* Delivery status. See below. +/* DIAGNOSTICS +/* Fatal errors: out of memory. The delivery status is non-zero +/* when delivery should be tried again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* Application-specific. */ + +#define NO 0 +#define YES 1 + +/* deliver_alias - expand alias file entry */ + +int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr, + char *name, int *statusp) +{ + const char *myname = "deliver_alias"; + const char *alias_result; + char *saved_alias_result; + char *owner; + char **cpp; + struct mypasswd *alias_pwd; + VSTRING *canon_owner; + DICT *dict; + const char *owner_rhs; /* owner alias, RHS */ + int alias_count; + int dsn_notify; + char *dsn_envid; + int dsn_ret; + const char *dsn_orcpt; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE/LOOP ELIMINATION + * + * We cannot do duplicate elimination here. Sendmail compatibility requires + * that we allow multiple deliveries to the same alias, even recursively! + * For example, we must deliver to mailbox any messages that are addressed + * to the alias of a user that lists that same alias in her own .forward + * file. Yuck! This is just an example of some really perverse semantics + * that people will expect Postfix to implement just like sendmail. + * + * We can recognize one special case: when an alias includes its own name, + * deliver to the user instead, just like sendmail. Otherwise, we just + * bail out when nesting reaches some unreasonable depth, and blame it on + * a possible alias loop. + */ + if (state.msg_attr.exp_from != 0 + && strcasecmp_utf8(state.msg_attr.exp_from, name) == 0) + return (NO); + if (state.level > 100) { + msg_warn("alias database loop for %s", name); + dsb_simple(state.msg_attr.why, "5.4.6", + "alias database loop for %s", name); + *statusp = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + state.msg_attr.exp_from = name; + + /* + * There are a bunch of roles that we're trying to keep track of. + * + * First, there's the issue of whose rights should be used when delivering + * to "|command" or to /file/name. With alias databases, the rights are + * those of who owns the alias, i.e. the database owner. With aliases + * owned by root, a default user is used instead. When an alias with + * default rights references an include file owned by an ordinary user, + * we must use the rights of the include file owner, otherwise the + * include file owner could take control of the default account. + * + * Secondly, there's the question of who to notify of delivery problems. + * With aliases that have an owner- alias, the latter is used to set the + * sender and owner attributes. Otherwise, the owner attribute is reset + * (the alias is globally visible and could be sent to by anyone). + */ + for (cpp = alias_maps->argv->argv; *cpp; cpp++) { + if ((dict = dict_handle(*cpp)) == 0) + msg_panic("%s: dictionary not found: %s", myname, *cpp); + if ((alias_result = dict_get(dict, name)) != 0) { + if (msg_verbose) + msg_info("%s: %s: %s = %s", myname, *cpp, name, alias_result); + + /* + * Don't expand a verify-only request. + */ + if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) { + dsb_simple(state.msg_attr.why, "2.0.0", + "aliased to %s", alias_result); + *statusp = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + return (YES); + } + + /* + * DELIVERY POLICY + * + * Update the expansion type attribute, so we can decide if + * deliveries to |command and /file/name are allowed at all. + */ + state.msg_attr.exp_type = EXPAND_TYPE_ALIAS; + + /* + * DELIVERY RIGHTS + * + * What rights to use for |command and /file/name deliveries? The + * command and file code will use default rights when the alias + * database is owned by root, otherwise it will use the rights of + * the alias database owner. + */ + if (dict->owner.status == DICT_OWNER_TRUSTED) { + alias_pwd = 0; + RESET_USER_ATTR(usr_attr, state.level); + } else { + if (dict->owner.status == DICT_OWNER_UNKNOWN) { + msg_warn("%s: no owner UID for alias database %s", + myname, *cpp); + dsb_simple(state.msg_attr.why, "4.3.0", + "mail system configuration error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + if ((errno = mypwuid_err(dict->owner.uid, &alias_pwd)) != 0 + || alias_pwd == 0) { + msg_warn(errno ? + "cannot find alias database owner for %s: %m" : + "cannot find alias database owner for %s", *cpp); + dsb_simple(state.msg_attr.why, "4.3.0", + "cannot find alias database owner"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + SET_USER_ATTR(usr_attr, alias_pwd, state.level); + } + + /* + * WHERE TO REPORT DELIVERY PROBLEMS. + * + * Use the owner- alias if one is specified, otherwise reset the + * owner attribute and use the include file ownership if we can. + * Save the dict_lookup() result before something clobbers it. + * + * Don't match aliases that are based on regexps. + */ +#define OWNER_ASSIGN(own) \ + (own = (var_ownreq_special == 0 ? 0 : \ + concatenate("owner-", name, (char *) 0))) + + saved_alias_result = mystrdup(alias_result); + if (OWNER_ASSIGN(owner) != 0 + && (owner_rhs = maps_find(alias_maps, owner, DICT_FLAG_NONE)) != 0) { + canon_owner = canon_addr_internal(vstring_alloc(10), + var_exp_own_alias ? owner_rhs : owner); + /* Set envelope sender and owner attribute. */ + SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level); + } else { + canon_owner = 0; + /* Note: this does not reset the envelope sender. */ + if (var_reset_owner_attr) + RESET_OWNER_ATTR(state.msg_attr, state.level); + } + + /* + * EXTERNAL LOOP CONTROL + * + * Set the delivered message attribute to the recipient, so that + * this message will list the correct forwarding address. + */ + if (var_frozen_delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + + /* + * Deliver. + */ + alias_count = 0; + if (owner != 0 && alias_maps->error != 0) { + dsb_simple(state.msg_attr.why, "4.3.0", + "alias database unavailable"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + + /* + * XXX DSN + * + * When delivering to a mailing list (i.e. the envelope sender + * is replaced) the ENVID, NOTIFY, RET, and ORCPT parameters + * which accompany the redistributed message MUST NOT be + * derived from those of the original message. + * + * When delivering to an alias (i.e. the envelope sender is not + * replaced) any ENVID, RET, or ORCPT parameters are + * propagated to all forwarding addresses associated with + * that alias. The NOTIFY parameter is propagated to the + * forwarding addresses, except that any SUCCESS keyword is + * removed. + */ +#define DSN_SAVE_UPDATE(saved, old, new) do { \ + saved = old; \ + old = new; \ + } while (0) + + DSN_SAVE_UPDATE(dsn_notify, state.msg_attr.rcpt.dsn_notify, + dsn_notify == DSN_NOTIFY_SUCCESS ? + DSN_NOTIFY_NEVER : + dsn_notify & ~DSN_NOTIFY_SUCCESS); + if (canon_owner != 0) { + DSN_SAVE_UPDATE(dsn_envid, state.msg_attr.dsn_envid, ""); + DSN_SAVE_UPDATE(dsn_ret, state.msg_attr.dsn_ret, 0); + DSN_SAVE_UPDATE(dsn_orcpt, state.msg_attr.rcpt.dsn_orcpt, ""); + state.msg_attr.rcpt.orig_addr = ""; + } + *statusp = + deliver_token_string(state, usr_attr, saved_alias_result, + &alias_count); +#if 0 + if (var_ownreq_special + && strncmp("owner-", state.msg_attr.sender, 6) != 0 + && alias_count > 10) + msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias", + name, name); +#endif + if (alias_count < 1) { + msg_warn("no recipient in alias lookup result for %s", name); + dsb_simple(state.msg_attr.why, "4.3.0", + "alias database unavailable"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + + /* + * XXX DSN + * + * When delivering to a mailing list (i.e. the envelope + * sender address is replaced) and NOTIFY=SUCCESS was + * specified, report a DSN of "delivered". + * + * When delivering to an alias (i.e. the envelope sender + * address is not replaced) and NOTIFY=SUCCESS was + * specified, report a DSN of "expanded". + */ + if (dsn_notify & DSN_NOTIFY_SUCCESS) { + state.msg_attr.rcpt.dsn_notify = dsn_notify; + if (canon_owner != 0) { + state.msg_attr.dsn_envid = dsn_envid; + state.msg_attr.dsn_ret = dsn_ret; + state.msg_attr.rcpt.dsn_orcpt = dsn_orcpt; + } + dsb_update(state.msg_attr.why, "2.0.0", canon_owner ? + "delivered" : "expanded", + DSB_SKIP_RMTA, DSB_SKIP_REPLY, + "alias expanded"); + (void) trace_append(BOUNCE_FLAG_NONE, + SENT_ATTR(state.msg_attr)); + } + } + } + myfree(saved_alias_result); + if (owner) + myfree(owner); + if (canon_owner) + vstring_free(canon_owner); + if (alias_pwd) + mypwfree(alias_pwd); + return (YES); + } + + /* + * If the alias database was inaccessible for some reason, defer + * further delivery for the current top-level recipient. + */ + if (alias_result == 0 && dict->error != 0) { + msg_warn("%s:%s: lookup of '%s' failed", + dict->type, dict->name, name); + dsb_simple(state.msg_attr.why, "4.3.0", + "alias database unavailable"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } else { + if (msg_verbose) + msg_info("%s: %s: %s not found", myname, *cpp, name); + } + } + + /* + * Try delivery to a local user instead. + */ + return (NO); +} diff --git a/src/local/biff_notify.c b/src/local/biff_notify.c new file mode 100644 index 0000000..a6a4925 --- /dev/null +++ b/src/local/biff_notify.c @@ -0,0 +1,98 @@ +/*++ +/* NAME +/* biff_notify 3 +/* SUMMARY +/* send biff notification +/* SYNOPSIS +/* #include +/* +/* void biff_notify(text, len) +/* const char *text; +/* ssize_t len; +/* DESCRIPTION +/* biff_notify() sends a \fBBIFF\fR notification request to the +/* \fBcomsat\fR daemon. +/* +/* Arguments: +/* .IP text +/* Null-terminated text (username@mailbox-offset). +/* .IP len +/* Length of text, including null terminator. +/* BUGS +/* The \fBBIFF\fR "service" can be a noticeable load for +/* systems that have many logged-in users. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include + +/* biff_notify - notify recipient via the biff "protocol" */ + +void biff_notify(const char *text, ssize_t len) +{ + static struct sockaddr_in sin; + static int sock = -1; + struct hostent *hp; + struct servent *sp; + + /* + * Initialize a socket address structure, or re-use an existing one. + */ + if (sin.sin_family == 0) { + if ((sp = getservbyname("biff", "udp")) == 0) { + msg_warn("service not found: biff/udp"); + return; + } + if ((hp = gethostbyname("localhost")) == 0) { + msg_warn("host not found: localhost"); + return; + } + if ((int) hp->h_length > (int) sizeof(sin.sin_addr)) { + msg_warn("bad address size %d for localhost", hp->h_length); + return; + } + sin.sin_family = hp->h_addrtype; + sin.sin_port = sp->s_port; + memcpy((void *) &sin.sin_addr, hp->h_addr_list[0], hp->h_length); + } + + /* + * Open a socket, or re-use an existing one. + */ + if (sock < 0) { + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + msg_warn("socket: %m"); + return; + } + close_on_exec(sock, CLOSE_ON_EXEC); + } + + /* + * Biff! + */ + if (sendto(sock, text, len, 0, (struct sockaddr *) &sin, sizeof(sin)) != len) + msg_warn("biff_notify: %m"); +} diff --git a/src/local/biff_notify.h b/src/local/biff_notify.h new file mode 100644 index 0000000..8b76f9d --- /dev/null +++ b/src/local/biff_notify.h @@ -0,0 +1,30 @@ +#ifndef _BIFF_H_INCLUDED_ +#define _BIFF_H_INCLUDED_ + +/*++ +/* NAME +/* biff_notify 3h +/* SUMMARY +/* read logical line +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void biff_notify(const char *, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/local/bounce_workaround.c b/src/local/bounce_workaround.c new file mode 100644 index 0000000..7fe4aaa --- /dev/null +++ b/src/local/bounce_workaround.c @@ -0,0 +1,159 @@ +/*++ +/* NAME +/* bounce_workaround 3 +/* SUMMARY +/* Send non-delivery notification with sender override +/* SYNOPSIS +/* #include "local.h" +/* +/* int bounce_workaround(state) +/* LOCAL_STATE state; +/* DESCRIPTION +/* This module works around a limitation in the bounce daemon +/* protocol, namely, the assumption that the envelope sender +/* address in a queue file is the delivery status notification +/* address for all recipients in that queue file. The assumption +/* is not valid when the local(8) delivery agent overrides the +/* envelope sender address by an owner- alias, for one or more +/* recipients in the queue file. +/* +/* Sender address override is a problem only when delivering +/* to command or file, or when breaking a Delivered-To loop. +/* The local(8) delivery agent saves normal recipients to a +/* new queue file, together with the replacement envelope +/* sender address; delivery then proceeds from that new queue +/* file, and no workaround is needed. +/* +/* The workaround sends one non-delivery notification for each +/* failed delivery that has a replacement sender address. The +/* notifications are not aggregated, unlike notifications to +/* non-replaced sender addresses. In practice, a local alias +/* rarely has more than one file or command destination (if +/* only because soft error handling is problematic). +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* The non-delivery status must be either 4.X.X or 5.X.X. +/* DIAGNOSTICS +/* Fatal errors: out of memory. The result is non-zero when +/* the operation should be tried again. Warnings: malformed +/* address. +/* BUGS +/* The proper fix is to record in the bounce logfile an error +/* return address for each individual recipient. This would +/* eliminate the need for VERP-specific bounce protocol code, +/* and would move complexity from the bounce client side to +/* the bounce server side where it more likely belongs. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +int bounce_workaround(LOCAL_STATE state) +{ + const char *myname = "bounce_workaround"; + VSTRING *canon_owner = 0; + int rcpt_stat; + + /* + * Look up the substitute sender address. + */ + if (var_ownreq_special) { + char *stripped_recipient; + char *owner_alias; + const char *owner_expansion; + +#define FIND_OWNER(lhs, rhs, addr) { \ + lhs = concatenate("owner-", addr, (char *) 0); \ + (void) split_at_right(lhs, '@'); \ + rhs = maps_find(alias_maps, lhs, DICT_FLAG_NONE); \ + } + + FIND_OWNER(owner_alias, owner_expansion, state.msg_attr.rcpt.address); + if (alias_maps->error == 0 && owner_expansion == 0 + && (stripped_recipient = strip_addr(state.msg_attr.rcpt.address, + (char **) 0, + var_rcpt_delim)) != 0) { + myfree(owner_alias); + FIND_OWNER(owner_alias, owner_expansion, stripped_recipient); + myfree(stripped_recipient); + } + if (alias_maps->error == 0 && owner_expansion != 0) { + canon_owner = canon_addr_internal(vstring_alloc(10), + var_exp_own_alias ? + owner_expansion : owner_alias); + SET_OWNER_ATTR(state.msg_attr, STR(canon_owner), state.level); + } + myfree(owner_alias); + if (alias_maps->error != 0) { + /* At this point, canon_owner == 0. */ + dsb_simple(state.msg_attr.why, "4.3.0", + "alias database unavailable"); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + } + + /* + * Send a delivery status notification with a single recipient to the + * substitute sender address, before completion of the delivery request. + */ + if (canon_owner) { + rcpt_stat = + (STR(state.msg_attr.why->status)[0] == '4' ? + defer_one : bounce_one) + (BOUNCE_FLAGS(state.request), + BOUNCE_ONE_ATTR(state.msg_attr)); + vstring_free(canon_owner); + } + + /* + * Send a regular delivery status notification, after completion of the + * delivery request. + */ + else { + rcpt_stat = + (STR(state.msg_attr.why->status)[0] == '4' ? + defer_append : bounce_append) + (BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } + return (rcpt_stat); +} diff --git a/src/local/command.c b/src/local/command.c new file mode 100644 index 0000000..4781daf --- /dev/null +++ b/src/local/command.c @@ -0,0 +1,251 @@ +/*++ +/* NAME +/* command 3 +/* SUMMARY +/* message delivery to shell command +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_command(state, usr_attr, command) +/* LOCAL_STATE state; +/* USER_ATTR exp_attr; +/* const char *command; +/* DESCRIPTION +/* deliver_command() runs a command with a message as standard +/* input. A limited amount of standard output and standard error +/* output is captured for diagnostics purposes. +/* Duplicate commands for the same recipient are suppressed. +/* A limited amount of information is exported via the environment: +/* HOME, SHELL, LOGNAME, USER, EXTENSION, DOMAIN, RECIPIENT (entire +/* address) LOCAL (just the local part) and SENDER. The exported +/* information is censored with var_cmd_filter. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing the alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP command +/* The shell command to be executed. If possible, the command is +/* executed without actually invoking a shell. if the command is +/* the mailbox_command, it is subjected to $name expansion. +/* DIAGNOSTICS +/* deliver_command() returns non-zero when delivery should be +/* tried again, +/* SEE ALSO +/* mailbox(3) deliver to mailbox +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_command - deliver to shell command */ + +int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *command) +{ + const char *myname = "deliver_command"; + DSN_BUF *why = state.msg_attr.why; + int cmd_status; + int deliver_status; + ARGV *env; + int copy_flags; + char **cpp; + char *cp; + ARGV *export_env; + VSTRING *exec_dir; + int expand_status; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE ELIMINATION + * + * Skip this command if it was already delivered to as this user. + */ + if (been_here(state.dup_filter, "command %s:%ld %s", + state.msg_attr.user, (long) usr_attr.uid, command)) + return (0); + + /* + * Don't deliver a trace-only request. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to command: %s", command); + return (sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr))); + } + + /* + * DELIVERY RIGHTS + * + * Choose a default uid and gid when none have been selected (i.e. values + * are still zero). + */ + if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0) + msg_panic("privileged default user id"); + if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0) + msg_panic("privileged default group id"); + + /* + * Deliver. + */ + copy_flags = MAIL_COPY_FROM | MAIL_COPY_RETURN_PATH + | MAIL_COPY_ORIG_RCPT; + if (local_deliver_hdr_mask & DELIVER_HDR_CMD) + copy_flags |= MAIL_COPY_DELIVERED; + + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("%s: seek queue file %s: %m", + myname, VSTREAM_PATH(state.msg_attr.fp)); + + /* + * Pass additional environment information. XXX This should be + * configurable. However, passing untrusted information via environment + * parameters opens up a whole can of worms. Lesson from web servers: + * don't let any network data even near a shell. It causes trouble. + */ + env = argv_alloc(1); + if (usr_attr.home) + argv_add(env, "HOME", usr_attr.home, ARGV_END); + argv_add(env, + "LOGNAME", state.msg_attr.user, + "USER", state.msg_attr.user, + "SENDER", state.msg_attr.sender, + "RECIPIENT", state.msg_attr.rcpt.address, + "LOCAL", state.msg_attr.local, + ARGV_END); + if (usr_attr.shell) + argv_add(env, "SHELL", usr_attr.shell, ARGV_END); + if (state.msg_attr.domain) + argv_add(env, "DOMAIN", state.msg_attr.domain, ARGV_END); + if (state.msg_attr.extension) + argv_add(env, "EXTENSION", state.msg_attr.extension, ARGV_END); + if (state.msg_attr.rcpt.orig_addr && state.msg_attr.rcpt.orig_addr[0]) + argv_add(env, "ORIGINAL_RECIPIENT", state.msg_attr.rcpt.orig_addr, + ARGV_END); + +#define EXPORT_REQUEST(name, value) \ + if ((value)[0]) argv_add(env, (name), (value), ARGV_END); + + EXPORT_REQUEST("CLIENT_HOSTNAME", state.msg_attr.request->client_name); + EXPORT_REQUEST("CLIENT_ADDRESS", state.msg_attr.request->client_addr); + EXPORT_REQUEST("CLIENT_HELO", state.msg_attr.request->client_helo); + EXPORT_REQUEST("CLIENT_PROTOCOL", state.msg_attr.request->client_proto); + EXPORT_REQUEST("SASL_METHOD", state.msg_attr.request->sasl_method); + EXPORT_REQUEST("SASL_SENDER", state.msg_attr.request->sasl_sender); + EXPORT_REQUEST("SASL_USERNAME", state.msg_attr.request->sasl_username); + + argv_terminate(env); + + /* + * Censor out undesirable characters from exported data. + */ + for (cpp = env->argv; *cpp; cpp += 2) + for (cp = cpp[1]; *(cp += strspn(cp, var_cmd_exp_filter)) != 0;) + *cp++ = '_'; + + /* + * Evaluate the command execution directory. Defer delivery if expansion + * fails. + */ + export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); + exec_dir = vstring_alloc(10); + expand_status = local_expand(exec_dir, var_exec_directory, + &state, &usr_attr, var_exec_exp_filter); + + if (expand_status & MAC_PARSE_ERROR) { + cmd_status = PIPE_STAT_DEFER; + dsb_simple(why, "4.3.5", "mail system configuration error"); + msg_warn("bad parameter value syntax for %s: %s", + VAR_EXEC_DIRECTORY, var_exec_directory); + } else { + cmd_status = pipe_command(state.msg_attr.fp, why, + CA_PIPE_CMD_UID(usr_attr.uid), + CA_PIPE_CMD_GID(usr_attr.gid), + CA_PIPE_CMD_COMMAND(command), + CA_PIPE_CMD_COPY_FLAGS(copy_flags), + CA_PIPE_CMD_SENDER(state.msg_attr.sender), + CA_PIPE_CMD_ORIG_RCPT(state.msg_attr.rcpt.orig_addr), + CA_PIPE_CMD_DELIVERED(state.msg_attr.delivered), + CA_PIPE_CMD_TIME_LIMIT(var_command_maxtime), + CA_PIPE_CMD_ENV(env->argv), + CA_PIPE_CMD_EXPORT(export_env->argv), + CA_PIPE_CMD_SHELL(var_local_cmd_shell), + CA_PIPE_CMD_CWD(*STR(exec_dir) ? + STR(exec_dir) : (char *) 0), + CA_PIPE_CMD_END); + } + vstring_free(exec_dir); + argv_free(export_env); + argv_free(env); + + /* + * Depending on the result, bounce or defer the message. + */ + switch (cmd_status) { + case PIPE_STAT_OK: + dsb_simple(why, "2.0.0", "delivered to command: %s", command); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + break; + case PIPE_STAT_BOUNCE: + case PIPE_STAT_DEFER: + /* Account for possible owner- sender address override. */ + deliver_status = bounce_workaround(state); + break; + case PIPE_STAT_CORRUPT: + deliver_status = DEL_STAT_DEFER; + break; + default: + msg_panic("%s: bad status %d", myname, cmd_status); + /* NOTREACHED */ + } + + return (deliver_status); +} diff --git a/src/local/deliver_attr.c b/src/local/deliver_attr.c new file mode 100644 index 0000000..9ed18e2 --- /dev/null +++ b/src/local/deliver_attr.c @@ -0,0 +1,105 @@ +/*++ +/* NAME +/* deliver_attr 3 +/* SUMMARY +/* initialize message delivery attributes +/* SYNOPSIS +/* #include "local.h" +/* +/* void deliver_attr_init(attrp) +/* DELIVER_ATTR *attrp; +/* +/* void deliver_attr_dump(attrp) +/* DELIVER_ATTR *attrp; +/* +/* void deliver_attr_free(attrp) +/* DELIVER_ATTR *attrp; +/* DESCRIPTION +/* deliver_attr_init() initializes a structure with message delivery +/* attributes to a known initial state (all zeros). +/* +/* deliver_attr_dump() logs the contents of the given attribute list. +/* +/* deliver_attr_free() releases memory that was allocated by +/* deliver_attr_init(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_attr_init - set message delivery attributes to all-zero state */ + +void deliver_attr_init(DELIVER_ATTR *attrp) +{ + attrp->level = 0; + attrp->fp = 0; + attrp->queue_name = 0; + attrp->queue_id = 0; + attrp->offset = 0; + attrp->sender = 0; + RECIPIENT_ASSIGN(&(attrp->rcpt), 0, 0, 0, 0, 0); + attrp->domain = 0; + attrp->local = 0; + attrp->user = 0; + attrp->extension = 0; + attrp->unmatched = 0; + attrp->owner = 0; + attrp->delivered = 0; + attrp->relay = 0; + attrp->exp_type = 0; + attrp->exp_from = 0; + attrp->why = dsb_create(); +} + +/* deliver_attr_dump - log message delivery attributes */ + +void deliver_attr_dump(DELIVER_ATTR *attrp) +{ + msg_info("level: %d", attrp->level); + msg_info("path: %s", VSTREAM_PATH(attrp->fp)); + msg_info("fp: 0x%lx", (long) attrp->fp); + msg_info("queue_name: %s", attrp->queue_name ? attrp->queue_name : "null"); + msg_info("queue_id: %s", attrp->queue_id ? attrp->queue_id : "null"); + msg_info("offset: %ld", attrp->rcpt.offset); + msg_info("sender: %s", attrp->sender ? attrp->sender : "null"); + msg_info("recipient: %s", attrp->rcpt.address ? attrp->rcpt.address : "null"); + msg_info("domain: %s", attrp->domain ? attrp->domain : "null"); + msg_info("local: %s", attrp->local ? attrp->local : "null"); + msg_info("user: %s", attrp->user ? attrp->user : "null"); + msg_info("extension: %s", attrp->extension ? attrp->extension : "null"); + msg_info("unmatched: %s", attrp->unmatched ? attrp->unmatched : "null"); + msg_info("owner: %s", attrp->owner ? attrp->owner : "null"); + msg_info("delivered: %s", attrp->delivered ? attrp->delivered : "null"); + msg_info("relay: %s", attrp->relay ? attrp->relay : "null"); + msg_info("exp_type: %d", attrp->exp_type); + msg_info("exp_from: %s", attrp->exp_from ? attrp->exp_from : "null"); + msg_info("why: %s", attrp->why ? "buffer" : "null"); +} + +/* deliver_attr_free - release storage */ + +void deliver_attr_free(DELIVER_ATTR *attrp) +{ + dsb_free(attrp->why); +} diff --git a/src/local/dotforward.c b/src/local/dotforward.c new file mode 100644 index 0000000..3ce2cfc --- /dev/null +++ b/src/local/dotforward.c @@ -0,0 +1,304 @@ +/*++ +/* NAME +/* dotforward 3 +/* SUMMARY +/* $HOME/.forward file expansion +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_dotforward(state, usr_attr, statusp) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* int *statusp; +/* DESCRIPTION +/* deliver_dotforward() delivers a message to the destinations +/* listed in a recipient's .forward file(s) as specified through +/* the forward_path configuration parameter. The result is +/* zero when no acceptable .forward file was found, or when +/* a recipient is listed in her own .forward file. Expansions +/* are scrutinized with the forward_expansion_filter parameter. +/* +/* Arguments: +/* .IP state +/* Message delivery attributes (sender, recipient etc.). +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP statusp +/* Message delivery status. See below. +/* DIAGNOSTICS +/* Fatal errors: out of memory. Warnings: bad $HOME/.forward +/* file type, permissions or ownership. The message delivery +/* status is non-zero when delivery should be tried again. +/* SEE ALSO +/* include(3) include file processor. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#ifdef USE_PATHS_H +#include +#endif +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +#define NO 0 +#define YES 1 + +/* deliver_dotforward - expand contents of .forward file */ + +int deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) +{ + const char *myname = "deliver_dotforward"; + struct stat st; + VSTRING *path; + struct mypasswd *mypwd; + int fd; + VSTREAM *fp; + int status; + int forward_found = NO; + int lookup_status; + int addr_count; + char *saved_forward_path; + char *lhs; + char *next; + int expand_status; + int saved_notify; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Skip this module if per-user forwarding is disabled. + */ + if (*var_forward_path == 0) + return (NO); + + /* + * Skip non-existing users. The mailbox delivery routine will catch the + * error. + */ + if ((errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) { + msg_warn("error looking up passwd info for %s: %m", + state.msg_attr.user); + dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + if (mypwd == 0) + return (NO); + + /* + * From here on no early returns or we have a memory leak. + */ + + /* + * EXTERNAL LOOP CONTROL + * + * Set the delivered message attribute to the recipient, so that this + * message will list the correct forwarding address. + */ + if (var_frozen_delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + + /* + * DELIVERY RIGHTS + * + * Do not inherit rights from the .forward file owner. Instead, use the + * recipient's rights, and insist that the .forward file is owned by the + * recipient. This is a small but significant difference. Use the + * recipient's rights for all /file and |command deliveries, and pass on + * these rights to command/file destinations in included files. When + * these are the rights of root, the /file and |command delivery routines + * will use unprivileged default rights instead. Better safe than sorry. + */ + SET_USER_ATTR(usr_attr, mypwd, state.level); + + /* + * DELIVERY POLICY + * + * Update the expansion type attribute so that we can decide if deliveries + * to |command and /file/name are allowed at all. + */ + state.msg_attr.exp_type = EXPAND_TYPE_FWD; + + /* + * WHERE TO REPORT DELIVERY PROBLEMS + * + * Set the owner attribute so that 1) include files won't set the sender to + * be this user and 2) mail forwarded to other local users will be + * resubmitted as a new queue file. + */ + state.msg_attr.owner = state.msg_attr.user; + + /* + * Search the forward_path for an existing forward file. + * + * If unmatched extensions should never be propagated, or if a forward file + * name includes the address extension, don't propagate the extension to + * the recipient addresses. + */ + status = 0; + path = vstring_alloc(100); + saved_forward_path = mystrdup(var_forward_path); + next = saved_forward_path; + lookup_status = -1; + + while ((lhs = mystrtok(&next, CHARS_COMMA_SP)) != 0) { + expand_status = local_expand(path, lhs, &state, &usr_attr, + var_fwd_exp_filter); + if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) { + lookup_status = + lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid); + if (msg_verbose) + msg_info("%s: path %s expand_status %d look_status %d", myname, + STR(path), expand_status, lookup_status); + if (lookup_status >= 0) { + if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0 + || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0) + state.msg_attr.unmatched = 0; + break; + } + } + } + + /* + * Process the forward file. + * + * Assume that usernames do not have file system meta characters. Open the + * .forward file as the user. Ignore files that aren't regular files, + * files that are owned by the wrong user, or files that have world write + * permission enabled. + * + * DUPLICATE/LOOP ELIMINATION + * + * If this user includes (an alias of) herself in her own .forward file, + * deliver to the user instead. + */ + if (lookup_status >= 0) { + + /* + * Don't expand a verify-only request. + */ + if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) { + dsb_simple(state.msg_attr.why, "2.0.0", + "forward via file: %s", STR(path)); + *statusp = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + forward_found = YES; + } else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) { + state.msg_attr.exp_from = state.msg_attr.local; + if (S_ISREG(st.st_mode) == 0) { + msg_warn("file %s is not a regular file", STR(path)); + } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) { + msg_warn("file %s has bad owner uid %ld", + STR(path), (long) st.st_uid); + } else if (st.st_mode & 002) { + msg_warn("file %s is world writable", STR(path)); + } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) { + msg_warn("cannot open file %s: %m", STR(path)); + } else { + + /* + * XXX DSN. When delivering to an alias (i.e. the envelope + * sender address is not replaced) any ENVID, RET, or ORCPT + * parameters are propagated to all forwarding addresses + * associated with that alias. The NOTIFY parameter is + * propagated to the forwarding addresses, except that any + * SUCCESS keyword is removed. + */ + close_on_exec(fd, CLOSE_ON_EXEC); + addr_count = 0; + fp = vstream_fdopen(fd, O_RDONLY); + saved_notify = state.msg_attr.rcpt.dsn_notify; + state.msg_attr.rcpt.dsn_notify = + (saved_notify == DSN_NOTIFY_SUCCESS ? + DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS); + status = deliver_token_stream(state, usr_attr, fp, &addr_count); + if (vstream_fclose(fp)) + msg_warn("close file %s: %m", STR(path)); + if (addr_count > 0) { + forward_found = YES; + been_here(state.dup_filter, "forward-done %s", STR(path)); + + /* + * XXX DSN. When delivering to an alias (i.e. the + * envelope sender address is not replaced) and the + * original NOTIFY parameter for the alias contained the + * SUCCESS keyword, an "expanded" DSN is issued for the + * alias. + */ + if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) { + state.msg_attr.rcpt.dsn_notify = saved_notify; + dsb_update(state.msg_attr.why, "2.0.0", "expanded", + DSB_SKIP_RMTA, DSB_SKIP_REPLY, + "alias expanded"); + (void) trace_append(BOUNCE_FLAG_NONE, + SENT_ATTR(state.msg_attr)); + } + } + } + } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0) + forward_found = YES; /* else we're recursive */ + } + + /* + * Clean up. + */ + vstring_free(path); + myfree(saved_forward_path); + mypwfree(mypwd); + + *statusp = status; + return (forward_found); +} diff --git a/src/local/file.c b/src/local/file.c new file mode 100644 index 0000000..0cc4c18 --- /dev/null +++ b/src/local/file.c @@ -0,0 +1,195 @@ +/*++ +/* NAME +/* file 3 +/* SUMMARY +/* mail delivery to arbitrary file +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_file(state, usr_attr, path) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *path; +/* DESCRIPTION +/* deliver_file() appends a message to a file, UNIX mailbox format, +/* or qmail maildir format, +/* with duplicate suppression. It will deliver only to non-executable +/* regular files. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* .IP usr_attr +/* Attributes describing user rights and environment information. +/* .IP path +/* The file to deliver to. If the name ends in '/', delivery is done +/* in qmail maildir format, otherwise delivery is done in UNIX mailbox +/* format. +/* DIAGNOSTICS +/* deliver_file() returns non-zero when delivery should be tried again. +/* SEE ALSO +/* defer(3) +/* bounce(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_file - deliver to file */ + +int deliver_file(LOCAL_STATE state, USER_ATTR usr_attr, char *path) +{ + const char *myname = "deliver_file"; + struct stat st; + MBOX *mp; + DSN_BUF *why = state.msg_attr.why; + int mail_copy_status = MAIL_COPY_STAT_WRITE; + int deliver_status; + int copy_flags; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE ELIMINATION + * + * Skip this file if it was already delivered to as this user. + */ + if (been_here(state.dup_filter, "file %ld %s", (long) usr_attr.uid, path)) + return (0); + + /* + * DELIVERY POLICY + * + * Do we allow delivery to files? + */ + if ((local_file_deliver_mask & state.msg_attr.exp_type) == 0) { + dsb_simple(why, "5.7.1", "mail to file is restricted"); + /* Account for possible owner- sender address override. */ + return (bounce_workaround(state)); + } + + /* + * Don't deliver trace-only requests. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to file: %s", path); + return (sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr))); + } + + /* + * DELIVERY RIGHTS + * + * Use a default uid/gid when none are given. + */ + if (usr_attr.uid == 0 && (usr_attr.uid = var_default_uid) == 0) + msg_panic("privileged default user id"); + if (usr_attr.gid == 0 && (usr_attr.gid = var_default_gid) == 0) + msg_panic("privileged default group id"); + + /* + * If the name ends in /, use maildir-style delivery instead. + */ + if (path[strlen(path) - 1] == '/') + return (deliver_maildir(state, usr_attr, path)); + + /* + * Deliver. From here on, no early returns or we have a memory leak. + */ + if (msg_verbose) + msg_info("deliver_file (%ld,%ld): %s", + (long) usr_attr.uid, (long) usr_attr.gid, path); + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("seek queue file %s: %m", state.msg_attr.queue_id); + + /* + * As the specified user, open or create the file, lock it, and append + * the message. + */ + copy_flags = MAIL_COPY_MBOX; + if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0) + copy_flags &= ~MAIL_COPY_DELIVERED; + + set_eugid(usr_attr.uid, usr_attr.gid); + mp = mbox_open(path, O_APPEND | O_CREAT | O_WRONLY, + S_IRUSR | S_IWUSR, &st, -1, -1, + local_mbox_lock_mask | MBOX_DOT_LOCK_MAY_FAIL, + "5.2.0", why); + if (mp != 0) { + if (S_ISREG(st.st_mode) && st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) { + vstream_fclose(mp->fp); + dsb_simple(why, "5.7.1", "file is executable"); + } else { + mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, + S_ISREG(st.st_mode) ? copy_flags : + (copy_flags & ~MAIL_COPY_TOFILE), + "\n", why); + } + mbox_release(mp); + } + set_eugid(var_owner_uid, var_owner_gid); + + /* + * As the mail system, bounce, defer delivery, or report success. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + vstring_sprintf_prepend(why->reason, + "cannot append message to file %s: ", path); + /* Account for possible owner- sender address override. */ + deliver_status = bounce_workaround(state); + } else { + dsb_simple(why, "2.0.0", "delivered to file: %s", path); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + } + return (deliver_status); +} diff --git a/src/local/forward.c b/src/local/forward.c new file mode 100644 index 0000000..722dcf7 --- /dev/null +++ b/src/local/forward.c @@ -0,0 +1,393 @@ +/*++ +/* NAME +/* forward 3 +/* SUMMARY +/* message forwarding +/* SYNOPSIS +/* #include "local.h" +/* +/* int forward_init() +/* +/* int forward_append(attr) +/* DELIVER_ATTR attr; +/* +/* int forward_finish(request, attr, cancel) +/* DELIVER_REQUEST *request; +/* DELIVER_ATTR attr; +/* int cancel; +/* DESCRIPTION +/* This module implements the client interface for message +/* forwarding. +/* +/* forward_init() initializes internal data structures. +/* +/* forward_append() appends a recipient to the list of recipients +/* that will receive a message with the specified message sender +/* and delivered-to addresses. +/* +/* forward_finish() forwards the actual message contents and +/* releases the memory allocated by forward_init() and by +/* forward_append(). When the \fIcancel\fR argument is true, no +/* messages will be forwarded. The \fIattr\fR argument specifies +/* the original message delivery attributes as they were before +/* alias or forward expansions. +/* DIAGNOSTICS +/* A non-zero result means that the requested operation should +/* be tried again. +/* Warnings: problems connecting to the forwarding service, +/* corrupt message file. A corrupt message is saved to the +/* "corrupt" queue for further inspection. +/* Fatal: out of memory. +/* Panic: missing forward_init() or forward_finish() call. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + + /* + * Use one cleanup service connection for each (delivered to, sender) pair. + */ +static HTABLE *forward_dt; + +typedef struct FORWARD_INFO { + VSTREAM *cleanup; /* clean up service handle */ + char *queue_id; /* forwarded message queue id */ + struct timeval posting_time; /* posting time */ +} FORWARD_INFO; + +/* forward_init - prepare for forwarding */ + +int forward_init(void) +{ + + /* + * Sanity checks. + */ + if (forward_dt != 0) + msg_panic("forward_init: missing forward_finish call"); + + forward_dt = htable_create(0); + return (0); +} + +/* forward_open - open connection to cleanup service */ + +static FORWARD_INFO *forward_open(DELIVER_REQUEST *request, const char *sender) +{ + VSTRING *buffer = vstring_alloc(100); + FORWARD_INFO *info; + VSTREAM *cleanup; + +#define FORWARD_OPEN_RETURN(res) do { \ + vstring_free(buffer); \ + return (res); \ + } while (0) + + /* + * Contact the cleanup service and save the new mail queue id. Request + * that the cleanup service bounces bad messages to the sender so that we + * can avoid the trouble of bounce management. + * + * In case you wonder what kind of bounces, examples are "too many hops", + * "message too large", perhaps some others. The reason not to bounce + * ourselves is that we don't really know who the recipients are. + */ + cleanup = mail_connect(MAIL_CLASS_PUBLIC, var_cleanup_service, BLOCKING); + if (cleanup == 0) { + msg_warn("connect to %s/%s: %m", + MAIL_CLASS_PUBLIC, var_cleanup_service); + FORWARD_OPEN_RETURN(0); + } + close_on_exec(vstream_fileno(cleanup), CLOSE_ON_EXEC); + if (attr_scan(cleanup, ATTR_FLAG_STRICT, + RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP), + RECV_ATTR_STR(MAIL_ATTR_QUEUEID, buffer), + ATTR_TYPE_END) != 1) { + vstream_fclose(cleanup); + FORWARD_OPEN_RETURN(0); + } + info = (FORWARD_INFO *) mymalloc(sizeof(FORWARD_INFO)); + info->cleanup = cleanup; + info->queue_id = mystrdup(STR(buffer)); + GETTIMEOFDAY(&info->posting_time); + +#define FORWARD_CLEANUP_FLAGS \ + (CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_MASK_INTERNAL \ + | smtputf8_autodetect(MAIL_SRC_MASK_FORWARD) \ + | ((request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) ? \ + CLEANUP_FLAG_SMTPUTF8 : 0)) + + attr_print(cleanup, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_FLAGS, FORWARD_CLEANUP_FLAGS), + ATTR_TYPE_END); + + /* + * Send initial message envelope information. For bounces, set the + * designated sender: mailing list owner, posting user, whatever. + */ + rec_fprintf(cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, + REC_TYPE_TIME_ARG(info->posting_time)); + rec_fputs(cleanup, REC_TYPE_FROM, sender); + + /* + * Don't send the original envelope ID or full/headers return mask if it + * was reset due to mailing list expansion. + */ + if (request->dsn_ret) + rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_RET, request->dsn_ret); + if (request->dsn_envid && *(request->dsn_envid)) + rec_fprintf(cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_DSN_ENVID, request->dsn_envid); + + /* + * Zero-length attribute values are place holders for unavailable + * attribute values. See qmgr_message.c. They are not meant to be + * propagated to queue files. + */ +#define PASS_ATTR(fp, name, value) do { \ + if ((value) && *(value)) \ + rec_fprintf((fp), REC_TYPE_ATTR, "%s=%s", (name), (value)); \ + } while (0) + + /* + * XXX encapsulate these as one object. + */ + PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_NAME, request->client_name); + PASS_ATTR(cleanup, MAIL_ATTR_LOG_CLIENT_ADDR, request->client_addr); + PASS_ATTR(cleanup, MAIL_ATTR_LOG_PROTO_NAME, request->client_proto); + PASS_ATTR(cleanup, MAIL_ATTR_LOG_HELO_NAME, request->client_helo); + PASS_ATTR(cleanup, MAIL_ATTR_SASL_METHOD, request->sasl_method); + PASS_ATTR(cleanup, MAIL_ATTR_SASL_USERNAME, request->sasl_username); + PASS_ATTR(cleanup, MAIL_ATTR_SASL_SENDER, request->sasl_sender); + PASS_ATTR(cleanup, MAIL_ATTR_LOG_IDENT, request->log_ident); + PASS_ATTR(cleanup, MAIL_ATTR_RWR_CONTEXT, request->rewrite_context); + + FORWARD_OPEN_RETURN(info); +} + +/* forward_append - append recipient to message envelope */ + +int forward_append(DELIVER_ATTR attr) +{ + FORWARD_INFO *info; + HTABLE *table_snd; + + /* + * Sanity checks. + */ + if (msg_verbose) + msg_info("forward delivered=%s sender=%s recip=%s", + attr.delivered, attr.sender, attr.rcpt.address); + if (forward_dt == 0) + msg_panic("forward_append: missing forward_init call"); + + /* + * In order to find the recipient list, first index a table by + * delivered-to header address, then by envelope sender address. + */ + if ((table_snd = (HTABLE *) htable_find(forward_dt, attr.delivered)) == 0) { + table_snd = htable_create(0); + htable_enter(forward_dt, attr.delivered, (void *) table_snd); + } + if ((info = (FORWARD_INFO *) htable_find(table_snd, attr.sender)) == 0) { + if ((info = forward_open(attr.request, attr.sender)) == 0) + return (-1); + htable_enter(table_snd, attr.sender, (void *) info); + } + + /* + * Append the recipient to the message envelope. Don't send the original + * recipient or notification mask if it was reset due to mailing list + * expansion. + */ + if (*attr.rcpt.dsn_orcpt) + rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_DSN_ORCPT, attr.rcpt.dsn_orcpt); + if (attr.rcpt.dsn_notify) + rec_fprintf(info->cleanup, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_NOTIFY, attr.rcpt.dsn_notify); + if (*attr.rcpt.orig_addr) + rec_fputs(info->cleanup, REC_TYPE_ORCP, attr.rcpt.orig_addr); + rec_fputs(info->cleanup, REC_TYPE_RCPT, attr.rcpt.address); + + return (vstream_ferror(info->cleanup)); +} + +/* forward_send - send forwarded message */ + +static int forward_send(FORWARD_INFO *info, DELIVER_REQUEST *request, + DELIVER_ATTR attr, char *delivered) +{ + const char *myname = "forward_send"; + VSTRING *buffer = vstring_alloc(100); + VSTRING *folded; + int status; + int rec_type = 0; + + /* + * Start the message content segment. Prepend our Delivered-To: header to + * the message data. Stop at the first error. XXX Rely on the front-end + * services to enforce record size limits. + */ + rec_fputs(info->cleanup, REC_TYPE_MESG, ""); + vstring_strcpy(buffer, delivered); + rec_fprintf(info->cleanup, REC_TYPE_NORM, "Received: by %s (%s)", + var_myhostname, var_mail_name); + rec_fprintf(info->cleanup, REC_TYPE_NORM, "\tid %s; %s", + info->queue_id, mail_date(info->posting_time.tv_sec)); + if (local_deliver_hdr_mask & DELIVER_HDR_FWD) { + folded = vstring_alloc(100); + rec_fprintf(info->cleanup, REC_TYPE_NORM, "Delivered-To: %s", + casefold(folded, (STR(buffer)))); + vstring_free(folded); + } + if ((status = vstream_ferror(info->cleanup)) == 0) + if (vstream_fseek(attr.fp, attr.offset, SEEK_SET) < 0) + msg_fatal("%s: seek queue file %s: %m:", + myname, VSTREAM_PATH(attr.fp)); + while (status == 0 && (rec_type = rec_get(attr.fp, buffer, 0)) > 0) { + if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM) + break; + status = (REC_PUT_BUF(info->cleanup, rec_type, buffer) != rec_type); + } + if (status == 0 && rec_type != REC_TYPE_XTRA) { + msg_warn("%s: bad record type: %d in message content", + info->queue_id, rec_type); + status |= mark_corrupt(attr.fp); + } + + /* + * Send the end-of-data marker only when there were no errors. + */ + if (status == 0) { + rec_fputs(info->cleanup, REC_TYPE_XTRA, ""); + rec_fputs(info->cleanup, REC_TYPE_END, ""); + } + + /* + * Retrieve the cleanup service completion status only if there are no + * problems. + */ + if (status == 0) + if (vstream_fflush(info->cleanup) + || attr_scan(info->cleanup, ATTR_FLAG_MISSING, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + ATTR_TYPE_END) != 1) + status = 1; + + /* + * Log successful forwarding. + * + * XXX DSN alias and .forward expansion already report SUCCESS, so don't do + * it again here. + */ + if (status == 0) { + attr.rcpt.dsn_notify = + (attr.rcpt.dsn_notify == DSN_NOTIFY_SUCCESS ? + DSN_NOTIFY_NEVER : attr.rcpt.dsn_notify & ~DSN_NOTIFY_SUCCESS); + dsb_update(attr.why, "2.0.0", "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY, + "forwarded as %s", info->queue_id); + status = sent(BOUNCE_FLAGS(request), SENT_ATTR(attr)); + } + + /* + * Cleanup. + */ + vstring_free(buffer); + return (status); +} + +/* forward_finish - complete message forwarding requests and clean up */ + +int forward_finish(DELIVER_REQUEST *request, DELIVER_ATTR attr, int cancel) +{ + HTABLE_INFO **dt_list; + HTABLE_INFO **dt; + HTABLE_INFO **sn_list; + HTABLE_INFO **sn; + HTABLE *table_snd; + char *delivered; + char *sender; + FORWARD_INFO *info; + int status = cancel; + + /* + * Sanity checks. + */ + if (forward_dt == 0) + msg_panic("forward_finish: missing forward_init call"); + + /* + * Walk over all delivered-to header addresses and over each envelope + * sender address. + */ + for (dt = dt_list = htable_list(forward_dt); *dt; dt++) { + delivered = dt[0]->key; + table_snd = (HTABLE *) dt[0]->value; + for (sn = sn_list = htable_list(table_snd); *sn; sn++) { + sender = sn[0]->key; + info = (FORWARD_INFO *) sn[0]->value; + if (status == 0) + status |= forward_send(info, request, attr, delivered); + if (msg_verbose) + msg_info("forward_finish: delivered %s sender %s status %d", + delivered, sender, status); + (void) vstream_fclose(info->cleanup); + myfree(info->queue_id); + myfree((void *) info); + } + myfree((void *) sn_list); + htable_free(table_snd, (void (*) (void *)) 0); + } + myfree((void *) dt_list); + htable_free(forward_dt, (void (*) (void *)) 0); + forward_dt = 0; + return (status); +} diff --git a/src/local/include.c b/src/local/include.c new file mode 100644 index 0000000..a213d3c --- /dev/null +++ b/src/local/include.c @@ -0,0 +1,223 @@ +/*++ +/* NAME +/* deliver_include 3 +/* SUMMARY +/* deliver to addresses listed in include file +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_include(state, usr_attr, path) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *path; +/* DESCRIPTION +/* deliver_include() processes the contents of the named include +/* file and delivers to each address listed. Some sanity checks +/* are done on the include file permissions and type. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include, or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP path +/* Pathname of the include file. +/* DIAGNOSTICS +/* Fatal errors: out of memory. Warnings: bad include file type +/* or permissions. The result is non-zero when delivery should be +/* tried again. +/* SEE ALSO +/* token(3) tokenize list +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_include - open include file and deliver */ + +int deliver_include(LOCAL_STATE state, USER_ATTR usr_attr, char *path) +{ + const char *myname = "deliver_include"; + struct stat st; + struct mypasswd *file_pwd = 0; + int status; + VSTREAM *fp; + int fd; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE ELIMINATION + * + * Don't process this include file more than once as this particular user. + */ + if (been_here(state.dup_filter, "include %ld %s", (long) usr_attr.uid, path)) + return (0); + state.msg_attr.exp_from = state.msg_attr.local; + + /* + * Can of worms. Allow this include file to be symlinked, but disallow + * inclusion of special files or of files with world write permission + * enabled. + */ + if (*path != '/') { + msg_warn(":include:%s uses a relative path", path); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (stat_as(path, &st, usr_attr.uid, usr_attr.gid) < 0) { + msg_warn("unable to lookup :include: file %s: %m", path); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (S_ISREG(st.st_mode) == 0) { + msg_warn(":include: file %s is not a regular file", path); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (st.st_mode & S_IWOTH) { + msg_warn(":include: file %s is world writable", path); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + + /* + * DELIVERY POLICY + * + * Set the expansion type attribute so that we can decide if destinations + * such as /file/name and |command are allowed at all. + */ + state.msg_attr.exp_type = EXPAND_TYPE_INCL; + + /* + * DELIVERY RIGHTS + * + * When a non-root include file is listed in a root-owned alias, use the + * rights of the include file owner. We do not want to give the include + * file owner control of the default account. + * + * When an include file is listed in a user-owned alias or .forward file, + * leave the delivery rights alone. Users should not be able to make + * things happen with someone else's rights just by including some file + * that is owned by their victim. + */ + if (usr_attr.uid == 0) { + if ((errno = mypwuid_err(st.st_uid, &file_pwd)) != 0 || file_pwd == 0) { + msg_warn(errno ? "cannot find username for uid %ld: %m" : + "cannot find username for uid %ld", (long) st.st_uid); + msg_warn("%s: cannot find :include: file owner username", path); + dsb_simple(state.msg_attr.why, "4.3.5", + "mail system configuration error"); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (file_pwd->pw_uid != 0) + SET_USER_ATTR(usr_attr, file_pwd, state.level); + } + + /* + * MESSAGE FORWARDING + * + * When no owner attribute is set (either via an owner- alias, or as part of + * .forward file processing), set the owner attribute, to disable direct + * delivery of local recipients. By now it is clear that the owner + * attribute should have been called forwarder instead. + */ + if (state.msg_attr.owner == 0) + state.msg_attr.owner = state.msg_attr.rcpt.address; + + /* + * From here on no early returns or we have a memory leak. + * + * FILE OPEN RIGHTS + * + * Use the delivery rights to open the include file. When no delivery rights + * were established sofar, the file containing the :include: is owned by + * root, so it should be OK to open any file that is accessible to root. + * The command and file delivery routines are responsible for setting the + * proper delivery rights. These are the rights of the default user, in + * case the :include: is in a root-owned alias. + * + * Don't propagate unmatched extensions unless permitted to do so. + */ +#define FOPEN_AS(p,u,g) ((fd = open_as(p,O_RDONLY,0,u,g)) >= 0 ? \ + vstream_fdopen(fd,O_RDONLY) : 0) + + if ((fp = FOPEN_AS(path, usr_attr.uid, usr_attr.gid)) == 0) { + msg_warn("cannot open include file %s: %m", path); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + if ((local_ext_prop_mask & EXT_PROP_INCLUDE) == 0) + state.msg_attr.unmatched = 0; + close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); + status = deliver_token_stream(state, usr_attr, fp, (int *) 0); + if (vstream_fclose(fp)) + msg_warn("close %s: %m", path); + } + + /* + * Cleanup. + */ + if (file_pwd) + mypwfree(file_pwd); + + return (status); +} diff --git a/src/local/indirect.c b/src/local/indirect.c new file mode 100644 index 0000000..a9699a5 --- /dev/null +++ b/src/local/indirect.c @@ -0,0 +1,94 @@ +/*++ +/* NAME +/* indirect 3 +/* SUMMARY +/* indirect delivery +/* SYNOPSIS +/* #include "local.h" +/* +/* void deliver_indirect(state) +/* LOCAL_STATE state; +/* char *recipient; +/* DESCRIPTION +/* deliver_indirect() delivers a message via the message +/* forwarding service, with duplicate filtering up to a +/* configurable number of recipients. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, sender and more. +/* A table with the results from expanding aliases or lists. +/* CONFIGURATION VARIABLES +/* duplicate_filter_limit, duplicate filter size limit +/* DIAGNOSTICS +/* The result is non-zero when the operation should be tried again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_indirect - deliver mail via forwarding service */ + +int deliver_indirect(LOCAL_STATE state) +{ + + /* + * Suppress duplicate expansion results. Add some sugar to the name to + * avoid collisions with other duplicate filters. Allow the user to + * specify an upper bound on the size of the duplicate filter, so that we + * can handle huge mailing lists with millions of recipients. + */ + if (msg_verbose) + msg_info("deliver_indirect: %s", state.msg_attr.rcpt.address); + if (been_here(state.dup_filter, "indirect %s", + state.msg_attr.rcpt.address)) + return (0); + + /* + * Don't forward a trace-only request. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(state.msg_attr.why, "2.0.0", "forwards to %s", + state.msg_attr.rcpt.address); + return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); + } + + /* + * Send the address to the forwarding service. Inherit the delivered + * attribute from the alias or from the .forward file owner. + */ + if (forward_append(state.msg_attr)) { + dsb_simple(state.msg_attr.why, "4.3.0", "unable to forward message"); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + return (0); +} diff --git a/src/local/local.c b/src/local/local.c new file mode 100644 index 0000000..32bdea7 --- /dev/null +++ b/src/local/local.c @@ -0,0 +1,988 @@ +/*++ +/* NAME +/* local 8 +/* SUMMARY +/* Postfix local mail delivery +/* SYNOPSIS +/* \fBlocal\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBlocal\fR(8) daemon processes delivery requests from the +/* Postfix queue manager to deliver mail to local recipients. +/* Each delivery request specifies a queue file, a sender address, +/* a domain or host to deliver to, and one or more recipients. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* The \fBlocal\fR(8) daemon updates queue files and marks recipients +/* as finished, or it informs the queue manager that delivery should +/* be tried again at a later time. Delivery status reports are sent +/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as +/* appropriate. +/* CASE FOLDING +/* .ad +/* .fi +/* All delivery decisions are made using the bare recipient +/* name (i.e. the address localpart), folded to lower case. +/* See also under ADDRESS EXTENSION below for a few exceptions. +/* SYSTEM-WIDE AND USER-LEVEL ALIASING +/* .ad +/* .fi +/* The system administrator can set up one or more system-wide +/* \fBsendmail\fR-style alias databases. +/* Users can have \fBsendmail\fR-style ~/.\fBforward\fR files. +/* Mail for \fIname\fR is delivered to the alias \fIname\fR, to +/* destinations in ~\fIname\fR/.\fBforward\fR, to the mailbox owned +/* by the user \fIname\fR, or it is sent back as undeliverable. +/* +/* The system administrator can specify a comma/space separated list +/* of ~\fR/.\fBforward\fR like files through the \fBforward_path\fR +/* configuration parameter. Upon delivery, the local delivery agent +/* tries each pathname in the list until a file is found. +/* +/* Delivery via ~/.\fBforward\fR files is done with the privileges +/* of the recipient. +/* Thus, ~/.\fBforward\fR like files must be readable by the +/* recipient, and their parent directory needs to have "execute" +/* permission for the recipient. +/* +/* The \fBforward_path\fR parameter is subject to interpolation of +/* \fB$user\fR (recipient username), \fB$home\fR (recipient home +/* directory), \fB$shell\fR (recipient shell), \fB$recipient\fR +/* (complete recipient address), \fB$extension\fR (recipient address +/* extension), \fB$domain\fR (recipient domain), \fB$local\fR +/* (entire recipient address localpart) and +/* \fB$recipient_delimiter.\fR The forms \fI${name?value}\fR +/* and \fI${name?{value}}\fR (Postfix 3.0 and later) expand +/* conditionally to \fIvalue\fR when \fI$name\fR is defined, +/* and the forms \fI${name:value}\fR \fI${name:{value}}\fR +/* (Postfix 3.0 and later) expand conditionally to \fIvalue\fR +/* when \fI$name\fR is not defined. The form +/* \fI${name?{value1}:{value2}}\fR (Postfix 3.0 and later) +/* expands conditionally to \fIvalue1\fR when \fI$name\fR is +/* defined, or \fIvalue2\fR otherwise. Characters that may +/* have special meaning to the shell or file system are replaced +/* with underscores. The list of acceptable characters is +/* specified with the \fBforward_expansion_filter\fR configuration +/* parameter. +/* +/* An alias or ~/.\fBforward\fR file may list any combination of external +/* commands, destination file names, \fB:include:\fR directives, or +/* mail addresses. +/* See \fBaliases\fR(5) for a precise description. Each line in a +/* user's .\fBforward\fR file has the same syntax as the right-hand part +/* of an alias. +/* +/* When an address is found in its own alias expansion, delivery is +/* made to the user instead. When a user is listed in the user's own +/* ~/.\fBforward\fR file, delivery is made to the user's mailbox instead. +/* An empty ~/.\fBforward\fR file means do not forward mail. +/* +/* In order to prevent the mail system from using up unreasonable +/* amounts of memory, input records read from \fB:include:\fR or from +/* ~/.\fBforward\fR files are broken up into chunks of length +/* \fBline_length_limit\fR. +/* +/* While expanding aliases, ~/.\fBforward\fR files, and so on, the +/* program attempts to avoid duplicate deliveries. The +/* \fBduplicate_filter_limit\fR configuration parameter limits the +/* number of remembered recipients. +/* MAIL FORWARDING +/* .ad +/* .fi +/* For the sake of reliability, forwarded mail is re-submitted as +/* a new message, so that each recipient has a separate on-file +/* delivery status record. +/* +/* In order to stop mail forwarding loops early, the software adds an +/* optional +/* \fBDelivered-To:\fR header with the final envelope recipient address. If +/* mail arrives for a recipient that is already listed in a +/* \fBDelivered-To:\fR header, the message is bounced. +/* MAILBOX DELIVERY +/* .ad +/* .fi +/* The default per-user mailbox is a file in the UNIX mail spool +/* directory (\fB/var/mail/\fIuser\fR or \fB/var/spool/mail/\fIuser\fR); +/* the location can be specified with the \fBmail_spool_directory\fR +/* configuration parameter. Specify a name ending in \fB/\fR for +/* \fBqmail\fR-compatible \fBmaildir\fR delivery. +/* +/* Alternatively, the per-user mailbox can be a file in the user's home +/* directory with a name specified via the \fBhome_mailbox\fR +/* configuration parameter. Specify a relative path name. Specify a name +/* ending in \fB/\fR for \fBqmail\fR-compatible \fBmaildir\fR delivery. +/* +/* Mailbox delivery can be delegated to an external command specified +/* with the \fBmailbox_command_maps\fR and \fBmailbox_command\fR +/* configuration parameters. The command +/* executes with the privileges of the recipient user (exceptions: +/* secondary groups are not enabled; in case of delivery as root, +/* the command executes with the privileges of \fBdefault_privs\fR). +/* +/* Mailbox delivery can be delegated to alternative message transports +/* specified in the \fBmaster.cf\fR file. +/* The \fBmailbox_transport_maps\fR and \fBmailbox_transport\fR +/* configuration parameters specify an optional +/* message transport that is to be used for all local recipients, +/* regardless of whether they are found in the UNIX passwd database. +/* The \fBfallback_transport_maps\fR and +/* \fBfallback_transport\fR parameters specify an optional +/* message transport +/* for recipients that are not found in the aliases(5) or UNIX +/* passwd database. +/* +/* In the case of UNIX-style mailbox delivery, +/* the \fBlocal\fR(8) daemon prepends a "\fBFrom \fIsender time_stamp\fR" +/* envelope header to each message, prepends an +/* \fBX-Original-To:\fR header with the recipient address as given to +/* Postfix, prepends an +/* optional \fBDelivered-To:\fR header +/* with the final envelope recipient address, prepends a \fBReturn-Path:\fR +/* header with the envelope sender address, prepends a \fB>\fR character +/* to lines beginning with "\fBFrom \fR", and appends an empty line. +/* The mailbox is locked for exclusive access while delivery is in +/* progress. In case of problems, an attempt is made to truncate the +/* mailbox to its original length. +/* +/* In the case of \fBmaildir\fR delivery, the local daemon prepends +/* an optional +/* \fBDelivered-To:\fR header with the final envelope recipient address, +/* prepends an +/* \fBX-Original-To:\fR header with the recipient address as given to +/* Postfix, +/* and prepends a \fBReturn-Path:\fR header with the envelope sender +/* address. +/* EXTERNAL COMMAND DELIVERY +/* .ad +/* .fi +/* The \fBallow_mail_to_commands\fR configuration parameter restricts +/* delivery to external commands. The default setting (\fBalias, +/* forward\fR) forbids command destinations in \fB:include:\fR files. +/* +/* Optionally, the process working directory is changed to the path +/* specified with \fBcommand_execution_directory\fR (Postfix 2.2 and +/* later). Failure to change directory causes mail to be deferred. +/* +/* The \fBcommand_execution_directory\fR parameter value is subject +/* to interpolation of \fB$user\fR (recipient username), +/* \fB$home\fR (recipient home directory), \fB$shell\fR +/* (recipient shell), \fB$recipient\fR (complete recipient +/* address), \fB$extension\fR (recipient address extension), +/* \fB$domain\fR (recipient domain), \fB$local\fR (entire +/* recipient address localpart) and \fB$recipient_delimiter.\fR +/* The forms \fI${name?value}\fR and \fI${name?{value}}\fR +/* (Postfix 3.0 and later) expand conditionally to \fIvalue\fR +/* when \fI$name\fR is defined, and the forms \fI${name:value}\fR +/* and \fI${name:{value}}\fR (Postfix 3.0 and later) expand +/* conditionally to \fIvalue\fR when \fI$name\fR is not defined. +/* The form \fI${name?{value1}:{value2}}\fR (Postfix 3.0 and +/* later) expands conditionally to \fIvalue1\fR when \fI$name\fR +/* is defined, or \fIvalue2\fR otherwise. Characters that may +/* have special meaning to the shell or file system are replaced +/* with underscores. The list of acceptable characters +/* is specified with the \fBexecution_directory_expansion_filter\fR +/* configuration parameter. +/* +/* The command is executed directly where possible. Assistance by the +/* shell (\fB/bin/sh\fR on UNIX systems) is used only when the command +/* contains shell magic characters, or when the command invokes a shell +/* built-in command. +/* +/* A limited amount of command output (standard output and standard +/* error) is captured for inclusion with non-delivery status reports. +/* A command is forcibly terminated if it does not complete within +/* \fBcommand_time_limit\fR seconds. Command exit status codes are +/* expected to follow the conventions defined in <\fBsysexits.h\fR>. +/* Exit status 0 means normal successful completion. +/* +/* Postfix version 2.3 and later support RFC 3463-style enhanced +/* status codes. If a command terminates with a non-zero exit +/* status, and the command output begins with an enhanced +/* status code, this status code takes precedence over the +/* non-zero exit status. +/* +/* A limited amount of message context is exported via environment +/* variables. Characters that may have special meaning to the shell +/* are replaced with underscores. The list of acceptable characters +/* is specified with the \fBcommand_expansion_filter\fR configuration +/* parameter. +/* .IP \fBSHELL\fR +/* The recipient user's login shell. +/* .IP \fBHOME\fR +/* The recipient user's home directory. +/* .IP \fBUSER\fR +/* The bare recipient name. +/* .IP \fBEXTENSION\fR +/* The optional recipient address extension. +/* .IP \fBDOMAIN\fR +/* The recipient address domain part. +/* .IP \fBLOGNAME\fR +/* The bare recipient name. +/* .IP \fBLOCAL\fR +/* The entire recipient address localpart (text to the left of the +/* rightmost @ character). +/* .IP \fBORIGINAL_RECIPIENT\fR +/* The entire recipient address, before any address rewriting +/* or aliasing (Postfix 2.5 and later). +/* .IP \fBRECIPIENT\fR +/* The entire recipient address. +/* .IP \fBSENDER\fR +/* The entire sender address. +/* .PP +/* Additional remote client information is made available via +/* the following environment variables: +/* .IP \fBCLIENT_ADDRESS\fR +/* Remote client network address. Available as of Postfix 2.2. +/* .IP \fBCLIENT_HELO\fR +/* Remote client EHLO command parameter. Available as of Postfix 2.2. +/* .IP \fBCLIENT_HOSTNAME\fR +/* Remote client hostname. Available as of Postfix 2.2. +/* .IP \fBCLIENT_PROTOCOL\fR +/* Remote client protocol. Available as of Postfix 2.2. +/* .IP \fBSASL_METHOD\fR +/* SASL authentication method specified in the +/* remote client AUTH command. Available as of Postfix 2.2. +/* .IP \fBSASL_SENDER\fR +/* SASL sender address specified in the remote client MAIL +/* FROM command. Available as of Postfix 2.2. +/* .IP \fBSASL_USERNAME\fR +/* SASL username specified in the remote client AUTH command. +/* Available as of Postfix 2.2. +/* .PP +/* The \fBPATH\fR environment variable is always reset to a +/* system-dependent default path, and environment variables +/* whose names are blessed by the \fBexport_environment\fR +/* configuration parameter are exported unchanged. +/* +/* The current working directory is the mail queue directory. +/* +/* The \fBlocal\fR(8) daemon prepends a "\fBFrom \fIsender time_stamp\fR" +/* envelope header to each message, prepends an +/* \fBX-Original-To:\fR header with the recipient address as given to +/* Postfix, prepends an +/* optional \fBDelivered-To:\fR +/* header with the final recipient envelope address, prepends a +/* \fBReturn-Path:\fR header with the sender envelope address, +/* and appends no empty line. +/* EXTERNAL FILE DELIVERY +/* .ad +/* .fi +/* The delivery format depends on the destination filename syntax. +/* The default is to use UNIX-style mailbox format. Specify a name +/* ending in \fB/\fR for \fBqmail\fR-compatible \fBmaildir\fR delivery. +/* +/* The \fBallow_mail_to_files\fR configuration parameter restricts +/* delivery to external files. The default setting (\fBalias, +/* forward\fR) forbids file destinations in \fB:include:\fR files. +/* +/* In the case of UNIX-style mailbox delivery, +/* the \fBlocal\fR(8) daemon prepends a "\fBFrom \fIsender time_stamp\fR" +/* envelope header to each message, prepends an +/* \fBX-Original-To:\fR header with the recipient address as given to +/* Postfix, prepends an +/* optional \fBDelivered-To:\fR +/* header with the final recipient envelope address, prepends a \fB>\fR +/* character to lines beginning with "\fBFrom \fR", and appends an +/* empty line. +/* The envelope sender address is available in the \fBReturn-Path:\fR +/* header. +/* When the destination is a regular file, it is locked for exclusive +/* access while delivery is in progress. In case of problems, an attempt +/* is made to truncate a regular file to its original length. +/* +/* In the case of \fBmaildir\fR delivery, the local daemon prepends +/* an optional +/* \fBDelivered-To:\fR header with the final envelope recipient address, +/* and prepends an +/* \fBX-Original-To:\fR header with the recipient address as given to +/* Postfix. +/* The envelope sender address is available in the \fBReturn-Path:\fR +/* header. +/* ADDRESS EXTENSION +/* .ad +/* .fi +/* The optional \fBrecipient_delimiter\fR configuration parameter +/* specifies how to separate address extensions from local recipient +/* names. +/* +/* For example, with "\fBrecipient_delimiter = +\fR", mail for +/* \fIname\fR+\fIfoo\fR is delivered to the alias \fIname\fR+\fIfoo\fR +/* or to the alias \fIname\fR, to the destinations listed in +/* ~\fIname\fR/.\fBforward\fR+\fIfoo\fR or in ~\fIname\fR/.\fBforward\fR, +/* to the mailbox owned by the user \fIname\fR, or it is sent back as +/* undeliverable. +/* DELIVERY RIGHTS +/* .ad +/* .fi +/* Deliveries to external files and external commands are made with +/* the rights of the receiving user on whose behalf the delivery is made. +/* In the absence of a user context, the \fBlocal\fR(8) daemon uses the +/* owner rights of the \fB:include:\fR file or alias database. +/* When those files are owned by the superuser, delivery is made with +/* the rights specified with the \fBdefault_privs\fR configuration +/* parameter. +/* STANDARDS +/* RFC 822 (ARPA Internet Text Messages) +/* RFC 3463 (Enhanced status codes) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* Corrupted message files are marked so that the queue +/* manager can move them to the \fBcorrupt\fR queue afterwards. +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces and of other trouble. +/* SECURITY +/* .ad +/* .fi +/* The \fBlocal\fR(8) delivery agent needs a dual personality +/* 1) to access the private Postfix queue and IPC mechanisms, +/* 2) to impersonate the recipient and deliver to recipient-specified +/* files or commands. It is therefore security sensitive. +/* +/* The \fBlocal\fR(8) delivery agent disallows regular expression +/* substitution of $1 etc. in \fBalias_maps\fR, because that +/* would open a security hole. +/* +/* The \fBlocal\fR(8) delivery agent will silently ignore +/* requests to use the \fBproxymap\fR(8) server within +/* \fBalias_maps\fR. Instead it will open the table directly. +/* Before Postfix version 2.2, the \fBlocal\fR(8) delivery +/* agent will terminate with a fatal error. +/* BUGS +/* For security reasons, the message delivery status of external commands +/* or of external files is never checkpointed to file. As a result, +/* the program may occasionally deliver more than once to a command or +/* external file. Better safe than sorry. +/* +/* Mutually-recursive aliases or ~/.\fBforward\fR files are not detected +/* early. The resulting mail forwarding loop is broken by the use of the +/* \fBDelivered-To:\fR message header. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically, as \fBlocal\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* .IP "\fBbiff (yes)\fR" +/* Whether or not to use the local biff service. +/* .IP "\fBexpand_owner_alias (no)\fR" +/* When delivering to an alias "\fIaliasname\fR" that has an +/* "owner-\fIaliasname\fR" companion alias, set the envelope sender +/* address to the expansion of the "owner-\fIaliasname\fR" alias. +/* .IP "\fBowner_request_special (yes)\fR" +/* Enable special treatment for owner-\fIlistname\fR entries in the +/* \fBaliases\fR(5) file, and don't split owner-\fIlistname\fR and +/* \fIlistname\fR-request address localparts when the recipient_delimiter +/* is set to "-". +/* .IP "\fBsun_mailtool_compatibility (no)\fR" +/* Obsolete SUN mailtool compatibility feature. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBfrozen_delivered_to (yes)\fR" +/* Update the \fBlocal\fR(8) delivery agent's idea of the Delivered-To: +/* address (see prepend_delivered_header) only once, at the start of +/* a delivery attempt; do not update the Delivered-To: address while +/* expanding aliases or .forward files. +/* .PP +/* Available in Postfix version 2.5.3 and later: +/* .IP "\fBstrict_mailbox_ownership (yes)\fR" +/* Defer delivery when a mailbox file is not owned by its recipient. +/* .IP "\fBreset_owner_alias (no)\fR" +/* Reset the \fBlocal\fR(8) delivery agent's idea of the owner-alias +/* attribute, when delivering mail to a child alias that does not have +/* its own owner alias. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBlocal_delivery_status_filter ($default_delivery_status_filter)\fR" +/* Optional filter for the \fBlocal\fR(8) delivery agent to change the +/* status code or explanatory text of successful or unsuccessful +/* deliveries. +/* DELIVERY METHOD CONTROLS +/* .ad +/* .fi +/* The precedence of \fBlocal\fR(8) delivery methods from high to low is: +/* aliases, .forward files, mailbox_transport_maps, +/* mailbox_transport, mailbox_command_maps, mailbox_command, +/* home_mailbox, mail_spool_directory, fallback_transport_maps, +/* fallback_transport, and luser_relay. +/* .IP "\fBalias_maps (see 'postconf -d' output)\fR" +/* The alias databases that are used for \fBlocal\fR(8) delivery. +/* .IP "\fBforward_path (see 'postconf -d' output)\fR" +/* The \fBlocal\fR(8) delivery agent search list for finding a .forward +/* file with user-specified delivery methods. +/* .IP "\fBmailbox_transport_maps (empty)\fR" +/* Optional lookup tables with per-recipient message delivery +/* transports to use for \fBlocal\fR(8) mailbox delivery, whether or not the +/* recipients are found in the UNIX passwd database. +/* .IP "\fBmailbox_transport (empty)\fR" +/* Optional message delivery transport that the \fBlocal\fR(8) delivery +/* agent should use for mailbox delivery to all local recipients, +/* whether or not they are found in the UNIX passwd database. +/* .IP "\fBmailbox_command_maps (empty)\fR" +/* Optional lookup tables with per-recipient external commands to use +/* for \fBlocal\fR(8) mailbox delivery. +/* .IP "\fBmailbox_command (empty)\fR" +/* Optional external command that the \fBlocal\fR(8) delivery agent should +/* use for mailbox delivery. +/* .IP "\fBhome_mailbox (empty)\fR" +/* Optional pathname of a mailbox file relative to a \fBlocal\fR(8) user's +/* home directory. +/* .IP "\fBmail_spool_directory (see 'postconf -d' output)\fR" +/* The directory where \fBlocal\fR(8) UNIX-style mailboxes are kept. +/* .IP "\fBfallback_transport_maps (empty)\fR" +/* Optional lookup tables with per-recipient message delivery +/* transports for recipients that the \fBlocal\fR(8) delivery agent could +/* not find in the \fBaliases\fR(5) or UNIX password database. +/* .IP "\fBfallback_transport (empty)\fR" +/* Optional message delivery transport that the \fBlocal\fR(8) delivery +/* agent should use for names that are not found in the \fBaliases\fR(5) +/* or UNIX password database. +/* .IP "\fBluser_relay (empty)\fR" +/* Optional catch-all destination for unknown \fBlocal\fR(8) recipients. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBcommand_execution_directory (empty)\fR" +/* The \fBlocal\fR(8) delivery agent working directory for delivery to +/* external commands. +/* MAILBOX LOCKING CONTROLS +/* .ad +/* .fi +/* .IP "\fBdeliver_lock_attempts (20)\fR" +/* The maximal number of attempts to acquire an exclusive lock on a +/* mailbox file or \fBbounce\fR(8) logfile. +/* .IP "\fBdeliver_lock_delay (1s)\fR" +/* The time between attempts to acquire an exclusive lock on a mailbox +/* file or \fBbounce\fR(8) logfile. +/* .IP "\fBstale_lock_time (500s)\fR" +/* The time after which a stale exclusive mailbox lockfile is removed. +/* .IP "\fBmailbox_delivery_lock (see 'postconf -d' output)\fR" +/* How to lock a UNIX-style \fBlocal\fR(8) mailbox before attempting delivery. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBcommand_time_limit (1000s)\fR" +/* Time limit for delivery to external commands. +/* .IP "\fBduplicate_filter_limit (1000)\fR" +/* The maximal number of addresses remembered by the address +/* duplicate filter for \fBaliases\fR(5) or \fBvirtual\fR(5) alias expansion, or +/* for \fBshowq\fR(8) queue displays. +/* .IP "\fBmailbox_size_limit (51200000)\fR" +/* The maximal size of any \fBlocal\fR(8) individual mailbox or maildir +/* file, or zero (no limit). +/* .PP +/* Implemented in the qmgr(8) daemon: +/* .IP "\fBlocal_destination_concurrency_limit (2)\fR" +/* The maximal number of parallel deliveries via the local mail +/* delivery transport to the same recipient (when +/* "local_destination_recipient_limit = 1") or the maximal number of +/* parallel deliveries to the same local domain (when +/* "local_destination_recipient_limit > 1"). +/* .IP "\fBlocal_destination_recipient_limit (1)\fR" +/* The maximal number of recipients per message delivery via the +/* local mail delivery transport. +/* SECURITY CONTROLS +/* .ad +/* .fi +/* .IP "\fBallow_mail_to_commands (alias, forward)\fR" +/* Restrict \fBlocal\fR(8) mail delivery to external commands. +/* .IP "\fBallow_mail_to_files (alias, forward)\fR" +/* Restrict \fBlocal\fR(8) mail delivery to external files. +/* .IP "\fBcommand_expansion_filter (see 'postconf -d' output)\fR" +/* Restrict the characters that the \fBlocal\fR(8) delivery agent allows in +/* $name expansions of $mailbox_command and $command_execution_directory. +/* .IP "\fBdefault_privs (nobody)\fR" +/* The default rights used by the \fBlocal\fR(8) delivery agent for delivery +/* to an external file or command. +/* .IP "\fBforward_expansion_filter (see 'postconf -d' output)\fR" +/* Restrict the characters that the \fBlocal\fR(8) delivery agent allows in +/* $name expansions of $forward_path. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBexecution_directory_expansion_filter (see 'postconf -d' output)\fR" +/* Restrict the characters that the \fBlocal\fR(8) delivery agent allows +/* in $name expansions of $command_execution_directory. +/* .PP +/* Available in Postfix version 2.5.3 and later: +/* .IP "\fBstrict_mailbox_ownership (yes)\fR" +/* Defer delivery when a mailbox file is not owned by its recipient. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBexport_environment (see 'postconf -d' output)\fR" +/* The list of environment variables that a Postfix process will export +/* to non-Postfix processes. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBlocal_command_shell (empty)\fR" +/* Optional shell program for \fBlocal\fR(8) delivery to non-Postfix commands. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprepend_delivered_header (command, file, forward)\fR" +/* The message delivery contexts where the Postfix \fBlocal\fR(8) delivery +/* agent prepends a Delivered-To: message header with the address +/* that the mail was delivered to. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBpropagate_unmatched_extensions (canonical, virtual)\fR" +/* What address lookup tables copy an address extension from the lookup +/* key to the lookup result. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBrecipient_delimiter (empty)\fR" +/* The set of characters that can separate an email address +/* localpart, user name, or a .forward file name from its extension. +/* .IP "\fBrequire_home_directory (no)\fR" +/* Require that a \fBlocal\fR(8) recipient's home directory exists +/* before mail delivery is attempted. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 3.3 and later: +/* .IP "\fBenable_original_recipient (yes)\fR" +/* Enable support for the original recipient address after an +/* address is rewritten to a different address (for example with +/* aliasing or with canonical mapping). +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.5 and later: +/* .IP "\fBinfo_log_address_format (external)\fR" +/* The email address form that will be used in non-debug logging +/* (info, warning, etc.). +/* FILES +/* The following are examples; details differ between systems. +/* $HOME/.forward, per-user aliasing +/* /etc/aliases, system-wide alias database +/* /var/spool/mail, system mailboxes +/* SEE ALSO +/* qmgr(8), queue manager +/* bounce(8), delivery status reports +/* newaliases(1), create/update alias database +/* postalias(1), create/update alias database +/* aliases(5), format of alias database +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* The \fBDelivered-To:\fR message header appears in the \fBqmail\fR +/* system by Daniel Bernstein. +/* +/* The \fImaildir\fR structure appears in the \fBqmail\fR system +/* by Daniel Bernstein. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#ifdef USE_PATHS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single server skeleton. */ + +#include + +/* Application-specific. */ + +#include "local.h" + + /* + * Tunable parameters. + */ +char *var_allow_commands; +char *var_allow_files; +char *var_alias_maps; +int var_dup_filter_limit; +int var_command_maxtime; /* You can now leave this here. */ +char *var_home_mailbox; +char *var_mailbox_command; +char *var_mailbox_cmd_maps; +char *var_rcpt_fdelim; +char *var_local_cmd_shell; +char *var_luser_relay; +int var_biff; +char *var_mail_spool_dir; +char *var_mailbox_transport; +char *var_mbox_transp_maps; +char *var_fallback_transport; +char *var_fbck_transp_maps; +char *var_exec_directory; +char *var_exec_exp_filter; +char *var_forward_path; +char *var_cmd_exp_filter; +char *var_fwd_exp_filter; +char *var_prop_extension; +int var_exp_own_alias; +char *var_deliver_hdr; +int var_stat_home_dir; +int var_mailtool_compat; +char *var_mailbox_lock; +long var_mailbox_limit; +bool var_frozen_delivered; +bool var_reset_owner_attr; +bool var_strict_mbox_owner; + +int local_cmd_deliver_mask; +int local_file_deliver_mask; +int local_ext_prop_mask; +int local_deliver_hdr_mask; +int local_mbox_lock_mask; +MAPS *alias_maps; +char *var_local_dsn_filter; + +/* local_deliver - deliver message with extreme prejudice */ + +static int local_deliver(DELIVER_REQUEST *rqst, char *service) +{ + const char *myname = "local_deliver"; + RECIPIENT *rcpt_end = rqst->rcpt_list.info + rqst->rcpt_list.len; + RECIPIENT *rcpt; + int rcpt_stat; + int msg_stat; + LOCAL_STATE state; + USER_ATTR usr_attr; + + if (msg_verbose) + msg_info("local_deliver: %s from %s", rqst->queue_id, rqst->sender); + + /* + * Initialize the delivery attributes that are not recipient specific. + * While messages are being delivered and while aliases or forward files + * are being expanded, this attribute list is being changed constantly. + * For this reason, the list is passed on by value (except when it is + * being initialized :-), so that there is no need to undo attribute + * changes made by lower-level routines. The alias/include/forward + * expansion attribute list is part of a tree with self and parent + * references (see the EXPAND_ATTR definitions). The user-specific + * attributes are security sensitive, and are therefore kept separate. + * All this results in a noticeable level of clumsiness, but passing + * things around by value gives good protection against accidental change + * by subroutines. + */ + state.level = 0; + deliver_attr_init(&state.msg_attr); + state.msg_attr.queue_name = rqst->queue_name; + state.msg_attr.queue_id = rqst->queue_id; + state.msg_attr.fp = rqst->fp; + state.msg_attr.offset = rqst->data_offset; + state.msg_attr.encoding = rqst->encoding; + state.msg_attr.smtputf8 = rqst->smtputf8; + state.msg_attr.sender = rqst->sender; + state.msg_attr.dsn_envid = rqst->dsn_envid; + state.msg_attr.dsn_ret = rqst->dsn_ret; + state.msg_attr.relay = service; + state.msg_attr.msg_stats = rqst->msg_stats; + state.msg_attr.request = rqst; + RESET_OWNER_ATTR(state.msg_attr, state.level); + RESET_USER_ATTR(usr_attr, state.level); + state.loop_info = delivered_hdr_init(rqst->fp, rqst->data_offset, + FOLD_ADDR_ALL); + state.request = rqst; + + /* + * Iterate over each recipient named in the delivery request. When the + * mail delivery status for a given recipient is definite (i.e. bounced + * or delivered), update the message queue file and cross off the + * recipient. Update the per-message delivery status. + */ + for (msg_stat = 0, rcpt = rqst->rcpt_list.info; rcpt < rcpt_end; rcpt++) { + state.dup_filter = been_here_init(var_dup_filter_limit, BH_FLAG_FOLD); + forward_init(); + state.msg_attr.rcpt = *rcpt; + rcpt_stat = deliver_recipient(state, usr_attr); + rcpt_stat |= forward_finish(rqst, state.msg_attr, rcpt_stat); + if (rcpt_stat == 0 && (rqst->flags & DEL_REQ_FLAG_SUCCESS)) + deliver_completed(state.msg_attr.fp, rcpt->offset); + been_here_free(state.dup_filter); + msg_stat |= rcpt_stat; + } + + /* + * Clean up. + */ + delivered_hdr_free(state.loop_info); + deliver_attr_free(&state.msg_attr); + + return (msg_stat); +} + +/* local_service - perform service for client */ + +static void local_service(VSTREAM *stream, char *service, char **argv) +{ + DELIVER_REQUEST *request; + int status; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * that is dedicated to local mail delivery service. What we see below is + * a little protocol to (1) tell the client that we are ready, (2) read a + * delivery request from the client, and (3) report the completion status + * of that request. + */ + if ((request = deliver_request_read(stream)) != 0) { + status = local_deliver(request, service); + deliver_request_done(stream, request, status); + } +} + +/* local_mask_init - initialize delivery restrictions */ + +static void local_mask_init(void) +{ + static const NAME_MASK file_mask[] = { + "alias", EXPAND_TYPE_ALIAS, + "forward", EXPAND_TYPE_FWD, + "include", EXPAND_TYPE_INCL, + 0, + }; + static const NAME_MASK command_mask[] = { + "alias", EXPAND_TYPE_ALIAS, + "forward", EXPAND_TYPE_FWD, + "include", EXPAND_TYPE_INCL, + 0, + }; + static const NAME_MASK deliver_mask[] = { + "command", DELIVER_HDR_CMD, + "file", DELIVER_HDR_FILE, + "forward", DELIVER_HDR_FWD, + 0, + }; + + local_file_deliver_mask = name_mask(VAR_ALLOW_FILES, file_mask, + var_allow_files); + local_cmd_deliver_mask = name_mask(VAR_ALLOW_COMMANDS, command_mask, + var_allow_commands); + local_ext_prop_mask = + ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension); + local_deliver_hdr_mask = name_mask(VAR_DELIVER_HDR, deliver_mask, + var_deliver_hdr); + local_mbox_lock_mask = mbox_lock_mask(var_mailbox_lock); + if (var_mailtool_compat) { + msg_warn("%s: deprecated parameter, use \"%s = dotlock\" instead", + VAR_MAILTOOL_COMPAT, VAR_MAILBOX_LOCK); + local_mbox_lock_mask &= MBOX_DOT_LOCK; + } + if (local_mbox_lock_mask == 0) + msg_fatal("parameter %s specifies no applicable mailbox locking method", + VAR_MAILBOX_LOCK); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* post_init - post-jail initialization */ + +static void post_init(char *unused_name, char **unused_argv) +{ + + /* + * Drop privileges most of the time, and set up delivery restrictions. + */ + set_eugid(var_owner_uid, var_owner_gid); + local_mask_init(); +} + +/* pre_init - pre-jail initialization */ + +static void pre_init(char *unused_name, char **unused_argv) +{ + + /* + * Reset the file size limit from the message size limit to the mailbox + * size limit. XXX This still isn't accurate because the file size limit + * also affects delivery to command. + * + * A file size limit protects the machine against runaway software errors. + * It is not suitable to enforce mail quota, because users can get around + * mail quota by delivering to /file/name or to |command. + * + * We can't have mailbox size limit smaller than the message size limit, + * because that prohibits the delivery agent from updating the queue + * file. + */ + if (ENFORCING_SIZE_LIMIT(var_mailbox_limit)) { + if (!ENFORCING_SIZE_LIMIT(var_message_limit)) + msg_fatal("configuration error: %s is limited but %s is " + "unlimited", VAR_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT); + if (var_mailbox_limit < var_message_limit) + msg_fatal("configuration error: %s is smaller than %s", + VAR_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT); + set_file_limit(var_mailbox_limit); + } + alias_maps = maps_create("aliases", var_alias_maps, + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + + flush_init(); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0, + 0, + }; + static const CONFIG_INT_TABLE int_table[] = { + VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0, + 0, + }; + static const CONFIG_LONG_TABLE long_table[] = { + VAR_MAILBOX_LIMIT, DEF_MAILBOX_LIMIT, &var_mailbox_limit, 0, 0, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0, + VAR_HOME_MAILBOX, DEF_HOME_MAILBOX, &var_home_mailbox, 0, 0, + VAR_ALLOW_COMMANDS, DEF_ALLOW_COMMANDS, &var_allow_commands, 0, 0, + VAR_ALLOW_FILES, DEF_ALLOW_FILES, &var_allow_files, 0, 0, + VAR_LOCAL_CMD_SHELL, DEF_LOCAL_CMD_SHELL, &var_local_cmd_shell, 0, 0, + VAR_MAIL_SPOOL_DIR, DEF_MAIL_SPOOL_DIR, &var_mail_spool_dir, 0, 0, + VAR_MAILBOX_TRANSP, DEF_MAILBOX_TRANSP, &var_mailbox_transport, 0, 0, + VAR_MBOX_TRANSP_MAPS, DEF_MBOX_TRANSP_MAPS, &var_mbox_transp_maps, 0, 0, + VAR_FALLBACK_TRANSP, DEF_FALLBACK_TRANSP, &var_fallback_transport, 0, 0, + VAR_FBCK_TRANSP_MAPS, DEF_FBCK_TRANSP_MAPS, &var_fbck_transp_maps, 0, 0, + VAR_CMD_EXP_FILTER, DEF_CMD_EXP_FILTER, &var_cmd_exp_filter, 1, 0, + VAR_FWD_EXP_FILTER, DEF_FWD_EXP_FILTER, &var_fwd_exp_filter, 1, 0, + VAR_EXEC_EXP_FILTER, DEF_EXEC_EXP_FILTER, &var_exec_exp_filter, 1, 0, + VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0, + VAR_DELIVER_HDR, DEF_DELIVER_HDR, &var_deliver_hdr, 0, 0, + VAR_MAILBOX_LOCK, DEF_MAILBOX_LOCK, &var_mailbox_lock, 1, 0, + VAR_MAILBOX_CMD_MAPS, DEF_MAILBOX_CMD_MAPS, &var_mailbox_cmd_maps, 0, 0, + VAR_LOCAL_DSN_FILTER, DEF_LOCAL_DSN_FILTER, &var_local_dsn_filter, 0, 0, + 0, + }; + static const CONFIG_BOOL_TABLE bool_table[] = { + VAR_BIFF, DEF_BIFF, &var_biff, + VAR_EXP_OWN_ALIAS, DEF_EXP_OWN_ALIAS, &var_exp_own_alias, + VAR_STAT_HOME_DIR, DEF_STAT_HOME_DIR, &var_stat_home_dir, + VAR_MAILTOOL_COMPAT, DEF_MAILTOOL_COMPAT, &var_mailtool_compat, + VAR_FROZEN_DELIVERED, DEF_FROZEN_DELIVERED, &var_frozen_delivered, + VAR_RESET_OWNER_ATTR, DEF_RESET_OWNER_ATTR, &var_reset_owner_attr, + VAR_STRICT_MBOX_OWNER, DEF_STRICT_MBOX_OWNER, &var_strict_mbox_owner, + 0, + }; + + /* Suppress $name expansion upon loading. */ + static const CONFIG_RAW_TABLE raw_table[] = { + VAR_EXEC_DIRECTORY, DEF_EXEC_DIRECTORY, &var_exec_directory, 0, 0, + VAR_FORWARD_PATH, DEF_FORWARD_PATH, &var_forward_path, 0, 0, + VAR_MAILBOX_COMMAND, DEF_MAILBOX_COMMAND, &var_mailbox_command, 0, 0, + VAR_LUSER_RELAY, DEF_LUSER_RELAY, &var_luser_relay, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, local_service, + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_LONG_TABLE(long_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_RAW_TABLE(raw_table), + CA_MAIL_SERVER_BOOL_TABLE(bool_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(pre_init), + CA_MAIL_SERVER_POST_INIT(post_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_PRIVILEGED, + CA_MAIL_SERVER_BOUNCE_INIT(VAR_LOCAL_DSN_FILTER, + &var_local_dsn_filter), + 0); +} diff --git a/src/local/local.h b/src/local/local.h new file mode 100644 index 0000000..4052000 --- /dev/null +++ b/src/local/local.h @@ -0,0 +1,251 @@ +/*++ +/* NAME +/* local 3h +/* SUMMARY +/* local mail delivery +/* SYNOPSIS +/* #include "local.h" +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * User attributes: these control the privileges for delivery to external + * commands, external files, or mailboxes, and the initial environment of + * external commands. + */ +typedef struct USER_ATTR { + uid_t uid; /* file/command access */ + gid_t gid; /* file/command access */ + char *home; /* null or home directory */ + char *logname; /* null or login name */ + char *shell; /* null or login shell */ +} USER_ATTR; + + /* + * Critical macros. Not for obscurity, but to ensure consistency. + */ +#define RESET_USER_ATTR(usr_attr, level) { \ + usr_attr.uid = 0; usr_attr.gid = 0; usr_attr.home = 0; \ + usr_attr.logname = 0; usr_attr.shell = 0; \ + if (msg_verbose) \ + msg_info("%s[%d]: reset user_attr", myname, level); \ + } + +#define SET_USER_ATTR(usr_attr, pwd, level) { \ + usr_attr.uid = pwd->pw_uid; usr_attr.gid = pwd->pw_gid; \ + usr_attr.home = pwd->pw_dir; usr_attr.logname = pwd->pw_name; \ + usr_attr.shell = pwd->pw_shell; \ + if (msg_verbose) \ + msg_info("%s[%d]: set user_attr: %s", \ + myname, level, pwd->pw_name); \ + } + + /* + * The delivery attributes are inherited from files, from aliases, and from + * whatnot. Some of the information is changed on the fly. DELIVER_ATTR + * structures are therefore passed by value, so there is no need to undo + * changes. + */ +typedef struct DELIVER_ATTR { + int level; /* recursion level */ + VSTREAM *fp; /* open queue file */ + char *queue_name; /* mail queue id */ + char *queue_id; /* mail queue id */ + long offset; /* data offset */ + char *encoding; /* MIME encoding */ + int smtputf8; /* from delivery request */ + const char *sender; /* taken from envelope */ + char *dsn_envid; /* DSN envelope ID */ + int dsn_ret; /* DSN headers/full */ + RECIPIENT rcpt; /* from delivery request */ + char *domain; /* recipient domain */ + char *local; /* recipient full localpart */ + char *user; /* recipient localpart, base name */ + char *extension; /* recipient localpart, extension */ + char *unmatched; /* unmatched extension */ + const char *owner; /* null or list owner */ + const char *delivered; /* for loop detection */ + char *relay; /* relay host */ + MSG_STATS msg_stats; /* time profile */ + int exp_type; /* expansion type. see below */ + char *exp_from; /* expanded_from */ + DELIVER_REQUEST *request; /* the kitchen sink */ + DSN_BUF *why; /* delivery status */ +} DELIVER_ATTR; + +extern void deliver_attr_init(DELIVER_ATTR *); +extern void deliver_attr_dump(DELIVER_ATTR *); +extern void deliver_attr_free(DELIVER_ATTR *); + +#define EXPAND_TYPE_ALIAS (1<<0) +#define EXPAND_TYPE_FWD (1<<1) +#define EXPAND_TYPE_INCL (1<<2) + + /* + * Rather than schlepping around dozens of arguments, here is one that has + * all. Well, almost. The user attributes are just a bit too sensitive, so + * they are passed around separately. + */ +typedef struct LOCAL_STATE { + int level; /* nesting level, for logging */ + DELIVER_ATTR msg_attr; /* message attributes */ + BH_TABLE *dup_filter; /* internal duplicate filter */ + DELIVERED_HDR_INFO *loop_info; /* external loop filter */ + DELIVER_REQUEST *request; /* as from queue manager */ +} LOCAL_STATE; + +#define RESET_OWNER_ATTR(msg_attr, level) { \ + msg_attr.owner = 0; \ + if (msg_verbose) \ + msg_info("%s[%d]: reset owner attr", myname, level); \ + } + +#define SET_OWNER_ATTR(msg_attr, who, level) { \ + msg_attr.sender = msg_attr.owner = who; \ + if (msg_verbose) \ + msg_info("%s[%d]: set owner attr: %s", \ + myname, level, who); \ + } + + /* + * Bundle up some often-user attributes. + */ +#define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS((request)->flags) + +#define BOUNCE_ATTR(attr) \ + attr.queue_id, &attr.msg_stats, &attr.rcpt, attr.relay, \ + DSN_FROM_DSN_BUF(attr.why) +#define BOUNCE_ONE_ATTR(attr) \ + attr.queue_name, attr.queue_id, attr.encoding, attr.smtputf8, \ + attr.sender, attr.dsn_envid, attr.dsn_ret, \ + &attr.msg_stats, &attr.rcpt, attr.relay, \ + DSN_FROM_DSN_BUF(attr.why) +#define SENT_ATTR(attr) \ + attr.queue_id, &attr.msg_stats, &attr.rcpt, attr.relay, \ + DSN_FROM_DSN_BUF(attr.why) +#define OPENED_ATTR(attr) \ + attr.queue_id, attr.sender +#define COPY_ATTR(attr) \ + attr.sender, attr.rcpt.orig_addr, attr.delivered, attr.fp + +#define MSG_LOG_STATE(m, p) \ + msg_info("%s[%d]: local %s recip %s exten %s deliver %s exp_from %s", \ + m, \ + p.level, \ + p.msg_attr.local ? p.msg_attr.local : "" , \ + p.msg_attr.rcpt.address ? p.msg_attr.rcpt.address : "", \ + p.msg_attr.extension ? p.msg_attr.extension : "", \ + p.msg_attr.delivered ? p.msg_attr.delivered : "", \ + p.msg_attr.exp_from ? p.msg_attr.exp_from : "") + + /* + * "inner" nodes of the delivery graph. + */ +extern int deliver_recipient(LOCAL_STATE, USER_ATTR); +extern int deliver_alias(LOCAL_STATE, USER_ATTR, char *, int *); +extern int deliver_dotforward(LOCAL_STATE, USER_ATTR, int *); +extern int deliver_include(LOCAL_STATE, USER_ATTR, char *); +extern int deliver_token(LOCAL_STATE, USER_ATTR, TOK822 *); +extern int deliver_token_string(LOCAL_STATE, USER_ATTR, char *, int *); +extern int deliver_token_stream(LOCAL_STATE, USER_ATTR, VSTREAM *, int *); +extern int deliver_resolve_tree(LOCAL_STATE, USER_ATTR, TOK822 *); +extern int deliver_resolve_addr(LOCAL_STATE, USER_ATTR, char *); + + /* + * "leaf" nodes of the delivery graph. + */ +extern int deliver_mailbox(LOCAL_STATE, USER_ATTR, int *); +extern int deliver_command(LOCAL_STATE, USER_ATTR, const char *); +extern int deliver_file(LOCAL_STATE, USER_ATTR, char *); +extern int deliver_indirect(LOCAL_STATE); +extern int deliver_maildir(LOCAL_STATE, USER_ATTR, char *); +extern int deliver_unknown(LOCAL_STATE, USER_ATTR); + + /* + * Restrictions on delivery to sensitive destinations. + */ +extern int local_file_deliver_mask; +extern int local_cmd_deliver_mask; + + /* + * Restrictions on extension propagation. + */ +extern int local_ext_prop_mask; + + /* + * Mailbox lock protocol. + */ +extern int local_mbox_lock_mask; + + /* + * When to prepend a Delivered-To: header upon external delivery. + */ +#define DELIVER_HDR_CMD (1<<0) +#define DELIVER_HDR_FILE (1<<1) +#define DELIVER_HDR_FWD (1<<2) + +extern int local_deliver_hdr_mask; + + /* + * forward.c + */ +extern int forward_init(void); +extern int forward_append(DELIVER_ATTR); +extern int forward_finish(DELIVER_REQUEST *, DELIVER_ATTR, int); + + /* + * feature.c + */ +extern int feature_control(const char *); + + /* + * local_expand.c + */ +int local_expand(VSTRING *, const char *, LOCAL_STATE *, USER_ATTR *, const char *); + +#define LOCAL_EXP_EXTENSION_MATCHED (1< +#include + +/* Utility library. */ + +#include +#include + +/* Global library */ + +#include + +/* Application-specific. */ + +#include "local.h" + +typedef struct { + LOCAL_STATE *state; + USER_ATTR *usr_attr; + int status; +} LOCAL_EXP; + +/* local_expand_lookup - mac_expand() lookup routine */ + +static const char *local_expand_lookup(const char *name, int mode, void *ptr) +{ + LOCAL_EXP *local = (LOCAL_EXP *) ptr; + static char rcpt_delim[2]; + +#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0) + + if (STREQ(name, "user")) { + return (local->state->msg_attr.user); + } else if (STREQ(name, "home")) { + return (local->usr_attr->home); + } else if (STREQ(name, "shell")) { + return (local->usr_attr->shell); + } else if (STREQ(name, "domain")) { + return (local->state->msg_attr.domain); + } else if (STREQ(name, "local")) { + return (local->state->msg_attr.local); + } else if (STREQ(name, "mailbox")) { + return (local->state->msg_attr.local); + } else if (STREQ(name, "recipient")) { + return (local->state->msg_attr.rcpt.address); + } else if (STREQ(name, "extension")) { + if (mode == MAC_EXP_MODE_USE) + local->status |= LOCAL_EXP_EXTENSION_MATCHED; + return (local->state->msg_attr.extension); + } else if (STREQ(name, "recipient_delimiter")) { + rcpt_delim[0] = + local->state->msg_attr.local[strlen(local->state->msg_attr.user)]; + if (rcpt_delim[0] == 0) + rcpt_delim[0] = var_rcpt_delim[0]; + rcpt_delim[1] = 0; + return (rcpt_delim[0] ? rcpt_delim : 0); +#if 0 + } else if (STREQ(name, "client_hostname")) { + return (local->state->msg_attr.request->client_name); + } else if (STREQ(name, "client_address")) { + return (local->state->msg_attr.request->client_addr); + } else if (STREQ(name, "client_protocol")) { + return (local->state->msg_attr.request->client_proto); + } else if (STREQ(name, "client_helo")) { + return (local->state->msg_attr.request->client_helo); + } else if (STREQ(name, "sasl_method")) { + return (local->state->msg_attr.request->sasl_method); + } else if (STREQ(name, "sasl_sender")) { + return (local->state->msg_attr.request->sasl_sender); + } else if (STREQ(name, "sasl_username")) { + return (local->state->msg_attr.request->sasl_username); +#endif + } else { + return (0); + } +} + +/* local_expand - expand message delivery attributes */ + +int local_expand(VSTRING *result, const char *pattern, + LOCAL_STATE *state, USER_ATTR *usr_attr, const char *filter) +{ + LOCAL_EXP local; + int expand_status; + + local.state = state; + local.usr_attr = usr_attr; + local.status = 0; + expand_status = mac_expand(result, pattern, MAC_EXP_FLAG_NONE, + filter, local_expand_lookup, (void *) &local); + return (local.status | expand_status); +} diff --git a/src/local/mailbox.c b/src/local/mailbox.c new file mode 100644 index 0000000..ed55291 --- /dev/null +++ b/src/local/mailbox.c @@ -0,0 +1,373 @@ +/*++ +/* NAME +/* mailbox 3 +/* SUMMARY +/* mailbox delivery +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_mailbox(state, usr_attr, statusp) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* int *statusp; +/* DESCRIPTION +/* deliver_mailbox() delivers to mailbox, with duplicate +/* suppression. The default is direct mailbox delivery to +/* /var/[spool/]mail/\fIuser\fR; when a \fIhome_mailbox\fR +/* has been configured, mail is delivered to ~/$\fIhome_mailbox\fR; +/* and when a \fImailbox_command\fR has been configured, the message +/* is piped into the command instead. +/* +/* A zero result means that the named user was not found. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP statusp +/* Delivery status: see below. +/* DIAGNOSTICS +/* The message delivery status is non-zero when delivery should be tried +/* again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" +#include "biff_notify.h" + +#define YES 1 +#define NO 0 + +/* deliver_mailbox_file - deliver to recipient mailbox */ + +static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_mailbox_file"; + char *spool_dir; + char *mailbox; + DSN_BUF *why = state.msg_attr.why; + MBOX *mp; + int mail_copy_status; + int deliver_status; + int copy_flags; + VSTRING *biff; + off_t end; + struct stat st; + uid_t spool_uid; + gid_t spool_gid; + uid_t chown_uid; + gid_t chown_gid; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Don't deliver trace-only requests. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to mailbox"); + return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); + } + + /* + * Initialize. Assume the operation will fail. Set the delivered + * attribute to reflect the final recipient. + */ + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); + if (var_frozen_delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + mail_copy_status = MAIL_COPY_STAT_WRITE; + if (*var_home_mailbox) { + spool_dir = 0; + mailbox = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); + } else { + spool_dir = var_mail_spool_dir; + mailbox = concatenate(spool_dir, "/", state.msg_attr.user, (char *) 0); + } + + /* + * Mailbox delivery with least privilege. As long as we do not use root + * privileges this code may also work over NFS. + * + * If delivering to the recipient's home directory, perform all operations + * (including file locking) as that user (Mike Muuss, Army Research + * Laboratory, USA). + * + * If delivering to the mail spool directory, and the spool directory is + * world-writable, deliver as the recipient; if the spool directory is + * group-writable, use the recipient user id and the mail spool group id. + * + * Otherwise, use root privileges and chown the mailbox if we create it. + */ + if (spool_dir == 0 + || stat(spool_dir, &st) < 0 + || (st.st_mode & S_IWOTH) != 0) { + spool_uid = usr_attr.uid; + spool_gid = usr_attr.gid; + } else if ((st.st_mode & S_IWGRP) != 0) { + spool_uid = usr_attr.uid; + spool_gid = st.st_gid; + } else { + spool_uid = 0; + spool_gid = 0; + } + if (spool_uid == usr_attr.uid) { + chown_uid = -1; + chown_gid = -1; + } else { + chown_uid = usr_attr.uid; + chown_gid = usr_attr.gid; + } + if (msg_verbose) + msg_info("spool_uid/gid %ld/%ld chown_uid/gid %ld/%ld", + (long) spool_uid, (long) spool_gid, + (long) chown_uid, (long) chown_gid); + + /* + * Lock the mailbox and open/create the mailbox file. Depending on the + * type of locking used, we lock first or we open first. + * + * Write the file as the recipient, so that file quota work. + */ + copy_flags = MAIL_COPY_MBOX; + if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0) + copy_flags &= ~MAIL_COPY_DELIVERED; + + set_eugid(spool_uid, spool_gid); + mp = mbox_open(mailbox, O_APPEND | O_WRONLY | O_CREAT, + S_IRUSR | S_IWUSR, &st, chown_uid, chown_gid, + local_mbox_lock_mask, "5.2.0", why); + if (mp != 0) { + if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) + set_eugid(usr_attr.uid, usr_attr.gid); + if (S_ISREG(st.st_mode) == 0) { + vstream_fclose(mp->fp); + dsb_simple(why, "5.2.0", + "destination %s is not a regular file", mailbox); + } else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) { + vstream_fclose(mp->fp); + dsb_simple(why, "4.2.0", + "destination %s is not owned by recipient", mailbox); + msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch", + VAR_STRICT_MBOX_OWNER); + } else { + if ((end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END)) < 0) + msg_fatal("seek mailbox file %s: %m", mailbox); + mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, + copy_flags, "\n", why); + } + if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) + set_eugid(spool_uid, spool_gid); + mbox_release(mp); + } + set_eugid(var_owner_uid, var_owner_gid); + + /* + * As the mail system, bounce, defer delivery, or report success. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + vstring_sprintf_prepend(why->reason, + "cannot update mailbox %s for user %s. ", + mailbox, state.msg_attr.user); + deliver_status = + (STR(why->status)[0] == '4' ? + defer_append : bounce_append) + (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); + } else { + dsb_simple(why, "2.0.0", "delivered to mailbox"); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + if (var_biff) { + biff = vstring_alloc(100); + vstring_sprintf(biff, "%s@%ld", usr_attr.logname, (long) end); + biff_notify(STR(biff), VSTRING_LEN(biff) + 1); + vstring_free(biff); + } + } + + /* + * Clean up. + */ + myfree(mailbox); + return (deliver_status); +} + +/* deliver_mailbox - deliver to recipient mailbox */ + +int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) +{ + const char *myname = "deliver_mailbox"; + int status; + struct mypasswd *mbox_pwd; + char *path; + static MAPS *transp_maps; + const char *map_transport; + static MAPS *cmd_maps; + const char *map_command; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE ELIMINATION + * + * Don't come here more than once, whether or not the recipient exists. + */ + if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.local)) + return (YES); + + /* + * Delegate mailbox delivery to another message transport. + */ + if (*var_mbox_transp_maps && transp_maps == 0) + transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_transp_maps, + DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB + | DICT_FLAG_UTF8_REQUEST); + /* The -1 is a hint for the down-stream deliver_completed() function. */ + if (transp_maps + && (map_transport = maps_find(transp_maps, state.msg_attr.user, + DICT_FLAG_NONE)) != 0) { + state.msg_attr.rcpt.offset = -1L; + *statusp = deliver_pass(MAIL_CLASS_PRIVATE, map_transport, + state.request, &state.msg_attr.rcpt); + return (YES); + } else if (transp_maps && transp_maps->error != 0) { + /* Details in the logfile. */ + dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + if (*var_mailbox_transport) { + state.msg_attr.rcpt.offset = -1L; + *statusp = deliver_pass(MAIL_CLASS_PRIVATE, var_mailbox_transport, + state.request, &state.msg_attr.rcpt); + return (YES); + } + + /* + * Skip delivery when this recipient does not exist. + */ + if ((errno = mypwnam_err(state.msg_attr.user, &mbox_pwd)) != 0) { + msg_warn("error looking up passwd info for %s: %m", + state.msg_attr.user); + dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + if (mbox_pwd == 0) + return (NO); + + /* + * No early returns or we have a memory leak. + */ + + /* + * DELIVERY RIGHTS + * + * Use the rights of the recipient user. + */ + SET_USER_ATTR(usr_attr, mbox_pwd, state.level); + + /* + * Deliver to mailbox, maildir or to external command. + */ +#define LAST_CHAR(s) (s[strlen(s) - 1]) + + if (*var_mailbox_cmd_maps && cmd_maps == 0) + cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps, + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_UTF8_REQUEST); + + if (cmd_maps && (map_command = maps_find(cmd_maps, state.msg_attr.user, + DICT_FLAG_NONE)) != 0) { + status = deliver_command(state, usr_attr, map_command); + } else if (cmd_maps && cmd_maps->error != 0) { + /* Details in the logfile. */ + dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); + status = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else if (*var_mailbox_command) { + status = deliver_command(state, usr_attr, var_mailbox_command); + } else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') { + path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); + status = deliver_maildir(state, usr_attr, path); + myfree(path); + } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') { + path = concatenate(var_mail_spool_dir, state.msg_attr.user, + "/", (char *) 0); + status = deliver_maildir(state, usr_attr, path); + myfree(path); + } else + status = deliver_mailbox_file(state, usr_attr); + + /* + * Cleanup. + */ + mypwfree(mbox_pwd); + *statusp = status; + return (YES); +} diff --git a/src/local/maildir.c b/src/local/maildir.c new file mode 100644 index 0000000..46b8641 --- /dev/null +++ b/src/local/maildir.c @@ -0,0 +1,257 @@ +/*++ +/* NAME +/* maildir 3 +/* SUMMARY +/* delivery to maildir +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_maildir(state, usr_attr, path) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *path; +/* DESCRIPTION +/* deliver_maildir() delivers a message to a qmail maildir. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* .IP usr_attr +/* Attributes describing user rights and environment information. +/* .IP path +/* The maildir to deliver to, including trailing slash. +/* DIAGNOSTICS +/* deliver_maildir() always succeeds or it bounces the message. +/* SEE ALSO +/* bounce(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_maildir - delivery to maildir-style mailbox */ + +int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr, char *path) +{ + const char *myname = "deliver_maildir"; + char *newdir; + char *tmpdir; + char *curdir; + char *tmpfile; + char *newfile; + DSN_BUF *why = state.msg_attr.why; + VSTRING *buf; + VSTREAM *dst; + int mail_copy_status; + int deliver_status; + int copy_flags; + struct stat st; + struct timeval starttime; + + GETTIMEOFDAY(&starttime); + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Don't deliver trace-only requests. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to maildir"); + return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); + } + + /* + * Initialize. Assume the operation will fail. Set the delivered + * attribute to reflect the final recipient. + */ + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); + if (var_frozen_delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + mail_copy_status = MAIL_COPY_STAT_WRITE; + buf = vstring_alloc(100); + + copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH | MAIL_COPY_ORIG_RCPT; + if (local_deliver_hdr_mask & DELIVER_HDR_FILE) + copy_flags |= MAIL_COPY_DELIVERED; + + newdir = concatenate(path, "new/", (char *) 0); + tmpdir = concatenate(path, "tmp/", (char *) 0); + curdir = concatenate(path, "cur/", (char *) 0); + + /* + * Create and write the file as the recipient, so that file quota work. + * Create any missing directories on the fly. The file name is chosen + * according to ftp://koobera.math.uic.edu/www/proto/maildir.html: + * + * "A unique name has three pieces, separated by dots. On the left is the + * result of time(). On the right is the result of gethostname(). In the + * middle is something that doesn't repeat within one second on a single + * host. I fork a new process for each delivery, so I just use the + * process ID. If you're delivering several messages from one process, + * use starttime.pid_count.host, where starttime is the time that your + * process started, and count is the number of messages you've + * delivered." + * + * Well, that stopped working on fast machines, and on operating systems + * that randomize process ID values. When creating a file in tmp/ we use + * the process ID because it still is an exclusive resource. When moving + * the file to new/ we use the device number and inode number. I do not + * care if this breaks on a remote AFS file system, because people should + * know better. + * + * On January 26, 2003, http://cr.yp.to/proto/maildir.html said: + * + * A unique name has three pieces, separated by dots. On the left is the + * result of time() or the second counter from gettimeofday(). On the + * right is the result of gethostname(). (To deal with invalid host + * names, replace / with \057 and : with \072.) In the middle is a + * delivery identifier, discussed below. + * + * [...] + * + * Modern delivery identifiers are created by concatenating enough of the + * following strings to guarantee uniqueness: + * + * [...] + * + * In, where n is (in hexadecimal) the UNIX inode number of this file. + * Unfortunately, inode numbers aren't always available through NFS. + * + * Vn, where n is (in hexadecimal) the UNIX device number of this file. + * Unfortunately, device numbers aren't always available through NFS. + * (Device numbers are also not helpful with the standard UNIX + * filesystem: a maildir has to be within a single UNIX device for link() + * and rename() to work.) + * + * Mn, where n is (in decimal) the microsecond counter from the same + * gettimeofday() used for the left part of the unique name. + * + * Pn, where n is (in decimal) the process ID. + * + * [...] + */ + set_eugid(usr_attr.uid, usr_attr.gid); + vstring_sprintf(buf, "%lu.P%d.%s", + (unsigned long) starttime.tv_sec, var_pid, get_hostname()); + tmpfile = concatenate(tmpdir, STR(buf), (char *) 0); + newfile = 0; + if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0 + && (errno != ENOENT + || make_dirs(tmpdir, 0700) < 0 + || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) { + dsb_simple(why, mbox_dsn(errno, "5.2.0"), + "create maildir file %s: %m", tmpfile); + } else if (fstat(vstream_fileno(dst), &st) < 0) { + + /* + * Coverity 200604: file descriptor leak in code that never executes. + * Code replaced by msg_fatal(), as it is not worthwhile to continue + * after an impossible error condition. + */ + msg_fatal("fstat %s: %m", tmpfile); + } else { + vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s", + (unsigned long) starttime.tv_sec, + (unsigned long) st.st_dev, + (unsigned long) st.st_ino, + (unsigned long) starttime.tv_usec, + get_hostname()); + newfile = concatenate(newdir, STR(buf), (char *) 0); + if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), + dst, copy_flags, "\n", + why)) == 0) { + if (sane_link(tmpfile, newfile) < 0 + && (errno != ENOENT + || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0 + || sane_link(tmpfile, newfile) < 0)) { + dsb_simple(why, mbox_dsn(errno, "5.2.0"), + "create maildir file %s: %m", newfile); + mail_copy_status = MAIL_COPY_STAT_WRITE; + } + } + if (unlink(tmpfile) < 0) + msg_warn("remove %s: %m", tmpfile); + } + set_eugid(var_owner_uid, var_owner_gid); + + /* + * As the mail system, bounce or defer delivery. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + if (errno == EACCES) { + msg_warn("maildir access problem for UID/GID=%lu/%lu: %s", + (long) usr_attr.uid, (long) usr_attr.gid, + STR(why->reason)); + msg_warn("perhaps you need to create the maildirs in advance"); + } + vstring_sprintf_prepend(why->reason, "maildir delivery failed: "); + deliver_status = + (STR(why->status)[0] == '4' ? + defer_append : bounce_append) + (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); + } else { + dsb_simple(why, "2.0.0", "delivered to maildir"); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + } + vstring_free(buf); + myfree(newdir); + myfree(tmpdir); + myfree(curdir); + myfree(tmpfile); + if (newfile) + myfree(newfile); + return (deliver_status); +} diff --git a/src/local/recipient.c b/src/local/recipient.c new file mode 100644 index 0000000..e3f4d1c --- /dev/null +++ b/src/local/recipient.c @@ -0,0 +1,307 @@ +/*++ +/* NAME +/* recipient 3 +/* SUMMARY +/* deliver to one local recipient +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_recipient(state, usr_attr) +/* LOCAL_STATE state; +/* USER_ATTR *usr_attr; +/* DESCRIPTION +/* deliver_recipient() delivers a message to a local recipient. +/* It is called initially when the queue manager requests +/* delivery to a local recipient, and is called recursively +/* when an alias or forward file expands to a local recipient. +/* +/* When called recursively with, for example, a result from alias +/* or forward file expansion, aliases are expanded immediately, +/* but mail for non-alias destinations is submitted as a new +/* message, so that each recipient has a dedicated queue file +/* message delivery status record (in a shared queue file). +/* +/* When the \fIrecipient_delimiter\fR configuration parameter +/* is set, it is used to separate cookies off recipient names. +/* A common setting is to have "recipient_delimiter = +" +/* so that mail for \fIuser+foo\fR is delivered to \fIuser\fR, +/* with a "Delivered-To: user+foo@domain" header line. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, sender, and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* DIAGNOSTICS +/* deliver_recipient() returns non-zero when delivery should be +/* tried again. +/* BUGS +/* Mutually-recursive aliases or $HOME/.forward files aren't +/* detected when they could be. The resulting mail forwarding loop +/* is broken by the use of the Delivered-To: message header. +/* SEE ALSO +/* alias(3) delivery to aliases +/* mailbox(3) delivery to mailbox +/* dotforward(3) delivery to destinations in .forward file +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_switch - branch on recipient type */ + +static int deliver_switch(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_switch"; + int status = 0; + struct stat st; + struct mypasswd *mypwd; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + + /* + * \user is special: it means don't do any alias or forward expansion. + * + * XXX This code currently does not work due to revision of the RFC822 + * address parser. \user should be permitted only in locally specified + * aliases, includes or forward files. + * + * XXX Should test for presence of user home directory. + */ + if (state.msg_attr.rcpt.address[0] == '\\') { + state.msg_attr.rcpt.address++, state.msg_attr.local++, state.msg_attr.user++; + if (deliver_mailbox(state, usr_attr, &status) == 0) + status = deliver_unknown(state, usr_attr); + return (status); + } + + /* + * Otherwise, alias expansion has highest precedence. First look up the + * full localpart, then the bare user. Obey the address extension + * propagation policy. + */ + state.msg_attr.unmatched = 0; + if (deliver_alias(state, usr_attr, state.msg_attr.local, &status)) + return (status); + if (state.msg_attr.extension != 0) { + if (local_ext_prop_mask & EXT_PROP_ALIAS) + state.msg_attr.unmatched = state.msg_attr.extension; + if (deliver_alias(state, usr_attr, state.msg_attr.user, &status)) + return (status); + state.msg_attr.unmatched = state.msg_attr.extension; + } + + /* + * Special case for mail locally forwarded or aliased to a different + * local address. Resubmit the message via the cleanup service, so that + * each recipient gets a separate delivery queue file status record in + * the new queue file. The downside of this approach is that mutually + * recursive .forward files cause a mail forwarding loop. Fortunately, + * the loop can be broken by the use of the Delivered-To: message header. + * + * The code below must not trigger on mail sent to an alias that has no + * owner- companion, so that mail for an alias first.last->username is + * delivered directly, instead of going through username->first.last + * canonical mappings in the cleanup service. The downside of this + * approach is that recipients in the expansion of an alias without + * owner- won't have separate delivery queue file status records, because + * for them, the message won't be resubmitted as a new queue file. + * + * Do something sensible on systems that receive mail for multiple domains, + * such as primary.name and secondary.name. Don't resubmit the message + * when mail for `user@secondary.name' is delivered to a .forward file + * that lists `user' or `user@primary.name'. We already know that the + * recipient domain is local, so we only have to compare local parts. + */ + if (state.msg_attr.owner != 0 + && strcasecmp_utf8(state.msg_attr.owner, state.msg_attr.user) != 0) + return (deliver_indirect(state)); + + /* + * Always forward recipients in :include: files. + */ + if (state.msg_attr.exp_type == EXPAND_TYPE_INCL) + return (deliver_indirect(state)); + + /* + * Delivery to local user. First try expansion of the recipient's + * $HOME/.forward file, then mailbox delivery. Back off when the user's + * home directory does not exist. + */ + mypwd = 0; + if (var_stat_home_dir + && (errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) { + msg_warn("error looking up passwd info for %s: %m", + state.msg_attr.user); + dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (mypwd != 0) { + if (stat_as(mypwd->pw_dir, &st, mypwd->pw_uid, mypwd->pw_gid) < 0) { + dsb_simple(state.msg_attr.why, "4.3.0", + "cannot access home directory %s: %m", mypwd->pw_dir); + mypwfree(mypwd); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + mypwfree(mypwd); + } + if (deliver_dotforward(state, usr_attr, &status) == 0 + && deliver_mailbox(state, usr_attr, &status) == 0) + status = deliver_unknown(state, usr_attr); + return (status); +} + +/* deliver_recipient - deliver one local recipient */ + +int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_recipient"; + VSTRING *folded; + int rcpt_stat; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Duplicate filter. + */ + if (been_here(state.dup_filter, "recipient %d %s", + state.level, state.msg_attr.rcpt.address)) + return (0); + + /* + * With each level of recursion, detect and break external message + * forwarding loops. + * + * If the looping recipient address has an owner- alias, send the error + * report there instead. + * + * XXX A delivery agent cannot change the envelope sender address for + * bouncing. As a workaround we use a one-recipient bounce procedure. + * + * The proper fix would be to record in the bounce logfile an error return + * address for each individual recipient. This would also eliminate the + * need for VERP specific bouncing code, at the cost of complicating the + * normal bounce sending procedure, but would simplify the code below. + */ + if (delivered_hdr_find(state.loop_info, state.msg_attr.rcpt.address)) { + dsb_simple(state.msg_attr.why, "5.4.6", "mail forwarding loop for %s", + state.msg_attr.rcpt.address); + /* Account for possible owner- sender address override. */ + return (bounce_workaround(state)); + } + + /* + * Set up the recipient-specific attributes. If this is forwarded mail, + * leave the delivered attribute alone, so that the forwarded message + * will show the correct forwarding recipient. + */ + if (state.msg_attr.delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + folded = vstring_alloc(100); + state.msg_attr.local = casefold(folded, state.msg_attr.rcpt.address); + if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0) + msg_warn("no @ in recipient address: %s", state.msg_attr.local); + + /* + * Address extension management. + * + * XXX Fix 20100422, finalized 20100529: it is too error-prone to + * distinguish between "no extension" and "no valid extension", so we + * drop an invalid extension from the recipient address local-part. + */ + state.msg_attr.user = mystrdup(state.msg_attr.local); + if (*var_rcpt_delim) { + state.msg_attr.extension = + split_addr(state.msg_attr.user, var_rcpt_delim); + if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) { + msg_warn("%s: address with illegal extension: %s", + state.msg_attr.queue_id, state.msg_attr.local); + state.msg_attr.extension = 0; + /* XXX Can't myfree + mystrdup, must truncate instead. */ + state.msg_attr.local[strlen(state.msg_attr.user)] = 0; + /* Truncating is safe. The code below rejects null usernames. */ + } + } else + state.msg_attr.extension = 0; + state.msg_attr.unmatched = state.msg_attr.extension; + + /* + * Do not allow null usernames. + */ + if (state.msg_attr.user[0] == 0) { + dsb_simple(state.msg_attr.why, "5.1.3", + "null username in \"%s\"", state.msg_attr.rcpt.address); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + + /* + * Run the recipient through the delivery switch. + */ + if (msg_verbose) + deliver_attr_dump(&state.msg_attr); + rcpt_stat = deliver_switch(state, usr_attr); + + /* + * Clean up. + */ + vstring_free(folded); + myfree(state.msg_attr.user); + + return (rcpt_stat); +} diff --git a/src/local/resolve.c b/src/local/resolve.c new file mode 100644 index 0000000..a6aa9d0 --- /dev/null +++ b/src/local/resolve.c @@ -0,0 +1,170 @@ +/*++ +/* NAME +/* resolve 3 +/* SUMMARY +/* resolve recipient and deliver locally or remotely +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_resolve_tree(state, usr_attr, addr) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* TOK822 *addr; +/* +/* int deliver_resolve_addr(state, usr_attr, addr) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *addr; +/* DESCRIPTION +/* deliver_resolve_XXX() resolves a recipient that is the result from +/* e.g., alias expansion, and delivers locally or via forwarding. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, sender and more. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP addr +/* An address from, e.g., alias expansion. +/* DIAGNOSTICS +/* Fatal errors: out of memory. The result is non-zero when the +/* operation should be tried again. Warnings: malformed address. +/* SEE ALSO +/* recipient(3) local delivery +/* indirect(3) deliver via forwarding +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_resolve_addr - resolve and deliver */ + +int deliver_resolve_addr(LOCAL_STATE state, USER_ATTR usr_attr, char *addr) +{ + TOK822 *tree; + int result; + + tree = tok822_scan_addr(addr); + result = deliver_resolve_tree(state, usr_attr, tree); + tok822_free_tree(tree); + return (result); +} + +/* deliver_resolve_tree - resolve and deliver */ + +int deliver_resolve_tree(LOCAL_STATE state, USER_ATTR usr_attr, TOK822 *addr) +{ + const char *myname = "deliver_resolve_tree"; + RESOLVE_REPLY reply; + int status; + ssize_t ext_len; + char *ratsign; + int rcpt_delim; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Initialize. + */ + resolve_clnt_init(&reply); + + /* + * Rewrite the address to canonical form, just like the cleanup service + * does. Then, resolve the address to (transport, nexhop, recipient), + * just like the queue manager does. The only part missing here is the + * virtual address substitution. Message forwarding fixes most of that. + */ + tok822_rewrite(addr, REWRITE_CANON); + tok822_resolve(addr, &reply); + + /* + * First, a healthy portion of error handling. + */ + if (reply.flags & RESOLVE_FLAG_FAIL) { + dsb_simple(state.msg_attr.why, "4.3.0", "address resolver failure"); + status = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else if (reply.flags & RESOLVE_FLAG_ERROR) { + dsb_simple(state.msg_attr.why, "5.1.3", + "bad recipient address syntax: %s", STR(reply.recipient)); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + + /* + * Splice in the optional unmatched address extension. + */ + if (state.msg_attr.unmatched) { + rcpt_delim = state.msg_attr.local[strlen(state.msg_attr.user)]; + if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) { + VSTRING_ADDCH(reply.recipient, rcpt_delim); + vstring_strcat(reply.recipient, state.msg_attr.unmatched); + } else { + ext_len = strlen(state.msg_attr.unmatched); + VSTRING_SPACE(reply.recipient, ext_len + 2); + if ((ratsign = strrchr(STR(reply.recipient), '@')) == 0) + msg_panic("%s: recipient @ botch", myname); + memmove(ratsign + ext_len + 1, ratsign, strlen(ratsign) + 1); + *ratsign = rcpt_delim; + memcpy(ratsign + 1, state.msg_attr.unmatched, ext_len); + VSTRING_SKIP(reply.recipient); + } + } + state.msg_attr.rcpt.address = STR(reply.recipient); + + /* + * Delivery to a local or non-local address. For a while there was + * some ugly code to force local recursive alias expansions on a host + * with no authority over the local domain, but that code was just + * too unclean. + */ + if (strcmp(state.msg_attr.relay, STR(reply.transport)) == 0) { + status = deliver_recipient(state, usr_attr); + } else { + status = deliver_indirect(state); + } + } + + /* + * Cleanup. + */ + resolve_clnt_free(&reply); + + return (status); +} diff --git a/src/local/token.c b/src/local/token.c new file mode 100644 index 0000000..2eb0c28 --- /dev/null +++ b/src/local/token.c @@ -0,0 +1,222 @@ +/*++ +/* NAME +/* token 3 +/* SUMMARY +/* tokenize alias/include/.forward entries and deliver +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_token(state, usr_attr, addr) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* TOK822 *addr; +/* +/* int deliver_token_string(state, usr_attr, string, addr_count) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* char *string; +/* int *addr_count; +/* +/* int deliver_token_stream(state, usr_attr, fp, addr_count) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* VSTREAM *fp; +/* int *addr_count; +/* DESCRIPTION +/* This module delivers to addresses listed in an alias database +/* entry, in an include file, or in a .forward file. +/* +/* deliver_token() delivers to the address in the given token: +/* an absolute /path/name, a ~/path/name relative to the recipient's +/* home directory, an :include:/path/name request, an external +/* "|command", or a mail address. +/* +/* deliver_token_string() delivers to all addresses listed in +/* the specified string. +/* +/* deliver_token_stream() delivers to all addresses listed in +/* the specified stream. Input records > \fIline_length_limit\fR +/* are broken up into multiple records, to prevent the mail +/* system from using unreasonable amounts of memory. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* .IP addr +/* A parsed address from an include file, alias file or .forward file. +/* .IP string +/* A null-terminated string. +/* .IP fp +/* A readable stream. +/* .IP addr_count +/* Null pointer, or the address of a counter that is incremented +/* by the number of destinations found by the tokenizer. +/* DIAGNOSTICS +/* Fatal errors: out of memory. The result is non-zero when the +/* operation should be tried again. Warnings: malformed address. +/* SEE ALSO +/* list_token(3) tokenize list +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +/* deliver_token_home - expand ~token */ + +static int deliver_token_home(LOCAL_STATE state, USER_ATTR usr_attr, char *addr) +{ + char *full_path; + int status; + + if (addr[1] != '/') { /* disallow ~user */ + msg_warn("bad home directory syntax for: %s", addr); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else if (usr_attr.home == 0) { /* require user context */ + msg_warn("unknown home directory for: %s", addr); + dsb_simple(state.msg_attr.why, "5.3.5", + "mail system configuration error"); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else if (usr_attr.home[0] == '/' && usr_attr.home[1] == 0) { + status = deliver_file(state, usr_attr, addr + 1); + } else { /* expand ~ to home */ + full_path = concatenate(usr_attr.home, addr + 1, (char *) 0); + status = deliver_file(state, usr_attr, full_path); + myfree(full_path); + } + return (status); +} + +/* deliver_token - deliver to expansion of include file or alias */ + +int deliver_token(LOCAL_STATE state, USER_ATTR usr_attr, TOK822 *addr) +{ + VSTRING *addr_buf = vstring_alloc(100); + static char include[] = ":include:"; + int status; + char *path; + + tok822_internalize(addr_buf, addr->head, TOK822_STR_DEFL); + if (msg_verbose) + msg_info("deliver_token: %s", STR(addr_buf)); + + if (*STR(addr_buf) == '/') { + status = deliver_file(state, usr_attr, STR(addr_buf)); + } else if (*STR(addr_buf) == '~') { + status = deliver_token_home(state, usr_attr, STR(addr_buf)); + } else if (*STR(addr_buf) == '|') { + if ((local_cmd_deliver_mask & state.msg_attr.exp_type) == 0) { + dsb_simple(state.msg_attr.why, "5.7.1", + "mail to command is restricted"); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else + status = deliver_command(state, usr_attr, STR(addr_buf) + 1); + } else if (strncasecmp(STR(addr_buf), include, sizeof(include) - 1) == 0) { + path = STR(addr_buf) + sizeof(include) - 1; + status = deliver_include(state, usr_attr, path); + } else { + status = deliver_resolve_tree(state, usr_attr, addr); + } + vstring_free(addr_buf); + + return (status); +} + +/* deliver_token_string - tokenize string and deliver */ + +int deliver_token_string(LOCAL_STATE state, USER_ATTR usr_attr, + char *string, int *addr_count) +{ + TOK822 *tree; + TOK822 *addr; + int status = 0; + + if (msg_verbose) + msg_info("deliver_token_string: %s", string); + + tree = tok822_parse(string); + for (addr = tree; addr != 0; addr = addr->next) { + if (addr->type == TOK822_ADDR) { + if (addr_count) + (*addr_count)++; + status |= deliver_token(state, usr_attr, addr); + } + } + tok822_free_tree(tree); + return (status); +} + +/* deliver_token_stream - tokenize stream and deliver */ + +int deliver_token_stream(LOCAL_STATE state, USER_ATTR usr_attr, + VSTREAM *fp, int *addr_count) +{ + VSTRING *buf = vstring_alloc(100); + int status = 0; + + if (msg_verbose) + msg_info("deliver_token_stream: %s", VSTREAM_PATH(fp)); + + while (vstring_fgets_bound(buf, fp, var_line_limit)) { + if (*STR(buf) != '#') { + status = deliver_token_string(state, usr_attr, STR(buf), addr_count); + if (status != 0) + break; + } + } + if (vstream_ferror(fp)) { + dsb_simple(state.msg_attr.why, "4.3.0", + "error reading forwarding file: %m"); + status = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } + vstring_free(buf); + return (status); +} diff --git a/src/local/unknown.c b/src/local/unknown.c new file mode 100644 index 0000000..96443e1 --- /dev/null +++ b/src/local/unknown.c @@ -0,0 +1,187 @@ +/*++ +/* NAME +/* unknown 3 +/* SUMMARY +/* delivery of unknown recipients +/* SYNOPSIS +/* #include "local.h" +/* +/* int deliver_unknown(state, usr_attr) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* DESCRIPTION +/* deliver_unknown() delivers a message for unknown recipients. +/* .IP \(bu +/* If an alternative message transport is specified via the +/* fallback_transport parameter, delivery is delegated to the +/* named transport. +/* .IP \(bu +/* If an alternative address is specified via the luser_relay +/* configuration parameter, mail is forwarded to that address. +/* .IP \(bu +/* Otherwise the recipient is bounced. +/* .PP +/* The luser_relay parameter is subjected to $name expansion of +/* the standard message attributes: $user, $home, $shell, $domain, +/* $recipient, $mailbox, $extension, $recipient_delimiter, not +/* all of which actually make sense. +/* +/* Arguments: +/* .IP state +/* Message delivery attributes (sender, recipient etc.). +/* Attributes describing alias, include or forward expansion. +/* A table with the results from expanding aliases or lists. +/* A table with delivered-to: addresses taken from the message. +/* .IP usr_attr +/* Attributes describing user rights and environment. +/* DIAGNOSTICS +/* The result status is non-zero when delivery should be tried again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "local.h" + +#define STREQ(x,y) (strcasecmp((x),(y)) == 0) + +/* deliver_unknown - delivery for unknown recipients */ + +int deliver_unknown(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_unknown"; + int status; + VSTRING *expand_luser; + VSTRING *canon_luser; + static MAPS *transp_maps; + const char *map_transport; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * DUPLICATE/LOOP ELIMINATION + * + * Don't deliver the same user twice. + */ + if (been_here(state.dup_filter, "%s %s", myname, state.msg_attr.local)) + return (0); + + /* + * The fall-back transport specifies a delivery mechanism that handles + * users not found in the aliases or UNIX passwd databases. + */ + if (*var_fbck_transp_maps && transp_maps == 0) + transp_maps = maps_create(VAR_FBCK_TRANSP_MAPS, var_fbck_transp_maps, + DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB + | DICT_FLAG_UTF8_REQUEST); + /* The -1 is a hint for the down-stream deliver_completed() function. */ + if (transp_maps + && (map_transport = maps_find(transp_maps, state.msg_attr.user, + DICT_FLAG_NONE)) != 0) { + state.msg_attr.rcpt.offset = -1L; + return (deliver_pass(MAIL_CLASS_PRIVATE, map_transport, + state.request, &state.msg_attr.rcpt)); + } else if (transp_maps && transp_maps->error != 0) { + /* Details in the logfile. */ + dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); + return (defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + if (*var_fallback_transport) { + state.msg_attr.rcpt.offset = -1L; + return (deliver_pass(MAIL_CLASS_PRIVATE, var_fallback_transport, + state.request, &state.msg_attr.rcpt)); + } + + /* + * Subject the luser_relay address to $name expansion, disable + * propagation of unmatched address extension, and re-inject the address + * into the delivery machinery. Do not give special treatment to "|stuff" + * or /stuff. + */ + if (*var_luser_relay) { + state.msg_attr.unmatched = 0; + expand_luser = vstring_alloc(100); + canon_luser = vstring_alloc(100); + local_expand(expand_luser, var_luser_relay, &state, &usr_attr, (void *) 0); + /* In case luser_relay specifies a domain-less address. */ + canon_addr_external(canon_luser, vstring_str(expand_luser)); + /* Assumes that the address resolver won't change the address. */ + if (STREQ(vstring_str(canon_luser), state.msg_attr.rcpt.address)) { + dsb_simple(state.msg_attr.why, "5.1.1", + "unknown user: \"%s\"", state.msg_attr.user); + status = bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + status = deliver_resolve_addr(state, usr_attr, STR(expand_luser)); + } + vstring_free(canon_luser); + vstring_free(expand_luser); + return (status); + } + + /* + * If no alias was found for a required reserved name, toss the message + * into the bit bucket, and issue a warning instead. + */ + if (STREQ(state.msg_attr.user, MAIL_ADDR_MAIL_DAEMON) + || STREQ(state.msg_attr.user, MAIL_ADDR_POSTMASTER)) { + msg_warn("required alias not found: %s", state.msg_attr.user); + dsb_simple(state.msg_attr.why, "2.0.0", "discarded"); + return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); + } + + /* + * Bounce the message when no luser relay is specified. + */ + dsb_simple(state.msg_attr.why, "5.1.1", + "unknown user: \"%s\"", state.msg_attr.user); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); +} diff --git a/src/master/.indent.pro b/src/master/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/master/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/master/.printfck b/src/master/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/master/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/master/Makefile.in b/src/master/Makefile.in new file mode 100644 index 0000000..db67a68 --- /dev/null +++ b/src/master/Makefile.in @@ -0,0 +1,477 @@ +SHELL = /bin/sh +SRCS = master.c master_conf.c master_ent.c master_sig.c master_avail.c \ + master_spawn.c master_service.c master_status.c master_listen.c \ + master_proto.c single_server.c multi_server.c master_vars.c \ + master_wakeup.c master_flow.c master_watch.c mail_flow.c \ + master_monitor.c dgram_server.c +OBJS = master.o master_conf.o master_ent.o master_sig.o master_avail.o \ + master_spawn.o master_service.o master_status.o master_listen.o \ + master_vars.o master_wakeup.o master_watch.o master_flow.o \ + master_monitor.o +LIB_OBJ = single_server.o multi_server.o trigger_server.o master_proto.o \ + mail_flow.o event_server.o dgram_server.o +HDRS = mail_server.h master_proto.h mail_flow.h +INT_HDR = master.h +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +LIB = lib$(LIB_PREFIX)master$(LIB_SUFFIX) +PROG = master +TESTPROG= +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) +LIB_DIR = ../../lib +INC_DIR = ../../include +BIN_DIR = ../../libexec + +.c.o:; $(CC) `for i in $(LIB_OBJ); do if [ $$i = $@ ]; then echo $(SHLIB_CFLAGS); else true; fi; done` $(CFLAGS) -c $*.c + +all: $(PROG) $(LIB) + +$(OBJS) $(LIB_OBJ): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +test: $(TESTPROG) + +tests: + +root_tests: + +$(LIB): $(LIB_OBJ) + $(AR) $(ARFL) $(LIB) $? + $(RANLIB) $(LIB) + $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(LIB_OBJ) $(SHLIB_SYSLIBS) + +$(LIB_DIR)/$(LIB): $(LIB) + cp $(LIB) $(LIB_DIR)/$(LIB) + $(RANLIB) $(LIB_DIR)/$(LIB) + +$(BIN_DIR)/$(PROG): $(PROG) + cp $(PROG) $(BIN_DIR) + +update: $(LIB_DIR)/$(LIB) $(BIN_DIR)/$(PROG) $(HDRS) + -for i in $(HDRS); \ + do \ + cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \ + done + cd $(INC_DIR); chmod 644 $(HDRS) + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) junk $(LIB) + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +dgram_server.o: ../../include/argv.h +dgram_server.o: ../../include/attr.h +dgram_server.o: ../../include/bounce.h +dgram_server.o: ../../include/check_arg.h +dgram_server.o: ../../include/chroot_uid.h +dgram_server.o: ../../include/debug_process.h +dgram_server.o: ../../include/deliver_request.h +dgram_server.o: ../../include/dict.h +dgram_server.o: ../../include/dsn.h +dgram_server.o: ../../include/dsn_buf.h +dgram_server.o: ../../include/events.h +dgram_server.o: ../../include/htable.h +dgram_server.o: ../../include/iostuff.h +dgram_server.o: ../../include/listen.h +dgram_server.o: ../../include/mail_conf.h +dgram_server.o: ../../include/mail_dict.h +dgram_server.o: ../../include/mail_params.h +dgram_server.o: ../../include/mail_task.h +dgram_server.o: ../../include/mail_version.h +dgram_server.o: ../../include/maillog_client.h +dgram_server.o: ../../include/msg.h +dgram_server.o: ../../include/msg_stats.h +dgram_server.o: ../../include/msg_vstream.h +dgram_server.o: ../../include/myflock.h +dgram_server.o: ../../include/mymalloc.h +dgram_server.o: ../../include/nvtable.h +dgram_server.o: ../../include/recipient_list.h +dgram_server.o: ../../include/resolve_local.h +dgram_server.o: ../../include/safe_open.h +dgram_server.o: ../../include/sane_accept.h +dgram_server.o: ../../include/split_at.h +dgram_server.o: ../../include/stringops.h +dgram_server.o: ../../include/sys_defs.h +dgram_server.o: ../../include/vbuf.h +dgram_server.o: ../../include/vstream.h +dgram_server.o: ../../include/vstring.h +dgram_server.o: ../../include/watchdog.h +dgram_server.o: dgram_server.c +dgram_server.o: mail_flow.h +dgram_server.o: mail_server.h +dgram_server.o: master_proto.h +event_server.o: ../../include/argv.h +event_server.o: ../../include/attr.h +event_server.o: ../../include/bounce.h +event_server.o: ../../include/check_arg.h +event_server.o: ../../include/chroot_uid.h +event_server.o: ../../include/debug_process.h +event_server.o: ../../include/deliver_request.h +event_server.o: ../../include/dict.h +event_server.o: ../../include/dsn.h +event_server.o: ../../include/dsn_buf.h +event_server.o: ../../include/events.h +event_server.o: ../../include/htable.h +event_server.o: ../../include/iostuff.h +event_server.o: ../../include/listen.h +event_server.o: ../../include/mail_conf.h +event_server.o: ../../include/mail_dict.h +event_server.o: ../../include/mail_params.h +event_server.o: ../../include/mail_task.h +event_server.o: ../../include/mail_version.h +event_server.o: ../../include/maillog_client.h +event_server.o: ../../include/msg.h +event_server.o: ../../include/msg_stats.h +event_server.o: ../../include/msg_vstream.h +event_server.o: ../../include/myflock.h +event_server.o: ../../include/mymalloc.h +event_server.o: ../../include/nvtable.h +event_server.o: ../../include/recipient_list.h +event_server.o: ../../include/resolve_local.h +event_server.o: ../../include/safe_open.h +event_server.o: ../../include/sane_accept.h +event_server.o: ../../include/split_at.h +event_server.o: ../../include/stringops.h +event_server.o: ../../include/sys_defs.h +event_server.o: ../../include/timed_ipc.h +event_server.o: ../../include/vbuf.h +event_server.o: ../../include/vstream.h +event_server.o: ../../include/vstring.h +event_server.o: ../../include/watchdog.h +event_server.o: event_server.c +event_server.o: mail_flow.h +event_server.o: mail_server.h +event_server.o: master_proto.h +mail_flow.o: ../../include/iostuff.h +mail_flow.o: ../../include/msg.h +mail_flow.o: ../../include/sys_defs.h +mail_flow.o: ../../include/warn_stat.h +mail_flow.o: mail_flow.c +mail_flow.o: mail_flow.h +mail_flow.o: master_proto.h +master.o: ../../include/argv.h +master.o: ../../include/check_arg.h +master.o: ../../include/clean_env.h +master.o: ../../include/debug_process.h +master.o: ../../include/events.h +master.o: ../../include/inet_proto.h +master.o: ../../include/iostuff.h +master.o: ../../include/mail_conf.h +master.o: ../../include/mail_params.h +master.o: ../../include/mail_parm_split.h +master.o: ../../include/mail_task.h +master.o: ../../include/mail_version.h +master.o: ../../include/maillog_client.h +master.o: ../../include/msg.h +master.o: ../../include/myflock.h +master.o: ../../include/mymalloc.h +master.o: ../../include/open_lock.h +master.o: ../../include/safe.h +master.o: ../../include/set_eugid.h +master.o: ../../include/set_ugid.h +master.o: ../../include/stringops.h +master.o: ../../include/sys_defs.h +master.o: ../../include/vbuf.h +master.o: ../../include/vstream.h +master.o: ../../include/vstring.h +master.o: ../../include/watchdog.h +master.o: master.c +master.o: master.h +master_avail.o: ../../include/events.h +master_avail.o: ../../include/msg.h +master_avail.o: ../../include/sys_defs.h +master_avail.o: master.h +master_avail.o: master_avail.c +master_avail.o: master_proto.h +master_conf.o: ../../include/argv.h +master_conf.o: ../../include/msg.h +master_conf.o: ../../include/sys_defs.h +master_conf.o: master.h +master_conf.o: master_conf.c +master_ent.o: ../../include/argv.h +master_ent.o: ../../include/attr.h +master_ent.o: ../../include/check_arg.h +master_ent.o: ../../include/compat_level.h +master_ent.o: ../../include/host_port.h +master_ent.o: ../../include/htable.h +master_ent.o: ../../include/inet_addr_host.h +master_ent.o: ../../include/inet_addr_list.h +master_ent.o: ../../include/inet_proto.h +master_ent.o: ../../include/iostuff.h +master_ent.o: ../../include/mail_conf.h +master_ent.o: ../../include/mail_params.h +master_ent.o: ../../include/mail_proto.h +master_ent.o: ../../include/match_service.h +master_ent.o: ../../include/msg.h +master_ent.o: ../../include/myaddrinfo.h +master_ent.o: ../../include/mymalloc.h +master_ent.o: ../../include/nvtable.h +master_ent.o: ../../include/own_inet_addr.h +master_ent.o: ../../include/readlline.h +master_ent.o: ../../include/sock_addr.h +master_ent.o: ../../include/stringops.h +master_ent.o: ../../include/sys_defs.h +master_ent.o: ../../include/vbuf.h +master_ent.o: ../../include/vstream.h +master_ent.o: ../../include/vstring.h +master_ent.o: ../../include/wildcard_inet_addr.h +master_ent.o: master.h +master_ent.o: master_ent.c +master_ent.o: master_proto.h +master_flow.o: ../../include/iostuff.h +master_flow.o: ../../include/msg.h +master_flow.o: ../../include/sys_defs.h +master_flow.o: master.h +master_flow.o: master_flow.c +master_flow.o: master_proto.h +master_listen.o: ../../include/check_arg.h +master_listen.o: ../../include/htable.h +master_listen.o: ../../include/inet_addr_list.h +master_listen.o: ../../include/iostuff.h +master_listen.o: ../../include/listen.h +master_listen.o: ../../include/mail_params.h +master_listen.o: ../../include/msg.h +master_listen.o: ../../include/myaddrinfo.h +master_listen.o: ../../include/mymalloc.h +master_listen.o: ../../include/set_eugid.h +master_listen.o: ../../include/set_ugid.h +master_listen.o: ../../include/sock_addr.h +master_listen.o: ../../include/stringops.h +master_listen.o: ../../include/sys_defs.h +master_listen.o: ../../include/vbuf.h +master_listen.o: ../../include/vstring.h +master_listen.o: master.h +master_listen.o: master_listen.c +master_monitor.o: ../../include/iostuff.h +master_monitor.o: ../../include/msg.h +master_monitor.o: ../../include/sys_defs.h +master_monitor.o: master.h +master_monitor.o: master_monitor.c +master_proto.o: ../../include/msg.h +master_proto.o: ../../include/sys_defs.h +master_proto.o: master_proto.c +master_proto.o: master_proto.h +master_service.o: ../../include/msg.h +master_service.o: ../../include/mymalloc.h +master_service.o: ../../include/sys_defs.h +master_service.o: master.h +master_service.o: master_service.c +master_sig.o: ../../include/events.h +master_sig.o: ../../include/iostuff.h +master_sig.o: ../../include/killme_after.h +master_sig.o: ../../include/msg.h +master_sig.o: ../../include/posix_signals.h +master_sig.o: ../../include/sys_defs.h +master_sig.o: master.h +master_sig.o: master_sig.c +master_spawn.o: ../../include/argv.h +master_spawn.o: ../../include/binhash.h +master_spawn.o: ../../include/check_arg.h +master_spawn.o: ../../include/events.h +master_spawn.o: ../../include/mail_conf.h +master_spawn.o: ../../include/msg.h +master_spawn.o: ../../include/mymalloc.h +master_spawn.o: ../../include/sys_defs.h +master_spawn.o: ../../include/vbuf.h +master_spawn.o: ../../include/vstring.h +master_spawn.o: master.h +master_spawn.o: master_proto.h +master_spawn.o: master_spawn.c +master_status.o: ../../include/binhash.h +master_status.o: ../../include/events.h +master_status.o: ../../include/iostuff.h +master_status.o: ../../include/msg.h +master_status.o: ../../include/sys_defs.h +master_status.o: master.h +master_status.o: master_proto.h +master_status.o: master_status.c +master_vars.o: ../../include/check_arg.h +master_vars.o: ../../include/mail_conf.h +master_vars.o: ../../include/mail_params.h +master_vars.o: ../../include/msg.h +master_vars.o: ../../include/mymalloc.h +master_vars.o: ../../include/stringops.h +master_vars.o: ../../include/sys_defs.h +master_vars.o: ../../include/vbuf.h +master_vars.o: ../../include/vstring.h +master_vars.o: master.h +master_vars.o: master_vars.c +master_wakeup.o: ../../include/attr.h +master_wakeup.o: ../../include/check_arg.h +master_wakeup.o: ../../include/events.h +master_wakeup.o: ../../include/htable.h +master_wakeup.o: ../../include/iostuff.h +master_wakeup.o: ../../include/mail_conf.h +master_wakeup.o: ../../include/mail_params.h +master_wakeup.o: ../../include/mail_proto.h +master_wakeup.o: ../../include/msg.h +master_wakeup.o: ../../include/mymalloc.h +master_wakeup.o: ../../include/nvtable.h +master_wakeup.o: ../../include/set_eugid.h +master_wakeup.o: ../../include/set_ugid.h +master_wakeup.o: ../../include/sys_defs.h +master_wakeup.o: ../../include/trigger.h +master_wakeup.o: ../../include/vbuf.h +master_wakeup.o: ../../include/vstream.h +master_wakeup.o: ../../include/vstring.h +master_wakeup.o: mail_server.h +master_wakeup.o: master.h +master_wakeup.o: master_wakeup.c +master_watch.o: ../../include/msg.h +master_watch.o: ../../include/mymalloc.h +master_watch.o: ../../include/sys_defs.h +master_watch.o: master.h +master_watch.o: master_watch.c +multi_server.o: ../../include/argv.h +multi_server.o: ../../include/attr.h +multi_server.o: ../../include/bounce.h +multi_server.o: ../../include/check_arg.h +multi_server.o: ../../include/chroot_uid.h +multi_server.o: ../../include/debug_process.h +multi_server.o: ../../include/deliver_request.h +multi_server.o: ../../include/dict.h +multi_server.o: ../../include/dsn.h +multi_server.o: ../../include/dsn_buf.h +multi_server.o: ../../include/events.h +multi_server.o: ../../include/htable.h +multi_server.o: ../../include/iostuff.h +multi_server.o: ../../include/listen.h +multi_server.o: ../../include/mail_conf.h +multi_server.o: ../../include/mail_dict.h +multi_server.o: ../../include/mail_params.h +multi_server.o: ../../include/mail_task.h +multi_server.o: ../../include/mail_version.h +multi_server.o: ../../include/maillog_client.h +multi_server.o: ../../include/msg.h +multi_server.o: ../../include/msg_stats.h +multi_server.o: ../../include/msg_vstream.h +multi_server.o: ../../include/myflock.h +multi_server.o: ../../include/mymalloc.h +multi_server.o: ../../include/nvtable.h +multi_server.o: ../../include/recipient_list.h +multi_server.o: ../../include/resolve_local.h +multi_server.o: ../../include/safe_open.h +multi_server.o: ../../include/sane_accept.h +multi_server.o: ../../include/split_at.h +multi_server.o: ../../include/stringops.h +multi_server.o: ../../include/sys_defs.h +multi_server.o: ../../include/timed_ipc.h +multi_server.o: ../../include/vbuf.h +multi_server.o: ../../include/vstream.h +multi_server.o: ../../include/vstring.h +multi_server.o: ../../include/watchdog.h +multi_server.o: mail_flow.h +multi_server.o: mail_server.h +multi_server.o: master_proto.h +multi_server.o: multi_server.c +single_server.o: ../../include/argv.h +single_server.o: ../../include/attr.h +single_server.o: ../../include/bounce.h +single_server.o: ../../include/check_arg.h +single_server.o: ../../include/chroot_uid.h +single_server.o: ../../include/debug_process.h +single_server.o: ../../include/deliver_request.h +single_server.o: ../../include/dict.h +single_server.o: ../../include/dsn.h +single_server.o: ../../include/dsn_buf.h +single_server.o: ../../include/events.h +single_server.o: ../../include/htable.h +single_server.o: ../../include/iostuff.h +single_server.o: ../../include/listen.h +single_server.o: ../../include/mail_conf.h +single_server.o: ../../include/mail_dict.h +single_server.o: ../../include/mail_params.h +single_server.o: ../../include/mail_task.h +single_server.o: ../../include/mail_version.h +single_server.o: ../../include/maillog_client.h +single_server.o: ../../include/msg.h +single_server.o: ../../include/msg_stats.h +single_server.o: ../../include/msg_vstream.h +single_server.o: ../../include/myflock.h +single_server.o: ../../include/mymalloc.h +single_server.o: ../../include/nvtable.h +single_server.o: ../../include/recipient_list.h +single_server.o: ../../include/resolve_local.h +single_server.o: ../../include/safe_open.h +single_server.o: ../../include/sane_accept.h +single_server.o: ../../include/split_at.h +single_server.o: ../../include/stringops.h +single_server.o: ../../include/sys_defs.h +single_server.o: ../../include/timed_ipc.h +single_server.o: ../../include/vbuf.h +single_server.o: ../../include/vstream.h +single_server.o: ../../include/vstring.h +single_server.o: ../../include/watchdog.h +single_server.o: mail_flow.h +single_server.o: mail_server.h +single_server.o: master_proto.h +single_server.o: single_server.c +trigger_server.o: ../../include/argv.h +trigger_server.o: ../../include/attr.h +trigger_server.o: ../../include/bounce.h +trigger_server.o: ../../include/check_arg.h +trigger_server.o: ../../include/chroot_uid.h +trigger_server.o: ../../include/debug_process.h +trigger_server.o: ../../include/deliver_request.h +trigger_server.o: ../../include/dict.h +trigger_server.o: ../../include/dsn.h +trigger_server.o: ../../include/dsn_buf.h +trigger_server.o: ../../include/events.h +trigger_server.o: ../../include/htable.h +trigger_server.o: ../../include/iostuff.h +trigger_server.o: ../../include/listen.h +trigger_server.o: ../../include/mail_conf.h +trigger_server.o: ../../include/mail_dict.h +trigger_server.o: ../../include/mail_params.h +trigger_server.o: ../../include/mail_task.h +trigger_server.o: ../../include/mail_version.h +trigger_server.o: ../../include/maillog_client.h +trigger_server.o: ../../include/msg.h +trigger_server.o: ../../include/msg_stats.h +trigger_server.o: ../../include/msg_vstream.h +trigger_server.o: ../../include/myflock.h +trigger_server.o: ../../include/mymalloc.h +trigger_server.o: ../../include/nvtable.h +trigger_server.o: ../../include/recipient_list.h +trigger_server.o: ../../include/resolve_local.h +trigger_server.o: ../../include/safe_open.h +trigger_server.o: ../../include/sane_accept.h +trigger_server.o: ../../include/split_at.h +trigger_server.o: ../../include/stringops.h +trigger_server.o: ../../include/sys_defs.h +trigger_server.o: ../../include/vbuf.h +trigger_server.o: ../../include/vstream.h +trigger_server.o: ../../include/vstring.h +trigger_server.o: ../../include/watchdog.h +trigger_server.o: mail_flow.h +trigger_server.o: mail_server.h +trigger_server.o: master_proto.h +trigger_server.o: trigger_server.c diff --git a/src/master/dgram_server.c b/src/master/dgram_server.c new file mode 100644 index 0000000..e49500e --- /dev/null +++ b/src/master/dgram_server.c @@ -0,0 +1,665 @@ +/*++ +/* NAME +/* dgram_server 3 +/* SUMMARY +/* skeleton datagram server subsystem +/* SYNOPSIS +/* #include +/* +/* NORETURN dgram_server_main(argc, argv, service, key, value, ...) +/* int argc; +/* char **argv; +/* void (*service)(char *buf, int len, char *service_name, char **argv); +/* int key; +/* DESCRIPTION +/* This module implements a skeleton for mail subsystem programs +/* that wake up on client request and perform some activity +/* without further client interaction. This module supports +/* local IPC via a UNIX-domain datagram socket. The resulting +/* program expects to be run from the \fBmaster\fR process. +/* +/* dgram_server_main() is the skeleton entry point. It should +/* be called from the application main program. The skeleton +/* does the generic command-line options processing, initialization +/* of configurable parameters, and receiving datagrams. The +/* skeleton never returns. +/* +/* Arguments: +/* .IP "void (*service)(char *buf, int len, char *service_name, char **argv)" +/* A pointer to a function that is called by the skeleton each +/* time a client sends a datagram to the program's service +/* port. The function is run after the program has irrevocably +/* dropped its privileges. The buffer argument specifies the +/* data read from the datagram port; this data corresponds to +/* request. The len argument specifies how much client data +/* is available. The maximal size of the buffer is specified +/* via the DGRAM_BUF_SIZE manifest constant. The service name +/* argument corresponds to the service name in the master.cf +/* file. The argv argument specifies command-line arguments +/* left over after options processing. +/* .PP +/* Optional arguments are specified as a null-terminated list +/* with macros that have zero or more arguments: +/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. Raw parameters are not subjected to $name +/* evaluation. +/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has read the global configuration file +/* and after it has processed command-line arguments, but before +/* the skeleton has optionally relinquished the process privileges. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has optionally relinquished the process +/* privileges, but before servicing client connection requests. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))" +/* A pointer to function that is executed from +/* within the event loop, whenever an I/O or timer event has happened, +/* or whenever nothing has happened for a specified amount of time. +/* The result value of the function specifies how long to wait until +/* the next event. Specify -1 to wait for "as long as it takes". +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))" +/* A pointer to function that is executed immediately before normal +/* process termination. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))" +/* Function to be executed prior to accepting a new request. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_IN_FLOW_DELAY(none)" +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. +/* .IP CA_MAIL_SERVER_SOLITARY +/* This service must be configured with process limit of 1. +/* .IP CA_MAIL_SERVER_UNLIMITED +/* This service must be configured with process limit of 0. +/* .IP CA_MAIL_SERVER_PRIVILEGED +/* This service must be configured as privileged. +/* .IP "CA_MAIL_SERVER_WATCHDOG(int *)" +/* Override the default 1000s watchdog timeout. The value is +/* used after command-line and main.cf file processing. +/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)" +/* Initialize the DSN filter for the bounce/defer service +/* clients with the specified map source and map names. +/* .PP +/* The var_use_limit variable limits the number of clients that +/* a server can service before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the client limit. +/* +/* The var_idle_limit variable limits the time that a service +/* receives no client connection requests before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the idle limit. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* SEE ALSO +/* master(8), master process +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif +#include + +/* Utility library. */ + +#include +#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 +#include + +/* Process manager. */ + +#include "master_proto.h" + +/* Application-specific */ + +#include "mail_server.h" + + /* + * Global state. + */ +static int use_count; + +static DGRAM_SERVER_FN dgram_server_service; +static char *dgram_server_name; +static char **dgram_server_argv; +static void (*dgram_server_accept) (int, void *); +static void (*dgram_server_onexit) (char *, char **); +static void (*dgram_server_pre_accept) (char *, char **); +static VSTREAM *dgram_server_lock; +static int dgram_server_in_flow_delay; +static unsigned dgram_server_generation; +static int dgram_server_watchdog = 1000; + +/* dgram_server_exit - normal termination */ + +static NORETURN dgram_server_exit(void) +{ + if (dgram_server_onexit) + dgram_server_onexit(dgram_server_name, dgram_server_argv); + exit(0); +} + +/* dgram_server_abort - terminate after abnormal master exit */ + +static void dgram_server_abort(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("master disconnect -- exiting"); + dgram_server_exit(); +} + +/* dgram_server_timeout - idle time exceeded */ + +static void dgram_server_timeout(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("idle timeout -- exiting"); + dgram_server_exit(); +} + +/* dgram_server_wakeup - wake up application */ + +static void dgram_server_wakeup(int fd) +{ + char buf[DGRAM_BUF_SIZE]; + ssize_t len; + + /* + * Commit suicide when the master process disconnected from us, after + * handling the client request. + */ + if (master_notify(var_pid, dgram_server_generation, MASTER_STAT_TAKEN) < 0) + /* void */ ; + if (dgram_server_in_flow_delay && mail_flow_get(1) < 0) + doze(var_in_flow_delay * 1000000); + if ((len = recv(fd, buf, sizeof(buf), 0)) >= 0) + dgram_server_service(buf, len, dgram_server_name, dgram_server_argv); + if (master_notify(var_pid, dgram_server_generation, MASTER_STAT_AVAIL) < 0) + dgram_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + if (var_idle_limit > 0) + event_request_timer(dgram_server_timeout, (void *) 0, var_idle_limit); + /* Avoid integer wrap-around in a persistent process. */ + if (use_count < INT_MAX) + use_count++; +} + +/* dgram_server_accept_unix - handle UNIX-domain socket event */ + +static void dgram_server_accept_unix(int unused_event, void *context) +{ + const char *myname = "dgram_server_accept"; + int listen_fd = CAST_ANY_PTR_TO_INT(context); + + if (dgram_server_lock != 0 + && myflock(vstream_fileno(dgram_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + + if (msg_verbose) + msg_info("%s: request arrived", myname); + + /* + * Read whatever the other side wrote. The socket is non-blocking so we + * won't get stuck when multiple processes wake up. + */ + if (dgram_server_pre_accept) + dgram_server_pre_accept(dgram_server_name, dgram_server_argv); + dgram_server_wakeup(listen_fd); +} + +/* dgram_server_main - the real main program */ + +NORETURN dgram_server_main(int argc, char **argv, DGRAM_SERVER_FN service,...) +{ + const char *myname = "dgram_server_main"; + char *root_dir = 0; + char *user_name = 0; + int debug_me = 0; + int daemon_mode = 1; + char *service_name = basename(argv[0]); + int delay; + int c; + int socket_count = 1; + int fd; + va_list ap; + MAIL_SERVER_INIT_FN pre_init = 0; + MAIL_SERVER_INIT_FN post_init = 0; + MAIL_SERVER_LOOP_FN loop = 0; + int key; + char *transport = 0; + char *lock_path; + VSTRING *why; + int alone = 0; + int zerolimit = 0; + WATCHDOG *watchdog; + char *oname_val; + char *oname; + char *oval; + const char *err; + char *generation; + int msg_vstream_needed = 0; + const char *dsn_filter_title; + const char **dsn_filter_maps; + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Don't die for frivolous reasons. + */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif + + /* + * May need this every now and then. + */ + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + + /* + * Initialize logging and exit handler. Do the syslog first, so that its + * initialization completes before we enter the optional chroot jail. + */ + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + if (msg_verbose) + msg_info("daemon started"); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Initialize from the configuration file. Allow command-line options to + * override compiled-in defaults or configured parameter values. + */ + mail_conf_suck(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; + + /* + * Pick up policy settings from master process. Shut up error messages to + * stderr, because no-one is going to see them. + */ + opterr = 0; + while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:t:uvVz")) > 0) { + switch (c) { + case 'c': + root_dir = "setme"; + break; + case 'd': + daemon_mode = 0; + break; + case 'D': + debug_me = 1; + break; + case 'i': + mail_conf_update(VAR_MAX_IDLE, optarg); + break; + case 'l': + alone = 1; + break; + case 'm': + mail_conf_update(VAR_MAX_USE, optarg); + break; + case 'n': + service_name = optarg; + break; + case 'o': + oname_val = mystrdup(optarg); + if ((err = split_nameval(oname_val, &oname, &oval)) != 0) + msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); + mail_conf_update(oname, oval); + myfree(oname_val); + break; + case 's': + if ((socket_count = atoi(optarg)) <= 0) + msg_fatal("invalid socket_count: %s", optarg); + break; + case 't': + transport = optarg; + break; + case 'u': + user_name = "setme"; + break; + case 'v': + msg_verbose++; + break; + case 'V': + if (++msg_vstream_needed == 1) + msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); + break; + case 'z': + zerolimit = 1; + break; + default: + msg_fatal("invalid option: %c", optopt); + break; + } + } + set_mail_conf_str(VAR_SERVNAME, service_name); + + /* + * Initialize generic parameters and re-initialize logging in case of a + * non-default program name or logging destination. + */ + mail_params_init(); + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Register higher-level dictionaries and initialize the support for + * dynamically-loaded dictionaries. + */ + mail_dict_init(); + + /* + * If not connected to stdin, stdin must not be a terminal. + */ + if (daemon_mode && isatty(STDIN_FILENO)) { + msg_vstream_init(var_procname, VSTREAM_ERR); + msg_fatal("do not run this command by hand"); + } + + /* + * Application-specific initialization. + */ + va_start(ap, service); + while ((key = va_arg(ap, int)) != 0) { + switch (key) { + case MAIL_SERVER_INT_TABLE: + get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); + break; + case MAIL_SERVER_LONG_TABLE: + get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); + break; + case MAIL_SERVER_STR_TABLE: + get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); + break; + case MAIL_SERVER_BOOL_TABLE: + get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); + break; + case MAIL_SERVER_TIME_TABLE: + get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); + break; + case MAIL_SERVER_RAW_TABLE: + get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); + break; + case MAIL_SERVER_NINT_TABLE: + get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); + break; + case MAIL_SERVER_NBOOL_TABLE: + get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); + break; + case MAIL_SERVER_PRE_INIT: + pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_POST_INIT: + post_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_LOOP: + loop = va_arg(ap, MAIL_SERVER_LOOP_FN); + break; + case MAIL_SERVER_EXIT: + dgram_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); + break; + case MAIL_SERVER_PRE_ACCEPT: + dgram_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); + break; + case MAIL_SERVER_IN_FLOW_DELAY: + dgram_server_in_flow_delay = 1; + break; + case MAIL_SERVER_SOLITARY: + if (!alone) + msg_fatal("service %s requires a process limit of 1", + service_name); + break; + case MAIL_SERVER_UNLIMITED: + if (!zerolimit) + msg_fatal("service %s requires a process limit of 0", + service_name); + break; + case MAIL_SERVER_PRIVILEGED: + if (user_name) + msg_fatal("service %s requires privileged operation", + service_name); + break; + case MAIL_SERVER_WATCHDOG: + dgram_server_watchdog = *va_arg(ap, int *); + break; + case MAIL_SERVER_BOUNCE_INIT: + dsn_filter_title = va_arg(ap, const char *); + dsn_filter_maps = va_arg(ap, const char **); + bounce_client_init(dsn_filter_title, *dsn_filter_maps); + break; + default: + msg_panic("%s: unknown argument type: %d", myname, key); + } + } + va_end(ap); + + if (root_dir) + root_dir = var_queue_dir; + if (user_name) + user_name = var_mail_owner; + + /* + * Can options be required? + */ + if (transport == 0) + msg_fatal("no transport type specified"); + else if (strcasecmp(transport, MASTER_XPORT_NAME_UXDG) == 0) + dgram_server_accept = dgram_server_accept_unix; + else + msg_fatal("unsupported transport type: %s", transport); + + /* + * Retrieve process generation from environment. + */ + if ((generation = getenv(MASTER_GEN_NAME)) != 0) { + if (!alldig(generation)) + msg_fatal("bad generation: %s", generation); + OCTAL_TO_UNSIGNED(dgram_server_generation, generation); + if (msg_verbose) + msg_info("process generation: %s (%o)", + generation, dgram_server_generation); + } + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Traditionally, BSD select() can't handle multiple processes selecting + * on the same socket, and wakes up every process in select(). See TCP/IP + * Illustrated volume 2 page 532. We avoid select() collisions with an + * external lock file. + */ + if (!alone) { + lock_path = concatenate(DEF_PID_DIR, "/", transport, + ".", service_name, (char *) 0); + why = vstring_alloc(1); + if ((dgram_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, + (struct stat *) 0, -1, -1, why)) == 0) + msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); + close_on_exec(vstream_fileno(dgram_server_lock), CLOSE_ON_EXEC); + myfree(lock_path); + vstring_free(why); + } + + /* + * Set up call-back info. + */ + dgram_server_service = service; + dgram_server_name = service_name; + dgram_server_argv = argv + optind; + + /* + * Run pre-jail initialization. + */ + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(\"%s\"): %m", var_queue_dir); + if (pre_init) + pre_init(dgram_server_name, dgram_server_argv); + + /* + * Optionally, restrict the damage that this process can do. + */ + resolve_local_init(); + tzset(); + chroot_uid(root_dir, user_name); + + /* + * Run post-jail initialization. + */ + if (post_init) + post_init(dgram_server_name, dgram_server_argv); + + /* + * Running as a semi-resident server. Service requests. Terminate when we + * have serviced a sufficient number of requests, when no-one has been + * talking to us for a configurable amount of time, or when the master + * process terminated abnormally. + */ + if (var_idle_limit > 0) + event_request_timer(dgram_server_timeout, (void *) 0, var_idle_limit); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_enable_read(fd, dgram_server_accept, CAST_INT_TO_VOID_PTR(fd)); + close_on_exec(fd, CLOSE_ON_EXEC); + } + event_enable_read(MASTER_STATUS_FD, dgram_server_abort, (void *) 0); + close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); + watchdog = watchdog_create(dgram_server_watchdog, + (WATCHDOG_FN) 0, (void *) 0); + + /* + * The event loop, at last. + */ + while (var_use_limit == 0 || use_count < var_use_limit) { + if (dgram_server_lock != 0) { + watchdog_stop(watchdog); + if (myflock(vstream_fileno(dgram_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("select lock: %m"); + } + watchdog_start(watchdog); + delay = loop ? loop(dgram_server_name, dgram_server_argv) : -1; + event_loop(delay); + } + dgram_server_exit(); +} diff --git a/src/master/event_server.c b/src/master/event_server.c new file mode 100644 index 0000000..9802bdf --- /dev/null +++ b/src/master/event_server.c @@ -0,0 +1,968 @@ +/*++ +/* NAME +/* event_server 3 +/* SUMMARY +/* skeleton multi-threaded mail subsystem +/* SYNOPSIS +/* #include +/* +/* NORETURN event_server_main(argc, argv, service, key, value, ...) +/* int argc; +/* char **argv; +/* void (*service)(VSTREAM *stream, char *service_name, char **argv); +/* int key; +/* +/* void event_server_disconnect(fd) +/* VSTREAM *stream; +/* +/* void event_server_drain() +/* DESCRIPTION +/* This module implements a skeleton for event-driven +/* mail subsystems: mail subsystem programs that service multiple +/* clients at the same time. The resulting program expects to be run +/* from the \fBmaster\fR process. +/* +/* event_server_main() is the skeleton entry point. It should be +/* called from the application main program. The skeleton does all +/* the generic command-line processing, initialization of +/* configurable parameters, and connection management. +/* Unlike multi_server, this skeleton does not attempt to manage +/* all the events on a client connection. +/* The skeleton never returns. +/* +/* Arguments: +/* .IP "void (*service)(VSTREAM *stream, char *service_name, char **argv)" +/* A pointer to a function that is called by the skeleton each +/* time a client connects to the program's service port. The +/* function is run after the program has optionally dropped +/* its privileges. The application is responsible for managing +/* subsequent I/O events on the stream, and is responsible for +/* calling event_server_disconnect() when the stream is closed. +/* The stream initial state is non-blocking mode. +/* Optional connection attributes are provided as a hash that +/* is attached as stream context. NOTE: the attributes are +/* destroyed after this function is called. The service +/* name argument corresponds to the service name in the master.cf +/* file. The argv argument specifies command-line arguments +/* left over after options processing. +/* .PP +/* Optional arguments are specified as a null-terminated list +/* with macros that have zero or more arguments: +/* .IP "CA_MAIL_SERVER_REQ_INT_TABLE(CONFIG_INT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_LONG_TABLE(CONFIG_LONG_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_STR_TABLE(CONFIG_STR_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_BOOL_TABLE(CONFIG_BOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_TIME_TABLE(CONFIG_TIME_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_RAW_TABLE(CONFIG_RAW_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. Raw parameters are not subjected to $name +/* evaluation. +/* .IP "CA_MAIL_SERVER_REQ_NINT_TABLE(CONFIG_NINT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_REQ_PRE_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has read the global configuration file +/* and after it has processed command-line arguments, but before +/* the skeleton has optionally relinquished the process privileges. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_REQ_POST_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has optionally relinquished the process +/* privileges, but before servicing client connection requests. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_REQ_LOOP(int *(char *service_name, char **argv))" +/* A pointer to function that is executed from +/* within the event loop, whenever an I/O or timer event has happened, +/* or whenever nothing has happened for a specified amount of time. +/* The result value of the function specifies how long to wait until +/* the next event. Specify -1 to wait for "as long as it takes". +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))" +/* A pointer to function that is executed immediately before normal +/* process termination. +/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))" +/* Function to be executed prior to accepting a new connection. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_PRE_DISCONN(VSTREAM *, char *service_name, char **argv)" +/* A pointer to a function that is called +/* by the event_server_disconnect() function (see below). +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP CA_MAIL_SERVER_IN_FLOW_DELAY +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. +/* .IP CA_MAIL_SERVER_SOLITARY +/* This service must be configured with process limit of 1. +/* .IP CA_MAIL_SERVER_UNLIMITED +/* This service must be configured with process limit of 0. +/* .IP CA_MAIL_SERVER_PRIVILEGED +/* This service must be configured as privileged. +/* .IP "CA_MAIL_SERVER_SLOW_EXIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called after "postfix reload" +/* or "master exit". The application can call event_server_drain() +/* (see below) to finish ongoing activities in the background. +/* .IP "CA_MAIL_SERVER_WATCHDOG(int *)" +/* Override the default 1000s watchdog timeout. The value is +/* used after command-line and main.cf file processing. +/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)" +/* Initialize the DSN filter for the bounce/defer service +/* clients with the specified map source and map names. +/* .IP "CA_MAIL_SERVER_RETIRE_ME" +/* Prevent a process from being reused indefinitely. After +/* (var_max_use * var_max_idle) seconds or some sane constant, +/* stop accepting new connections and terminate voluntarily +/* when the process becomes idle. +/* .PP +/* event_server_disconnect() should be called by the application +/* to close a client connection. +/* +/* event_server_drain() should be called when the application +/* no longer wishes to accept new client connections. Existing +/* clients are handled in a background process, and the process +/* terminates when the last client is disconnected. A non-zero +/* result means this call should be tried again later. +/* +/* The var_use_limit variable limits the number of clients +/* that a server can service before it commits suicide. This +/* value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the +/* client limit. +/* +/* The var_idle_limit variable limits the time that a service +/* receives no client connection requests before it commits +/* suicide. This value is taken from the global \fBmain.cf\fR +/* configuration file. Setting \fBvar_idle_limit\fR to zero +/* disables the idle limit. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* SEE ALSO +/* master(8), master process +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include /* select() */ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif +#include + +#ifdef USE_SYS_SELECT_H +#include /* select() */ +#endif + +/* Utility library. */ + +#include +#include +#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 +#include +#include + +/* Process manager. */ + +#include "master_proto.h" + +/* Application-specific */ + +#include "mail_server.h" + + /* + * Global state. + */ +static int client_count; +static int use_count; +static int socket_count = 1; + +static void (*event_server_service) (VSTREAM *, char *, char **); +static char *event_server_name; +static char **event_server_argv; +static void (*event_server_accept) (int, void *); +static void (*event_server_onexit) (char *, char **); +static void (*event_server_pre_accept) (char *, char **); +static VSTREAM *event_server_lock; +static int event_server_in_flow_delay; +static unsigned event_server_generation; +static void (*event_server_pre_disconn) (VSTREAM *, char *, char **); +static void (*event_server_slow_exit) (char *, char **); +static int event_server_watchdog = 1000; + +/* event_server_exit - normal termination */ + +static NORETURN event_server_exit(void) +{ + if (event_server_onexit) + event_server_onexit(event_server_name, event_server_argv); + exit(0); +} + +/* event_server_retire - retire when idle */ + +static void event_server_retire(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("time to retire -- %s", event_server_slow_exit ? + "draining" : "exiting"); + event_disable_readwrite(MASTER_STATUS_FD); + if (event_server_slow_exit) + event_server_slow_exit(event_server_name, event_server_argv); + else + event_server_exit(); +} + +/* event_server_abort - terminate after abnormal master exit */ + +static void event_server_abort(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("master disconnect -- %s", event_server_slow_exit ? + "draining" : "exiting"); + event_disable_readwrite(MASTER_STATUS_FD); + if (event_server_slow_exit) + event_server_slow_exit(event_server_name, event_server_argv); + else + event_server_exit(); +} + +/* event_server_timeout - idle time exceeded */ + +static void event_server_timeout(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("idle timeout -- exiting"); + event_server_exit(); +} + +/* event_server_drain - stop accepting new clients */ + +int event_server_drain(void) +{ + const char *myname = "event_server_drain"; + int fd; + + switch (fork()) { + /* Try again later. */ + case -1: + return (-1); + /* Finish existing clients in the background, then terminate. */ + case 0: + (void) msg_cleanup((MSG_CLEANUP_FN) 0); + event_fork(); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_disable_readwrite(fd); + (void) close(fd); + /* Play safe - don't reuse this file number. */ + if (DUP2(STDIN_FILENO, fd) < 0) + msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd); + } + var_use_limit = 1; + return (0); + /* Let the master start a new process. */ + default: + exit(0); + } +} + +/* event_server_disconnect - terminate client session */ + +void event_server_disconnect(VSTREAM *stream) +{ + if (msg_verbose) + msg_info("connection closed fd %d", vstream_fileno(stream)); + if (event_server_pre_disconn) + event_server_pre_disconn(stream, event_server_name, event_server_argv); + (void) vstream_fclose(stream); + client_count--; + /* Avoid integer wrap-around in a persistent process. */ + if (use_count < INT_MAX) + use_count++; + if (client_count == 0 && var_idle_limit > 0) + event_request_timer(event_server_timeout, (void *) 0, var_idle_limit); +} + +/* event_server_execute - in case (char *) != (struct *) */ + +static void event_server_execute(int unused_event, void *context) +{ + VSTREAM *stream = (VSTREAM *) context; + HTABLE *attr = (HTABLE *) vstream_context(stream); + + if (event_server_lock != 0 + && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + + /* + * Do bother the application when the client disconnected. Don't drop the + * already accepted client request after "postfix reload"; that would be + * rude. + */ + if (master_notify(var_pid, event_server_generation, MASTER_STAT_TAKEN) < 0) + /* void */ ; + event_server_service(stream, event_server_name, event_server_argv); + if (master_notify(var_pid, event_server_generation, MASTER_STAT_AVAIL) < 0) + event_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + if (attr) + htable_free(attr, myfree); +} + +/* event_server_wakeup - wake up application */ + +static void event_server_wakeup(int fd, HTABLE *attr) +{ + VSTREAM *stream; + char *tmp; + +#if defined(F_DUPFD) && (EVENTS_STYLE != EVENTS_STYLE_SELECT) +#ifndef THRESHOLD_FD_WORKAROUND +#define THRESHOLD_FD_WORKAROUND 128 +#endif + int new_fd; + + /* + * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely + * case of a multi-server with a thousand clients. + */ + if (fd < THRESHOLD_FD_WORKAROUND) { + if ((new_fd = fcntl(fd, F_DUPFD, THRESHOLD_FD_WORKAROUND)) < 0) + msg_fatal("fcntl F_DUPFD: %m"); + (void) close(fd); + fd = new_fd; + } +#endif + if (msg_verbose) + msg_info("connection established fd %d", fd); + non_blocking(fd, BLOCKING); + close_on_exec(fd, CLOSE_ON_EXEC); + client_count++; + stream = vstream_fdopen(fd, O_RDWR); + tmp = concatenate(event_server_name, " socket", (char *) 0); + vstream_control(stream, + CA_VSTREAM_CTL_PATH(tmp), + CA_VSTREAM_CTL_CONTEXT((void *) attr), + CA_VSTREAM_CTL_END); + myfree(tmp); + timed_ipc_setup(stream); + if (event_server_in_flow_delay && mail_flow_get(1) < 0) + event_request_timer(event_server_execute, (void *) stream, + var_in_flow_delay); + else + event_server_execute(0, (void *) stream); +} + +/* event_server_accept_local - accept client connection request */ + +static void event_server_accept_local(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(event_server_timeout, (void *) 0); + + if (event_server_pre_accept) + event_server_pre_accept(event_server_name, event_server_argv); + fd = LOCAL_ACCEPT(listen_fd); + if (event_server_lock != 0 + && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(event_server_timeout, (void *) 0, time_left); + return; + } + event_server_wakeup(fd, (HTABLE *) 0); +} + +#ifdef MASTER_XPORT_NAME_PASS + +/* event_server_accept_pass - accept descriptor */ + +static void event_server_accept_pass(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + HTABLE *attr = 0; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(event_server_timeout, (void *) 0); + + if (event_server_pre_accept) + event_server_pre_accept(event_server_name, event_server_argv); + fd = pass_accept_attr(listen_fd, &attr); + if (event_server_lock != 0 + && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(event_server_timeout, (void *) 0, time_left); + return; + } + event_server_wakeup(fd, attr); +} + +#endif + +/* event_server_accept_inet - accept client connection request */ + +static void event_server_accept_inet(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(event_server_timeout, (void *) 0); + + if (event_server_pre_accept) + event_server_pre_accept(event_server_name, event_server_argv); + fd = inet_accept(listen_fd); + if (event_server_lock != 0 + && myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(event_server_timeout, (void *) 0, time_left); + return; + } + event_server_wakeup(fd, (HTABLE *) 0); +} + +/* event_server_main - the real main program */ + +NORETURN event_server_main(int argc, char **argv, MULTI_SERVER_FN service,...) +{ + const char *myname = "event_server_main"; + VSTREAM *stream = 0; + char *root_dir = 0; + char *user_name = 0; + int debug_me = 0; + int daemon_mode = 1; + char *service_name = basename(argv[0]); + int delay; + int c; + int fd; + va_list ap; + MAIL_SERVER_INIT_FN pre_init = 0; + MAIL_SERVER_INIT_FN post_init = 0; + MAIL_SERVER_LOOP_FN loop = 0; + int key; + char *transport = 0; + +#if 0 + char *lock_path; + VSTRING *why; + +#endif + int alone = 0; + int zerolimit = 0; + WATCHDOG *watchdog; + char *oname_val; + char *oname; + char *oval; + const char *err; + char *generation; + int msg_vstream_needed = 0; + const char *dsn_filter_title; + const char **dsn_filter_maps; + int retire_me_from_flags = 0; + int retire_me = 0; + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Don't die for frivolous reasons. + */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif + + /* + * May need this every now and then. + */ + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + + /* + * Initialize logging and exit handler. Do the syslog first, so that its + * initialization completes before we enter the optional chroot jail. + */ + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + if (msg_verbose) + msg_info("daemon started"); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Initialize from the configuration file. Allow command-line options to + * override compiled-in defaults or configured parameter values. + */ + mail_conf_suck(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; + + /* + * Pick up policy settings from master process. Shut up error messages to + * stderr, because no-one is going to see them. + */ + opterr = 0; + while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:r:s:St:uvVz")) > 0) { + switch (c) { + case 'c': + root_dir = "setme"; + break; + case 'd': + daemon_mode = 0; + break; + case 'D': + debug_me = 1; + break; + case 'i': + mail_conf_update(VAR_MAX_IDLE, optarg); + break; + case 'l': + alone = 1; + break; + case 'm': + mail_conf_update(VAR_MAX_USE, optarg); + break; + case 'n': + service_name = optarg; + break; + case 'o': + oname_val = mystrdup(optarg); + if ((err = split_nameval(oname_val, &oname, &oval)) != 0) + msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); + mail_conf_update(oname, oval); + myfree(oname_val); + break; + case 'r': + if ((retire_me_from_flags = atoi(optarg)) <= 0) + msg_fatal("invalid retirement time: %s", optarg); + break; + case 's': + if ((socket_count = atoi(optarg)) <= 0) + msg_fatal("invalid socket_count: %s", optarg); + break; + case 'S': + stream = VSTREAM_IN; + break; + case 'u': + user_name = "setme"; + break; + case 't': + transport = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'V': + if (++msg_vstream_needed == 1) + msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); + break; + case 'z': + zerolimit = 1; + break; + default: + msg_fatal("invalid option: %c", optopt); + break; + } + } + set_mail_conf_str(VAR_SERVNAME, service_name); + + /* + * Initialize generic parameters and re-initialize logging in case of a + * non-default program name or logging destination. + */ + mail_params_init(); + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Register higher-level dictionaries and initialize the support for + * dynamically-loaded dictionaries. + */ + mail_dict_init(); + + /* + * If not connected to stdin, stdin must not be a terminal. + */ + if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { + msg_vstream_init(var_procname, VSTREAM_ERR); + msg_fatal("do not run this command by hand"); + } + + /* + * Application-specific initialization. + */ + va_start(ap, service); + while ((key = va_arg(ap, int)) != 0) { + switch (key) { + case MAIL_SERVER_INT_TABLE: + get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); + break; + case MAIL_SERVER_LONG_TABLE: + get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); + break; + case MAIL_SERVER_STR_TABLE: + get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); + break; + case MAIL_SERVER_BOOL_TABLE: + get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); + break; + case MAIL_SERVER_TIME_TABLE: + get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); + break; + case MAIL_SERVER_RAW_TABLE: + get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); + break; + case MAIL_SERVER_NINT_TABLE: + get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); + break; + case MAIL_SERVER_NBOOL_TABLE: + get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); + break; + case MAIL_SERVER_PRE_INIT: + pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_POST_INIT: + post_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_LOOP: + loop = va_arg(ap, MAIL_SERVER_LOOP_FN); + break; + case MAIL_SERVER_EXIT: + event_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); + break; + case MAIL_SERVER_PRE_ACCEPT: + event_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); + break; + case MAIL_SERVER_PRE_DISCONN: + event_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN); + break; + case MAIL_SERVER_IN_FLOW_DELAY: + event_server_in_flow_delay = 1; + break; + case MAIL_SERVER_SOLITARY: + if (stream == 0 && !alone) + msg_fatal("service %s requires a process limit of 1", + service_name); + break; + case MAIL_SERVER_UNLIMITED: + if (stream == 0 && !zerolimit) + msg_fatal("service %s requires a process limit of 0", + service_name); + break; + case MAIL_SERVER_PRIVILEGED: + if (user_name) + msg_fatal("service %s requires privileged operation", + service_name); + break; + case MAIL_SERVER_WATCHDOG: + event_server_watchdog = *va_arg(ap, int *); + break; + case MAIL_SERVER_SLOW_EXIT: + event_server_slow_exit = va_arg(ap, MAIL_SERVER_SLOW_EXIT_FN); + break; + case MAIL_SERVER_BOUNCE_INIT: + dsn_filter_title = va_arg(ap, const char *); + dsn_filter_maps = va_arg(ap, const char **); + bounce_client_init(dsn_filter_title, *dsn_filter_maps); + break; + case MAIL_SERVER_RETIRE_ME: + if (retire_me_from_flags > 0) + retire_me = retire_me_from_flags; + else if (var_idle_limit == 0 || var_use_limit == 0 + || var_idle_limit > 18000 / var_use_limit) + retire_me = 18000; + else + retire_me = var_idle_limit * var_use_limit; + break; + default: + msg_panic("%s: unknown argument type: %d", myname, key); + } + } + va_end(ap); + + if (root_dir) + root_dir = var_queue_dir; + if (user_name) + user_name = var_mail_owner; + + /* + * Can options be required? + */ + if (stream == 0) { + if (transport == 0) + msg_fatal("no transport type specified"); + if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0) + event_server_accept = event_server_accept_inet; + else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) + event_server_accept = event_server_accept_local; +#ifdef MASTER_XPORT_NAME_PASS + else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) + event_server_accept = event_server_accept_pass; +#endif + else + msg_fatal("unsupported transport type: %s", transport); + } + + /* + * Retrieve process generation from environment. + */ + if ((generation = getenv(MASTER_GEN_NAME)) != 0) { + if (!alldig(generation)) + msg_fatal("bad generation: %s", generation); + OCTAL_TO_UNSIGNED(event_server_generation, generation); + if (msg_verbose) + msg_info("process generation: %s (%o)", + generation, event_server_generation); + } + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Traditionally, BSD select() can't handle multiple processes selecting + * on the same socket, and wakes up every process in select(). See TCP/IP + * Illustrated volume 2 page 532. We avoid select() collisions with an + * external lock file. + */ + + /* + * XXX Can't compete for exclusive access to the listen socket because we + * also have to monitor existing client connections for service requests. + */ +#if 0 + if (stream == 0 && !alone) { + lock_path = concatenate(DEF_PID_DIR, "/", transport, + ".", service_name, (char *) 0); + why = vstring_alloc(1); + if ((event_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, + (struct stat *) 0, -1, -1, why)) == 0) + msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); + close_on_exec(vstream_fileno(event_server_lock), CLOSE_ON_EXEC); + myfree(lock_path); + vstring_free(why); + } +#endif + + /* + * Set up call-back info. + */ + event_server_service = service; + event_server_name = service_name; + event_server_argv = argv + optind; + + /* + * Run pre-jail initialization. + */ + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(\"%s\"): %m", var_queue_dir); + if (pre_init) + pre_init(event_server_name, event_server_argv); + + /* + * Optionally, restrict the damage that this process can do. + */ + resolve_local_init(); + tzset(); + chroot_uid(root_dir, user_name); + + /* + * Run post-jail initialization. + */ + if (post_init) + post_init(event_server_name, event_server_argv); + + /* + * Are we running as a one-shot server with the client connection on + * standard input? If so, make sure the output is written to stdout so as + * to satisfy common expectation. + */ + if (stream != 0) { + vstream_control(stream, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO), + CA_VSTREAM_CTL_END); + service(stream, event_server_name, event_server_argv); + vstream_fflush(stream); + event_server_exit(); + } + + /* + * Running as a semi-resident server. Service connection requests. + * Terminate when we have serviced a sufficient number of clients, when + * no-one has been talking to us for a configurable amount of time, or + * when the master process terminated abnormally. + */ + if (var_idle_limit > 0) + event_request_timer(event_server_timeout, (void *) 0, var_idle_limit); + if (retire_me) + event_request_timer(event_server_retire, (void *) 0, retire_me); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_enable_read(fd, event_server_accept, CAST_INT_TO_VOID_PTR(fd)); + close_on_exec(fd, CLOSE_ON_EXEC); + } + event_enable_read(MASTER_STATUS_FD, event_server_abort, (void *) 0); + close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); + watchdog = watchdog_create(event_server_watchdog, + (WATCHDOG_FN) 0, (void *) 0); + + /* + * The event loop, at last. + */ + while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) { + if (event_server_lock != 0) { + watchdog_stop(watchdog); + if (myflock(vstream_fileno(event_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("select lock: %m"); + } + watchdog_start(watchdog); + delay = loop ? loop(event_server_name, event_server_argv) : -1; + event_loop(delay); + } + event_server_exit(); +} diff --git a/src/master/mail_flow.c b/src/master/mail_flow.c new file mode 100644 index 0000000..2958500 --- /dev/null +++ b/src/master/mail_flow.c @@ -0,0 +1,142 @@ +/*++ +/* NAME +/* mail_flow 3 +/* SUMMARY +/* global mail flow control +/* SYNOPSIS +/* #include +/* +/* ssize_t mail_flow_get(count) +/* ssize_t count; +/* +/* ssize_t mail_flow_put(count) +/* ssize_t count; +/* +/* ssize_t mail_flow_count() +/* DESCRIPTION +/* This module implements a simple flow control mechanism that +/* is based on tokens that are consumed by mail receiving processes +/* and that are produced by mail sending processes. +/* +/* mail_flow_get() attempts to read specified number of tokens. The +/* result is > 0 for success, < 0 for failure. In the latter case, +/* the process is expected to slow down a little. +/* +/* mail_flow_put() produces the specified number of tokens. The +/* token producing process is expected to produce new tokens +/* whenever it falls idle and no more tokens are available. +/* +/* mail_flow_count() returns the number of available tokens. +/* BUGS +/* The producer needs to wake up periodically to ensure that +/* tokens are not lost due to leakage. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* Master library. */ + +#include + +#define BUFFER_SIZE 1024 + +/* mail_flow_get - read N tokens */ + +ssize_t mail_flow_get(ssize_t len) +{ + const char *myname = "mail_flow_get"; + char buf[BUFFER_SIZE]; + struct stat st; + ssize_t count; + ssize_t n = 0; + + /* + * Sanity check. + */ + if (len <= 0) + msg_panic("%s: bad length %ld", myname, (long) len); + + /* + * Silence some wild claims. + */ + if (fstat(MASTER_FLOW_WRITE, &st) < 0) + msg_fatal("fstat flow pipe write descriptor: %m"); + + /* + * Read and discard N bytes. XXX AIX read() can return 0 when an open + * pipe is empty. + */ + for (count = len; count > 0; count -= n) + if ((n = read(MASTER_FLOW_READ, buf, count > BUFFER_SIZE ? + BUFFER_SIZE : count)) <= 0) + return (-1); + if (msg_verbose) + msg_info("%s: %ld %ld", myname, (long) len, (long) (len - count)); + return (len - count); +} + +/* mail_flow_put - put N tokens */ + +ssize_t mail_flow_put(ssize_t len) +{ + const char *myname = "mail_flow_put"; + char buf[BUFFER_SIZE]; + ssize_t count; + ssize_t n = 0; + + /* + * Sanity check. + */ + if (len <= 0) + msg_panic("%s: bad length %ld", myname, (long) len); + + /* + * Write or discard N bytes. + */ + memset(buf, 0, len > BUFFER_SIZE ? BUFFER_SIZE : len); + + for (count = len; count > 0; count -= n) + if ((n = write(MASTER_FLOW_WRITE, buf, count > BUFFER_SIZE ? + BUFFER_SIZE : count)) < 0) + return (-1); + if (msg_verbose) + msg_info("%s: %ld %ld", myname, (long) len, (long) (len - count)); + return (len - count); +} + +/* mail_flow_count - return number of available tokens */ + +ssize_t mail_flow_count(void) +{ + const char *myname = "mail_flow_count"; + ssize_t count; + + if ((count = peekfd(MASTER_FLOW_READ)) < 0) + msg_warn("%s: %m", myname); + return (count); +} diff --git a/src/master/mail_flow.h b/src/master/mail_flow.h new file mode 100644 index 0000000..3f7f7bd --- /dev/null +++ b/src/master/mail_flow.h @@ -0,0 +1,32 @@ +#ifndef _MAIL_FLOW_H_INCLUDED_ +#define _MAIL_FLOW_H_INCLUDED_ + +/*++ +/* NAME +/* mail_flow 3h +/* SUMMARY +/* global mail flow control +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Functional interface. + */ +extern ssize_t mail_flow_get(ssize_t); +extern ssize_t mail_flow_put(ssize_t); +extern ssize_t mail_flow_count(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/master/mail_server.h b/src/master/mail_server.h new file mode 100644 index 0000000..93703da --- /dev/null +++ b/src/master/mail_server.h @@ -0,0 +1,155 @@ +/*++ +/* NAME +/* mail_server 3h +/* SUMMARY +/* skeleton servers +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * External interface. Tables are defined in mail_conf.h. + */ +#define MAIL_SERVER_INT_TABLE 1 +#define MAIL_SERVER_STR_TABLE 2 +#define MAIL_SERVER_BOOL_TABLE 3 +#define MAIL_SERVER_TIME_TABLE 4 +#define MAIL_SERVER_RAW_TABLE 5 +#define MAIL_SERVER_NINT_TABLE 6 +#define MAIL_SERVER_NBOOL_TABLE 7 +#define MAIL_SERVER_LONG_TABLE 8 + +#define MAIL_SERVER_PRE_INIT 10 +#define MAIL_SERVER_POST_INIT 11 +#define MAIL_SERVER_LOOP 12 +#define MAIL_SERVER_EXIT 13 +#define MAIL_SERVER_PRE_ACCEPT 14 +#define MAIL_SERVER_SOLITARY 15 +#define MAIL_SERVER_UNLIMITED 16 +#define MAIL_SERVER_PRE_DISCONN 17 +#define MAIL_SERVER_PRIVILEGED 18 +#define MAIL_SERVER_WATCHDOG 19 + +#define MAIL_SERVER_IN_FLOW_DELAY 20 +#define MAIL_SERVER_SLOW_EXIT 21 +#define MAIL_SERVER_BOUNCE_INIT 22 +#define MAIL_SERVER_RETIRE_ME 23 +#define MAIL_SERVER_POST_ACCEPT 24 + +typedef void (*MAIL_SERVER_INIT_FN) (char *, char **); +typedef int (*MAIL_SERVER_LOOP_FN) (char *, char **); +typedef void (*MAIL_SERVER_EXIT_FN) (char *, char **); +typedef void (*MAIL_SERVER_ACCEPT_FN) (char *, char **); +typedef void (*MAIL_SERVER_POST_ACCEPT_FN) (VSTREAM *, char *, char **, HTABLE *); +typedef void (*MAIL_SERVER_DISCONN_FN) (VSTREAM *, char *, char **); +typedef void (*MAIL_SERVER_SLOW_EXIT_FN) (char *, char **); + +/* Type-checked API for external use. */ +#define CA_MAIL_SERVER_INT_TABLE(v) MAIL_SERVER_INT_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_INT_TABLE, (v)) +#define CA_MAIL_SERVER_STR_TABLE(v) MAIL_SERVER_STR_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_STR_TABLE, (v)) +#define CA_MAIL_SERVER_BOOL_TABLE(v) MAIL_SERVER_BOOL_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_BOOL_TABLE, (v)) +#define CA_MAIL_SERVER_TIME_TABLE(v) MAIL_SERVER_TIME_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_TIME_TABLE, (v)) +#define CA_MAIL_SERVER_RAW_TABLE(v) MAIL_SERVER_RAW_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_RAW_TABLE, (v)) +#define CA_MAIL_SERVER_NINT_TABLE(v) MAIL_SERVER_NINT_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_NINT_TABLE, (v)) +#define CA_MAIL_SERVER_NBOOL_TABLE(v) MAIL_SERVER_NBOOL_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_NBOOL_TABLE, (v)) +#define CA_MAIL_SERVER_LONG_TABLE(v) MAIL_SERVER_LONG_TABLE, CHECK_CPTR(MAIL_SERVER, CONFIG_LONG_TABLE, (v)) +#define CA_MAIL_SERVER_PRE_INIT(v) MAIL_SERVER_PRE_INIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_INIT_FN, (v)) +#define CA_MAIL_SERVER_POST_INIT(v) MAIL_SERVER_POST_INIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_INIT_FN, (v)) +#define CA_MAIL_SERVER_LOOP(v) MAIL_SERVER_LOOP, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_LOOP_FN, (v)) +#define CA_MAIL_SERVER_EXIT(v) MAIL_SERVER_EXIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_EXIT_FN, (v)) +#define CA_MAIL_SERVER_PRE_ACCEPT(v) MAIL_SERVER_PRE_ACCEPT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_ACCEPT_FN, (v)) +#define CA_MAIL_SERVER_POST_ACCEPT(v) MAIL_SERVER_POST_ACCEPT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_POST_ACCEPT_FN, (v)) +#define CA_MAIL_SERVER_SOLITARY MAIL_SERVER_SOLITARY +#define CA_MAIL_SERVER_UNLIMITED MAIL_SERVER_UNLIMITED +#define CA_MAIL_SERVER_PRE_DISCONN(v) MAIL_SERVER_PRE_DISCONN, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_DISCONN_FN, (v)) +#define CA_MAIL_SERVER_PRIVILEGED MAIL_SERVER_PRIVILEGED +#define CA_MAIL_SERVER_WATCHDOG(v) MAIL_SERVER_WATCHDOG, CHECK_PTR(MAIL_SERVER, int, (v)) +#define CA_MAIL_SERVER_IN_FLOW_DELAY MAIL_SERVER_IN_FLOW_DELAY +#define CA_MAIL_SERVER_SLOW_EXIT(v) MAIL_SERVER_SLOW_EXIT, CHECK_VAL(MAIL_SERVER, MAIL_SERVER_SLOW_EXIT_FN, (v)) +#define CA_MAIL_SERVER_BOUNCE_INIT(v, w) MAIL_SERVER_BOUNCE_INIT, CHECK_PTR(MAIL_SERVER, char, (v)), CHECK_PPTR(MAIL_SERVER, char, (w)) +#define CA_MAIL_SERVER_RETIRE_ME MAIL_SERVER_RETIRE_ME + +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_SLOW_EXIT_FN); +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_LOOP_FN); +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_INIT_FN); +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_EXIT_FN); +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_DISCONN_FN); +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_ACCEPT_FN); +CHECK_VAL_HELPER_DCL(MAIL_SERVER, MAIL_SERVER_POST_ACCEPT_FN); +CHECK_PTR_HELPER_DCL(MAIL_SERVER, int); +CHECK_PTR_HELPER_DCL(MAIL_SERVER, char); +CHECK_PPTR_HELPER_DCL(MAIL_SERVER, char); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_TIME_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_STR_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_RAW_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_NINT_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_NBOOL_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_LONG_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_INT_TABLE); +CHECK_CPTR_HELPER_DCL(MAIL_SERVER, CONFIG_BOOL_TABLE); + + /* + * single_server.c + */ +typedef void (*SINGLE_SERVER_FN) (VSTREAM *, char *, char **); +extern NORETURN single_server_main(int, char **, SINGLE_SERVER_FN,...); + + /* + * multi_server.c + */ +typedef void (*MULTI_SERVER_FN) (VSTREAM *, char *, char **); +extern NORETURN multi_server_main(int, char **, MULTI_SERVER_FN,...); +extern void multi_server_disconnect(VSTREAM *); +extern int multi_server_drain(void); + + /* + * event_server.c + */ +typedef void (*EVENT_SERVER_FN) (VSTREAM *, char *, char **); +extern NORETURN event_server_main(int, char **, EVENT_SERVER_FN,...); +extern void event_server_disconnect(VSTREAM *); +extern int event_server_drain(void); + + /* + * trigger_server.c + */ +typedef void (*TRIGGER_SERVER_FN) (char *, ssize_t, char *, char **); +extern NORETURN trigger_server_main(int, char **, TRIGGER_SERVER_FN,...); + +#define TRIGGER_BUF_SIZE 1024 + + /* + * dgram_server.c + */ +typedef void (*DGRAM_SERVER_FN) (char *, ssize_t, char *, char **); +extern NORETURN dgram_server_main(int, char **, DGRAM_SERVER_FN,...); + +#define DGRAM_BUF_SIZE 4096 + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/master/master.c b/src/master/master.c new file mode 100644 index 0000000..1fc3fe9 --- /dev/null +++ b/src/master/master.c @@ -0,0 +1,598 @@ +/*++ +/* NAME +/* master 8 +/* SUMMARY +/* Postfix master process +/* SYNOPSIS +/* \fBmaster\fR [\fB-Dditvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-e \fIexit_time\fR] +/* DESCRIPTION +/* The \fBmaster\fR(8) daemon is the resident process that runs Postfix +/* daemons on demand: daemons to send or receive messages via the +/* network, daemons to deliver mail locally, etc. These daemons are +/* created on demand up to a configurable maximum number per service. +/* +/* Postfix daemons terminate voluntarily, either after being idle for +/* a configurable amount of time, or after having serviced a +/* configurable number of requests. Exceptions to this rule are the +/* resident queue manager, address verification server, and the TLS +/* session cache and pseudo-random number server. +/* +/* The behavior of the \fBmaster\fR(8) daemon is controlled by the +/* \fBmaster.cf\fR configuration file, as described in \fBmaster\fR(5). +/* +/* Options: +/* .IP "\fB-c \fIconfig_dir\fR" +/* Read the \fBmain.cf\fR and \fBmaster.cf\fR configuration files in +/* the named directory instead of the default configuration directory. +/* This also overrides the configuration files for other Postfix +/* daemon processes. +/* .IP \fB-D\fR +/* After initialization, run a debugger on the master process. The +/* debugging command is specified with the \fBdebugger_command\fR in +/* the \fBmain.cf\fR global configuration file. +/* .IP \fB-d\fR +/* Do not redirect stdin, stdout or stderr to /dev/null, and +/* do not discard the controlling terminal. This must be used +/* for debugging only. +/* .IP "\fB-e \fIexit_time\fR" +/* Terminate the master process after \fIexit_time\fR seconds. Child +/* processes terminate at their convenience. +/* .IP \fB-i\fR +/* Enable \fBinit\fR mode: do not become a session or process +/* group leader; and similar to \fB-s\fR, do not redirect stdout +/* to /dev/null, so that "maillog_file = /dev/stdout" works. +/* This mode is allowed only if the process ID equals 1. +/* .sp +/* This feature is available in Postfix 3.3 and later. +/* .IP \fB-s\fR +/* Do not redirect stdout to /dev/null, so that "maillog_file +/* = /dev/stdout" works. +/* .sp +/* This feature is available in Postfix 3.4 and later. +/* .IP \fB-t\fR +/* Test mode. Return a zero exit status when the \fBmaster.pid\fR lock +/* file does not exist or when that file is not locked. This is evidence +/* that the \fBmaster\fR(8) daemon is not running. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. This option +/* is passed on to child processes. Multiple \fB-v\fR options +/* make the software increasingly verbose. +/* .IP \fB-w\fR +/* Wait in a dummy foreground process, while the real master +/* daemon initializes in a background process. The dummy +/* foreground process returns a zero exit status only if the +/* master daemon initialization is successful, and if it +/* completes in a reasonable amount of time. +/* .sp +/* This feature is available in Postfix 2.10 and later. +/* .PP +/* Signals: +/* .IP \fBSIGHUP\fR +/* Upon receipt of a \fBHUP\fR signal (e.g., after "\fBpostfix reload\fR"), +/* the master process re-reads its configuration files. If a service has +/* been removed from the \fBmaster.cf\fR file, its running processes +/* are terminated immediately. +/* Otherwise, running processes are allowed to terminate as soon +/* as is convenient, so that changes in configuration settings +/* affect only new service requests. +/* .IP \fBSIGTERM\fR +/* Upon receipt of a \fBTERM\fR signal (e.g., after "\fBpostfix abort\fR"), +/* the master process passes the signal on to its child processes and +/* terminates. +/* This is useful for an emergency shutdown. Normally one would +/* terminate only the master ("\fBpostfix stop\fR") and allow running +/* processes to finish what they are doing. +/* DIAGNOSTICS +/* Problems are reported to \fBsyslogd\fR(8) or \fBpostlogd\fR(8). +/* The exit status +/* is non-zero in case of problems, including problems while +/* initializing as a master daemon process in the background. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP \fBMAIL_DEBUG\fR +/* After initialization, start a debugger as specified with the +/* \fBdebugger_command\fR configuration parameter in the \fBmain.cf\fR +/* configuration file. +/* .IP \fBMAIL_CONFIG\fR +/* Directory with Postfix configuration files. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Unlike most Postfix daemon processes, the \fBmaster\fR(8) server does +/* not automatically pick up changes to \fBmain.cf\fR. Changes +/* to \fBmaster.cf\fR are never picked up automatically. +/* Use the "\fBpostfix reload\fR" command after a configuration change. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBdefault_process_limit (100)\fR" +/* The default maximal number of Postfix child processes that provide +/* a given service. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBservice_throttle_time (60s)\fR" +/* How long the Postfix \fBmaster\fR(8) waits before forking a server that +/* appears to be malfunctioning. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBmaster_service_disable (empty)\fR" +/* Selectively disable \fBmaster\fR(8) listener ports by service type +/* or by service name and type. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix support programs and daemon programs. +/* .IP "\fBdebugger_command (empty)\fR" +/* The external command to execute when a Postfix daemon program is +/* invoked with the -D option. +/* .IP "\fBinet_interfaces (all)\fR" +/* The network interface addresses that this mail system receives +/* mail on. +/* .IP "\fBinet_protocols (see 'postconf -d output')\fR" +/* The Internet protocols Postfix will attempt to use when making +/* or accepting connections. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment parameters that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBmail_owner (postfix)\fR" +/* The UNIX system account that owns the Postfix queue and most Postfix +/* daemon processes. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.6 and later: +/* .IP "\fBknown_tcp_ports (lmtp=24, smtp=25, smtps=submissions=465, submission=587)\fR" +/* Optional setting that avoids lookups in the \fBservices\fR(5) database. +/* FILES +/* .ad +/* .fi +/* To expand the directory names below into their actual values, +/* use the command "\fBpostconf config_directory\fR" etc. +/* .na +/* .nf +/* +/* $config_directory/main.cf, global configuration file. +/* $config_directory/master.cf, master server configuration file. +/* $queue_directory/pid/master.pid, master lock file. +/* $data_directory/master.lock, master lock file. +/* SEE ALSO +/* qmgr(8), queue manager +/* verify(8), address verification +/* master(5), master.cf configuration file syntax +/* postconf(5), main.cf configuration file syntax +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#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 + +/* Application-specific. */ + +#include "master.h" + +int master_detach = 1; +int init_mode = 0; + +/* master_exit_event - exit for memory leak testing purposes */ + +static void master_exit_event(int unused_event, void *unused_context) +{ + msg_info("master exit time has arrived"); + exit(0); +} + +/* usage - show hint and terminate */ + +static NORETURN usage(const char *me) +{ + msg_fatal("usage: %s [-c config_dir] [-D (debug)] [-d (don't detach from terminal)] [-e exit_time] [-t (test)] [-v] [-w (wait for initialization)]", me); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - main program */ + +int main(int argc, char **argv) +{ + static VSTREAM *lock_fp; + static VSTREAM *data_lock_fp; + VSTRING *lock_path; + VSTRING *data_lock_path; + off_t inherited_limit; + int debug_me = 0; + int keep_stdout = 0; + int ch; + int fd; + int n; + int test_lock = 0; + VSTRING *why; + WATCHDOG *watchdog; + ARGV *import_env; + int wait_flag = 0; + int monitor_fd = -1; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Initialize. + */ + umask(077); /* never fails! */ + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Strip and save the process name for diagnostics etc. + */ + var_procname = mystrdup(basename(argv[0])); + + /* + * When running a child process, don't leak any open files that were + * leaked to us by our own (privileged) parent process. Descriptors 0-2 + * are taken care of after we have initialized error logging. + * + * Some systems such as AIX have a huge per-process open file limit. In + * those cases, limit the search for potential file descriptor leaks to + * just the first couple hundred. + * + * The Debian post-installation script passes an open file descriptor into + * the master process and waits forever for someone to close it. Because + * of this we have to close descriptors > 2, and pray that doing so does + * not break things. + */ + closefrom(3); + + /* + * Initialize logging and exit handler. + */ + maillog_client_init(mail_task(var_procname), + MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * The mail system must be run by the superuser so it can revoke + * privileges for selected operations. That's right - it takes privileges + * to toss privileges. + */ + if (getuid() != 0) + msg_fatal("the master command is reserved for the superuser"); + if (unsafe() != 0) + msg_fatal("the master command must not run as a set-uid process"); + + /* + * Process JCL. + */ + while ((ch = GETOPT(argc, argv, "c:Dde:istvw")) > 0) { + switch (ch) { + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'd': + master_detach = 0; + break; + case 'e': + event_request_timer(master_exit_event, (void *) 0, atoi(optarg)); + break; + case 'i': + if (getpid() != 1) + msg_fatal("-i is allowed only for PID 1 process"); + init_mode = 1; + keep_stdout = 1; + break; + case 'D': + debug_me = 1; + break; + case 's': + keep_stdout = 1; + break; + case 't': + test_lock = 1; + break; + case 'v': + msg_verbose++; + break; + case 'w': + wait_flag = 1; + break; + default: + usage(argv[0]); + /* NOTREACHED */ + } + } + + /* + * This program takes no other arguments. + */ + if (argc > optind) + usage(argv[0]); + + /* + * Sanity check. + */ + if (test_lock && wait_flag) + msg_fatal("the -t and -w options cannot be used together"); + if (init_mode && (debug_me || !master_detach || wait_flag)) + msg_fatal("the -i option cannot be used with -D, -d, or -w"); + + /* + * Run a foreground monitor process that returns an exit status of 0 when + * the child background process reports successful initialization as a + * daemon process. We use a generous limit in case main/master.cf specify + * symbolic hosts/ports and the naming service is slow. + */ +#define MASTER_INIT_TIMEOUT 100 /* keep this limit generous */ + + if (wait_flag) + monitor_fd = master_monitor(MASTER_INIT_TIMEOUT); + + /* + * If started from a terminal, get rid of any tty association. This also + * means that all errors and warnings must go to the syslog daemon. + * Some new world has no terminals and prefers logging to stdout. + */ + if (master_detach) + for (fd = 0; fd < 3; fd++) { + if (fd == STDOUT_FILENO && keep_stdout) + continue; + (void) close(fd); + if (open("/dev/null", O_RDWR, 0) != fd) + msg_fatal("open /dev/null: %m"); + } + + /* + * Run in a separate process group, so that "postfix stop" can terminate + * all MTA processes cleanly. Give up if we can't separate from our + * parent process. We're not supposed to blow away the parent. + */ + if (init_mode == 0 && debug_me == 0 && master_detach != 0 + && setsid() == -1 && getsid(0) != getpid()) + msg_fatal("unable to set session and process group ID: %m"); + + /* + * Make some room for plumbing with file descriptors. XXX This breaks + * when a service listens on many ports. In order to do this right we + * must change the master-child interface so that descriptors do not need + * to have fixed numbers. + * + * In a child we need two descriptors for the flow control pipe, one for + * child->master status updates and at least one for listening. + */ + for (n = 0; n < 5; n++) { + if (close_on_exec(dup(0), CLOSE_ON_EXEC) < 0) + msg_fatal("dup(0): %m"); + } + + /* + * Final initializations. Unfortunately, we must read the global Postfix + * configuration file after doing command-line processing, so that we get + * consistent results when we SIGHUP the server to reload configuration + * files. + */ + master_vars_init(); + + /* + * In case of multi-protocol support. This needs to be done because + * master does not invoke mail_params_init() (it was written before that + * code existed). + */ + (void) inet_proto_init(VAR_INET_PROTOCOLS, var_inet_protocols); + + /* + * Environment import filter, to enforce consistent behavior whether + * Postfix is started by hand, or at system boot time. + */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + clean_env(import_env->argv); + argv_free(import_env); + + if ((inherited_limit = get_file_limit()) < 0) + set_file_limit(OFF_T_MAX); + + if (chdir(var_queue_dir)) + msg_fatal("chdir %s: %m", var_queue_dir); + + /* + * Lock down the master.pid file. In test mode, no file means that it + * isn't locked. + */ + lock_path = vstring_alloc(10); + data_lock_path = vstring_alloc(10); + why = vstring_alloc(10); + + vstring_sprintf(lock_path, "%s/%s.pid", DEF_PID_DIR, var_procname); + if (test_lock && access(vstring_str(lock_path), F_OK) < 0) + exit(0); + lock_fp = open_lock(vstring_str(lock_path), O_RDWR | O_CREAT, 0644, why); + if (test_lock) + exit(lock_fp ? 0 : 1); + if (lock_fp == 0) + msg_fatal("open lock file %s: %s", + vstring_str(lock_path), vstring_str(why)); + vstream_fprintf(lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4, + (unsigned long) var_pid); + if (vstream_fflush(lock_fp)) + msg_fatal("cannot update lock file %s: %m", vstring_str(lock_path)); + close_on_exec(vstream_fileno(lock_fp), CLOSE_ON_EXEC); + + /* + * Lock down the Postfix-writable data directory. + */ + vstring_sprintf(data_lock_path, "%s/%s.lock", var_data_dir, var_procname); + set_eugid(var_owner_uid, var_owner_gid); + data_lock_fp = + open_lock(vstring_str(data_lock_path), O_RDWR | O_CREAT, 0644, why); + set_ugid(getuid(), getgid()); + if (data_lock_fp == 0) + msg_fatal("open lock file %s: %s", + vstring_str(data_lock_path), vstring_str(why)); + vstream_fprintf(data_lock_fp, "%*lu\n", (int) sizeof(unsigned long) * 4, + (unsigned long) var_pid); + if (vstream_fflush(data_lock_fp)) + msg_fatal("cannot update lock file %s: %m", vstring_str(data_lock_path)); + close_on_exec(vstream_fileno(data_lock_fp), CLOSE_ON_EXEC); + + /* + * Clean up. + */ + vstring_free(why); + vstring_free(lock_path); + vstring_free(data_lock_path); + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Finish initialization, last part. We must process configuration files + * after processing command-line parameters, so that we get consistent + * results when we SIGHUP the server to reload configuration files. + */ + master_config(); + master_sigsetup(); + master_flow_init(); + maillog_client_init(mail_task(var_procname), + MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + msg_info("daemon started -- version %s, configuration %s", + var_mail_version, var_config_dir); + + /* + * Report successful initialization to the foreground monitor process. + */ + if (monitor_fd >= 0) { + write(monitor_fd, "", 1); + (void) close(monitor_fd); + } + + /* + * Process events. The event handler will execute the read/write/timer + * action routines. Whenever something has happened, see if we received + * any signal in the mean time. Although the master process appears to do + * multiple things at the same time, it really is all a single thread, so + * that there are no concurrency conflicts within the master process. + */ +#define MASTER_WATCHDOG_TIME 1000 + + watchdog = watchdog_create(MASTER_WATCHDOG_TIME, (WATCHDOG_FN) 0, (void *) 0); + for (;;) { +#ifdef HAS_VOLATILE_LOCKS + if (myflock(vstream_fileno(lock_fp), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("refresh exclusive lock: %m"); + if (myflock(vstream_fileno(data_lock_fp), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("refresh exclusive lock: %m"); +#endif + watchdog_start(watchdog); /* same as trigger servers */ + event_loop(MASTER_WATCHDOG_TIME / 2); + if (master_gotsighup) { + msg_info("reload -- version %s, configuration %s", + var_mail_version, var_config_dir); + master_gotsighup = 0; /* this first */ + master_vars_init(); /* then this */ + master_refresh(); /* then this */ + maillog_client_init(mail_task(var_procname), + MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + } + if (master_gotsigchld) { + if (msg_verbose) + msg_info("got sigchld"); + master_gotsigchld = 0; /* this first */ + master_reap_child(); /* then this */ + } + } +} diff --git a/src/master/master.h b/src/master/master.h new file mode 100644 index 0000000..ce07ab7 --- /dev/null +++ b/src/master/master.h @@ -0,0 +1,246 @@ +/*++ +/* NAME +/* master 3h +/* SUMMARY +/* Postfix master - data structures and prototypes +/* SYNOPSIS +/* #include "master.h" +/* DESCRIPTION +/* .nf + + /* + * Server processes that provide the same service share a common "listen" + * socket to accept connection requests, and share a common pipe to the + * master process to send status reports. Server processes die voluntarily + * when idle for a configurable amount of time, or after servicing a + * configurable number of requests; the master process spawns new processes + * on demand up to a configurable concurrency limit and/or periodically. + * + * The canonical service name is what we use internally, so that we correctly + * handle a request to "reload" after someone changes "smtp" into "25". + * + * We use the external service name from master.cf when reporting problems, so + * that the user can figure out what we are talking about. Of course we also + * include the canonical service name so that the UNIX-domain smtp service + * can be distinguished from the Internet smtp service. + */ +typedef struct MASTER_SERV { + int flags; /* status, features, etc. */ + char *ext_name; /* service endpoint name (master.cf) */ + char *name; /* service endpoint name (canonical) */ + int type; /* UNIX-domain, INET, etc. */ + time_t busy_warn_time; /* limit "all servers busy" warning */ + int wakeup_time; /* wakeup interval */ + int *listen_fd; /* incoming requests */ + int listen_fd_count; /* nr of descriptors */ + union { + struct { + char *port; /* inet listen port */ + struct INET_ADDR_LIST *addr;/* inet listen address */ + } inet_ep; +#define MASTER_INET_ADDRLIST(s) ((s)->endpoint.inet_ep.addr) +#define MASTER_INET_PORT(s) ((s)->endpoint.inet_ep.port) + } endpoint; + int max_proc; /* upper bound on # processes */ + char *path; /* command pathname */ + struct ARGV *args; /* argument vector */ + char *stress_param_val; /* stress value: "yes" or empty */ + time_t stress_expire_time; /* stress pulse stretcher */ + int avail_proc; /* idle processes */ + int total_proc; /* number of processes */ + int throttle_delay; /* failure recovery parameter */ + int status_fd[2]; /* child status reports */ + struct BINHASH *children; /* linkage */ + struct MASTER_SERV *next; /* linkage */ +} MASTER_SERV; + + /* + * Per-service flag bits. We assume trouble when a child process terminates + * before completing its first request: either the program is defective, + * some configuration is wrong, or the system is out of resources. + */ +#define MASTER_FLAG_THROTTLE (1<<0) /* we're having trouble */ +#define MASTER_FLAG_MARK (1<<1) /* garbage collection support */ +#define MASTER_FLAG_CONDWAKE (1<<2) /* wake up if actually used */ +#define MASTER_FLAG_INETHOST (1<<3) /* endpoint name specifies host */ +#define MASTER_FLAG_LOCAL_ONLY (1<<4) /* no remote clients */ +#define MASTER_FLAG_LISTEN (1<<5) /* monitor this port */ + +#define MASTER_THROTTLED(f) ((f)->flags & MASTER_FLAG_THROTTLE) +#define MASTER_MARKED_FOR_DELETION(f) ((f)->flags & MASTER_FLAG_MARK) +#define MASTER_LISTENING(f) ((f)->flags & MASTER_FLAG_LISTEN) + +#define MASTER_LIMIT_OK(limit, count) ((limit) == 0 || ((count) < (limit))) + + /* + * Service types, stream sockets unless indicated otherwise. + */ +#define MASTER_SERV_TYPE_UNIX 1 /* AF_UNIX domain socket */ +#define MASTER_SERV_TYPE_INET 2 /* AF_INET domain socket */ +#define MASTER_SERV_TYPE_FIFO 3 /* fifo (named pipe) */ +#define MASTER_SERV_TYPE_PASS 4 /* AF_UNIX domain socket */ +#define MASTER_SERV_TYPE_UXDG 5 /* AF_UNIX domain datagram socket */ + + /* + * Default process management policy values. This is only the bare minimum. + * Most policy management is delegated to child processes. The process + * manager runs at high privilege level and has to be kept simple. + */ +#define MASTER_DEF_MIN_IDLE 1 /* preferred # of idle processes */ + + /* + * Structure of child process. + */ +typedef int MASTER_PID; /* pid is key into binhash table */ + +typedef struct MASTER_PROC { + MASTER_PID pid; /* child process id */ + unsigned gen; /* child generation number */ + int avail; /* availability */ + MASTER_SERV *serv; /* parent linkage */ + int use_count; /* number of service requests */ +} MASTER_PROC; + + /* + * Other manifest constants. + */ +#define MASTER_BUF_LEN 2048 /* logical config line length */ + + /* + * master.c + */ +extern int master_detach; +extern int init_mode; + + /* + * master_ent.c + */ +extern void fset_master_ent(char *); +extern void set_master_ent(void); +extern void end_master_ent(void); +extern void print_master_ent(MASTER_SERV *); +extern MASTER_SERV *get_master_ent(void); +extern void free_master_ent(MASTER_SERV *); + + /* + * master_conf.c + */ +extern void master_config(void); +extern void master_refresh(void); + + /* + * master_vars.c + */ +extern void master_vars_init(void); + + /* + * master_service.c + */ +extern MASTER_SERV *master_head; +extern void master_start_service(MASTER_SERV *); +extern void master_stop_service(MASTER_SERV *); +extern void master_restart_service(MASTER_SERV *, int); + +#define DO_CONF_RELOAD 1 /* config files were reloaded */ +#define NO_CONF_RELOAD 0 /* no config file was reloaded */ + + /* + * master_events.c + */ +extern int master_gotsighup; +extern int master_gotsigchld; +extern void master_sigsetup(void); + + /* + * master_status.c + */ +extern void master_status_init(MASTER_SERV *); +extern void master_status_cleanup(MASTER_SERV *); + + /* + * master_wakeup.c + */ +extern void master_wakeup_init(MASTER_SERV *); +extern void master_wakeup_cleanup(MASTER_SERV *); + + + /* + * master_listen.c + */ +extern void master_listen_init(MASTER_SERV *); +extern void master_listen_cleanup(MASTER_SERV *); + + /* + * master_avail.c + */ +extern void master_avail_listen(MASTER_SERV *); +extern void master_avail_cleanup(MASTER_SERV *); +extern void master_avail_more(MASTER_SERV *, MASTER_PROC *); +extern void master_avail_less(MASTER_SERV *, MASTER_PROC *); + + /* + * master_spawn.c + */ +extern struct BINHASH *master_child_table; +extern void master_spawn(MASTER_SERV *); +extern void master_reap_child(void); +extern void master_delete_children(MASTER_SERV *); + + /* + * master_flow.c + */ +extern void master_flow_init(void); +extern int master_flow_pipe[2]; + + /* + * master_watch.c + * + * Support to warn about main.cf parameters that can only be initialized but + * not updated, and to initialize or update data structures that derive + * values from main.cf parameters. + */ +typedef struct { + const char *name; /* parameter name */ + char **value; /* current main.cf value */ + char **backup; /* actual value that is being used */ + int flags; /* see below */ + void (*notify) (void); /* init or update data structure */ +} MASTER_STR_WATCH; + +typedef struct { + const char *name; /* parameter name */ + int *value; /* current main.cf value */ + int backup; /* actual value that is being used */ + int flags; /* see below */ + void (*notify) (void); /* init or update data structure */ +} MASTER_INT_WATCH; + +#define MASTER_WATCH_FLAG_UPDATABLE (1<<0) /* support update after init */ +#define MASTER_WATCH_FLAG_ISSET (1<<1) /* backup is initialized */ + +extern void master_str_watch(const MASTER_STR_WATCH *); +extern void master_int_watch(MASTER_INT_WATCH *); + + /* + * master_monitor.c + */ +extern int master_monitor(int); + +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/master/master_avail.c b/src/master/master_avail.c new file mode 100644 index 0000000..046503d --- /dev/null +++ b/src/master/master_avail.c @@ -0,0 +1,251 @@ +/*++ +/* NAME +/* master_avail 3 +/* SUMMARY +/* Postfix master - process creation policy +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_avail_listen(serv) +/* MASTER_SERV *serv; +/* +/* void master_avail_cleanup(serv) +/* MASTER_SERV *serv; +/* +/* void master_avail_more(serv, proc) +/* MASTER_SERV *serv; +/* MASTER_PROC *proc; +/* +/* void master_avail_less(serv, proc) +/* MASTER_SERV *serv; +/* MASTER_PROC *proc; +/* DESCRIPTION +/* This module implements the process creation policy. As long as +/* the allowed number of processes for the given service is not +/* exceeded, a connection request is either handled by an existing +/* available process, or this module causes a new process to be +/* created to service the request. +/* +/* When the service runs out of process slots, and the service +/* is eligible for stress-mode operation, a warning is logged, +/* servers are asked to restart at their convenience, and new +/* servers are created with stress mode enabled. +/* +/* master_avail_listen() ensures that someone monitors the service's +/* listen socket for connection requests (as long as resources +/* to handle connection requests are available). This function may +/* be called at random times, but it must be called after each status +/* change of a service (throttled, process limit, etc.) or child +/* process (taken, available, dead, etc.). +/* +/* master_avail_cleanup() should be called when the named service +/* is taken out of operation. It terminates child processes by +/* sending SIGTERM. +/* +/* master_avail_more() should be called when the named process +/* has become available for servicing new connection requests. +/* This function updates the process availability status and +/* counter, and implicitly calls master_avail_listen(). +/* +/* master_avail_less() should be called when the named process +/* has become unavailable for servicing new connection requests. +/* This function updates the process availability status and +/* counter, and implicitly calls master_avail_listen(). +/* DIAGNOSTICS +/* Panic: internal inconsistencies. +/* BUGS +/* SEE ALSO +/* master_spawn(3), child process birth and death +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include "master_proto.h" +#include "master.h" + +/* master_avail_event - create child process to handle connection request */ + +static void master_avail_event(int event, void *context) +{ + MASTER_SERV *serv = (MASTER_SERV *) context; + time_t now; + + if (event == 0) /* XXX Can this happen? */ + msg_panic("master_avail_event: null event"); + else { + + /* + * When all servers for a public internet service are busy, we start + * creating server processes with "-o stress=yes" on the command + * line, and keep creating such processes until the process count is + * below the limit for at least 1000 seconds. This provides a minimal + * solution that can be adopted into legacy and stable Postfix + * releases. + * + * This is not the right place to update serv->stress_param_val in + * response to stress level changes. Doing so would would contaminate + * the "postfix reload" code with stress management implementation + * details, creating a source of future bugs. Instead, we update + * simple counters or flags here, and use their values to determine + * the proper serv->stress_param_val value when exec-ing a server + * process. + */ + if (serv->stress_param_val != 0 + && !MASTER_LIMIT_OK(serv->max_proc, serv->total_proc + 1)) { + now = event_time(); + if (serv->stress_expire_time < now) + master_restart_service(serv, NO_CONF_RELOAD); + serv->stress_expire_time = now + 1000; + } + master_spawn(serv); + } +} + +/* master_avail_listen - enforce the socket monitoring policy */ + +void master_avail_listen(MASTER_SERV *serv) +{ + const char *myname = "master_avail_listen"; + int listen_flag; + time_t now; + int n; + + /* + * Caution: several other master_XXX modules call master_avail_listen(), + * master_avail_more() or master_avail_less(). To avoid mutual dependency + * problems, the code below invokes no code in other master_XXX modules, + * and modifies no data that is maintained by other master_XXX modules. + * + * When no-one else is monitoring the service's listen socket, start + * monitoring the socket for connection requests. All this under the + * restriction that we have sufficient resources to service a connection + * request. + */ + if (msg_verbose) + msg_info("%s: %s avail %d total %d max %d", myname, serv->name, + serv->avail_proc, serv->total_proc, serv->max_proc); + if (MASTER_THROTTLED(serv) || serv->avail_proc > 0) { + listen_flag = 0; + } else if (MASTER_LIMIT_OK(serv->max_proc, serv->total_proc)) { + listen_flag = 1; + } else { + listen_flag = 0; + if (serv->stress_param_val != 0) { + now = event_time(); + if (serv->busy_warn_time < now - 1000) { + serv->busy_warn_time = now; + msg_warn("service \"%s\" (%s) has reached its process limit \"%d\": " + "new clients may experience noticeable delays", + serv->ext_name, serv->name, serv->max_proc); + msg_warn("to avoid this condition, increase the process count " + "in master.cf or reduce the service time per client"); + msg_warn("see http://www.postfix.org/STRESS_README.html for " + "examples of stress-adapting configuration settings"); + } + } + } + if (listen_flag && !MASTER_LISTENING(serv)) { + if (msg_verbose) + msg_info("%s: enable events %s", myname, serv->name); + for (n = 0; n < serv->listen_fd_count; n++) + event_enable_read(serv->listen_fd[n], master_avail_event, + (void *) serv); + serv->flags |= MASTER_FLAG_LISTEN; + } else if (!listen_flag && MASTER_LISTENING(serv)) { + if (msg_verbose) + msg_info("%s: disable events %s", myname, serv->name); + for (n = 0; n < serv->listen_fd_count; n++) + event_disable_readwrite(serv->listen_fd[n]); + serv->flags &= ~MASTER_FLAG_LISTEN; + } +} + +/* master_avail_cleanup - cleanup */ + +void master_avail_cleanup(MASTER_SERV *serv) +{ + int n; + + master_delete_children(serv); /* XXX calls + * master_avail_listen */ + + /* + * This code is redundant because master_delete_children() throttles the + * service temporarily before calling master_avail_listen/less(), which + * then turn off read events. This temporary throttling is not documented + * (it is only an optimization), and therefore we must not depend on it. + */ + if (MASTER_LISTENING(serv)) { + for (n = 0; n < serv->listen_fd_count; n++) + event_disable_readwrite(serv->listen_fd[n]); + serv->flags &= ~MASTER_FLAG_LISTEN; + } +} + +/* master_avail_more - one more available child process */ + +void master_avail_more(MASTER_SERV *serv, MASTER_PROC *proc) +{ + const char *myname = "master_avail_more"; + + /* + * Caution: several other master_XXX modules call master_avail_listen(), + * master_avail_more() or master_avail_less(). To avoid mutual dependency + * problems, the code below invokes no code in other master_XXX modules, + * and modifies no data that is maintained by other master_XXX modules. + * + * This child process has become available for servicing connection + * requests, so we can stop monitoring the service's listen socket. The + * child will do it for us. + */ + if (msg_verbose) + msg_info("%s: pid %d (%s)", myname, proc->pid, proc->serv->name); + if (proc->avail == MASTER_STAT_AVAIL) + msg_panic("%s: process already available", myname); + serv->avail_proc++; + proc->avail = MASTER_STAT_AVAIL; + master_avail_listen(serv); +} + +/* master_avail_less - one less available child process */ + +void master_avail_less(MASTER_SERV *serv, MASTER_PROC *proc) +{ + const char *myname = "master_avail_less"; + + /* + * Caution: several other master_XXX modules call master_avail_listen(), + * master_avail_more() or master_avail_less(). To avoid mutual dependency + * problems, the code below invokes no code in other master_XXX modules, + * and modifies no data that is maintained by other master_XXX modules. + * + * This child is no longer available for servicing connection requests. When + * no child processes are available, start monitoring the service's + * listen socket for new connection requests. + */ + if (msg_verbose) + msg_info("%s: pid %d (%s)", myname, proc->pid, proc->serv->name); + if (proc->avail != MASTER_STAT_AVAIL) + msg_panic("%s: process not available", myname); + serv->avail_proc--; + proc->avail = MASTER_STAT_TAKEN; + master_avail_listen(serv); +} diff --git a/src/master/master_conf.c b/src/master/master_conf.c new file mode 100644 index 0000000..37cad2a --- /dev/null +++ b/src/master/master_conf.c @@ -0,0 +1,152 @@ +/*++ +/* NAME +/* master_conf 3 +/* SUMMARY +/* Postfix master - master.cf file processing +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_config(serv) +/* MASTER_SERV *serv; +/* +/* void master_refresh(serv) +/* MASTER_SERV *serv; +/* DESCRIPTION +/* Use master_config() to read the master.cf configuration file +/* during program initialization. +/* +/* Use master_refresh() to re-read the master.cf configuration file +/* when the process is already running. +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* master_ent(3), configuration file programmatic interface. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include "master.h" + +/* master_refresh - re-read configuration table */ + +void master_refresh(void) +{ + MASTER_SERV *serv; + MASTER_SERV **servp; + + /* + * Mark all existing services. + */ + for (serv = master_head; serv != 0; serv = serv->next) + serv->flags |= MASTER_FLAG_MARK; + + /* + * Read the master.cf configuration file. The master_conf() routine + * unmarks services upon update. New services are born with the mark bit + * off. After this, anything with the mark bit on should be removed. + */ + master_config(); + + /* + * Delete all services that are still marked - they disappeared from the + * configuration file and are therefore no longer needed. + */ + for (servp = &master_head; (serv = *servp) != 0; /* void */ ) { + if ((serv->flags & MASTER_FLAG_MARK) != 0) { + *servp = serv->next; + master_stop_service(serv); + free_master_ent(serv); + } else { + servp = &serv->next; + } + } +} + +/* master_config - read config file */ + +void master_config(void) +{ + MASTER_SERV *entry; + MASTER_SERV *serv; + +#define STR_DIFF strcmp +#define STR_SAME !strcmp +#define SWAP(type,a,b) { type temp = a; a = b; b = temp; } + + /* + * A service is identified by its endpoint name AND by its transport + * type, not just by its name alone. The name is unique within its + * transport type. XXX Service privacy is encoded in the service name. + */ + set_master_ent(); + while ((entry = get_master_ent()) != 0) { + if (msg_verbose) + print_master_ent(entry); + for (serv = master_head; serv != 0; serv = serv->next) + if (STR_SAME(serv->name, entry->name) && serv->type == entry->type) + break; + + /* + * Add a new service entry. We do not really care in what order the + * service entries are kept in memory. + */ + if (serv == 0) { + entry->next = master_head; + master_head = entry; + master_start_service(entry); + } + + /* + * Update an existing service entry. Make the current generation of + * child processes commit suicide whenever it is convenient. The next + * generation of child processes will run with the new configuration + * settings. + */ + else { + if ((serv->flags & MASTER_FLAG_MARK) == 0) + msg_warn("duplicate master.cf entry for service \"%s\" (%s) " + "-- using the last entry", serv->ext_name, serv->name); + else + serv->flags &= ~MASTER_FLAG_MARK; + if (entry->flags & MASTER_FLAG_CONDWAKE) + serv->flags |= MASTER_FLAG_CONDWAKE; + else + serv->flags &= ~MASTER_FLAG_CONDWAKE; + serv->wakeup_time = entry->wakeup_time; + serv->max_proc = entry->max_proc; + serv->throttle_delay = entry->throttle_delay; + SWAP(char *, serv->ext_name, entry->ext_name); + SWAP(char *, serv->path, entry->path); + SWAP(ARGV *, serv->args, entry->args); + SWAP(char *, serv->stress_param_val, entry->stress_param_val); + master_restart_service(serv, DO_CONF_RELOAD); + free_master_ent(entry); + } + } + end_master_ent(); +} diff --git a/src/master/master_ent.c b/src/master/master_ent.c new file mode 100644 index 0000000..5edc308 --- /dev/null +++ b/src/master/master_ent.c @@ -0,0 +1,648 @@ +/*++ +/* NAME +/* master_ent 3 +/* SUMMARY +/* Postfix master - config file access +/* SYNOPSIS +/* #include "master.h" +/* +/* void fset_master_ent(path) +/* char *path; +/* +/* void set_master_ent() +/* +/* MASTER_SERV *get_master_ent() +/* +/* void end_master_ent() +/* +/* void print_master_ent(entry) +/* MASTER_SERV *entry; +/* +/* void free_master_ent(entry) +/* MASTER_SERV *entry; +/* DESCRIPTION +/* This module implements a simple programmatic interface +/* for accessing Postfix master process configuration files. +/* +/* fset_master_ent() specifies the location of the master process +/* configuration file. The pathname is copied. +/* +/* set_master_ent() opens the configuration file. It is an error +/* to call this routine while the configuration file is still open. +/* It is an error to open a configuration file without specifying +/* its name to fset_master_ent(). +/* +/* get_master_ent() reads the next entry from an open configuration +/* file and returns the parsed result. A null result means the end +/* of file was reached. +/* +/* print_master_ent() prints the specified service entry. +/* +/* end_master_ent() closes an open configuration file. It is an error +/* to call this routine when the configuration file is not open. +/* +/* free_master_ent() destroys the memory used for a parsed configuration +/* file entry. +/* DIAGNOSTICS +/* Panics: interface violations. Fatal errors: memory allocation +/* failure. +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility libraries. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Local stuff. */ + +#include "master_proto.h" +#include "master.h" + +static char *master_path; /* config file name */ +static VSTREAM *master_fp; /* config file pointer */ +static int master_line_last; /* config file line number */ +static int master_line; /* config file line number */ +static ARGV *master_disable; /* disabled service patterns */ + +static char master_blanks[] = CHARS_SPACE; /* field delimiters */ + +/* fset_master_ent - specify configuration file pathname */ + +void fset_master_ent(char *path) +{ + if (master_path != 0) + myfree(master_path); + master_path = mystrdup(path); +} + +/* set_master_ent - open configuration file */ + +void set_master_ent() +{ + const char *myname = "set_master_ent"; + char *disable; + + if (master_fp != 0) + msg_panic("%s: configuration file still open", myname); + if (master_path == 0) + msg_panic("%s: no configuration file specified", myname); + if ((master_fp = vstream_fopen(master_path, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", master_path); + master_line_last = 0; + if (master_disable != 0) + msg_panic("%s: service disable list still exists", myname); + if (inet_proto_info()->ai_family_list[0] == 0) { + msg_warn("all network protocols are disabled (%s = %s)", + VAR_INET_PROTOCOLS, var_inet_protocols); + msg_warn("disabling all type \"inet\" services in master.cf"); + disable = concatenate(MASTER_XPORT_NAME_INET, ",", + var_master_disable, (char *) 0); + master_disable = match_service_init(disable); + myfree(disable); + } else + master_disable = match_service_init(var_master_disable); +} + +/* end_master_ent - close configuration file */ + +void end_master_ent() +{ + const char *myname = "end_master_ent"; + + if (master_fp == 0) + msg_panic("%s: configuration file not open", myname); + if (vstream_fclose(master_fp) != 0) + msg_fatal("%s: close configuration file: %m", myname); + master_fp = 0; + if (master_disable == 0) + msg_panic("%s: no service disable list", myname); + match_service_free(master_disable); + master_disable = 0; +} + +/* master_conf_context - plot the target range */ + +static const char *master_conf_context(void) +{ + static VSTRING *context_buf = 0; + + if (context_buf == 0) + context_buf = vstring_alloc(100); + vstring_sprintf(context_buf, "%s: line %d", master_path, master_line); + return (vstring_str(context_buf)); +} + +/* fatal_with_context - print fatal error with file/line context */ + +static NORETURN PRINTFLIKE(1, 2) fatal_with_context(char *format,...) +{ + const char *myname = "fatal_with_context"; + VSTRING *vp = vstring_alloc(100); + va_list ap; + + if (master_path == 0) + msg_panic("%s: no configuration file specified", myname); + + va_start(ap, format); + vstring_vsprintf(vp, format, ap); + va_end(ap); + msg_fatal("%s: %s", master_conf_context(), vstring_str(vp)); +} + +/* fatal_invalid_field - report invalid field value */ + +static NORETURN fatal_invalid_field(char *name, char *value) +{ + fatal_with_context("field \"%s\": bad value: \"%s\"", name, value); +} + +/* get_str_ent - extract string field */ + +static char *get_str_ent(char **bufp, char *name, char *def_val) +{ + char *value; + + if ((value = mystrtok(bufp, master_blanks)) == 0) + fatal_with_context("missing \"%s\" field", name); + if (strcmp(value, "-") == 0) { + if (def_val == 0) + fatal_with_context("field \"%s\" has no default value", name); + if (warn_compat_break_chroot && strcmp(name, "chroot") == 0) + msg_info("%s: using backwards-compatible default setting " + "%s=%s", master_conf_context(), name, def_val); + return (def_val); + } else { + return (value); + } +} + +/* get_bool_ent - extract boolean field */ + +static int get_bool_ent(char **bufp, char *name, char *def_val) +{ + char *value; + + value = get_str_ent(bufp, name, def_val); + if (strcmp("y", value) == 0) { + return (1); + } else if (strcmp("n", value) == 0) { + return (0); + } else { + fatal_invalid_field(name, value); + } + /* NOTREACHED */ +} + +/* get_int_ent - extract integer field */ + +static int get_int_ent(char **bufp, char *name, char *def_val, int min_val) +{ + char *value; + int n; + + value = get_str_ent(bufp, name, def_val); + if (!ISDIGIT(*value) || (n = atoi(value)) < min_val) + fatal_invalid_field(name, value); + return (n); +} + +/* get_master_ent - read entry from configuration file */ + +MASTER_SERV *get_master_ent() +{ + VSTRING *buf = vstring_alloc(100); + VSTRING *junk = vstring_alloc(100); + MASTER_SERV *serv; + char *cp; + char *name; + char *host = 0; + char *port = 0; + char *transport; + int private; + int unprivileged; /* passed on to child */ + int chroot; /* passed on to child */ + char *command; + int n; + char *bufp; + char *atmp; + const char *parse_err; + static char *saved_interfaces = 0; + char *err; + + if (master_fp == 0) + msg_panic("get_master_ent: config file not open"); + if (master_disable == 0) + msg_panic("get_master_ent: no service disable list"); + + /* + * XXX We cannot change the inet_interfaces setting for a running master + * process. Listening sockets are inherited by child processes so that + * closing and reopening those sockets in the master does not work. + * + * Another problem is that library routines still cache results that are + * based on the old inet_interfaces setting. It is too much trouble to + * recompute everything. + * + * In order to keep our data structures consistent we ignore changes in + * inet_interfaces settings, and issue a warning instead. + */ + if (saved_interfaces == 0) + saved_interfaces = mystrdup(var_inet_interfaces); + + /* + * Skip blank lines and comment lines. + */ + for (;;) { + if (readllines(buf, master_fp, &master_line_last, &master_line) == 0) { + vstring_free(buf); + vstring_free(junk); + return (0); + } + bufp = vstring_str(buf); + if ((cp = mystrtok(&bufp, master_blanks)) == 0) + continue; + name = cp; + transport = get_str_ent(&bufp, "transport type", (char *) 0); + vstring_sprintf(junk, "%s/%s", name, transport); + if (match_service_match(master_disable, vstring_str(junk)) == 0) + break; + } + + /* + * Parse one logical line from the configuration file. Initialize service + * structure members in order. + */ + serv = (MASTER_SERV *) mymalloc(sizeof(MASTER_SERV)); + serv->next = 0; + + /* + * Flags member. + */ + serv->flags = 0; + + /* + * All servers busy warning timer. + */ + serv->busy_warn_time = 0; + + /* + * Service name. Syntax is transport-specific. + */ + serv->ext_name = mystrdup(name); + + /* + * Transport type: inet (wild-card listen or virtual) or unix. + */ +#define STR_SAME !strcmp + + if (STR_SAME(transport, MASTER_XPORT_NAME_INET)) { + if (!STR_SAME(saved_interfaces, var_inet_interfaces)) { + msg_warn("service %s: ignoring %s change", + serv->ext_name, VAR_INET_INTERFACES); + msg_warn("to change %s, stop and start Postfix", + VAR_INET_INTERFACES); + } + serv->type = MASTER_SERV_TYPE_INET; + atmp = mystrdup(name); + if ((parse_err = host_port(atmp, &host, "", &port, (char *) 0)) != 0) + fatal_with_context("%s in \"%s\"", parse_err, name); + if (*host) { + serv->flags |= MASTER_FLAG_INETHOST;/* host:port */ + MASTER_INET_ADDRLIST(serv) = (INET_ADDR_LIST *) + mymalloc(sizeof(*MASTER_INET_ADDRLIST(serv))); + inet_addr_list_init(MASTER_INET_ADDRLIST(serv)); + if (inet_addr_host(MASTER_INET_ADDRLIST(serv), host) == 0) + fatal_with_context("bad hostname or network address: %s", name); + inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv)); + serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used; + } else { + MASTER_INET_ADDRLIST(serv) = + strcasecmp(saved_interfaces, INET_INTERFACES_ALL) ? + own_inet_addr_list() : /* virtual */ + wildcard_inet_addr_list(); /* wild-card */ + inet_addr_list_uniq(MASTER_INET_ADDRLIST(serv)); + serv->listen_fd_count = MASTER_INET_ADDRLIST(serv)->used; + } + MASTER_INET_PORT(serv) = mystrdup(port); + for (n = 0; /* see below */ ; n++) { + if (n >= MASTER_INET_ADDRLIST(serv)->used) { + serv->flags |= MASTER_FLAG_LOCAL_ONLY; + break; + } + if (!sock_addr_in_loopback(SOCK_ADDR_PTR(MASTER_INET_ADDRLIST(serv)->addrs + n))) + break; + } + } else if (STR_SAME(transport, MASTER_XPORT_NAME_UNIX)) { + serv->type = MASTER_SERV_TYPE_UNIX; + serv->listen_fd_count = 1; + serv->flags |= MASTER_FLAG_LOCAL_ONLY; + } else if (STR_SAME(transport, MASTER_XPORT_NAME_UXDG)) { + serv->type = MASTER_SERV_TYPE_UXDG; + serv->listen_fd_count = 1; + serv->flags |= MASTER_FLAG_LOCAL_ONLY; + } else if (STR_SAME(transport, MASTER_XPORT_NAME_FIFO)) { + serv->type = MASTER_SERV_TYPE_FIFO; + serv->listen_fd_count = 1; + serv->flags |= MASTER_FLAG_LOCAL_ONLY; +#ifdef MASTER_SERV_TYPE_PASS + } else if (STR_SAME(transport, MASTER_XPORT_NAME_PASS)) { + serv->type = MASTER_SERV_TYPE_PASS; + serv->listen_fd_count = 1; + /* If this is a connection screener, remote clients are likely. */ +#endif + } else { + fatal_with_context("bad transport type: %s", transport); + } + + /* + * Service class: public or private. + */ + private = get_bool_ent(&bufp, "private", "y"); + + /* + * Derive an internal service name. The name may depend on service + * attributes such as privacy. + */ + if (serv->type == MASTER_SERV_TYPE_INET) { + MAI_HOSTADDR_STR host_addr; + MAI_SERVPORT_STR serv_port; + struct addrinfo *res0; + + if (private) + fatal_with_context("inet service cannot be private"); + + /* + * Canonicalize endpoint names so that we correctly handle "reload" + * requests after someone changes "25" into "smtp" or vice versa. + */ + if (*host == 0) + host = 0; + /* Canonicalize numeric host and numeric or symbolic service. */ + if (hostaddr_to_sockaddr(host, port, 0, &res0) == 0) { + SOCKADDR_TO_HOSTADDR(res0->ai_addr, res0->ai_addrlen, + host ? &host_addr : (MAI_HOSTADDR_STR *) 0, + &serv_port, 0); + serv->name = (host ? concatenate("[", host_addr.buf, "]:", + serv_port.buf, (char *) 0) : + mystrdup(serv_port.buf)); + freeaddrinfo(res0); + } + /* Canonicalize numeric or symbolic service. */ + else if (hostaddr_to_sockaddr((char *) 0, port, 0, &res0) == 0) { + SOCKADDR_TO_HOSTADDR(res0->ai_addr, res0->ai_addrlen, + (MAI_HOSTADDR_STR *) 0, &serv_port, 0); + serv->name = (host ? concatenate("[", host, "]:", + serv_port.buf, (char *) 0) : + mystrdup(serv_port.buf)); + freeaddrinfo(res0); + } + /* Bad service name? */ + else + serv->name = mystrdup(name); + myfree(atmp); + } else if (serv->type == MASTER_SERV_TYPE_UNIX) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); + } else if (serv->type == MASTER_SERV_TYPE_UXDG) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); + } else if (serv->type == MASTER_SERV_TYPE_FIFO) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); +#ifdef MASTER_SERV_TYPE_PASS + } else if (serv->type == MASTER_SERV_TYPE_PASS) { + serv->name = mail_pathname(private ? MAIL_CLASS_PRIVATE : + MAIL_CLASS_PUBLIC, name); +#endif + } else { + msg_panic("bad transport type: %d", serv->type); + } + + /* + * Listen socket(s). XXX We pre-allocate storage because the number of + * sockets is frozen anyway once we build the command-line vector below. + */ + if (serv->listen_fd_count == 0) { + fatal_with_context("no valid IP address found: %s", name); + } + serv->listen_fd = (int *) mymalloc(sizeof(int) * serv->listen_fd_count); + for (n = 0; n < serv->listen_fd_count; n++) + serv->listen_fd[n] = -1; + + /* + * Privilege level. Default is to restrict process privileges to those of + * the mail owner. + */ + unprivileged = get_bool_ent(&bufp, "unprivileged", "y"); + + /* + * Chroot. Default is to restrict file system access to the mail queue. + * XXX Chroot cannot imply unprivileged service (for example, the pickup + * service runs chrooted but needs privileges to open files as the user). + */ + chroot = get_bool_ent(&bufp, "chroot", compat_level + < compat_level_from_string(COMPAT_LEVEL_1, msg_panic) ? + "y" : "n"); + + /* + * Wakeup timer. XXX should we require that var_proc_limit == 1? Right + * now, the only services that have a wakeup timer also happen to be the + * services that have at most one running instance: local pickup and + * local delivery. + */ + serv->wakeup_time = get_int_ent(&bufp, "wakeup_time", "0", 0); + + /* + * Find out if the wakeup time is conditional, i.e., wakeup triggers + * should not be sent until the service has actually been used. + */ + if (serv->wakeup_time > 0 && bufp[*bufp ? -2 : -1] == '?') + serv->flags |= MASTER_FLAG_CONDWAKE; + + /* + * Concurrency limit. Zero means no limit. + */ + vstring_sprintf(junk, "%d", var_proc_limit); + serv->max_proc = get_int_ent(&bufp, "max_proc", vstring_str(junk), 0); + + /* + * Path to command, + */ + command = get_str_ent(&bufp, "command", (char *) 0); + serv->path = concatenate(var_daemon_dir, "/", command, (char *) 0); + + /* + * Idle and total process count. + */ + serv->avail_proc = 0; + serv->total_proc = 0; + + /* + * Backoff time in case a service is broken. + */ + serv->throttle_delay = var_throttle_time; + + /* + * Shared channel for child status updates. + */ + serv->status_fd[0] = serv->status_fd[1] = -1; + + /* + * Child process structures. + */ + serv->children = 0; + + /* + * Command-line vector. Add "-n service_name" when the process name + * basename differs from the service name. Always add the transport. + */ + serv->args = argv_alloc(0); + argv_add(serv->args, command, (char *) 0); + if (serv->max_proc == 1) + argv_add(serv->args, "-l", (char *) 0); + if (serv->max_proc == 0) + argv_add(serv->args, "-z", (char *) 0); + if (strcmp(basename(command), name) != 0) + argv_add(serv->args, "-n", name, (char *) 0); + argv_add(serv->args, "-t", transport, (char *) 0); + if (master_detach == 0) + argv_add(serv->args, "-d", (char *) 0); + if (msg_verbose) + argv_add(serv->args, "-v", (char *) 0); + if (unprivileged) + argv_add(serv->args, "-u", (char *) 0); + if (chroot) + argv_add(serv->args, "-c", (char *) 0); + if ((serv->flags & MASTER_FLAG_LOCAL_ONLY) == 0 && serv->max_proc > 1) { + argv_add(serv->args, "-o", "stress=" CONFIG_BOOL_YES, (char *) 0); + serv->stress_param_val = + serv->args->argv[serv->args->argc - 1] + sizeof("stress=") - 1; + serv->stress_param_val[0] = 0; + } else + serv->stress_param_val = 0; + serv->stress_expire_time = 0; + if (serv->listen_fd_count > 1) + argv_add(serv->args, "-s", + vstring_str(vstring_sprintf(junk, "%d", serv->listen_fd_count)), + (char *) 0); + while ((cp = mystrtokq(&bufp, master_blanks, CHARS_BRACE)) != 0) { + if (*cp == CHARS_BRACE[0] + && (err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0) + fatal_with_context("%s", err); + argv_add(serv->args, cp, (char *) 0); + } + argv_terminate(serv->args); + + /* + * Cleanup. + */ + vstring_free(buf); + vstring_free(junk); + return (serv); +} + +/* print_master_ent - show service entry contents */ + +void print_master_ent(MASTER_SERV *serv) +{ + char **cpp; + + msg_info("====start service entry"); + msg_info("flags: %d", serv->flags); + msg_info("name: %s", serv->name); + msg_info("type: %s", + serv->type == MASTER_SERV_TYPE_UNIX ? MASTER_XPORT_NAME_UNIX : + serv->type == MASTER_SERV_TYPE_FIFO ? MASTER_XPORT_NAME_FIFO : + serv->type == MASTER_SERV_TYPE_INET ? MASTER_XPORT_NAME_INET : +#ifdef MASTER_SERV_TYPE_PASS + serv->type == MASTER_SERV_TYPE_PASS ? MASTER_XPORT_NAME_PASS : +#endif + serv->type == MASTER_SERV_TYPE_UXDG ? MASTER_XPORT_NAME_UXDG : + "unknown transport type"); + msg_info("listen_fd_count: %d", serv->listen_fd_count); + msg_info("wakeup: %d", serv->wakeup_time); + msg_info("max_proc: %d", serv->max_proc); + msg_info("path: %s", serv->path); + for (cpp = serv->args->argv; *cpp; cpp++) + msg_info("arg[%d]: %s", (int) (cpp - serv->args->argv), *cpp); + msg_info("avail_proc: %d", serv->avail_proc); + msg_info("total_proc: %d", serv->total_proc); + msg_info("throttle_delay: %d", serv->throttle_delay); + msg_info("status_fd %d %d", serv->status_fd[0], serv->status_fd[1]); + msg_info("children: 0x%lx", (long) serv->children); + msg_info("next: 0x%lx", (long) serv->next); + msg_info("====end service entry"); +} + +/* free_master_ent - destroy process entry */ + +void free_master_ent(MASTER_SERV *serv) +{ + + /* + * Undo what get_master_ent() created. + */ + if (serv->flags & MASTER_FLAG_INETHOST) { + inet_addr_list_free(MASTER_INET_ADDRLIST(serv)); + myfree((void *) MASTER_INET_ADDRLIST(serv)); + } + if (serv->type == MASTER_SERV_TYPE_INET) + myfree(MASTER_INET_PORT(serv)); + myfree(serv->ext_name); + myfree(serv->name); + myfree(serv->path); + argv_free(serv->args); + myfree((void *) serv->listen_fd); + myfree((void *) serv); +} diff --git a/src/master/master_flow.c b/src/master/master_flow.c new file mode 100644 index 0000000..68ae57d --- /dev/null +++ b/src/master/master_flow.c @@ -0,0 +1,33 @@ +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include +#include + +int master_flow_pipe[2]; + +/* master_flow_init - initialize the flow control channel */ + +void master_flow_init(void) +{ + const char *myname = "master_flow_init"; + + if (pipe(master_flow_pipe) < 0) + msg_fatal("%s: pipe: %m", myname); + + non_blocking(master_flow_pipe[0], NON_BLOCKING); + non_blocking(master_flow_pipe[1], NON_BLOCKING); + + close_on_exec(master_flow_pipe[0], CLOSE_ON_EXEC); + close_on_exec(master_flow_pipe[1], CLOSE_ON_EXEC); +} diff --git a/src/master/master_listen.c b/src/master/master_listen.c new file mode 100644 index 0000000..1e7f6fa --- /dev/null +++ b/src/master/master_listen.c @@ -0,0 +1,186 @@ +/*++ +/* NAME +/* master_listen 3 +/* SUMMARY +/* Postfix master - start/stop listeners +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_listen_init(serv) +/* MASTER_SERV *serv; +/* +/* void master_listen_cleanup(serv) +/* MASTER_SERV *serv; +/* DESCRIPTION +/* master_listen_init() turns on the listener implemented by the +/* named process. FIFOs and UNIX-domain sockets are created with +/* mode 0622 and with ownership mail_owner. +/* +/* master_listen_cleanup() turns off the listener implemented by the +/* named process. +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* inet_listen(3), internet-domain listener +/* unix_listen(3), unix-domain listener +/* fifo_listen(3), named-pipe listener +/* upass_listen(3), file descriptor passing listener +/* set_eugid(3), set effective user/group attributes +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include "master.h" + +/* master_listen_init - enable connection requests */ + +void master_listen_init(MASTER_SERV *serv) +{ + const char *myname = "master_listen_init"; + char *end_point; + int n; + MAI_HOSTADDR_STR hostaddr; + struct sockaddr *sa; + + /* + * Find out what transport we should use, then create one or more + * listener sockets. Make the listener sockets non-blocking, so that + * child processes don't block in accept() when multiple processes are + * selecting on the same socket and only one of them gets the connection. + */ + switch (serv->type) { + + /* + * UNIX-domain or stream listener endpoints always come as singlets. + */ + case MASTER_SERV_TYPE_UNIX: + set_eugid(var_owner_uid, var_owner_gid); + serv->listen_fd[0] = + LOCAL_LISTEN(serv->name, serv->max_proc > var_proc_limit ? + serv->max_proc : var_proc_limit, NON_BLOCKING); + close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); + set_ugid(getuid(), getgid()); + break; + + /* + * UNIX-domain datagram listener endpoints always come as singlets. + */ + case MASTER_SERV_TYPE_UXDG: + set_eugid(var_owner_uid, var_owner_gid); + serv->listen_fd[0] = + unix_dgram_listen(serv->name, NON_BLOCKING); + close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); + set_ugid(getuid(), getgid()); + break; + + /* + * FIFO listener endpoints always come as singlets. + */ + case MASTER_SERV_TYPE_FIFO: + set_eugid(var_owner_uid, var_owner_gid); + serv->listen_fd[0] = fifo_listen(serv->name, 0622, NON_BLOCKING); + close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); + set_ugid(getuid(), getgid()); + break; + + /* + * INET-domain listener endpoints can be wildcarded (the default) or + * bound to specific interface addresses. + * + * With dual-stack IPv4/6 systems it does not matter, we have to specify + * the addresses anyway, either explicit or wild-card. + */ + case MASTER_SERV_TYPE_INET: + for (n = 0; n < serv->listen_fd_count; n++) { + sa = SOCK_ADDR_PTR(MASTER_INET_ADDRLIST(serv)->addrs + n); + SOCKADDR_TO_HOSTADDR(sa, SOCK_ADDR_LEN(sa), &hostaddr, + (MAI_SERVPORT_STR *) 0, 0); + end_point = concatenate(hostaddr.buf, + ":", MASTER_INET_PORT(serv), (char *) 0); + serv->listen_fd[n] + = inet_listen(end_point, serv->max_proc > var_proc_limit ? + serv->max_proc : var_proc_limit, NON_BLOCKING); + close_on_exec(serv->listen_fd[n], CLOSE_ON_EXEC); + myfree(end_point); + } + break; + + /* + * Descriptor passing endpoints always come as singlets. + */ +#ifdef MASTER_SERV_TYPE_PASS + case MASTER_SERV_TYPE_PASS: + set_eugid(var_owner_uid, var_owner_gid); + serv->listen_fd[0] = + LOCAL_LISTEN(serv->name, serv->max_proc > var_proc_limit ? + serv->max_proc : var_proc_limit, NON_BLOCKING); + close_on_exec(serv->listen_fd[0], CLOSE_ON_EXEC); + set_ugid(getuid(), getgid()); + break; +#endif + default: + msg_panic("%s: unknown service type: %d", myname, serv->type); + } +} + +/* master_listen_cleanup - disable connection requests */ + +void master_listen_cleanup(MASTER_SERV *serv) +{ + const char *myname = "master_listen_cleanup"; + int n; + + /* + * XXX The listen socket is shared with child processes. Closing the + * socket in the master process does not really disable listeners in + * child processes. There seems to be no documented way to turn off a + * listener. The 4.4BSD shutdown(2) man page promises an ENOTCONN error + * when shutdown(2) is applied to a socket that is not connected. + */ + for (n = 0; n < serv->listen_fd_count; n++) { + if (close(serv->listen_fd[n]) < 0) + msg_warn("%s: close listener socket %d: %m", + myname, serv->listen_fd[n]); + serv->listen_fd[n] = -1; + } +} diff --git a/src/master/master_monitor.c b/src/master/master_monitor.c new file mode 100644 index 0000000..6b2ca65 --- /dev/null +++ b/src/master/master_monitor.c @@ -0,0 +1,106 @@ +/*++ +/* NAME +/* master_monitor 3 +/* SUMMARY +/* Postfix master - start-up monitoring +/* SYNOPSIS +/* #include "master.h" +/* +/* int master_monitor(time_limit) +/* int time_limit; +/* DESCRIPTION +/* master_monitor() forks off a background child process, and +/* returns in the child. The result value is the file descriptor +/* on which the child process must write one byte after it +/* completes successful initialization as a daemon process. +/* +/* The foreground process waits for the child's completion for +/* a limited amount of time. It terminates with exit status 0 +/* in case of success, non-zero otherwise. +/* DIAGNOSTICS +/* Fatal errors: system call failure. +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include + +/* master_monitor - fork off a foreground monitor process */ + +int master_monitor(int time_limit) +{ + pid_t pid; + int pipes[2]; + char buf[1]; + + /* + * Sanity check. + */ + if (time_limit <= 0) + msg_panic("master_monitor: bad time limit: %d", time_limit); + + /* + * Set up the plumbing for child-to-parent communication. + */ + if (pipe(pipes) < 0) + msg_fatal("pipe: %m"); + close_on_exec(pipes[0], CLOSE_ON_EXEC); + close_on_exec(pipes[1], CLOSE_ON_EXEC); + + /* + * Fork the child, and wait for it to report successful initialization. + */ + switch (pid = fork()) { + case -1: + /* Error. */ + msg_fatal("fork: %m"); + case 0: + /* Child. Initialize as daemon in the background. */ + close(pipes[0]); + return (pipes[1]); + default: + /* Parent. Monitor the child in the foreground. */ + close(pipes[1]); + switch (timed_read(pipes[0], buf, 1, time_limit, (void *) 0)) { + default: + msg_warn("%m while waiting for daemon initialization"); + /* The child process still runs, but something is wrong. */ + (void) kill(pid, SIGKILL); + /* FALLTHROUGH */ + case 0: + /* The child process exited prematurely. */ + msg_fatal("daemon initialization failure -- see logs for details"); + case 1: + /* The child process initialized successfully. */ + exit(0); + } + } +} diff --git a/src/master/master_proto.c b/src/master/master_proto.c new file mode 100644 index 0000000..e38d2e1 --- /dev/null +++ b/src/master/master_proto.c @@ -0,0 +1,89 @@ +/*++ +/* NAME +/* master_proto 3 +/* SUMMARY +/* Postfix master - status notification protocol +/* SYNOPSIS +/* #include +/* +/* int master_notify(pid, generation, status) +/* int pid; +/* unsigned generation; +/* int status; +/* DESCRIPTION +/* The master process provides a standard environment for its +/* child processes. Part of this environment is a pair of file +/* descriptors that the master process shares with all child +/* processes that provide the same service. +/* .IP MASTER_LISTEN_FD +/* The shared file descriptor for accepting client connection +/* requests. The master process listens on this socket or FIFO +/* when all child processes are busy. +/* .IP MASTER_STATUS_FD +/* The shared file descriptor for sending child status updates to +/* the master process. +/* .PP +/* A child process uses master_notify() to send a status notification +/* message to the master process. +/* .IP MASTER_STAT_AVAIL +/* The child process is ready to accept client connections. +/* .IP MASTER_STAT_TAKEN +/* Until further notice, the child process is unavailable for +/* accepting client connections. +/* .PP +/* When a child process terminates without sending a status update, +/* the master process will figure out that the child is no longer +/* available. +/* DIAGNOSTICS +/* The result is -1 in case of problems. This usually means that +/* the parent disconnected after a reload request, in order to +/* force children to commit suicide. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +#include "master_proto.h" + +int master_notify(int pid, unsigned generation, int status) +{ + const char *myname = "master_notify"; + MASTER_STATUS stat; + + /* + * We use a simple binary protocol to minimize security risks. Since this + * is local IPC, there are no byte order or word length issues. The + * server treats this information as gossip, so sending a bad PID or a + * bad status code will only have amusement value. + */ + stat.pid = pid; + stat.gen = generation; + stat.avail = status; + + if (write(MASTER_STATUS_FD, (void *) &stat, sizeof(stat)) != sizeof(stat)) { + if (msg_verbose) + msg_info("%s: status %d: %m", myname, status); + return (-1); + } else { + if (msg_verbose) + msg_info("%s: status %d", myname, status); + return (0); + } +} diff --git a/src/master/master_proto.h b/src/master/master_proto.h new file mode 100644 index 0000000..6084514 --- /dev/null +++ b/src/master/master_proto.h @@ -0,0 +1,75 @@ +/*++ +/* NAME +/* master_proto 3h +/* SUMMARY +/* master process protocol +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Transport names. The master passes the transport name on the command + * line, and thus the name is part of the master to child protocol. + */ +#define MASTER_XPORT_NAME_UNIX "unix" /* local IPC */ +#define MASTER_XPORT_NAME_FIFO "fifo" /* local IPC */ +#define MASTER_XPORT_NAME_INET "inet" /* non-local IPC */ +#define MASTER_XPORT_NAME_PASS "pass" /* local IPC */ +#define MASTER_XPORT_NAME_UXDG "unix-dgram" /* local IPC */ + + /* + * Format of a status message sent by a child process to the process + * manager. Since this is between processes on the same machine we need not + * worry about byte order and word length. + */ +typedef struct MASTER_STATUS { + int pid; /* process ID */ + unsigned gen; /* child generation number */ + int avail; /* availability */ +} MASTER_STATUS; + +#define MASTER_GEN_NAME "GENERATION" /* passed via environment */ + +#define MASTER_STAT_TAKEN 0 /* this one is occupied */ +#define MASTER_STAT_AVAIL 1 /* this process is idle */ + +extern int master_notify(int, unsigned, int); /* encapsulate status msg */ + + /* + * File descriptors inherited from the master process. The flow control pipe + * is read by receive processes and is written to by send processes. If + * receive processes get too far ahead they will pause for a brief moment. + */ +#define MASTER_FLOW_READ 3 +#define MASTER_FLOW_WRITE 4 + + /* + * File descriptors inherited from the master process. All processes that + * provide a given service share the same status file descriptor, and listen + * on the same service socket(s). The kernel decides what process gets the + * next connection. Usually the number of listening processes is small, so + * one connection will not cause a "thundering herd" effect. When no process + * listens on a given socket, the master process will. MASTER_LISTEN_FD is + * actually the lowest-numbered descriptor of a sequence of descriptors to + * listen on. + */ +#define MASTER_STATUS_FD 5 /* shared channel to parent */ +#define MASTER_LISTEN_FD 6 /* accept connections here */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + diff --git a/src/master/master_service.c b/src/master/master_service.c new file mode 100644 index 0000000..d5663b9 --- /dev/null +++ b/src/master/master_service.c @@ -0,0 +1,113 @@ +/*++ +/* NAME +/* master_service 3 +/* SUMMARY +/* Postfix master - start/stop services +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_start_service(serv) +/* MASTER_SERV *serv; +/* +/* void master_stop_service(serv) +/* MASTER_SERV *serv; +/* +/* void master_restart_service(serv, conf_reload) +/* MASTER_SERV *serv; +/* int conf_reload; +/* DESCRIPTION +/* master_start_service() enables the named service. +/* +/* master_stop_service() disables named service. +/* +/* master_restart_service() requests all running child processes to +/* commit suicide. The conf_reload argument is either DO_CONF_RELOAD +/* (configuration files were reloaded, re-evaluate the child process +/* creation policy) or NO_CONF_RELOAD. +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* master_avail(3), process creation policy +/* master_wakeup(3), service automatic wakeup +/* master_status(3), child status reports +/* master_listen(3), unix/inet listeners +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include "master.h" + +MASTER_SERV *master_head; + +/* master_start_service - activate service */ + +void master_start_service(MASTER_SERV *serv) +{ + + /* + * Enable connection requests, wakeup timers, and status updates from + * child processes. + */ + master_listen_init(serv); + master_avail_listen(serv); + master_status_init(serv); + master_wakeup_init(serv); +} + +/* master_stop_service - deactivate service */ + +void master_stop_service(MASTER_SERV *serv) +{ + + /* + * Undo the things that master_start_service() did. + */ + master_wakeup_cleanup(serv); + master_status_cleanup(serv); + master_avail_cleanup(serv); + master_listen_cleanup(serv); +} + +/* master_restart_service - restart service after configuration reload */ + +void master_restart_service(MASTER_SERV *serv, int conf_reload) +{ + + /* + * Undo some of the things that master_start_service() did. + */ + master_wakeup_cleanup(serv); + master_status_cleanup(serv); + + /* + * Now undo the undone. + */ + master_status_init(serv); + master_wakeup_init(serv); + + /* + * Respond to configuration change. + */ + if (conf_reload) + master_avail_listen(serv); +} diff --git a/src/master/master_sig.c b/src/master/master_sig.c new file mode 100644 index 0000000..db5e39d --- /dev/null +++ b/src/master/master_sig.c @@ -0,0 +1,275 @@ +/*++ +/* NAME +/* master_sig 3 +/* SUMMARY +/* Postfix master - signal processing +/* SYNOPSIS +/* #include "master.h" +/* +/* int master_gotsighup; +/* int master_gotsigchld; +/* +/* int master_sigsetup() +/* DESCRIPTION +/* This module implements the master process signal handling interface. +/* +/* master_gotsighup (master_gotsigchld) is set to SIGHUP (SIGCHLD) +/* when the process receives a hangup (child death) signal. +/* +/* master_sigsetup() enables processing of hangup and child death signals. +/* Receipt of SIGINT, SIGQUIT, SIGSEGV, SIGILL, or SIGTERM +/* is interpreted as a request for termination. Child processes are +/* notified of the master\'s demise by sending them a SIGTERM signal. +/* DIAGNOSTICS +/* BUGS +/* Need a way to register cleanup actions. +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include "master.h" + +#ifdef USE_SIG_RETURN +#include +#undef USE_SIG_PIPE +#else +#define USE_SIG_PIPE +#endif + +/* Local stuff. */ + +#ifdef USE_SIG_PIPE +#include +#include +#include +#include + +int master_sig_pipe[2]; + +#define SIG_PIPE_WRITE_FD master_sig_pipe[1] +#define SIG_PIPE_READ_FD master_sig_pipe[0] +#endif + +int master_gotsigchld; +int master_gotsighup; + +#ifdef USE_SIG_RETURN + +/* master_sighup - register arrival of hangup signal */ + +static void master_sighup(int sig) +{ + + /* + * WARNING WARNING WARNING. + * + * This code runs at unpredictable moments, as a signal handler. Don't put + * any code here other than for setting a global flag. + */ + master_gotsighup = sig; +} + +/* master_sigchld - register arrival of child death signal */ + +static void master_sigchld(int sig, int code, struct sigcontext * scp) +{ + + /* + * WARNING WARNING WARNING. + * + * This code runs at unpredictable moments, as a signal handler. Don't put + * any code here other than for setting a global flag, or code that is + * intended to be run within a signal handler. + */ + master_gotsigchld = sig; + if (scp != NULL && scp->sc_syscall == SYS_select) { + scp->sc_syscall_action = SIG_RETURN; +#ifndef SA_RESTART + } else if (scp != NULL) { + scp->sc_syscall_action = SIG_RESTART; +#endif + } +} + +#else + +/* master_sighup - register arrival of hangup signal */ + +static void master_sighup(int sig) +{ + int saved_errno = errno; + + /* + * WARNING WARNING WARNING. + * + * This code runs at unpredictable moments, as a signal handler. Don't put + * any code here other than for setting a global flag, or code that is + * intended to be run within a signal handler. Restore errno in case we + * are interrupting the epilog of a failed system call. + */ + master_gotsighup = sig; + if (write(SIG_PIPE_WRITE_FD, "", 1) != 1) + msg_warn("write to SIG_PIPE_WRITE_FD failed: %m"); + errno = saved_errno; +} + +/* master_sigchld - force wakeup from select() */ + +static void master_sigchld(int unused_sig) +{ + int saved_errno = errno; + + /* + * WARNING WARNING WARNING. + * + * This code runs at unpredictable moments, as a signal handler. Don't put + * any code here other than for setting a global flag, or code that is + * intended to be run within a signal handler. Restore errno in case we + * are interrupting the epilog of a failed system call. + */ + master_gotsigchld = 1; + if (write(SIG_PIPE_WRITE_FD, "", 1) != 1) + msg_warn("write to SIG_PIPE_WRITE_FD failed: %m"); + errno = saved_errno; +} + +/* master_sig_event - called upon return from select() */ + +static void master_sig_event(int unused_event, void *unused_context) +{ + char c[1]; + + while (read(SIG_PIPE_READ_FD, c, 1) > 0) + /* void */ ; +} + +#endif + +/* master_sigdeath - die, women and children first */ + +static void master_sigdeath(int sig) +{ + const char *myname = "master_sigdeath"; + struct sigaction action; + pid_t pid = getpid(); + + /* + * Set alarm clock here for suicide after 5s. + */ + killme_after(5); + + /* + * Terminate all processes in our process group, except ourselves. + */ + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = SIG_IGN; + if (sigaction(SIGTERM, &action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction: %m", myname); + if (kill(-pid, SIGTERM) < 0) + msg_fatal("%s: kill process group: %m", myname); + + /* + * XXX We're running from a signal handler, and should not call complex + * routines at all, but it would be even worse to silently terminate + * without informing the sysadmin. For this reason, msg(3) was made safe + * for usage by signal handlers that terminate the process. + */ + msg_info("terminating on signal %d", sig); + + /* + * Undocumented: when a process runs with PID 1, Linux won't deliver a + * signal unless the process specifies a handler (i.e. SIG_DFL is treated + * as SIG_IGN). + */ + if (init_mode) + /* Don't call exit() from a signal handler. */ + _exit(0); + + /* + * Deliver the signal to ourselves and clean up. XXX We're running as a + * signal handler and really should not be doing complicated things... + */ + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = SIG_DFL; + if (sigaction(sig, &action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction: %m", myname); + if (kill(pid, sig) < 0) + msg_fatal("%s: kill myself: %m", myname); +} + +/* master_sigsetup - set up signal handlers */ + +void master_sigsetup(void) +{ + const char *myname = "master_sigsetup"; + struct sigaction action; + static int sigs[] = { + SIGINT, SIGQUIT, SIGILL, SIGBUS, SIGSEGV, SIGTERM, + }; + unsigned i; + + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + + /* + * Prepare to kill our children when we receive any of the above signals. + */ + action.sa_handler = master_sigdeath; + for (i = 0; i < sizeof(sigs) / sizeof(sigs[0]); i++) + if (sigaction(sigs[i], &action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction(%d): %m", myname, sigs[i]); + +#ifdef USE_SIG_PIPE + if (pipe(master_sig_pipe)) + msg_fatal("pipe: %m"); + non_blocking(SIG_PIPE_WRITE_FD, NON_BLOCKING); + non_blocking(SIG_PIPE_READ_FD, NON_BLOCKING); + close_on_exec(SIG_PIPE_WRITE_FD, CLOSE_ON_EXEC); + close_on_exec(SIG_PIPE_READ_FD, CLOSE_ON_EXEC); + event_enable_read(SIG_PIPE_READ_FD, master_sig_event, (void *) 0); +#endif + + /* + * Intercept SIGHUP (re-read config file) and SIGCHLD (child exit). + */ +#ifdef SA_RESTART + action.sa_flags |= SA_RESTART; +#endif + action.sa_handler = master_sighup; + if (sigaction(SIGHUP, &action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction(%d): %m", myname, SIGHUP); + + action.sa_flags |= SA_NOCLDSTOP; + action.sa_handler = master_sigchld; + if (sigaction(SIGCHLD, &action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction(%d): %m", myname, SIGCHLD); +} diff --git a/src/master/master_spawn.c b/src/master/master_spawn.c new file mode 100644 index 0000000..c3b70f2 --- /dev/null +++ b/src/master/master_spawn.c @@ -0,0 +1,371 @@ +/*++ +/* NAME +/* master_spawn 3 +/* SUMMARY +/* Postfix master - child process birth and death +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_spawn(serv) +/* MASTER_SERV *serv; +/* +/* void master_reap_child() +/* +/* void master_delete_children(serv) +/* MASTER_SERV *serv; +/* DESCRIPTION +/* This module creates and cleans up child processes, and applies +/* a process creation throttle in case of serious trouble. +/* This module is the working horse for the master_avail(3) process +/* creation policy module. +/* +/* master_spawn() spawns off a child process for the specified service, +/* making the child process available for servicing connection requests. +/* It is an error to call this function then the specified service is +/* throttled. +/* +/* master_reap_child() cleans up all dead child processes. One typically +/* runs this function at a convenient moment after receiving a SIGCHLD +/* signal. When a child process terminates abnormally after being used +/* for the first time, process creation for that service is throttled +/* for a configurable amount of time. +/* +/* master_delete_children() deletes all child processes that provide +/* the named service. Upon exit, the process creation throttle for that +/* service is released. +/* DIAGNOSTICS +/* Panic: interface violations, internal inconsistencies. +/* Fatal errors: out of memory. Warnings: throttle on/off. +/* BUGS +/* SEE ALSO +/* master_avail(3), process creation policy. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include +#include +#include /* closelog() */ +#include +#include +#include + +/* Utility libraries. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include "master_proto.h" +#include "master.h" + +BINHASH *master_child_table; +static void master_unthrottle(MASTER_SERV *serv); + +/* master_unthrottle_wrapper - in case (char *) != (struct *) */ + +static void master_unthrottle_wrapper(int unused_event, void *ptr) +{ + MASTER_SERV *serv = (MASTER_SERV *) ptr; + + /* + * This routine runs after expiry of the timer set in master_throttle(), + * which gets called when it appears that the world is falling apart. + */ + master_unthrottle(serv); +} + +/* master_unthrottle - enable process creation */ + +static void master_unthrottle(MASTER_SERV *serv) +{ + + /* + * Enable process creation within this class. Disable the "unthrottle" + * timer just in case we're being called directly from the cleanup + * routine, instead of from the event manager. + */ + if ((serv->flags & MASTER_FLAG_THROTTLE) != 0) { + serv->flags &= ~MASTER_FLAG_THROTTLE; + event_cancel_timer(master_unthrottle_wrapper, (void *) serv); + if (msg_verbose) + msg_info("throttle released for command %s", serv->path); + master_avail_listen(serv); + } +} + +/* master_throttle - suspend process creation */ + +static void master_throttle(MASTER_SERV *serv) +{ + + /* + * Perhaps the command to be run is defective, perhaps some configuration + * is wrong, or perhaps the system is out of resources. Disable further + * process creation attempts for a while. + */ + if ((serv->flags & MASTER_FLAG_THROTTLE) == 0) { + serv->flags |= MASTER_FLAG_THROTTLE; + event_request_timer(master_unthrottle_wrapper, (void *) serv, + serv->throttle_delay); + if (msg_verbose) + msg_info("throttling command %s", serv->path); + master_avail_listen(serv); + } +} + +/* master_spawn - spawn off new child process if we can */ + +void master_spawn(MASTER_SERV *serv) +{ + const char *myname = "master_spawn"; + MASTER_PROC *proc; + MASTER_PID pid; + int n; + static unsigned master_generation = 0; + static VSTRING *env_gen = 0; + + if (master_child_table == 0) + master_child_table = binhash_create(0); + if (env_gen == 0) + env_gen = vstring_alloc(100); + + /* + * Sanity checks. The master_avail module is supposed to know what it is + * doing. + */ + if (!MASTER_LIMIT_OK(serv->max_proc, serv->total_proc)) + msg_panic("%s: at process limit %d", myname, serv->total_proc); + if (serv->avail_proc > 0) + msg_panic("%s: processes available: %d", myname, serv->avail_proc); + if (serv->flags & MASTER_FLAG_THROTTLE) + msg_panic("%s: throttled service: %s", myname, serv->path); + + /* + * Create a child process and connect parent and child via the status + * pipe. + */ + master_generation += 1; + switch (pid = fork()) { + + /* + * Error. We're out of some essential resource. Best recourse is to + * try again later. + */ + case -1: + msg_warn("%s: fork: %m -- throttling", myname); + master_throttle(serv); + return; + + /* + * Child process. Redirect child stdin/stdout to the parent-child + * connection and run the requested command. Leave child stderr + * alone. Disable exit handlers: they should be executed by the + * parent only. + * + * When we reach the process limit on a public internet service, we + * create stress-mode processes until the process count stays below + * the limit for some amount of time. See master_avail_listen(). + */ + case 0: + msg_cleanup((void (*) (void)) 0); /* disable exit handler */ + closelog(); /* avoid filedes leak */ + + if (master_flow_pipe[0] <= MASTER_FLOW_READ) + msg_fatal("%s: flow pipe read descriptor <= %d", + myname, MASTER_FLOW_READ); + if (DUP2(master_flow_pipe[0], MASTER_FLOW_READ) < 0) + msg_fatal("%s: dup2: %m", myname); + if (close(master_flow_pipe[0]) < 0) + msg_fatal("close %d: %m", master_flow_pipe[0]); + + if (master_flow_pipe[1] <= MASTER_FLOW_WRITE) + msg_fatal("%s: flow pipe read descriptor <= %d", + myname, MASTER_FLOW_WRITE); + if (DUP2(master_flow_pipe[1], MASTER_FLOW_WRITE) < 0) + msg_fatal("%s: dup2: %m", myname); + if (close(master_flow_pipe[1]) < 0) + msg_fatal("close %d: %m", master_flow_pipe[1]); + + close(serv->status_fd[0]); /* status channel */ + if (serv->status_fd[1] <= MASTER_STATUS_FD) + msg_fatal("%s: status file descriptor collision", myname); + if (DUP2(serv->status_fd[1], MASTER_STATUS_FD) < 0) + msg_fatal("%s: dup2 status_fd: %m", myname); + (void) close(serv->status_fd[1]); + + for (n = 0; n < serv->listen_fd_count; n++) { + if (serv->listen_fd[n] <= MASTER_LISTEN_FD + n) + msg_fatal("%s: listen file descriptor collision", myname); + if (DUP2(serv->listen_fd[n], MASTER_LISTEN_FD + n) < 0) + msg_fatal("%s: dup2 listen_fd %d: %m", + myname, serv->listen_fd[n]); + (void) close(serv->listen_fd[n]); + } + vstring_sprintf(env_gen, "%s=%o", MASTER_GEN_NAME, master_generation); + if (putenv(vstring_str(env_gen)) < 0) + msg_fatal("%s: putenv: %m", myname); + if (serv->stress_param_val && serv->stress_expire_time > event_time()) + serv->stress_param_val[0] = CONFIG_BOOL_YES[0]; + + execvp(serv->path, serv->args->argv); + msg_fatal("%s: exec %s: %m", myname, serv->path); + /* NOTREACHED */ + + /* + * Parent. Fill in a process member data structure and set up links + * between child and process. Say this process has become available. + * If this service has a wakeup timer that is turned on only when the + * service is actually used, turn on the wakeup timer. + */ + default: + if (msg_verbose) + msg_info("spawn command %s; pid %d", serv->path, pid); + proc = (MASTER_PROC *) mymalloc(sizeof(MASTER_PROC)); + proc->serv = serv; + proc->pid = pid; + proc->gen = master_generation; + proc->use_count = 0; + proc->avail = 0; + binhash_enter(master_child_table, (void *) &pid, + sizeof(pid), (void *) proc); + serv->total_proc++; + master_avail_more(serv, proc); + if (serv->flags & MASTER_FLAG_CONDWAKE) { + serv->flags &= ~MASTER_FLAG_CONDWAKE; + master_wakeup_init(serv); + if (msg_verbose) + msg_info("start conditional timer for %s", serv->name); + } + return; + } +} + +/* master_delete_child - destroy child process info */ + +static void master_delete_child(MASTER_PROC *proc) +{ + MASTER_SERV *serv; + + /* + * Undo the things that master_spawn did. Stop the process if it still + * exists, and remove it from the lookup tables. Update the number of + * available processes. + */ + serv = proc->serv; + serv->total_proc--; + if (proc->avail == MASTER_STAT_AVAIL) + master_avail_less(serv, proc); + else + master_avail_listen(serv); + binhash_delete(master_child_table, (void *) &proc->pid, + sizeof(proc->pid), (void (*) (void *)) 0); + myfree((void *) proc); +} + +/* master_reap_child - reap dead children */ + +void master_reap_child(void) +{ + MASTER_SERV *serv; + MASTER_PROC *proc; + MASTER_PID pid; + WAIT_STATUS_T status; + + /* + * Pick up termination status of all dead children. When a process failed + * on its first job, assume we see the symptom of a structural problem + * (configuration problem, system running out of resources) and back off. + */ + while ((pid = waitpid((pid_t) - 1, &status, WNOHANG)) > 0) { + if (msg_verbose) + msg_info("master_reap_child: pid %d", pid); + if ((proc = (MASTER_PROC *) binhash_find(master_child_table, + (void *) &pid, sizeof(pid))) == 0) { + if (init_mode) + continue; /* non-Postfix process */ + msg_panic("master_reap: unknown pid: %d", pid); + } + serv = proc->serv; + +#define MASTER_KILL_SIGNAL SIGTERM +#define MASTER_SENT_SIGNAL(serv, status) \ + (MASTER_MARKED_FOR_DELETION(serv) \ + && WTERMSIG(status) == MASTER_KILL_SIGNAL) + + /* + * XXX The code for WIFSTOPPED() is here in case some buggy kernel + * reports WIFSTOPPED() events to a Postfix daemon's parent process + * (the master(8) daemon) instead of the tracing process (e.g., gdb). + * + * The WIFSTOPPED() test prevents master(8) from deleting its record of + * a child process that is stopped. That would cause a master(8) + * panic (unknown child) when the child terminates. + */ + if (!NORMAL_EXIT_STATUS(status)) { + if (WIFSTOPPED(status)) { + msg_warn("process %s pid %d stopped by signal %d", + serv->path, pid, WSTOPSIG(status)); + continue; + } + if (WIFEXITED(status)) + msg_warn("process %s pid %d exit status %d", + serv->path, pid, WEXITSTATUS(status)); + if (WIFSIGNALED(status) && !MASTER_SENT_SIGNAL(serv, status)) + msg_warn("process %s pid %d killed by signal %d", + serv->path, pid, WTERMSIG(status)); + /* master_delete_children() throttles first, then kills. */ + if (proc->use_count == 0 + && (serv->flags & MASTER_FLAG_THROTTLE) == 0) { + msg_warn("%s: bad command startup -- throttling", serv->path); + master_throttle(serv); + } + } + master_delete_child(proc); + } +} + +/* master_delete_children - delete all child processes of service */ + +void master_delete_children(MASTER_SERV *serv) +{ + BINHASH_INFO **list; + BINHASH_INFO **info; + MASTER_PROC *proc; + + /* + * XXX turn on the throttle so that master_reap_child() doesn't. Someone + * has to turn off the throttle in order to stop the associated timer + * request, so we might just as well do it at the end. + */ + master_throttle(serv); + for (info = list = binhash_list(master_child_table); *info; info++) { + proc = (MASTER_PROC *) info[0]->value; + if (proc->serv == serv) + (void) kill(proc->pid, MASTER_KILL_SIGNAL); + } + while (serv->total_proc > 0) + master_reap_child(); + myfree((void *) list); + master_unthrottle(serv); +} diff --git a/src/master/master_status.c b/src/master/master_status.c new file mode 100644 index 0000000..fb3bb73 --- /dev/null +++ b/src/master/master_status.c @@ -0,0 +1,198 @@ +/*++ +/* NAME +/* master_status 3 +/* SUMMARY +/* Postfix master - process child status reports +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_status_init(serv) +/* MASTER_SERV *serv; +/* +/* void master_status_cleanup(serv) +/* MASTER_SERV *serv; +/* DESCRIPTION +/* This module reads and processes status reports from child processes. +/* +/* master_status_init() enables the processing of child status updates +/* for the specified service. Child process status updates (process +/* available, process taken) are passed on to the master_avail_XXX() +/* routines. +/* +/* master_status_cleanup() disables child status update processing +/* for the specified service. +/* DIAGNOSTICS +/* Panic: internal inconsistency. Warnings: a child process sends +/* incomplete or incorrect information. +/* BUGS +/* SEE ALSO +/* master_avail(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include "master_proto.h" +#include "master.h" + +/* master_status_event - status read event handler */ + +static void master_status_event(int event, void *context) +{ + const char *myname = "master_status_event"; + MASTER_SERV *serv = (MASTER_SERV *) context; + MASTER_STATUS stat; + MASTER_PROC *proc; + MASTER_PID pid; + int n; + + if (event == 0) /* XXX Can this happen? */ + return; + + /* + * We always keep the child end of the status pipe open, so an EOF read + * condition means that we're seriously confused. We use non-blocking + * reads so that we don't get stuck when someone sends a partial message. + * Messages are short, so a partial read means someone wrote less than a + * whole status message. Hopefully the next read will be in sync again... + * We use a global child process status table because when a child dies + * only its pid is known - we do not know what service it came from. + */ + switch (n = read(serv->status_fd[0], (void *) &stat, sizeof(stat))) { + + case -1: + msg_warn("%s: read: %m", myname); + return; + + case 0: + msg_panic("%s: read EOF status", myname); + /* NOTREACHED */ + + default: + msg_warn("service %s(%s): child (pid %d) sent partial status update (%d bytes)", + serv->ext_name, serv->name, stat.pid, n); + return; + + case sizeof(stat): + pid = stat.pid; + if (msg_verbose) + msg_info("%s: pid %d gen %u avail %d", + myname, stat.pid, stat.gen, stat.avail); + } + + /* + * Sanity checks. Do not freak out when the child sends garbage because + * it is confused or for other reasons. However, be sure to freak out + * when our own data structures are inconsistent. A process not found + * condition can happen when we reap a process before receiving its + * status update, so this is not an error. + */ + if ((proc = (MASTER_PROC *) binhash_find(master_child_table, + (void *) &pid, sizeof(pid))) == 0) { + if (msg_verbose) + msg_info("%s: process id not found: %d", myname, stat.pid); + return; + } + if (proc->gen != stat.gen) { + msg_info("ignoring status update from child pid %d generation %u", + pid, stat.gen); + return; + } + if (proc->serv != serv) + msg_panic("%s: pointer corruption: %p != %p", + myname, (void *) proc->serv, (void *) serv); + + /* + * Update our idea of the child process status. Allow redundant status + * updates, because different types of events may be processed out of + * order. Otherwise, warn about weird status updates but do not take + * action. It's all gossip after all. + */ + if (proc->avail == stat.avail) + return; + switch (stat.avail) { + case MASTER_STAT_AVAIL: + proc->use_count++; + master_avail_more(serv, proc); + break; + case MASTER_STAT_TAKEN: + master_avail_less(serv, proc); + break; + default: + msg_warn("%s: ignoring unknown status: %d allegedly from pid: %d", + myname, stat.pid, stat.avail); + break; + } +} + +/* master_status_init - start status event processing for this service */ + +void master_status_init(MASTER_SERV *serv) +{ + const char *myname = "master_status_init"; + + /* + * Sanity checks. + */ + if (serv->status_fd[0] >= 0 || serv->status_fd[1] >= 0) + msg_panic("%s: status events already enabled", myname); + if (msg_verbose) + msg_info("%s: %s", myname, serv->name); + + /* + * Make the read end of this service's status pipe non-blocking so that + * we can detect partial writes on the child side. We use a duplex pipe + * so that the child side becomes readable when the master goes away. + */ + if (duplex_pipe(serv->status_fd) < 0) + msg_fatal("pipe: %m"); + non_blocking(serv->status_fd[0], BLOCKING); + close_on_exec(serv->status_fd[0], CLOSE_ON_EXEC); + close_on_exec(serv->status_fd[1], CLOSE_ON_EXEC); + event_enable_read(serv->status_fd[0], master_status_event, (void *) serv); +} + +/* master_status_cleanup - stop status event processing for this service */ + +void master_status_cleanup(MASTER_SERV *serv) +{ + const char *myname = "master_status_cleanup"; + + /* + * Sanity checks. + */ + if (serv->status_fd[0] < 0 || serv->status_fd[1] < 0) + msg_panic("%s: status events not enabled", myname); + if (msg_verbose) + msg_info("%s: %s", myname, serv->name); + + /* + * Dispose of this service's status pipe after disabling read events. + */ + event_disable_readwrite(serv->status_fd[0]); + if (close(serv->status_fd[0]) != 0) + msg_warn("%s: close status descriptor (read side): %m", myname); + if (close(serv->status_fd[1]) != 0) + msg_warn("%s: close status descriptor (write side): %m", myname); + serv->status_fd[0] = serv->status_fd[1] = -1; +} diff --git a/src/master/master_vars.c b/src/master/master_vars.c new file mode 100644 index 0000000..a2d5441 --- /dev/null +++ b/src/master/master_vars.c @@ -0,0 +1,98 @@ +/*++ +/* NAME +/* master_vars 3 +/* SUMMARY +/* Postfix master - global configuration file access +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_vars_init() +/* DESCRIPTION +/* master_vars_init() reads values from the global Postfix configuration +/* file and assigns them to tunable program parameters. Where no value +/* is specified, a compiled-in default value is used. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "master.h" + + /* + * Tunable parameters. + */ +int var_throttle_time; +char *var_master_disable; + +/* master_vars_init - initialize from global Postfix configuration file */ + +void master_vars_init(void) +{ + char *path; + static const CONFIG_STR_TABLE str_table[] = { + VAR_MASTER_DISABLE, DEF_MASTER_DISABLE, &var_master_disable, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_THROTTLE_TIME, DEF_THROTTLE_TIME, &var_throttle_time, 1, 0, + 0, + }; + static char *saved_inet_protocols; + static char *saved_queue_dir; + static char *saved_config_dir; + static const MASTER_STR_WATCH str_watch_table[] = { + VAR_CONFIG_DIR, &var_config_dir, &saved_config_dir, 0, 0, + VAR_QUEUE_DIR, &var_queue_dir, &saved_queue_dir, 0, 0, + VAR_INET_PROTOCOLS, &var_inet_protocols, &saved_inet_protocols, 0, 0, + /* XXX Add inet_interfaces here after this code is burned in. */ + 0, + }; + + /* + * Flush existing main.cf settings, so that we handle deleted main.cf + * settings properly. + */ + mail_conf_flush(); + set_mail_conf_str(VAR_PROCNAME, var_procname); + mail_conf_read(); + get_mail_conf_str_table(str_table); + get_mail_conf_time_table(time_table); + path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (void *) 0); + fset_master_ent(path); + myfree(path); + + /* + * Look for parameter changes that require special attention. + */ + master_str_watch(str_watch_table); +} diff --git a/src/master/master_wakeup.c b/src/master/master_wakeup.c new file mode 100644 index 0000000..cc50924 --- /dev/null +++ b/src/master/master_wakeup.c @@ -0,0 +1,192 @@ +/*++ +/* NAME +/* master_wakeup 3 +/* SUMMARY +/* Postfix master - start/stop service wakeup timers +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_wakeup_init(serv) +/* MASTER_SERV *serv; +/* +/* void master_wakeup_cleanup(serv) +/* MASTER_SERV *serv; +/* DESCRIPTION +/* This module implements automatic service wakeup. In order to +/* wakeup a service, a wakeup trigger is sent to the corresponding +/* service port or FIFO, and a timer is started to repeat this sequence +/* after a configurable amount of time. +/* +/* master_wakeup_init() wakes up the named service. No wakeup +/* is done or scheduled when a zero wakeup time is given, or when +/* the service has been throttled in the mean time. +/* It is OK to call master_wakeup_init() while a timer is already +/* running for the named service. The effect is to restart the +/* wakeup timer. +/* +/* master_wakeup_cleanup() cancels the wakeup timer for the named +/* service. It is an error to disable a service while it still has +/* an active wakeup timer (doing so would cause a dangling reference +/* to a non-existent service). +/* It is OK to call master_wakeup_cleanup() even when no timer is +/* active for the named service. +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* inet_trigger(3), internet-domain client +/* unix_trigger(3), unix-domain client +/* fifo_trigger(3), fifo client +/* upass_trigger(3), file descriptor passing client +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include /* triggers */ +#include + +/* Application-specific. */ + +#include "mail_server.h" +#include "master.h" + +/* master_wakeup_timer_event - wakeup event handler */ + +static void master_wakeup_timer_event(int unused_event, void *context) +{ + const char *myname = "master_wakeup_timer_event"; + MASTER_SERV *serv = (MASTER_SERV *) context; + int status; + static char wakeup = TRIGGER_REQ_WAKEUP; + + /* + * Don't wakeup services whose automatic wakeup feature was turned off in + * the mean time. + */ + if (serv->wakeup_time == 0) + return; + + /* + * Don't wake up services that are throttled. Find out what transport to + * use. We can't block here so we choose a short timeout. + */ +#define BRIEFLY 1 + + if (MASTER_THROTTLED(serv) == 0) { + if (msg_verbose) + msg_info("%s: service %s", myname, serv->name); + + switch (serv->type) { + case MASTER_SERV_TYPE_INET: + status = inet_trigger(serv->name, &wakeup, sizeof(wakeup), BRIEFLY); + break; + case MASTER_SERV_TYPE_UNIX: + status = LOCAL_TRIGGER(serv->name, &wakeup, sizeof(wakeup), BRIEFLY); + break; + case MASTER_SERV_TYPE_UXDG: + status = -1; + errno = EOPNOTSUPP; + break; +#ifdef MASTER_SERV_TYPE_PASS + case MASTER_SERV_TYPE_PASS: + status = pass_trigger(serv->name, &wakeup, sizeof(wakeup), BRIEFLY); + break; +#endif + + /* + * If someone compromises the postfix account then this must not + * overwrite files outside the chroot jail. Countermeasures: + * + * - Limit the damage by accessing the FIFO as postfix not root. + * + * - Have fifo_trigger() call safe_open() so we won't follow + * arbitrary hard/symlinks to files in/outside the chroot jail. + * + * - All non-chroot postfix-related files must be root owned (or + * postfix check complains). + * + * - The postfix user and group ID must not be shared with other + * applications (says the INSTALL documentation). + * + * Result of a discussion with Michael Tokarev, who received his + * insights from Solar Designer, who tested Postfix with a kernel + * module that is paranoid about open() calls. + */ + case MASTER_SERV_TYPE_FIFO: + set_eugid(var_owner_uid, var_owner_gid); + status = fifo_trigger(serv->name, &wakeup, sizeof(wakeup), BRIEFLY); + set_ugid(getuid(), getgid()); + break; + default: + msg_panic("%s: unknown service type: %d", myname, serv->type); + } + if (status < 0) + msg_warn("%s: service %s(%s): %m", + myname, serv->ext_name, serv->name); + } + + /* + * Schedule another wakeup event. + */ + event_request_timer(master_wakeup_timer_event, (void *) serv, + serv->wakeup_time); +} + +/* master_wakeup_init - start automatic service wakeup */ + +void master_wakeup_init(MASTER_SERV *serv) +{ + const char *myname = "master_wakeup_init"; + + if (serv->wakeup_time == 0 || (serv->flags & MASTER_FLAG_CONDWAKE)) + return; + if (msg_verbose) + msg_info("%s: service %s time %d", + myname, serv->name, serv->wakeup_time); + master_wakeup_timer_event(0, (void *) serv); +} + +/* master_wakeup_cleanup - cancel wakeup timer */ + +void master_wakeup_cleanup(MASTER_SERV *serv) +{ + const char *myname = "master_wakeup_cleanup"; + + /* + * Cleanup, even when the wakeup feature has been turned off. There might + * still be a pending timer. Don't depend on the code that reloads the + * config file to reset the wakeup timer when things change. + */ + if (msg_verbose) + msg_info("%s: service %s", myname, serv->name); + + event_cancel_timer(master_wakeup_timer_event, (void *) serv); +} diff --git a/src/master/master_watch.c b/src/master/master_watch.c new file mode 100644 index 0000000..1af26fe --- /dev/null +++ b/src/master/master_watch.c @@ -0,0 +1,151 @@ +/*++ +/* NAME +/* master_watch 3 +/* SUMMARY +/* Postfix master - monitor main.cf changes +/* SYNOPSIS +/* #include "master.h" +/* +/* void master_str_watch(str_watch_table) +/* const MASTER_STR_WATCH *str_watch_table; +/* +/* void master_int_watch(int_watch_table) +/* MASTER_INT_WATCH *int_watch_table; +/* DESCRIPTION +/* The Postfix master daemon is a long-running process. After +/* main.cf is changed, some parameter changes may require that +/* master data structures be recomputed. +/* +/* Unfortunately, some main.cf changes cannot be applied +/* on-the-fly, either because they require killing off existing +/* child processes and thus disrupt service, or because the +/* necessary support for on-the-fly data structure update has +/* not yet been implemented. Such main.cf changes trigger a +/* warning that they require that Postfix be stopped and +/* restarted. +/* +/* This module provides functions that monitor selected main.cf +/* parameters for change. The operation of these functions is +/* controlled by tables that specify the parameter name, the +/* current parameter value, a historical parameter value, +/* optional flags, and an optional notify call-back function. +/* +/* master_str_watch() monitors string-valued parameters for +/* change, and master_int_watch() does the same for integer-valued +/* parameters. Note that master_int_watch() needs read-write +/* access to its argument table, while master_str_watch() needs +/* read-only access only. +/* +/* The functions log a warning when a parameter value has +/* changed after re-reading main.cf, but the parameter is not +/* flagged in the MASTER_*_WATCH table as "updatable" with +/* MASTER_WATCH_FLAG_UPDATABLE. +/* +/* If the parameter has a notify call-back function, then the +/* function is called after main.cf is read for the first time. +/* If the parameter is flagged as "updatable", then the function +/* is also called when the parameter value changes after +/* re-reading main.cf. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include "master.h" + +/* master_str_watch - watch string-valued parameters for change */ + +void master_str_watch(const MASTER_STR_WATCH *str_watch_table) +{ + const MASTER_STR_WATCH *wp; + + for (wp = str_watch_table; wp->name != 0; wp++) { + + /* + * Detect changes to monitored parameter values. If a change is + * supported, we discard the backed up value and update it to the + * current value later. Otherwise we complain. + */ + if (wp->backup[0] != 0 + && strcmp(wp->backup[0], wp->value[0]) != 0) { + if ((wp->flags & MASTER_WATCH_FLAG_UPDATABLE) == 0) { + msg_warn("ignoring %s parameter value change", wp->name); + msg_warn("old value: \"%s\", new value: \"%s\"", + wp->backup[0], wp->value[0]); + msg_warn("to change %s, stop and start Postfix", wp->name); + } else { + myfree(wp->backup[0]); + wp->backup[0] = 0; + } + } + + /* + * Initialize the backed up parameter value, or update it if this + * parameter supports updates after initialization. Optionally + * notify the application that this parameter has changed. + */ + if (wp->backup[0] == 0) { + if (wp->notify != 0) + wp->notify(); + wp->backup[0] = mystrdup(wp->value[0]); + } + } +} + +/* master_int_watch - watch integer-valued parameters for change */ + +void master_int_watch(MASTER_INT_WATCH *int_watch_table) +{ + MASTER_INT_WATCH *wp; + + for (wp = int_watch_table; wp->name != 0; wp++) { + + /* + * Detect changes to monitored parameter values. If a change is + * supported, we discard the backed up value and update it to the + * current value later. Otherwise we complain. + */ + if ((wp->flags & MASTER_WATCH_FLAG_ISSET) != 0 + && wp->backup != wp->value[0]) { + if ((wp->flags & MASTER_WATCH_FLAG_UPDATABLE) == 0) { + msg_warn("ignoring %s parameter value change", wp->name); + msg_warn("old value: \"%d\", new value: \"%d\"", + wp->backup, wp->value[0]); + msg_warn("to change %s, stop and start Postfix", wp->name); + } else { + wp->flags &= ~MASTER_WATCH_FLAG_ISSET; + } + } + + /* + * Initialize the backed up parameter value, or update if it this + * parameter supports updates after initialization. Optionally + * notify the application that this parameter has changed. + */ + if ((wp->flags & MASTER_WATCH_FLAG_ISSET) == 0) { + if (wp->notify != 0) + wp->notify(); + wp->flags |= MASTER_WATCH_FLAG_ISSET; + wp->backup = wp->value[0]; + } + } +} diff --git a/src/master/multi_server.c b/src/master/multi_server.c new file mode 100644 index 0000000..6150f22 --- /dev/null +++ b/src/master/multi_server.c @@ -0,0 +1,931 @@ +/*++ +/* NAME +/* multi_server 3 +/* SUMMARY +/* skeleton multi-threaded mail subsystem +/* SYNOPSIS +/* #include +/* +/* NORETURN multi_server_main(argc, argv, service, key, value, ...) +/* int argc; +/* char **argv; +/* void (*service)(VSTREAM *stream, char *service_name, char **argv); +/* int key; +/* +/* void multi_server_disconnect(stream) +/* VSTREAM *stream; +/* +/* void multi_server_drain() +/* DESCRIPTION +/* This module implements a skeleton for multi-threaded +/* mail subsystems: mail subsystem programs that service multiple +/* clients at the same time. The resulting program expects to be run +/* from the \fBmaster\fR process. +/* +/* multi_server_main() is the skeleton entry point. It should be +/* called from the application main program. The skeleton does all +/* the generic command-line processing, initialization of +/* configurable parameters, and connection management. +/* The skeleton never returns. +/* +/* Arguments: +/* .IP "void (*service)(VSTREAM *stream, char *service_name, char **argv)" +/* A pointer to a function that is called by the skeleton each +/* time a client sends data to the program's service port. The +/* function is run after the program has optionally dropped its +/* privileges. This function should not attempt to preserve state +/* across calls. The stream initial state is non-blocking mode. +/* The service name argument corresponds to the service name in the +/* master.cf file. +/* The argv argument specifies command-line arguments left over +/* after options processing. +/* .PP +/* Optional arguments are specified as a null-terminated list +/* with macros that have zero or more arguments: +/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. Raw parameters are not subjected to $name +/* evaluation. +/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has read the global configuration file +/* and after it has processed command-line arguments, but before +/* the skeleton has optionally relinquished the process privileges. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has optionally relinquished the process +/* privileges, but before servicing client connection requests. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))" +/* A pointer to function that is executed from +/* within the event loop, whenever an I/O or timer event has happened, +/* or whenever nothing has happened for a specified amount of time. +/* The result value of the function specifies how long to wait until +/* the next event. Specify -1 to wait for "as long as it takes". +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))" +/* A pointer to function that is executed immediately before normal +/* process termination. +/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))" +/* Function to be executed prior to accepting a new connection. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_POST_ACCEPT(void *(VSTREAM *stream, char *service_name, char **argv, HTABLE *attr))" +/* Function to be executed after accepting a new connection. +/* The stream, service_name and argv arguments are the same +/* as with the "service" argument. The attr argument is null +/* or a pointer to a table with 'pass' connection attributes. +/* The table is destroyed after the function returns. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_PRE_DISCONN(VSTREAM *, char *service_name, char **argv)" +/* A pointer to a function that is called +/* by the multi_server_disconnect() function (see below). +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP CA_MAIL_SERVER_IN_FLOW_DELAY +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. +/* .IP CA_MAIL_SERVER_SOLITARY +/* This service must be configured with process limit of 1. +/* .IP CA_MAIL_SERVER_UNLIMITED +/* This service must be configured with process limit of 0. +/* .IP CA_MAIL_SERVER_PRIVILEGED +/* This service must be configured as privileged. +/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)" +/* Initialize the DSN filter for the bounce/defer service +/* clients with the specified map source and map names. +/* .PP +/* multi_server_disconnect() should be called by the application +/* to close a client connection. +/* +/* multi_server_drain() should be called when the application +/* no longer wishes to accept new client connections. Existing +/* clients are handled in a background process, and the process +/* terminates when the last client is disconnected. A non-zero +/* result means this call should be tried again later. +/* +/* The var_use_limit variable limits the number of clients that +/* a server can service before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the client limit. +/* +/* The var_idle_limit variable limits the time that a service +/* receives no client connection requests before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_idle_limit\fR to zero disables the idle limit. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* SEE ALSO +/* master(8), master process +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include /* select() */ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif +#include + +#ifdef USE_SYS_SELECT_H +#include /* select() */ +#endif + +/* Utility library. */ + +#include +#include +#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 +#include +#include + +/* Process manager. */ + +#include "master_proto.h" + +/* Application-specific */ + +#include "mail_server.h" + + /* + * Global state. + */ +static int client_count; +static int use_count; +static int socket_count = 1; + +static void (*multi_server_service) (VSTREAM *, char *, char **); +static char *multi_server_name; +static char **multi_server_argv; +static void (*multi_server_accept) (int, void *); +static void (*multi_server_onexit) (char *, char **); +static void (*multi_server_pre_accept) (char *, char **); +static void (*multi_server_post_accept) (VSTREAM *, char *, char **, HTABLE *); +static VSTREAM *multi_server_lock; +static int multi_server_in_flow_delay; +static unsigned multi_server_generation; +static void (*multi_server_pre_disconn) (VSTREAM *, char *, char **); + +/* multi_server_exit - normal termination */ + +static NORETURN multi_server_exit(void) +{ + if (multi_server_onexit) + multi_server_onexit(multi_server_name, multi_server_argv); + exit(0); +} + +/* multi_server_abort - terminate after abnormal master exit */ + +static void multi_server_abort(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("master disconnect -- exiting"); + multi_server_exit(); +} + +/* multi_server_timeout - idle time exceeded */ + +static void multi_server_timeout(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("idle timeout -- exiting"); + multi_server_exit(); +} + +/* multi_server_drain - stop accepting new clients */ + +int multi_server_drain(void) +{ + const char *myname = "multi_server_drain"; + int fd; + + switch (fork()) { + /* Try again later. */ + case -1: + return (-1); + /* Finish existing clients in the background, then terminate. */ + case 0: + (void) msg_cleanup((MSG_CLEANUP_FN) 0); + event_fork(); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_disable_readwrite(fd); + (void) close(fd); + /* Play safe - don't reuse this file number. */ + if (DUP2(STDIN_FILENO, fd) < 0) + msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd); + } + var_use_limit = 1; + return (0); + /* Let the master start a new process. */ + default: + exit(0); + } +} + +/* multi_server_disconnect - terminate client session */ + +void multi_server_disconnect(VSTREAM *stream) +{ + if (msg_verbose) + msg_info("connection closed fd %d", vstream_fileno(stream)); + if (multi_server_pre_disconn) + multi_server_pre_disconn(stream, multi_server_name, multi_server_argv); + event_disable_readwrite(vstream_fileno(stream)); + (void) vstream_fclose(stream); + client_count--; + /* Avoid integer wrap-around in a persistent process. */ + if (use_count < INT_MAX) + use_count++; + if (client_count == 0 && var_idle_limit > 0) + event_request_timer(multi_server_timeout, (void *) 0, var_idle_limit); +} + +/* multi_server_execute - in case (char *) != (struct *) */ + +static void multi_server_execute(int unused_event, void *context) +{ + VSTREAM *stream = (VSTREAM *) context; + + if (multi_server_lock != 0 + && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + + /* + * Do not bother the application when the client disconnected. Don't drop + * the already accepted client request after "postfix reload"; that would + * be rude. + */ + if (peekfd(vstream_fileno(stream)) > 0) { + if (master_notify(var_pid, multi_server_generation, MASTER_STAT_TAKEN) < 0) + /* void */ ; + multi_server_service(stream, multi_server_name, multi_server_argv); + if (master_notify(var_pid, multi_server_generation, MASTER_STAT_AVAIL) < 0) + multi_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + } else { + multi_server_disconnect(stream); + } +} + +/* multi_server_enable_read - enable read events */ + +static void multi_server_enable_read(int unused_event, void *context) +{ + VSTREAM *stream = (VSTREAM *) context; + + event_enable_read(vstream_fileno(stream), multi_server_execute, (void *) stream); +} + +/* multi_server_wakeup - wake up application */ + +static void multi_server_wakeup(int fd, HTABLE *attr) +{ + VSTREAM *stream; + char *tmp; + +#if defined(F_DUPFD) && (EVENTS_STYLE != EVENTS_STYLE_SELECT) +#ifndef THRESHOLD_FD_WORKAROUND +#define THRESHOLD_FD_WORKAROUND 128 +#endif + int new_fd; + + /* + * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely + * case of a multi-server with a thousand clients. + */ + if (fd < THRESHOLD_FD_WORKAROUND) { + if ((new_fd = fcntl(fd, F_DUPFD, THRESHOLD_FD_WORKAROUND)) < 0) + msg_fatal("fcntl F_DUPFD: %m"); + (void) close(fd); + fd = new_fd; + } +#endif + if (msg_verbose) + msg_info("connection established fd %d", fd); + non_blocking(fd, BLOCKING); + close_on_exec(fd, CLOSE_ON_EXEC); + client_count++; + stream = vstream_fdopen(fd, O_RDWR); + tmp = concatenate(multi_server_name, " socket", (char *) 0); + vstream_control(stream, + CA_VSTREAM_CTL_PATH(tmp), + CA_VSTREAM_CTL_END); + myfree(tmp); + timed_ipc_setup(stream); + if (multi_server_in_flow_delay && mail_flow_get(1) < 0) + event_request_timer(multi_server_enable_read, (void *) stream, + var_in_flow_delay); + else + multi_server_enable_read(0, (void *) stream); + if (multi_server_post_accept) + multi_server_post_accept(stream, multi_server_name, multi_server_argv, attr); + else if (attr) + msg_warn("service ignores 'pass' connection attributes"); + if (attr) + htable_free(attr, myfree); +} + +/* multi_server_accept_local - accept client connection request */ + +static void multi_server_accept_local(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(multi_server_timeout, (void *) 0); + + if (multi_server_pre_accept) + multi_server_pre_accept(multi_server_name, multi_server_argv); + fd = LOCAL_ACCEPT(listen_fd); + if (multi_server_lock != 0 + && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(multi_server_timeout, (void *) 0, time_left); + return; + } + multi_server_wakeup(fd, (HTABLE *) 0); +} + +#ifdef MASTER_XPORT_NAME_PASS + +/* multi_server_accept_pass - accept descriptor */ + +static void multi_server_accept_pass(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + HTABLE *attr = 0; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(multi_server_timeout, (void *) 0); + + if (multi_server_pre_accept) + multi_server_pre_accept(multi_server_name, multi_server_argv); + fd = pass_accept_attr(listen_fd, &attr); + if (multi_server_lock != 0 + && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(multi_server_timeout, (void *) 0, time_left); + return; + } + multi_server_wakeup(fd, attr); +} + +#endif + +/* multi_server_accept_inet - accept client connection request */ + +static void multi_server_accept_inet(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection (the number of processes competing for clients is + * kept small, so this is not a "thundering herd" problem). If the + * accept() succeeds, be sure to disable non-blocking I/O, in order to + * minimize confusion. + */ + if (client_count == 0 && var_idle_limit > 0) + time_left = event_cancel_timer(multi_server_timeout, (void *) 0); + + if (multi_server_pre_accept) + multi_server_pre_accept(multi_server_name, multi_server_argv); + fd = inet_accept(listen_fd); + if (multi_server_lock != 0 + && myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(multi_server_timeout, (void *) 0, time_left); + return; + } + multi_server_wakeup(fd, (HTABLE *) 0); +} + +/* multi_server_main - the real main program */ + +NORETURN multi_server_main(int argc, char **argv, MULTI_SERVER_FN service,...) +{ + const char *myname = "multi_server_main"; + VSTREAM *stream = 0; + char *root_dir = 0; + char *user_name = 0; + int debug_me = 0; + int daemon_mode = 1; + char *service_name = basename(argv[0]); + int delay; + int c; + int fd; + va_list ap; + MAIL_SERVER_INIT_FN pre_init = 0; + MAIL_SERVER_INIT_FN post_init = 0; + MAIL_SERVER_LOOP_FN loop = 0; + int key; + char *transport = 0; + +#if 0 + char *lock_path; + VSTRING *why; + +#endif + int alone = 0; + int zerolimit = 0; + WATCHDOG *watchdog; + char *oname_val; + char *oname; + char *oval; + const char *err; + char *generation; + int msg_vstream_needed = 0; + const char *dsn_filter_title; + const char **dsn_filter_maps; + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Don't die for frivolous reasons. + */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif + + /* + * May need this every now and then. + */ + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + + /* + * Initialize logging and exit handler. Do the syslog first, so that its + * initialization completes before we enter the optional chroot jail. + */ + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + if (msg_verbose) + msg_info("daemon started"); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Initialize from the configuration file. Allow command-line options to + * override compiled-in defaults or configured parameter values. + */ + mail_conf_suck(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; + + /* + * Pick up policy settings from master process. Shut up error messages to + * stderr, because no-one is going to see them. + */ + opterr = 0; + while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) { + switch (c) { + case 'c': + root_dir = "setme"; + break; + case 'd': + daemon_mode = 0; + break; + case 'D': + debug_me = 1; + break; + case 'i': + mail_conf_update(VAR_MAX_IDLE, optarg); + break; + case 'l': + alone = 1; + break; + case 'm': + mail_conf_update(VAR_MAX_USE, optarg); + break; + case 'n': + service_name = optarg; + break; + case 'o': + oname_val = mystrdup(optarg); + if ((err = split_nameval(oname_val, &oname, &oval)) != 0) + msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); + mail_conf_update(oname, oval); + myfree(oname_val); + break; + case 's': + if ((socket_count = atoi(optarg)) <= 0) + msg_fatal("invalid socket_count: %s", optarg); + break; + case 'S': + stream = VSTREAM_IN; + break; + case 'u': + user_name = "setme"; + break; + case 't': + transport = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'V': + if (++msg_vstream_needed == 1) + msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); + break; + case 'z': + zerolimit = 1; + break; + default: + msg_fatal("invalid option: %c", optopt); + break; + } + } + set_mail_conf_str(VAR_SERVNAME, service_name); + + /* + * Initialize generic parameters and re-initialize logging in case of a + * non-default program name or logging destination. + */ + mail_params_init(); + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Register higher-level dictionaries and initialize the support for + * dynamically-loaded dictionaries. + */ + mail_dict_init(); + + /* + * If not connected to stdin, stdin must not be a terminal. + */ + if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { + msg_vstream_init(var_procname, VSTREAM_ERR); + msg_fatal("do not run this command by hand"); + } + + /* + * Application-specific initialization. + */ + va_start(ap, service); + while ((key = va_arg(ap, int)) != 0) { + switch (key) { + case MAIL_SERVER_INT_TABLE: + get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); + break; + case MAIL_SERVER_LONG_TABLE: + get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); + break; + case MAIL_SERVER_STR_TABLE: + get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); + break; + case MAIL_SERVER_BOOL_TABLE: + get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); + break; + case MAIL_SERVER_TIME_TABLE: + get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); + break; + case MAIL_SERVER_RAW_TABLE: + get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); + break; + case MAIL_SERVER_NINT_TABLE: + get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); + break; + case MAIL_SERVER_NBOOL_TABLE: + get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); + break; + case MAIL_SERVER_PRE_INIT: + pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_POST_INIT: + post_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_LOOP: + loop = va_arg(ap, MAIL_SERVER_LOOP_FN); + break; + case MAIL_SERVER_EXIT: + multi_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); + break; + case MAIL_SERVER_PRE_ACCEPT: + multi_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); + break; + case MAIL_SERVER_POST_ACCEPT: + multi_server_post_accept = va_arg(ap, MAIL_SERVER_POST_ACCEPT_FN); + break; + case MAIL_SERVER_PRE_DISCONN: + multi_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN); + break; + case MAIL_SERVER_IN_FLOW_DELAY: + multi_server_in_flow_delay = 1; + break; + case MAIL_SERVER_SOLITARY: + if (stream == 0 && !alone) + msg_fatal("service %s requires a process limit of 1", + service_name); + break; + case MAIL_SERVER_UNLIMITED: + if (stream == 0 && !zerolimit) + msg_fatal("service %s requires a process limit of 0", + service_name); + break; + case MAIL_SERVER_PRIVILEGED: + if (user_name) + msg_fatal("service %s requires privileged operation", + service_name); + break; + case MAIL_SERVER_BOUNCE_INIT: + dsn_filter_title = va_arg(ap, const char *); + dsn_filter_maps = va_arg(ap, const char **); + bounce_client_init(dsn_filter_title, *dsn_filter_maps); + break; + default: + msg_panic("%s: unknown argument type: %d", myname, key); + } + } + va_end(ap); + + if (root_dir) + root_dir = var_queue_dir; + if (user_name) + user_name = var_mail_owner; + + /* + * Can options be required? + */ + if (stream == 0) { + if (transport == 0) + msg_fatal("no transport type specified"); + if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0) + multi_server_accept = multi_server_accept_inet; + else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) + multi_server_accept = multi_server_accept_local; +#ifdef MASTER_XPORT_NAME_PASS + else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) + multi_server_accept = multi_server_accept_pass; +#endif + else + msg_fatal("unsupported transport type: %s", transport); + } + + /* + * Retrieve process generation from environment. + */ + if ((generation = getenv(MASTER_GEN_NAME)) != 0) { + if (!alldig(generation)) + msg_fatal("bad generation: %s", generation); + OCTAL_TO_UNSIGNED(multi_server_generation, generation); + if (msg_verbose) + msg_info("process generation: %s (%o)", + generation, multi_server_generation); + } + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Traditionally, BSD select() can't handle multiple processes selecting + * on the same socket, and wakes up every process in select(). See TCP/IP + * Illustrated volume 2 page 532. We avoid select() collisions with an + * external lock file. + */ + + /* + * XXX Can't compete for exclusive access to the listen socket because we + * also have to monitor existing client connections for service requests. + */ +#if 0 + if (stream == 0 && !alone) { + lock_path = concatenate(DEF_PID_DIR, "/", transport, + ".", service_name, (char *) 0); + why = vstring_alloc(1); + if ((multi_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, + (struct stat *) 0, -1, -1, why)) == 0) + msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); + close_on_exec(vstream_fileno(multi_server_lock), CLOSE_ON_EXEC); + myfree(lock_path); + vstring_free(why); + } +#endif + + /* + * Set up call-back info. + */ + multi_server_service = service; + multi_server_name = service_name; + multi_server_argv = argv + optind; + + /* + * Run pre-jail initialization. + */ + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(\"%s\"): %m", var_queue_dir); + if (pre_init) + pre_init(multi_server_name, multi_server_argv); + + /* + * Optionally, restrict the damage that this process can do. + */ + resolve_local_init(); + tzset(); + chroot_uid(root_dir, user_name); + + /* + * Run post-jail initialization. + */ + if (post_init) + post_init(multi_server_name, multi_server_argv); + + /* + * Are we running as a one-shot server with the client connection on + * standard input? If so, make sure the output is written to stdout so as + * to satisfy common expectation. + */ + if (stream != 0) { + vstream_control(stream, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO), + CA_VSTREAM_CTL_END); + service(stream, multi_server_name, multi_server_argv); + vstream_fflush(stream); + multi_server_exit(); + } + + /* + * Running as a semi-resident server. Service connection requests. + * Terminate when we have serviced a sufficient number of clients, when + * no-one has been talking to us for a configurable amount of time, or + * when the master process terminated abnormally. + */ + if (var_idle_limit > 0) + event_request_timer(multi_server_timeout, (void *) 0, var_idle_limit); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_enable_read(fd, multi_server_accept, CAST_INT_TO_VOID_PTR(fd)); + close_on_exec(fd, CLOSE_ON_EXEC); + } + event_enable_read(MASTER_STATUS_FD, multi_server_abort, (void *) 0); + close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); + watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (void *) 0); + + /* + * The event loop, at last. + */ + while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) { + if (multi_server_lock != 0) { + watchdog_stop(watchdog); + if (myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("select lock: %m"); + } + watchdog_start(watchdog); + delay = loop ? loop(multi_server_name, multi_server_argv) : -1; + event_loop(delay); + } + multi_server_exit(); +} diff --git a/src/master/single_server.c b/src/master/single_server.c new file mode 100644 index 0000000..38f22b7 --- /dev/null +++ b/src/master/single_server.c @@ -0,0 +1,822 @@ +/*++ +/* NAME +/* single_server 3 +/* SUMMARY +/* skeleton single-threaded mail subsystem +/* SYNOPSIS +/* #include +/* +/* NORETURN single_server_main(argc, argv, service, key, value, ...) +/* int argc; +/* char **argv; +/* void (*service)(VSTREAM *stream, char *service_name, char **argv); +/* int key; +/* DESCRIPTION +/* This module implements a skeleton for single-threaded +/* mail subsystems: mail subsystem programs that service one +/* client at a time. The resulting program expects to be run +/* from the \fBmaster\fR process. +/* +/* single_server_main() is the skeleton entry point. It should be +/* called from the application main program. The skeleton does the +/* generic command-line options processing, initialization of +/* configurable parameters, and connection management. +/* The skeleton never returns. +/* +/* Arguments: +/* .IP "void (*service)(VSTREAM *fp, char *service_name, char **argv)" +/* A pointer to a function that is called by the skeleton each time +/* a client connects to the program's service port. The function is +/* run after the program has irrevocably dropped its privileges. +/* The stream initial state is non-blocking mode. +/* Optional connection attributes are provided as a hash that +/* is attached as stream context. +/* The service name argument corresponds to the service name in the +/* master.cf file. +/* The argv argument specifies command-line arguments left over +/* after options processing. +/* .PP +/* Optional arguments are specified as a null-terminated list +/* with macros that have zero or more arguments: +/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. Raw parameters are not subjected to $name +/* evaluation. +/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has read the global configuration file +/* and after it has processed command-line arguments, but before +/* the skeleton has optionally relinquished the process privileges. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has optionally relinquished the process +/* privileges, but before servicing client connection requests. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))" +/* A pointer to function that is executed from +/* within the event loop, whenever an I/O or timer event has happened, +/* or whenever nothing has happened for a specified amount of time. +/* The result value of the function specifies how long to wait until +/* the next event. Specify -1 to wait for "as long as it takes". +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_EXIT(void *(void))" +/* A pointer to function that is executed immediately before normal +/* process termination. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))" +/* Function to be executed prior to accepting a new connection. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_IN_FLOW_DELAY(none)" +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. +/* .IP CA_MAIL_SERVER_SOLITARY +/* This service must be configured with process limit of 1. +/* .IP CA_MAIL_SERVER_UNLIMITED +/* This service must be configured with process limit of 0. +/* .IP CA_MAIL_SERVER_PRIVILEGED +/* This service must be configured as privileged. +/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)" +/* Initialize the DSN filter for the bounce/defer service +/* clients with the specified map source and map names. +/* .IP "CA_MAIL_SERVER_RETIRE_ME" +/* Prevent a process from being reused indefinitely. After +/* (var_max_use * var_max_idle) seconds or some sane constant, +/* terminate voluntarily when the process becomes idle. +/* .PP +/* The var_use_limit variable limits the number of clients +/* that a server can service before it commits suicide. This +/* value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the +/* client limit. +/* +/* The var_idle_limit variable limits the time that a service +/* receives no client connection requests before it commits suicide. +/* Do not change this setting before calling single_server_main(). +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_idle_limit\fR to zero disables the idle limit. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* SEE ALSO +/* master(8), master process +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif +#include + +/* Utility library. */ + +#include +#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 +#include +#include + +/* Process manager. */ + +#include "master_proto.h" + +/* Application-specific */ + +#include "mail_server.h" + + /* + * Global state. + */ +static int use_count; + +static void (*single_server_service) (VSTREAM *, char *, char **); +static char *single_server_name; +static char **single_server_argv; +static void (*single_server_accept) (int, void *); +static void (*single_server_onexit) (char *, char **); +static void (*single_server_pre_accept) (char *, char **); +static VSTREAM *single_server_lock; +static int single_server_in_flow_delay; +static unsigned single_server_generation; + +/* single_server_exit - normal termination */ + +static NORETURN single_server_exit(void) +{ + if (single_server_onexit) + single_server_onexit(single_server_name, single_server_argv); + exit(0); +} + +/* single_server_retire - retire when idle */ + +static NORETURN single_server_retire(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("time to retire -- exiting"); + single_server_exit(); +} + +/* single_server_abort - terminate after abnormal master exit */ + +static void single_server_abort(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("master disconnect -- exiting"); + single_server_exit(); +} + +/* single_server_timeout - idle time exceeded */ + +static void single_server_timeout(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("idle timeout -- exiting"); + single_server_exit(); +} + +/* single_server_wakeup - wake up application */ + +static void single_server_wakeup(int fd, HTABLE *attr) +{ + VSTREAM *stream; + char *tmp; + + /* + * If the accept() succeeds, be sure to disable non-blocking I/O, because + * the application is supposed to be single-threaded. Notice the master + * of our (un)availability to service connection requests. Commit suicide + * when the master process disconnected from us. Don't drop the already + * accepted client request after "postfix reload"; that would be rude. + */ + if (msg_verbose) + msg_info("connection established"); + non_blocking(fd, BLOCKING); + close_on_exec(fd, CLOSE_ON_EXEC); + stream = vstream_fdopen(fd, O_RDWR); + tmp = concatenate(single_server_name, " socket", (char *) 0); + vstream_control(stream, + CA_VSTREAM_CTL_PATH(tmp), + CA_VSTREAM_CTL_CONTEXT((void *) attr), + CA_VSTREAM_CTL_END); + myfree(tmp); + timed_ipc_setup(stream); + if (master_notify(var_pid, single_server_generation, MASTER_STAT_TAKEN) < 0) + /* void */ ; + if (single_server_in_flow_delay && mail_flow_get(1) < 0) + doze(var_in_flow_delay * 1000000); + single_server_service(stream, single_server_name, single_server_argv); + (void) vstream_fclose(stream); + if (master_notify(var_pid, single_server_generation, MASTER_STAT_AVAIL) < 0) + single_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + if (msg_verbose) + msg_info("connection closed"); + /* Avoid integer wrap-around in a persistent process. */ + if (use_count < INT_MAX) + use_count++; + if (var_idle_limit > 0) + event_request_timer(single_server_timeout, (void *) 0, var_idle_limit); + if (attr) + htable_free(attr, myfree); +} + +/* single_server_accept_local - accept client connection request */ + +static void single_server_accept_local(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection. We use select() + accept(), instead of simply + * blocking in accept(), because we must be able to detect that the + * master process has gone away unexpectedly. + */ + if (var_idle_limit > 0) + time_left = event_cancel_timer(single_server_timeout, (void *) 0); + + if (single_server_pre_accept) + single_server_pre_accept(single_server_name, single_server_argv); + fd = LOCAL_ACCEPT(listen_fd); + if (single_server_lock != 0 + && myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(single_server_timeout, (void *) 0, time_left); + return; + } + single_server_wakeup(fd, (HTABLE *) 0); +} + +#ifdef MASTER_XPORT_NAME_PASS + +/* single_server_accept_pass - accept descriptor */ + +static void single_server_accept_pass(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + HTABLE *attr = 0; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection. We use select() + accept(), instead of simply + * blocking in accept(), because we must be able to detect that the + * master process has gone away unexpectedly. + */ + if (var_idle_limit > 0) + time_left = event_cancel_timer(single_server_timeout, (void *) 0); + + if (single_server_pre_accept) + single_server_pre_accept(single_server_name, single_server_argv); + fd = pass_accept_attr(listen_fd, &attr); + if (single_server_lock != 0 + && myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(single_server_timeout, (void *) 0, time_left); + return; + } + single_server_wakeup(fd, attr); +} + +#endif + +/* single_server_accept_inet - accept client connection request */ + +static void single_server_accept_inet(int unused_event, void *context) +{ + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = -1; + int fd; + + /* + * Be prepared for accept() to fail because some other process already + * got the connection. We use select() + accept(), instead of simply + * blocking in accept(), because we must be able to detect that the + * master process has gone away unexpectedly. + */ + if (var_idle_limit > 0) + time_left = event_cancel_timer(single_server_timeout, (void *) 0); + + if (single_server_pre_accept) + single_server_pre_accept(single_server_name, single_server_argv); + fd = inet_accept(listen_fd); + if (single_server_lock != 0 + && myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(single_server_timeout, (void *) 0, time_left); + return; + } + single_server_wakeup(fd, (HTABLE *) 0); +} + +/* single_server_main - the real main program */ + +NORETURN single_server_main(int argc, char **argv, SINGLE_SERVER_FN service,...) +{ + const char *myname = "single_server_main"; + VSTREAM *stream = 0; + char *root_dir = 0; + char *user_name = 0; + int debug_me = 0; + int daemon_mode = 1; + char *service_name = basename(argv[0]); + int delay; + int c; + int socket_count = 1; + int fd; + va_list ap; + MAIL_SERVER_INIT_FN pre_init = 0; + MAIL_SERVER_INIT_FN post_init = 0; + MAIL_SERVER_LOOP_FN loop = 0; + int key; + char *transport = 0; + char *lock_path; + VSTRING *why; + int alone = 0; + int zerolimit = 0; + WATCHDOG *watchdog; + char *oname_val; + char *oname; + char *oval; + const char *err; + char *generation; + int msg_vstream_needed = 0; + const char *dsn_filter_title; + const char **dsn_filter_maps; + int retire_me_from_flags = 0; + int retire_me = 0; + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Don't die for frivolous reasons. + */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif + + /* + * May need this every now and then. + */ + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + + /* + * Initialize logging and exit handler. Do the syslog first, so that its + * initialization completes before we enter the optional chroot jail. + */ + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + if (msg_verbose) + msg_info("daemon started"); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Initialize from the configuration file. Allow command-line options to + * override compiled-in defaults or configured parameter values. + */ + mail_conf_suck(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; + + /* + * Pick up policy settings from master process. Shut up error messages to + * stderr, because no-one is going to see them. + */ + opterr = 0; + while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:r:s:St:uvVz")) > 0) { + switch (c) { + case 'c': + root_dir = "setme"; + break; + case 'd': + daemon_mode = 0; + break; + case 'D': + debug_me = 1; + break; + case 'i': + mail_conf_update(VAR_MAX_IDLE, optarg); + break; + case 'l': + alone = 1; + break; + case 'm': + mail_conf_update(VAR_MAX_USE, optarg); + break; + case 'n': + service_name = optarg; + break; + case 'o': + oname_val = mystrdup(optarg); + if ((err = split_nameval(oname_val, &oname, &oval)) != 0) + msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); + mail_conf_update(oname, oval); + myfree(oname_val); + break; + case 'r': + if ((retire_me_from_flags = atoi(optarg)) <= 0) + msg_fatal("invalid retirement time: %s", optarg); + break; + case 's': + if ((socket_count = atoi(optarg)) <= 0) + msg_fatal("invalid socket_count: %s", optarg); + break; + case 'S': + stream = VSTREAM_IN; + break; + case 'u': + user_name = "setme"; + break; + case 't': + transport = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'V': + if (++msg_vstream_needed == 1) + msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); + break; + case 'z': + zerolimit = 1; + break; + default: + msg_fatal("invalid option: %c", optopt); + break; + } + } + set_mail_conf_str(VAR_SERVNAME, service_name); + + /* + * Initialize generic parameters. + */ + mail_params_init(); + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Register higher-level dictionaries and initialize the support for + * dynamically-loaded dictionaries. + */ + mail_dict_init(); + + /* + * If not connected to stdin, stdin must not be a terminal. + */ + if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { + msg_vstream_init(var_procname, VSTREAM_ERR); + msg_fatal("do not run this command by hand"); + } + + /* + * Application-specific initialization. + */ + va_start(ap, service); + while ((key = va_arg(ap, int)) != 0) { + switch (key) { + case MAIL_SERVER_INT_TABLE: + get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); + break; + case MAIL_SERVER_LONG_TABLE: + get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); + break; + case MAIL_SERVER_STR_TABLE: + get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); + break; + case MAIL_SERVER_BOOL_TABLE: + get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); + break; + case MAIL_SERVER_TIME_TABLE: + get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); + break; + case MAIL_SERVER_RAW_TABLE: + get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); + break; + case MAIL_SERVER_NINT_TABLE: + get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); + break; + case MAIL_SERVER_NBOOL_TABLE: + get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); + break; + case MAIL_SERVER_PRE_INIT: + pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_POST_INIT: + post_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_LOOP: + loop = va_arg(ap, MAIL_SERVER_LOOP_FN); + break; + case MAIL_SERVER_EXIT: + single_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); + break; + case MAIL_SERVER_PRE_ACCEPT: + single_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); + break; + case MAIL_SERVER_IN_FLOW_DELAY: + single_server_in_flow_delay = 1; + break; + case MAIL_SERVER_SOLITARY: + if (stream == 0 && !alone) + msg_fatal("service %s requires a process limit of 1", + service_name); + break; + case MAIL_SERVER_UNLIMITED: + if (stream == 0 && !zerolimit) + msg_fatal("service %s requires a process limit of 0", + service_name); + break; + case MAIL_SERVER_PRIVILEGED: + if (user_name) + msg_fatal("service %s requires privileged operation", + service_name); + break; + case MAIL_SERVER_BOUNCE_INIT: + dsn_filter_title = va_arg(ap, const char *); + dsn_filter_maps = va_arg(ap, const char **); + bounce_client_init(dsn_filter_title, *dsn_filter_maps); + break; + case MAIL_SERVER_RETIRE_ME: + if (retire_me_from_flags > 0) + retire_me = retire_me_from_flags; + else if (var_idle_limit == 0 || var_use_limit == 0 + || var_idle_limit > 18000 / var_use_limit) + retire_me = 18000; + else + retire_me = var_idle_limit * var_use_limit; + break; + default: + msg_panic("%s: unknown argument type: %d", myname, key); + } + } + va_end(ap); + + if (root_dir) + root_dir = var_queue_dir; + if (user_name) + user_name = var_mail_owner; + + /* + * Can options be required? + */ + if (stream == 0) { + if (transport == 0) + msg_fatal("no transport type specified"); + if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0) + single_server_accept = single_server_accept_inet; + else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) + single_server_accept = single_server_accept_local; +#ifdef MASTER_XPORT_NAME_PASS + else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) + single_server_accept = single_server_accept_pass; +#endif + else + msg_fatal("unsupported transport type: %s", transport); + } + + /* + * Retrieve process generation from environment. + */ + if ((generation = getenv(MASTER_GEN_NAME)) != 0) { + if (!alldig(generation)) + msg_fatal("bad generation: %s", generation); + OCTAL_TO_UNSIGNED(single_server_generation, generation); + if (msg_verbose) + msg_info("process generation: %s (%o)", + generation, single_server_generation); + } + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Traditionally, BSD select() can't handle multiple processes selecting + * on the same socket, and wakes up every process in select(). See TCP/IP + * Illustrated volume 2 page 532. We avoid select() collisions with an + * external lock file. + */ + if (stream == 0 && !alone) { + lock_path = concatenate(DEF_PID_DIR, "/", transport, + ".", service_name, (void *) 0); + why = vstring_alloc(1); + if ((single_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, + (struct stat *) 0, -1, -1, why)) == 0) + msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); + close_on_exec(vstream_fileno(single_server_lock), CLOSE_ON_EXEC); + myfree(lock_path); + vstring_free(why); + } + + /* + * Set up call-back info. + */ + single_server_service = service; + single_server_name = service_name; + single_server_argv = argv + optind; + + /* + * Run pre-jail initialization. + */ + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(\"%s\"): %m", var_queue_dir); + if (pre_init) + pre_init(single_server_name, single_server_argv); + + /* + * Optionally, restrict the damage that this process can do. + */ + resolve_local_init(); + tzset(); + chroot_uid(root_dir, user_name); + + /* + * Run post-jail initialization. + */ + if (post_init) + post_init(single_server_name, single_server_argv); + + /* + * Are we running as a one-shot server with the client connection on + * standard input? If so, make sure the output is written to stdout so as + * to satisfy common expectation. + */ + if (stream != 0) { + vstream_control(stream, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO), + CA_VSTREAM_CTL_END); + service(stream, single_server_name, single_server_argv); + vstream_fflush(stream); + single_server_exit(); + } + + /* + * Running as a semi-resident server. Service connection requests. + * Terminate when we have serviced a sufficient number of clients, when + * no-one has been talking to us for a configurable amount of time, or + * when the master process terminated abnormally. + */ + if (var_idle_limit > 0) + event_request_timer(single_server_timeout, (void *) 0, var_idle_limit); + if (retire_me) + event_request_timer(single_server_retire, (void *) 0, retire_me); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_enable_read(fd, single_server_accept, CAST_INT_TO_VOID_PTR(fd)); + close_on_exec(fd, CLOSE_ON_EXEC); + } + event_enable_read(MASTER_STATUS_FD, single_server_abort, (void *) 0); + close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); + watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (void *) 0); + + /* + * The event loop, at last. + */ + while (var_use_limit == 0 || use_count < var_use_limit) { + if (single_server_lock != 0) { + watchdog_stop(watchdog); + if (myflock(vstream_fileno(single_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("select lock: %m"); + } + watchdog_start(watchdog); + delay = loop ? loop(single_server_name, single_server_argv) : -1; + event_loop(delay); + } + single_server_exit(); +} diff --git a/src/master/trigger_server.c b/src/master/trigger_server.c new file mode 100644 index 0000000..c483a9e --- /dev/null +++ b/src/master/trigger_server.c @@ -0,0 +1,809 @@ +/*++ +/* NAME +/* trigger_server 3 +/* SUMMARY +/* skeleton triggered mail subsystem +/* SYNOPSIS +/* #include +/* +/* NORETURN trigger_server_main(argc, argv, service, key, value, ...) +/* int argc; +/* char **argv; +/* void (*service)(char *buf, int len, char *service_name, char **argv); +/* int key; +/* DESCRIPTION +/* This module implements a skeleton for triggered +/* mail subsystems: mail subsystem programs that wake up on +/* client request and perform some activity without further +/* client interaction. This module supports local IPC via FIFOs +/* and via UNIX-domain sockets. The resulting program expects to be +/* run from the \fBmaster\fR process. +/* +/* trigger_server_main() is the skeleton entry point. It should be +/* called from the application main program. The skeleton does the +/* generic command-line options processing, initialization of +/* configurable parameters, and connection management. +/* The skeleton never returns. +/* +/* Arguments: +/* .IP "void (*service)(char *buf, int len, char *service_name, char **argv)" +/* A pointer to a function that is called by the skeleton each time +/* a client connects to the program's service port. The function is +/* run after the program has irrevocably dropped its privileges. +/* The buffer argument specifies the data read from the trigger port; +/* this data corresponds to one or more trigger requests. +/* The len argument specifies how much client data is available. +/* The maximal size of the buffer is specified via the +/* TRIGGER_BUF_SIZE manifest constant. +/* The service name argument corresponds to the service name in the +/* master.cf file. +/* The argv argument specifies command-line arguments left over +/* after options processing. +/* The \fBserver\fR argument provides the following information: +/* .PP +/* Optional arguments are specified as a null-terminated list +/* with macros that have zero or more arguments: +/* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. Raw parameters are not subjected to $name +/* evaluation. +/* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)" +/* A table with configurable parameters, to be loaded from the +/* global Postfix configuration file. Tables are loaded in the +/* order as specified, and multiple instances of the same type +/* are allowed. +/* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has read the global configuration file +/* and after it has processed command-line arguments, but before +/* the skeleton has optionally relinquished the process privileges. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))" +/* A pointer to a function that is called once +/* by the skeleton after it has optionally relinquished the process +/* privileges, but before servicing client connection requests. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))" +/* A pointer to function that is executed from +/* within the event loop, whenever an I/O or timer event has happened, +/* or whenever nothing has happened for a specified amount of time. +/* The result value of the function specifies how long to wait until +/* the next event. Specify -1 to wait for "as long as it takes". +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))" +/* A pointer to function that is executed immediately before normal +/* process termination. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))" +/* Function to be executed prior to accepting a new request. +/* .sp +/* Only the last instance of this parameter type is remembered. +/* .IP "CA_MAIL_SERVER_IN_FLOW_DELAY(none)" +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. +/* .IP CA_MAIL_SERVER_SOLITARY +/* This service must be configured with process limit of 1. +/* .IP CA_MAIL_SERVER_UNLIMITED +/* This service must be configured with process limit of 0. +/* .IP CA_MAIL_SERVER_PRIVILEGED +/* This service must be configured as privileged. +/* .IP "CA_MAIL_SERVER_WATCHDOG(int *)" +/* Override the default 1000s watchdog timeout. The value is +/* used after command-line and main.cf file processing. +/* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)" +/* Initialize the DSN filter for the bounce/defer service +/* clients with the specified map source and map names. +/* .PP +/* The var_use_limit variable limits the number of clients that +/* a server can service before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the client limit. +/* +/* The var_idle_limit variable limits the time that a service +/* receives no client connection requests before it commits suicide. +/* This value is taken from the global \fBmain.cf\fR configuration +/* file. Setting \fBvar_use_limit\fR to zero disables the idle limit. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* Works with FIFO-based services only. +/* SEE ALSO +/* master(8), master process +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif +#include + +/* Utility library. */ + +#include +#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 +#include + +/* Process manager. */ + +#include "master_proto.h" + +/* Application-specific */ + +#include "mail_server.h" + + /* + * Global state. + */ +static int use_count; + +static TRIGGER_SERVER_FN trigger_server_service; +static char *trigger_server_name; +static char **trigger_server_argv; +static void (*trigger_server_accept) (int, void *); +static void (*trigger_server_onexit) (char *, char **); +static void (*trigger_server_pre_accept) (char *, char **); +static VSTREAM *trigger_server_lock; +static int trigger_server_in_flow_delay; +static unsigned trigger_server_generation; +static int trigger_server_watchdog = 1000; + +/* trigger_server_exit - normal termination */ + +static NORETURN trigger_server_exit(void) +{ + if (trigger_server_onexit) + trigger_server_onexit(trigger_server_name, trigger_server_argv); + exit(0); +} + +/* trigger_server_abort - terminate after abnormal master exit */ + +static void trigger_server_abort(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("master disconnect -- exiting"); + trigger_server_exit(); +} + +/* trigger_server_timeout - idle time exceeded */ + +static void trigger_server_timeout(int unused_event, void *unused_context) +{ + if (msg_verbose) + msg_info("idle timeout -- exiting"); + trigger_server_exit(); +} + +/* trigger_server_wakeup - wake up application */ + +static void trigger_server_wakeup(int fd) +{ + char buf[TRIGGER_BUF_SIZE]; + ssize_t len; + + /* + * Commit suicide when the master process disconnected from us. Don't + * drop the already accepted client request after "postfix reload"; that + * would be rude. + */ + if (master_notify(var_pid, trigger_server_generation, MASTER_STAT_TAKEN) < 0) + /* void */ ; + if (trigger_server_in_flow_delay && mail_flow_get(1) < 0) + doze(var_in_flow_delay * 1000000); + if ((len = read(fd, buf, sizeof(buf))) >= 0) + trigger_server_service(buf, len, trigger_server_name, + trigger_server_argv); + if (master_notify(var_pid, trigger_server_generation, MASTER_STAT_AVAIL) < 0) + trigger_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + if (var_idle_limit > 0) + event_request_timer(trigger_server_timeout, (void *) 0, var_idle_limit); + /* Avoid integer wrap-around in a persistent process. */ + if (use_count < INT_MAX) + use_count++; +} + +/* trigger_server_accept_fifo - accept fifo client request */ + +static void trigger_server_accept_fifo(int unused_event, void *context) +{ + const char *myname = "trigger_server_accept_fifo"; + int listen_fd = CAST_ANY_PTR_TO_INT(context); + + if (trigger_server_lock != 0 + && myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + + if (msg_verbose) + msg_info("%s: trigger arrived", myname); + + /* + * Read whatever the other side wrote into the FIFO. The FIFO read end is + * non-blocking so we won't get stuck when multiple processes wake up. + */ + if (trigger_server_pre_accept) + trigger_server_pre_accept(trigger_server_name, trigger_server_argv); + trigger_server_wakeup(listen_fd); +} + +/* trigger_server_accept_local - accept socket client request */ + +static void trigger_server_accept_local(int unused_event, void *context) +{ + const char *myname = "trigger_server_accept_local"; + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = 0; + int fd; + + if (msg_verbose) + msg_info("%s: trigger arrived", myname); + + /* + * Read a message from a socket. Be prepared for accept() to fail because + * some other process already got the connection. The socket is + * non-blocking so we won't get stuck when multiple processes wake up. + * Don't get stuck when the client connects but sends no data. Restart + * the idle timer if this was a false alarm. + */ + if (var_idle_limit > 0) + time_left = event_cancel_timer(trigger_server_timeout, (void *) 0); + + if (trigger_server_pre_accept) + trigger_server_pre_accept(trigger_server_name, trigger_server_argv); + fd = LOCAL_ACCEPT(listen_fd); + if (trigger_server_lock != 0 + && myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(trigger_server_timeout, (void *) 0, time_left); + return; + } + close_on_exec(fd, CLOSE_ON_EXEC); + if (read_wait(fd, 10) == 0) + trigger_server_wakeup(fd); + else if (time_left >= 0) + event_request_timer(trigger_server_timeout, (void *) 0, time_left); + close(fd); +} + +#ifdef MASTER_XPORT_NAME_PASS + +/* trigger_server_accept_pass - accept descriptor */ + +static void trigger_server_accept_pass(int unused_event, void *context) +{ + const char *myname = "trigger_server_accept_pass"; + int listen_fd = CAST_ANY_PTR_TO_INT(context); + int time_left = 0; + int fd; + + if (msg_verbose) + msg_info("%s: trigger arrived", myname); + + /* + * Read a message from a socket. Be prepared for accept() to fail because + * some other process already got the connection. The socket is + * non-blocking so we won't get stuck when multiple processes wake up. + * Don't get stuck when the client connects but sends no data. Restart + * the idle timer if this was a false alarm. + */ + if (var_idle_limit > 0) + time_left = event_cancel_timer(trigger_server_timeout, (void *) 0); + + if (trigger_server_pre_accept) + trigger_server_pre_accept(trigger_server_name, trigger_server_argv); + fd = pass_accept(listen_fd); + if (trigger_server_lock != 0 + && myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_NONE) < 0) + msg_fatal("select unlock: %m"); + if (fd < 0) { + if (errno != EAGAIN) + msg_error("accept connection: %m"); + if (time_left >= 0) + event_request_timer(trigger_server_timeout, (void *) 0, time_left); + return; + } + close_on_exec(fd, CLOSE_ON_EXEC); + if (read_wait(fd, 10) == 0) + trigger_server_wakeup(fd); + else if (time_left >= 0) + event_request_timer(trigger_server_timeout, (void *) 0, time_left); + close(fd); +} + +#endif + +/* trigger_server_main - the real main program */ + +NORETURN trigger_server_main(int argc, char **argv, TRIGGER_SERVER_FN service,...) +{ + const char *myname = "trigger_server_main"; + char *root_dir = 0; + char *user_name = 0; + int debug_me = 0; + int daemon_mode = 1; + char *service_name = basename(argv[0]); + VSTREAM *stream = 0; + int delay; + int c; + int socket_count = 1; + int fd; + va_list ap; + MAIL_SERVER_INIT_FN pre_init = 0; + MAIL_SERVER_INIT_FN post_init = 0; + MAIL_SERVER_LOOP_FN loop = 0; + int key; + char buf[TRIGGER_BUF_SIZE]; + ssize_t len; + char *transport = 0; + char *lock_path; + VSTRING *why; + int alone = 0; + int zerolimit = 0; + WATCHDOG *watchdog; + char *oname_val; + char *oname; + char *oval; + const char *err; + char *generation; + int msg_vstream_needed = 0; + const char *dsn_filter_title; + const char **dsn_filter_maps; + + /* + * Process environment options as early as we can. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Don't die when a process goes away unexpectedly. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Don't die for frivolous reasons. + */ +#ifdef SIGXFSZ + signal(SIGXFSZ, SIG_IGN); +#endif + + /* + * May need this every now and then. + */ + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + + /* + * Initialize logging and exit handler. Do the syslog first, so that its + * initialization completes before we enter the optional chroot jail. + */ + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + if (msg_verbose) + msg_info("daemon started"); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Initialize from the configuration file. Allow command-line options to + * override compiled-in defaults or configured parameter values. + */ + mail_conf_suck(); + + /* + * After database open error, continue execution with reduced + * functionality. + */ + dict_allow_surrogate = 1; + + /* + * Pick up policy settings from master process. Shut up error messages to + * stderr, because no-one is going to see them. + */ + opterr = 0; + while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) { + switch (c) { + case 'c': + root_dir = "setme"; + break; + case 'd': + daemon_mode = 0; + break; + case 'D': + debug_me = 1; + break; + case 'i': + mail_conf_update(VAR_MAX_IDLE, optarg); + break; + case 'l': + alone = 1; + break; + case 'm': + mail_conf_update(VAR_MAX_USE, optarg); + break; + case 'n': + service_name = optarg; + break; + case 'o': + oname_val = mystrdup(optarg); + if ((err = split_nameval(oname_val, &oname, &oval)) != 0) + msg_fatal("invalid \"-o %s\" option value: %s", optarg, err); + mail_conf_update(oname, oval); + myfree(oname_val); + break; + case 's': + if ((socket_count = atoi(optarg)) <= 0) + msg_fatal("invalid socket_count: %s", optarg); + break; + case 'S': + stream = VSTREAM_IN; + break; + case 't': + transport = optarg; + break; + case 'u': + user_name = "setme"; + break; + case 'v': + msg_verbose++; + break; + case 'V': + if (++msg_vstream_needed == 1) + msg_vstream_init(mail_task(var_procname), VSTREAM_ERR); + break; + case 'z': + zerolimit = 1; + break; + default: + msg_fatal("invalid option: %c", optopt); + break; + } + } + set_mail_conf_str(VAR_SERVNAME, service_name); + + /* + * Initialize generic parameters and re-initialize logging in case of a + * non-default program name or logging destination. + */ + mail_params_init(); + maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Register higher-level dictionaries and initialize the support for + * dynamically-loaded dictionaries. + */ + mail_dict_init(); + + /* + * If not connected to stdin, stdin must not be a terminal. + */ + if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) { + msg_vstream_init(var_procname, VSTREAM_ERR); + msg_fatal("do not run this command by hand"); + } + + /* + * Application-specific initialization. + */ + va_start(ap, service); + while ((key = va_arg(ap, int)) != 0) { + switch (key) { + case MAIL_SERVER_INT_TABLE: + get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *)); + break; + case MAIL_SERVER_LONG_TABLE: + get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *)); + break; + case MAIL_SERVER_STR_TABLE: + get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *)); + break; + case MAIL_SERVER_BOOL_TABLE: + get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *)); + break; + case MAIL_SERVER_TIME_TABLE: + get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *)); + break; + case MAIL_SERVER_RAW_TABLE: + get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *)); + break; + case MAIL_SERVER_NINT_TABLE: + get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *)); + break; + case MAIL_SERVER_NBOOL_TABLE: + get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *)); + break; + case MAIL_SERVER_PRE_INIT: + pre_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_POST_INIT: + post_init = va_arg(ap, MAIL_SERVER_INIT_FN); + break; + case MAIL_SERVER_LOOP: + loop = va_arg(ap, MAIL_SERVER_LOOP_FN); + break; + case MAIL_SERVER_EXIT: + trigger_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN); + break; + case MAIL_SERVER_PRE_ACCEPT: + trigger_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); + break; + case MAIL_SERVER_IN_FLOW_DELAY: + trigger_server_in_flow_delay = 1; + break; + case MAIL_SERVER_SOLITARY: + if (stream == 0 && !alone) + msg_fatal("service %s requires a process limit of 1", + service_name); + break; + case MAIL_SERVER_UNLIMITED: + if (stream == 0 && !zerolimit) + msg_fatal("service %s requires a process limit of 0", + service_name); + break; + case MAIL_SERVER_PRIVILEGED: + if (user_name) + msg_fatal("service %s requires privileged operation", + service_name); + break; + case MAIL_SERVER_WATCHDOG: + trigger_server_watchdog = *va_arg(ap, int *); + break; + case MAIL_SERVER_BOUNCE_INIT: + dsn_filter_title = va_arg(ap, const char *); + dsn_filter_maps = va_arg(ap, const char **); + bounce_client_init(dsn_filter_title, *dsn_filter_maps); + break; + default: + msg_panic("%s: unknown argument type: %d", myname, key); + } + } + va_end(ap); + + if (root_dir) + root_dir = var_queue_dir; + if (user_name) + user_name = var_mail_owner; + + /* + * Can options be required? + * + * XXX Initially this code was implemented with UNIX-domain sockets, but + * Solaris <= 2.5 UNIX-domain sockets misbehave hopelessly when the + * client disconnects before the server has accepted the connection. + * Symptom: the server accept() fails with EPIPE or EPROTO, but the + * socket stays readable, so that the program goes into a wasteful loop. + * + * The initial fix was to use FIFOs, but those turn out to have their own + * problems, witness the workarounds in the fifo_listen() routine. + * Therefore we support both FIFOs and UNIX-domain sockets, so that the + * user can choose whatever works best. + * + * Well, I give up. Solaris UNIX-domain sockets still don't work properly, + * so it will have to limp along with a streams-specific alternative. + */ + if (stream == 0) { + if (transport == 0) + msg_fatal("no transport type specified"); + if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0) + trigger_server_accept = trigger_server_accept_local; + else if (strcasecmp(transport, MASTER_XPORT_NAME_FIFO) == 0) + trigger_server_accept = trigger_server_accept_fifo; +#ifdef MASTER_XPORT_NAME_PASS + else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0) + trigger_server_accept = trigger_server_accept_pass; +#endif + else + msg_fatal("unsupported transport type: %s", transport); + } + + /* + * Retrieve process generation from environment. + */ + if ((generation = getenv(MASTER_GEN_NAME)) != 0) { + if (!alldig(generation)) + msg_fatal("bad generation: %s", generation); + OCTAL_TO_UNSIGNED(trigger_server_generation, generation); + if (msg_verbose) + msg_info("process generation: %s (%o)", + generation, trigger_server_generation); + } + + /* + * Optionally start the debugger on ourself. + */ + if (debug_me) + debug_process(); + + /* + * Traditionally, BSD select() can't handle multiple processes selecting + * on the same socket, and wakes up every process in select(). See TCP/IP + * Illustrated volume 2 page 532. We avoid select() collisions with an + * external lock file. + */ + if (stream == 0 && !alone) { + lock_path = concatenate(DEF_PID_DIR, "/", transport, + ".", service_name, (char *) 0); + why = vstring_alloc(1); + if ((trigger_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600, + (struct stat *) 0, -1, -1, why)) == 0) + msg_fatal("open lock file %s: %s", lock_path, vstring_str(why)); + close_on_exec(vstream_fileno(trigger_server_lock), CLOSE_ON_EXEC); + myfree(lock_path); + vstring_free(why); + } + + /* + * Set up call-back info. + */ + trigger_server_service = service; + trigger_server_name = service_name; + trigger_server_argv = argv + optind; + + /* + * Run pre-jail initialization. + */ + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(\"%s\"): %m", var_queue_dir); + if (pre_init) + pre_init(trigger_server_name, trigger_server_argv); + + /* + * Optionally, restrict the damage that this process can do. + */ + resolve_local_init(); + tzset(); + chroot_uid(root_dir, user_name); + + /* + * Run post-jail initialization. + */ + if (post_init) + post_init(trigger_server_name, trigger_server_argv); + + /* + * Are we running as a one-shot server with the client connection on + * standard input? + */ + if (stream != 0) { + if ((len = read(vstream_fileno(stream), buf, sizeof(buf))) <= 0) + msg_fatal("read: %m"); + service(buf, len, trigger_server_name, trigger_server_argv); + vstream_fflush(stream); + trigger_server_exit(); + } + + /* + * Running as a semi-resident server. Service connection requests. + * Terminate when we have serviced a sufficient number of clients, when + * no-one has been talking to us for a configurable amount of time, or + * when the master process terminated abnormally. + */ + if (var_idle_limit > 0) + event_request_timer(trigger_server_timeout, (void *) 0, var_idle_limit); + for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) { + event_enable_read(fd, trigger_server_accept, CAST_INT_TO_VOID_PTR(fd)); + close_on_exec(fd, CLOSE_ON_EXEC); + } + event_enable_read(MASTER_STATUS_FD, trigger_server_abort, (void *) 0); + close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); + watchdog = watchdog_create(trigger_server_watchdog, + (WATCHDOG_FN) 0, (void *) 0); + + /* + * The event loop, at last. + */ + while (var_use_limit == 0 || use_count < var_use_limit) { + if (trigger_server_lock != 0) { + watchdog_stop(watchdog); + if (myflock(vstream_fileno(trigger_server_lock), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("select lock: %m"); + } + watchdog_start(watchdog); + delay = loop ? loop(trigger_server_name, trigger_server_argv) : -1; + event_loop(delay); + } + trigger_server_exit(); +} diff --git a/src/milter/.indent.pro b/src/milter/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/milter/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/milter/Makefile.in b/src/milter/Makefile.in new file mode 100644 index 0000000..98e3ba0 --- /dev/null +++ b/src/milter/Makefile.in @@ -0,0 +1,145 @@ +SHELL = /bin/sh +SRCS = milter.c milter8.c milter_macros.c +OBJS = milter.o milter8.o milter_macros.o +HDRS = milter.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +INCL = +LIB = libmilter.a +TESTPROG= milter test-milter + +LIBS = ../../$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../$(LIB_PREFIX)util$(LIB_SUFFIX) +LIB_DIR = ../../lib +INC_DIR = ../../include +MAKES = + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +all: $(LIB) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +$(LIB): $(OBJS) + $(_AR) $(ARFL) $(LIB) $? + $(_RANLIB) $(LIB) + +$(LIB_DIR)/$(LIB): $(LIB) + cp $(LIB) $(LIB_DIR) + $(_RANLIB) $(LIB_DIR)/$(LIB) + +update: $(LIB_DIR)/$(LIB) $(HDRS) + -for i in $(HDRS); \ + do \ + cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \ + done + cd $(INC_DIR); chmod 644 $(HDRS) + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o $(LIB) *core $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +milter: milter.c $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +test-milter: test-milter.c + cc -g -I/usr/local/include -o $@ $? -L/usr/local/lib -lmilter -lpthread + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +milter.o: ../../include/argv.h +milter.o: ../../include/attr.h +milter.o: ../../include/attr_override.h +milter.o: ../../include/check_arg.h +milter.o: ../../include/htable.h +milter.o: ../../include/iostuff.h +milter.o: ../../include/mail_params.h +milter.o: ../../include/mail_proto.h +milter.o: ../../include/msg.h +milter.o: ../../include/mymalloc.h +milter.o: ../../include/nvtable.h +milter.o: ../../include/rec_type.h +milter.o: ../../include/record.h +milter.o: ../../include/stringops.h +milter.o: ../../include/sys_defs.h +milter.o: ../../include/vbuf.h +milter.o: ../../include/vstream.h +milter.o: ../../include/vstring.h +milter.o: milter.c +milter.o: milter.h +milter8.o: ../../include/argv.h +milter8.o: ../../include/attr.h +milter8.o: ../../include/check_arg.h +milter8.o: ../../include/compat_va_copy.h +milter8.o: ../../include/connect.h +milter8.o: ../../include/header_opts.h +milter8.o: ../../include/htable.h +milter8.o: ../../include/iostuff.h +milter8.o: ../../include/is_header.h +milter8.o: ../../include/mail_params.h +milter8.o: ../../include/mail_proto.h +milter8.o: ../../include/mime_state.h +milter8.o: ../../include/msg.h +milter8.o: ../../include/mymalloc.h +milter8.o: ../../include/name_code.h +milter8.o: ../../include/name_mask.h +milter8.o: ../../include/nvtable.h +milter8.o: ../../include/rec_type.h +milter8.o: ../../include/record.h +milter8.o: ../../include/split_at.h +milter8.o: ../../include/stringops.h +milter8.o: ../../include/sys_defs.h +milter8.o: ../../include/vbuf.h +milter8.o: ../../include/vstream.h +milter8.o: ../../include/vstring.h +milter8.o: milter.h +milter8.o: milter8.c +milter_macros.o: ../../include/argv.h +milter_macros.o: ../../include/attr.h +milter_macros.o: ../../include/check_arg.h +milter_macros.o: ../../include/htable.h +milter_macros.o: ../../include/iostuff.h +milter_macros.o: ../../include/mail_proto.h +milter_macros.o: ../../include/msg.h +milter_macros.o: ../../include/mymalloc.h +milter_macros.o: ../../include/nvtable.h +milter_macros.o: ../../include/sys_defs.h +milter_macros.o: ../../include/vbuf.h +milter_macros.o: ../../include/vstream.h +milter_macros.o: ../../include/vstring.h +milter_macros.o: milter.h +milter_macros.o: milter_macros.c +test-milter.o: test-milter.c diff --git a/src/milter/milter.c b/src/milter/milter.c new file mode 100644 index 0000000..dfd5e1c --- /dev/null +++ b/src/milter/milter.c @@ -0,0 +1,1121 @@ +/*++ +/* NAME +/* milter 3 +/* SUMMARY +/* generic MTA-side mail filter interface +/* SYNOPSIS +/* #include +/* +/* MILTERS *milter_create(milter_names, conn_timeout, cmd_timeout, +/* msg_timeout, protocol, def_action, +/* conn_macros, helo_macros, +/* mail_macros, rcpt_macros, +/* data_macros, eoh_macros, +/* eod_macros, unk_macros, +/* macro_deflts) +/* const char *milter_names; +/* int conn_timeout; +/* int cmd_timeout; +/* int msg_timeout; +/* const char *protocol; +/* const char *def_action; +/* const char *conn_macros; +/* const char *helo_macros; +/* const char *mail_macros; +/* const char *rcpt_macrps; +/* const char *data_macros; +/* const char *eoh_macros; +/* const char *eod_macros; +/* const char *unk_macros; +/* const char *macro_deflts; +/* +/* void milter_free(milters) +/* MILTERS *milters; +/* +/* void milter_macro_callback(milters, mac_lookup, mac_context) +/* const char *(*mac_lookup)(const char *name, void *context); +/* void *mac_context; +/* +/* void milter_edit_callback(milters, add_header, upd_header, +/* ins_header, del_header, chg_from, +/* add_rcpt, add_rcpt_par, del_rcpt, +/* repl_body, context) +/* MILTERS *milters; +/* MILTER_ADD_HEADER_FN add_header; +/* MILTER_EDIT_HEADER_FN upd_header; +/* MILTER_EDIT_HEADER_FN ins_header; +/* MILTER_DEL_HEADER_FN del_header; +/* MILTER_EDIT_FROM_FN chg_from; +/* MILTER_EDIT_RCPT_FN add_rcpt; +/* MILTER_EDIT_RCPT_PAR_FN add_rcpt_par; +/* MILTER_EDIT_RCPT_FN del_rcpt; +/* MILTER_EDIT_BODY_FN repl_body; +/* void *context; +/* +/* const char *milter_conn_event(milters, client_name, client_addr, +/* client_port, addr_family) +/* MILTERS *milters; +/* const char *client_name; +/* const char *client_addr; +/* const char *client_port; +/* int addr_family; +/* +/* const char *milter_disc_event(milters) +/* MILTERS *milters; +/* +/* const char *milter_helo_event(milters, helo_name, esmtp_flag) +/* MILTERS *milters; +/* const char *helo_name; +/* int esmtp_flag; +/* +/* const char *milter_mail_event(milters, argv) +/* MILTERS *milters; +/* const char **argv; +/* +/* const char *milter_rcpt_event(milters, flags, argv) +/* MILTERS *milters; +/* int flags; +/* const char **argv; +/* +/* const char *milter_data_event(milters) +/* MILTERS *milters; +/* +/* const char *milter_unknown_event(milters, command) +/* MILTERS *milters; +/* const char *command; +/* +/* const char *milter_other_event(milters) +/* MILTERS *milters; +/* +/* const char *milter_message(milters, qfile, data_offset, auto_hdrs) +/* MILTERS *milters; +/* VSTREAM *qfile; +/* off_t data_offset; +/* ARGV *auto_hdrs; +/* +/* const char *milter_abort(milters) +/* MILTERS *milters; +/* +/* int milter_send(milters, fp) +/* MILTERS *milters; +/* VSTREAM *fp; +/* +/* MILTERS *milter_receive(fp, count) +/* VSTREAM *fp; +/* int count; +/* +/* int milter_dummy(milters, fp) +/* MILTERS *milters; +/* VSTREAM *fp; +/* DESCRIPTION +/* The functions in this module manage one or more milter (mail +/* filter) clients. Currently, only the Sendmail 8 filter +/* protocol is supported. +/* +/* The functions that inspect content or envelope commands +/* return either an SMTP reply ([45]XX followed by enhanced +/* status code and text), "D" (discard), "H" (quarantine), +/* "S" (shutdown connection), or a null pointer, which means +/* "no news is good news". +/* +/* milter_create() instantiates the milter clients specified +/* with the milter_names argument. The conn_macros etc. +/* arguments specify the names of macros that are sent to the +/* mail filter applications upon a connect etc. event, and the +/* macro_deflts argument specifies macro defaults that will be used +/* only if the application's lookup call-back returns null. This +/* function should be called during process initialization, +/* before entering a chroot jail. The timeout parameters specify +/* time limits for the completion of the specified request +/* classes. The protocol parameter specifies a protocol version +/* and optional extensions. When the milter application is +/* unavailable, the milter client will go into a suitable error +/* state as specified with the def_action parameter (i.e. +/* reject, tempfail or accept all subsequent events). +/* +/* milter_free() disconnects from the milter instances that +/* are still opened, and destroys the data structures created +/* by milter_create(). This function is safe to call at any +/* point after milter_create(). +/* +/* milter_macro_callback() specifies a call-back function and +/* context for macro lookup. This function must be called +/* before milter_conn_event(). +/* +/* milter_edit_callback() specifies call-back functions and +/* context for editing the queue file after the end-of-data +/* is received. This function must be called before milter_message(); +/* +/* milter_conn_event() reports an SMTP client connection event +/* to the specified milter instances, after sending the macros +/* specified with the milter_create() conn_macros argument. +/* This function must be called before reporting any other +/* events. +/* +/* milter_disc_event() reports an SMTP client disconnection +/* event to the specified milter instances. No events can +/* reported after this call. To simplify usage, redundant calls +/* of this function are NO-OPs and don't raise a run-time +/* error. +/* +/* milter_helo_event() reports a HELO or EHLO event to the +/* specified milter instances, after sending the macros that +/* were specified with the milter_create() helo_macros argument. +/* +/* milter_mail_event() reports a MAIL FROM event to the specified +/* milter instances, after sending the macros that were specified +/* with the milter_create() mail_macros argument. +/* +/* milter_rcpt_event() reports an RCPT TO event to the specified +/* milter instances, after sending the macros that were specified +/* with the milter_create() rcpt_macros argument. The flags +/* argument supports the following: +/* .IP MILTER_FLAG_WANT_RCPT_REJ +/* When this flag is cleared, invoke all milters. When this +/* flag is set, invoke only milters that want to receive +/* rejected recipients; with Sendmail V8 Milters, {rcpt_mailer} +/* is set to "error", {rcpt_host} is set to an enhanced status +/* code, and {rcpt_addr} is set to descriptive text. +/* .PP +/* milter_data_event() reports a DATA event to the specified +/* milter instances, after sending the macros that were specified +/* with the milter_create() data_macros argument. +/* +/* milter_unknown_event() reports an unknown command event to +/* the specified milter instances, after sending the macros +/* that were specified with the milter_create() unk_macros +/* argument. +/* +/* milter_other_event() returns the current default mail filter +/* reply for the current SMTP connection state; it does not +/* change milter states. A null pointer result means that all +/* is well. This function can be used for SMTP commands such +/* as AUTH, STARTTLS that don't have their own milter event +/* routine. +/* +/* milter_message() sends the message header and body to the +/* to the specified milter instances, and sends the macros +/* specified with the milter_create() eoh_macros after the +/* message header, and with the eod_macros argument at +/* the end. Each milter sees the result of any changes made +/* by a preceding milter. This function must be called with +/* as argument an open Postfix queue file. +/* +/* milter_abort() cancels a mail transaction in progress. To +/* simplify usage, redundant calls of this function are NO-OPs +/* and don't raise a run-time error. +/* +/* milter_send() sends a list of mail filters over the specified +/* stream. When given a null list pointer, a "no filter" +/* indication is sent. The result is non-zero in case of +/* error. +/* +/* milter_receive() receives the specified number of mail +/* filters over the specified stream. The result is a null +/* pointer when no milters were sent, or when an error happened. +/* +/* milter_dummy() is like milter_send(), except that it sends +/* a dummy, but entirely valid, mail filter list. +/* SEE ALSO +/* milter8(3) Sendmail 8 Milter protocol +/* DIAGNOSTICS +/* Panic: interface violation. +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Postfix Milter library. */ + +#include + +/* Application-specific. */ + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* milter_macro_defaults_create - parse default macro entries */ + +HTABLE *milter_macro_defaults_create(const char *macro_defaults) +{ + const char myname[] = "milter_macro_defaults_create"; + char *saved_defaults = mystrdup(macro_defaults); + char *cp = saved_defaults; + HTABLE *table = 0; + VSTRING *canon_buf = 0; + char *nameval; + + while ((nameval = mystrtokq(&cp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) { + const char *err; + char *name; + char *value; + + /* + * Split the input into (name, value) pairs. Allow the forms + * name=value and { name = value }, where the last form ignores + * whitespace after the opening "{", around the "=", and before the + * closing "}". A name may also be specified as {name}. + * + * Use the form {name} for table lookups, because that is the form of + * the S8_MAC_* macro names. + */ + if (*nameval == CHARS_BRACE[0] + && nameval[balpar(nameval, CHARS_BRACE)] != '=' + && (err = extpar(&nameval, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0) + msg_fatal("malformed default macro entry: %s in \"%s\"", + err, macro_defaults); + if ((err = split_nameval(nameval, &name, &value)) != 0) + msg_fatal("malformed default macro entry: %s in \"%s\"", + err, macro_defaults); + if (*name != '{') /* } */ + name = STR(vstring_sprintf(canon_buf ? canon_buf : + (canon_buf = vstring_alloc(20)), "{%s}", name)); + if (table == 0) + table = htable_create(1); + if (htable_find(table, name) != 0) { + msg_warn("ignoring multiple default macro entries for %s in \"%s\"", + name, macro_defaults); + } else { + (void) htable_enter(table, name, mystrdup(value)); + if (msg_verbose) + msg_info("%s: add name=%s default=%s", myname, name, value); + } + } + myfree(saved_defaults); + if (canon_buf) + vstring_free(canon_buf); + return (table); +} + +/* milter_macro_lookup - look up macros */ + +static ARGV *milter_macro_lookup(MILTERS *milters, const char *macro_names) +{ + const char *myname = "milter_macro_lookup"; + char *saved_names = mystrdup(macro_names); + char *cp = saved_names; + ARGV *argv = argv_alloc(10); + VSTRING *canon_buf = vstring_alloc(20); + const char *value; + const char *name; + const char *cname; + + while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) { + if (msg_verbose) + msg_info("%s: \"%s\"", myname, name); + if (*name != '{') /* } */ + cname = STR(vstring_sprintf(canon_buf, "{%s}", name)); + else + cname = name; + if ((value = milters->mac_lookup(cname, milters->mac_context)) != 0) { + if (msg_verbose) + msg_info("%s: result \"%s\"", myname, value); + argv_add(argv, name, value, (char *) 0); + } else if (milters->macro_defaults != 0 + && (value = htable_find(milters->macro_defaults, cname)) != 0) { + if (msg_verbose) + msg_info("%s: using default \"%s\"", myname, value); + argv_add(argv, name, value, (char *) 0); + } + } + myfree(saved_names); + vstring_free(canon_buf); + return (argv); +} + +/* milter_macro_callback - specify macro lookup */ + +void milter_macro_callback(MILTERS *milters, + const char *(*mac_lookup) (const char *, void *), + void *mac_context) +{ + milters->mac_lookup = mac_lookup; + milters->mac_context = mac_context; +} + +/* milter_edit_callback - specify queue file edit call-back information */ + +void milter_edit_callback(MILTERS *milters, + MILTER_ADD_HEADER_FN add_header, + MILTER_EDIT_HEADER_FN upd_header, + MILTER_EDIT_HEADER_FN ins_header, + MILTER_DEL_HEADER_FN del_header, + MILTER_EDIT_FROM_FN chg_from, + MILTER_EDIT_RCPT_FN add_rcpt, + MILTER_EDIT_RCPT_PAR_FN add_rcpt_par, + MILTER_EDIT_RCPT_FN del_rcpt, + MILTER_EDIT_BODY_FN repl_body, + void *chg_context) +{ + milters->add_header = add_header; + milters->upd_header = upd_header; + milters->ins_header = ins_header; + milters->del_header = del_header; + milters->chg_from = chg_from; + milters->add_rcpt = add_rcpt; + milters->add_rcpt_par = add_rcpt_par; + milters->del_rcpt = del_rcpt; + milters->repl_body = repl_body; + milters->chg_context = chg_context; +} + +/* milter_conn_event - report connect event */ + +const char *milter_conn_event(MILTERS *milters, + const char *client_name, + const char *client_addr, + const char *client_port, + unsigned addr_family) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + +#define MILTER_MACRO_EVAL(global_macros, m, milters, member) \ + ((m->macros && m->macros->member[0]) ? \ + milter_macro_lookup(milters, m->macros->member) : \ + global_macros ? global_macros : \ + (global_macros = \ + milter_macro_lookup(milters, milters->macros->member))) + + if (msg_verbose) + msg_info("report connect to all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + if (m->connect_on_demand != 0) + m->connect_on_demand(m); + any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, conn_macros); + resp = m->conn_event(m, client_name, client_addr, client_port, + addr_family, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_helo_event - report helo event */ + +const char *milter_helo_event(MILTERS *milters, const char *helo_name, + int esmtp_flag) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + + if (msg_verbose) + msg_info("report helo to all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, helo_macros); + resp = m->helo_event(m, helo_name, esmtp_flag, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_mail_event - report mail from event */ + +const char *milter_mail_event(MILTERS *milters, const char **argv) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + + if (msg_verbose) + msg_info("report sender to all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, mail_macros); + resp = m->mail_event(m, argv, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_rcpt_event - report rcpt to event */ + +const char *milter_rcpt_event(MILTERS *milters, int flags, const char **argv) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + + if (msg_verbose) + msg_info("report recipient to all milters (flags=0x%x)", flags); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + if ((flags & MILTER_FLAG_WANT_RCPT_REJ) == 0 + || (m->flags & MILTER_FLAG_WANT_RCPT_REJ) != 0) { + any_macros = + MILTER_MACRO_EVAL(global_macros, m, milters, rcpt_macros); + resp = m->rcpt_event(m, argv, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_data_event - report data event */ + +const char *milter_data_event(MILTERS *milters) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + + if (msg_verbose) + msg_info("report data to all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, data_macros); + resp = m->data_event(m, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_unknown_event - report unknown command */ + +const char *milter_unknown_event(MILTERS *milters, const char *command) +{ + const char *resp; + MILTER *m; + ARGV *global_macros = 0; + ARGV *any_macros; + + if (msg_verbose) + msg_info("report unknown command to all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, unk_macros); + resp = m->unknown_event(m, command, any_macros); + if (any_macros != global_macros) + argv_free(any_macros); + } + if (global_macros) + argv_free(global_macros); + return (resp); +} + +/* milter_other_event - other SMTP event */ + +const char *milter_other_event(MILTERS *milters) +{ + const char *resp; + MILTER *m; + + if (msg_verbose) + msg_info("query milter states for other event"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) + resp = m->other_event(m); + return (resp); +} + +/* milter_message - inspect message content */ + +const char *milter_message(MILTERS *milters, VSTREAM *fp, off_t data_offset, + ARGV *auto_hdrs) +{ + const char *resp; + MILTER *m; + ARGV *global_eoh_macros = 0; + ARGV *global_eod_macros = 0; + ARGV *any_eoh_macros; + ARGV *any_eod_macros; + + if (msg_verbose) + msg_info("inspect content by all milters"); + for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) { + any_eoh_macros = MILTER_MACRO_EVAL(global_eoh_macros, m, milters, eoh_macros); + any_eod_macros = MILTER_MACRO_EVAL(global_eod_macros, m, milters, eod_macros); + resp = m->message(m, fp, data_offset, any_eoh_macros, any_eod_macros, + auto_hdrs); + if (any_eoh_macros != global_eoh_macros) + argv_free(any_eoh_macros); + if (any_eod_macros != global_eod_macros) + argv_free(any_eod_macros); + } + if (global_eoh_macros) + argv_free(global_eoh_macros); + if (global_eod_macros) + argv_free(global_eod_macros); + return (resp); +} + +/* milter_abort - cancel message receiving state, all milters */ + +void milter_abort(MILTERS *milters) +{ + MILTER *m; + + if (msg_verbose) + msg_info("abort all milters"); + for (m = milters->milter_list; m != 0; m = m->next) + m->abort(m); +} + +/* milter_disc_event - report client disconnect event to all milters */ + +void milter_disc_event(MILTERS *milters) +{ + MILTER *m; + + if (msg_verbose) + msg_info("disconnect event to all milters"); + for (m = milters->milter_list; m != 0; m = m->next) + m->disc_event(m); +} + + /* + * Table-driven parsing of main.cf parameter overrides for specific Milters. + * We derive the override names from the corresponding main.cf parameter + * names by skipping the redundant "milter_" prefix. + */ +static ATTR_OVER_TIME time_table[] = { + 7 + (const char *) VAR_MILT_CONN_TIME, DEF_MILT_CONN_TIME, 0, 1, 0, + 7 + (const char *) VAR_MILT_CMD_TIME, DEF_MILT_CMD_TIME, 0, 1, 0, + 7 + (const char *) VAR_MILT_MSG_TIME, DEF_MILT_MSG_TIME, 0, 1, 0, + 0, +}; +static ATTR_OVER_STR str_table[] = { + 7 + (const char *) VAR_MILT_PROTOCOL, 0, 1, 0, + 7 + (const char *) VAR_MILT_DEF_ACTION, 0, 1, 0, + 0, +}; + +#define link_override_table_to_variable(table, var) \ + do { table[var##_offset].target = &var; } while (0) + +#define my_conn_timeout_offset 0 +#define my_cmd_timeout_offset 1 +#define my_msg_timeout_offset 2 + +#define my_protocol_offset 0 +#define my_def_action_offset 1 + +/* milter_new - create milter list */ + +MILTERS *milter_new(const char *names, + int conn_timeout, + int cmd_timeout, + int msg_timeout, + const char *protocol, + const char *def_action, + MILTER_MACROS *macros, + HTABLE *macro_defaults) +{ + MILTERS *milters; + MILTER *head = 0; + MILTER *tail = 0; + char *name; + MILTER *milter; + const char *sep = CHARS_COMMA_SP; + const char *parens = CHARS_BRACE; + int my_conn_timeout; + int my_cmd_timeout; + int my_msg_timeout; + const char *my_protocol; + const char *my_def_action; + + /* + * Initialize. + */ + link_override_table_to_variable(time_table, my_conn_timeout); + link_override_table_to_variable(time_table, my_cmd_timeout); + link_override_table_to_variable(time_table, my_msg_timeout); + link_override_table_to_variable(str_table, my_protocol); + link_override_table_to_variable(str_table, my_def_action); + + /* + * Parse the milter list. + */ + milters = (MILTERS *) mymalloc(sizeof(*milters)); + if (names != 0 && *names != 0) { + char *saved_names = mystrdup(names); + char *cp = saved_names; + char *op; + char *err; + + /* + * Instantiate Milters, allowing for per-Milter overrides. + */ + while ((name = mystrtokq(&cp, sep, parens)) != 0) { + my_conn_timeout = conn_timeout; + my_cmd_timeout = cmd_timeout; + my_msg_timeout = msg_timeout; + my_protocol = protocol; + my_def_action = def_action; + if (name[0] == parens[0]) { + op = name; + if ((err = extpar(&op, parens, EXTPAR_FLAG_NONE)) != 0) + msg_fatal("milter service syntax error: %s", err); + if ((name = mystrtok(&op, sep)) == 0) + msg_fatal("empty milter definition: \"%s\"", names); + attr_override(op, sep, parens, + CA_ATTR_OVER_STR_TABLE(str_table), + CA_ATTR_OVER_TIME_TABLE(time_table), + CA_ATTR_OVER_END); + } + milter = milter8_create(name, my_conn_timeout, my_cmd_timeout, + my_msg_timeout, my_protocol, + my_def_action, milters); + if (head == 0) { + head = milter; + } else { + tail->next = milter; + } + tail = milter; + } + myfree(saved_names); + } + milters->milter_list = head; + milters->mac_lookup = 0; + milters->mac_context = 0; + milters->macros = macros; + milters->macro_defaults = macro_defaults; + milters->add_header = 0; + milters->upd_header = milters->ins_header = 0; + milters->del_header = 0; + milters->add_rcpt = milters->del_rcpt = 0; + milters->repl_body = 0; + milters->chg_context = 0; + return (milters); +} + +/* milter_free - destroy all milters */ + +void milter_free(MILTERS *milters) +{ + MILTER *m; + MILTER *next; + + if (msg_verbose) + msg_info("free all milters"); + for (m = milters->milter_list; m != 0; m = next) + next = m->next, m->free(m); + if (milters->macros) + milter_macros_free(milters->macros); + if (milters->macro_defaults) + htable_free(milters->macro_defaults, myfree); + myfree((void *) milters); +} + +/* milter_dummy - send empty milter list */ + +int milter_dummy(MILTERS *milters, VSTREAM *stream) +{ + MILTERS dummy = *milters; + + dummy.milter_list = 0; + return (milter_send(&dummy, stream)); +} + +/* milter_send - send Milter instances over stream */ + +int milter_send(MILTERS *milters, VSTREAM *stream) +{ + MILTER *m; + int status = 0; + int count = 0; + + /* + * XXX Optimization: send only the filters that are actually used in the + * remote process. No point sending a filter that looks at HELO commands + * to a cleanup server. For now we skip only the filters that are known + * to be disabled (either in global error state or in global accept + * state). + * + * XXX We must send *some* information, even when there are no active + * filters, otherwise the cleanup server would try to apply its own + * non_smtpd_milters settings. + */ + if (milters != 0) + for (m = milters->milter_list; m != 0; m = m->next) + if (m->active(m)) + count++; + (void) rec_fprintf(stream, REC_TYPE_MILT_COUNT, "%d", count); + + if (msg_verbose) + msg_info("send %d milters", count); + + /* + * XXX Optimization: don't send or receive further information when there + * aren't any active filters. + */ + if (count <= 0) + return (0); + + /* + * Send the filter macro name lists. + */ + (void) attr_print(stream, ATTR_FLAG_MORE, + SEND_ATTR_FUNC(milter_macros_print, + (const void *) milters->macros), + ATTR_TYPE_END); + + /* + * Send the filter macro defaults. + */ + count = milters->macro_defaults ? milters->macro_defaults->used : 0; + (void) attr_print(stream, ATTR_FLAG_MORE, + SEND_ATTR_INT(MAIL_ATTR_SIZE, count), + ATTR_TYPE_END); + if (count > 0) + (void) attr_print(stream, ATTR_FLAG_MORE, + SEND_ATTR_HASH(milters->macro_defaults), + ATTR_TYPE_END); + + /* + * Send the filter instances. + */ + for (m = milters->milter_list; m != 0; m = m->next) + if (m->active(m) && (status = m->send(m, stream)) != 0) + break; + + /* + * Over to you. + */ + if (status != 0 + || attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + ATTR_TYPE_END) != 1 + || status != 0) { + msg_warn("cannot send milters to service %s", VSTREAM_PATH(stream)); + return (-1); + } + return (0); +} + +/* milter_receive - receive milters from stream */ + +MILTERS *milter_receive(VSTREAM *stream, int count) +{ + MILTERS *milters; + MILTER *head = 0; + MILTER *tail = 0; + MILTER *milter = 0; + int macro_default_count; + + if (msg_verbose) + msg_info("receive %d milters", count); + + /* + * XXX We must instantiate a MILTERS structure even when the sender has + * no active filters, otherwise the cleanup server would try to use its + * own non_smtpd_milters settings. + */ +#define NO_MILTERS ((char *) 0) +#define NO_TIMEOUTS 0, 0, 0 +#define NO_PROTOCOL ((char *) 0) +#define NO_ACTION ((char *) 0) +#define NO_MACROS ((MILTER_MACROS *) 0) +#define NO_MACRO_DEFLTS ((HTABLE *) 0) + + milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION, + NO_MACROS, NO_MACRO_DEFLTS); + + /* + * XXX Optimization: don't send or receive further information when there + * aren't any active filters. + */ + if (count <= 0) + return (milters); + + /* + * Receive the global macro name lists. + */ + milters->macros = milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO); + if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, + RECV_ATTR_FUNC(milter_macros_scan, + (void *) milters->macros), + ATTR_TYPE_END) != 1) { + milter_free(milters); + return (0); + } + + /* + * Receive the filter macro defaults. + */ + if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, + RECV_ATTR_INT(MAIL_ATTR_SIZE, ¯o_default_count), + ATTR_TYPE_END) != 1 + || (macro_default_count > 0 + && attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, + RECV_ATTR_HASH(milters->macro_defaults + = htable_create(1)), + ATTR_TYPE_END) != macro_default_count)) { + milter_free(milters); + return (0); + } + + /* + * Receive the filters. + */ + for (; count > 0; count--) { + if ((milter = milter8_receive(stream, milters)) == 0) { + msg_warn("cannot receive milters via service %s socket", + VSTREAM_PATH(stream)); + milter_free(milters); + return (0); + } + if (head == 0) { + /* Coverity: milter_free() depends on milters->milter_list. */ + milters->milter_list = head = milter; + } else { + tail->next = milter; + } + tail = milter; + } + + /* + * Over to you. + */ + (void) attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, 0), + ATTR_TYPE_END); + return (milters); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. This can be used interactively, but is + * typically used for automated regression tests from a script. + */ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include "msg_vstream.h" +#include "vstring_vstream.h" + +/* Global library. */ + +#include + +int var_milt_conn_time = 10; +int var_milt_cmd_time = 10; +int var_milt_msg_time = 100; +char *var_milt_protocol = DEF_MILT_PROTOCOL; +char *var_milt_def_action = DEF_MILT_DEF_ACTION; + +static void usage(void) +{ + vstream_fprintf(VSTREAM_ERR, "usage: \n" + " create names... create and connect\n" +#if 0 + " conn_macros names... define connect macros\n" + " helo_macros names... define helo command macros\n" + " mail_macros names... define mail command macros\n" + " rcpt_macros names... define rcpt command macros\n" + " data_macros names... define data command macros\n" + " unk_macros names... unknown command macros\n" + " message_macros names... define message macros\n" +#endif + " free disconnect and destroy\n" + " connect name addr port family\n" + " helo hostname\n" + " ehlo hostname\n" + " mail from sender...\n" + " rcpt to recipient...\n" + " data\n" + " disconnect\n" + " unknown command\n"); + vstream_fflush(VSTREAM_ERR); +} + +int main(int argc, char **argv) +{ + MILTERS *milters = 0; + char *conn_macros, *helo_macros, *mail_macros, *rcpt_macros; + char *data_macros, *eoh_macros, *eod_macros, *unk_macros; + char *macro_deflts; + VSTRING *inbuf = vstring_alloc(100); + char *bufp; + char *cmd; + int ch; + int istty = isatty(vstream_fileno(VSTREAM_IN)); + + conn_macros = helo_macros = mail_macros = rcpt_macros = data_macros + = eoh_macros = eod_macros = unk_macros = macro_deflts = ""; + + msg_vstream_init(argv[0], VSTREAM_ERR); + while ((ch = GETOPT(argc, argv, "V:v")) > 0) { + switch (ch) { + default: + msg_fatal("usage: %s [-a action] [-p protocol] [-v]", argv[0]); + case 'a': + var_milt_def_action = optarg; + break; + case 'p': + var_milt_protocol = optarg; + break; + case 'v': + msg_verbose++; + break; + } + } + optind = OPTIND; + + for (;;) { + const char *resp = 0; + ARGV *argv; + char **args; + + if (istty) { + vstream_printf("- "); + vstream_fflush(VSTREAM_OUT); + } + if (vstring_fgets_nonl(inbuf, VSTREAM_IN) <= 0) + break; + bufp = vstring_str(inbuf); + if (!istty) { + vstream_printf("> %s\n", bufp); + vstream_fflush(VSTREAM_OUT); + } + if (*bufp == '#') + continue; + cmd = mystrtok(&bufp, " "); + if (cmd == 0) { + usage(); + continue; + } + argv = argv_split(bufp, " "); + args = argv->argv; + if (strcmp(cmd, "create") == 0 && argv->argc == 1) { + if (milters != 0) { + msg_warn("deleting existing milters"); + milter_free(milters); + } + milters = milter_create(args[0], var_milt_conn_time, + var_milt_cmd_time, var_milt_msg_time, + var_milt_protocol, var_milt_def_action, + conn_macros, helo_macros, mail_macros, + rcpt_macros, data_macros, eoh_macros, + eod_macros, unk_macros, macro_deflts); + } else if (strcmp(cmd, "free") == 0 && argv->argc == 0) { + if (milters == 0) { + msg_warn("no milters"); + continue; + } + milter_free(milters); + milters = 0; + } else if (strcmp(cmd, "connect") == 0 && argv->argc == 4) { + if (milters == 0) { + msg_warn("no milters"); + continue; + } + resp = milter_conn_event(milters, args[0], args[1], args[2], + strcmp(args[3], "AF_INET") == 0 ? AF_INET : + strcmp(args[3], "AF_INET6") == 0 ? AF_INET6 : + strcmp(args[3], "AF_UNIX") == 0 ? AF_UNIX : + AF_UNSPEC); + } else if (strcmp(cmd, "helo") == 0 && argv->argc == 1) { + if (milters == 0) { + msg_warn("no milters"); + continue; + } + resp = milter_helo_event(milters, args[0], 0); + } else if (strcmp(cmd, "ehlo") == 0 && argv->argc == 1) { + if (milters == 0) { + msg_warn("no milters"); + continue; + } + resp = milter_helo_event(milters, args[0], 1); + } else if (strcmp(cmd, "mail") == 0 && argv->argc > 0) { + if (milters == 0) { + msg_warn("no milters"); + continue; + } + resp = milter_mail_event(milters, (const char **) args); + } else if (strcmp(cmd, "rcpt") == 0 && argv->argc > 0) { + if (milters == 0) { + msg_warn("no milters"); + continue; + } + resp = milter_rcpt_event(milters, 0, (const char **) args); + } else if (strcmp(cmd, "unknown") == 0 && argv->argc > 0) { + if (milters == 0) { + msg_warn("no milters"); + continue; + } + resp = milter_unknown_event(milters, args[0]); + } else if (strcmp(cmd, "data") == 0 && argv->argc == 0) { + if (milters == 0) { + msg_warn("no milters"); + continue; + } + resp = milter_data_event(milters); + } else if (strcmp(cmd, "disconnect") == 0 && argv->argc == 0) { + if (milters == 0) { + msg_warn("no milters"); + continue; + } + milter_disc_event(milters); + } else { + usage(); + } + if (resp != 0) + msg_info("%s", resp); + argv_free(argv); + } + if (milters != 0) + milter_free(milters); + vstring_free(inbuf); + return (0); +} + +#endif diff --git a/src/milter/milter.h b/src/milter/milter.h new file mode 100644 index 0000000..3a1e3f9 --- /dev/null +++ b/src/milter/milter.h @@ -0,0 +1,223 @@ +#ifndef _MILTER_H_INCLUDED_ +#define _MILTER_H_INCLUDED_ + +/*++ +/* NAME +/* milter 3h +/* SUMMARY +/* smtp server +/* SYNOPSIS +/* Postfix MTA-side Milter implementation +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include + + /* + * Each Milter handle is an element of a null-terminated linked list. The + * functions are virtual so that we can support multiple MTA-side Milter + * implementations. The Sendmail 8 and Sendmail X Milter-side APIs are too + * different to implement the MTA side as a single hybrid. + */ +typedef struct MILTER { + char *name; /* full name including transport */ + int flags; /* see below */ + struct MILTER *next; /* linkage */ + struct MILTERS *parent; /* parent information */ + struct MILTER_MACROS *macros; /* private macros */ + void (*connect_on_demand) (struct MILTER *); + const char *(*conn_event) (struct MILTER *, const char *, const char *, const char *, unsigned, ARGV *); + const char *(*helo_event) (struct MILTER *, const char *, int, ARGV *); + const char *(*mail_event) (struct MILTER *, const char **, ARGV *); + const char *(*rcpt_event) (struct MILTER *, const char **, ARGV *); + const char *(*data_event) (struct MILTER *, ARGV *); + const char *(*message) (struct MILTER *, VSTREAM *, off_t, ARGV *, ARGV *, ARGV *); + const char *(*unknown_event) (struct MILTER *, const char *, ARGV *); + const char *(*other_event) (struct MILTER *); + void (*abort) (struct MILTER *); + void (*disc_event) (struct MILTER *); + int (*active) (struct MILTER *); + int (*send) (struct MILTER *, VSTREAM *); + void (*free) (struct MILTER *); +} MILTER; + +#define MILTER_FLAG_NONE (0) +#define MILTER_FLAG_WANT_RCPT_REJ (1<<0) /* see S8_RCPT_MAILER_ERROR */ + +extern MILTER *milter8_create(const char *, int, int, int, const char *, const char *, struct MILTERS *); +extern MILTER *milter8_receive(VSTREAM *, struct MILTERS *); + + /* + * As of Sendmail 8.14 each milter can override the default macro list. If a + * Milter has its own macro list, a null member means use the global + * definition. + */ +typedef struct MILTER_MACROS { + char *conn_macros; /* macros for connect event */ + char *helo_macros; /* macros for HELO/EHLO command */ + char *mail_macros; /* macros for MAIL FROM command */ + char *rcpt_macros; /* macros for RCPT TO command */ + char *data_macros; /* macros for DATA command */ + char *eoh_macros; /* macros for end-of-headers */ + char *eod_macros; /* macros for END-OF-DATA command */ + char *unk_macros; /* macros for unknown command */ +} MILTER_MACROS; + +extern MILTER_MACROS *milter_macros_create(const char *, const char *, + const char *, const char *, + const char *, const char *, + const char *, const char *); +extern MILTER_MACROS *milter_macros_alloc(int); +extern void milter_macros_free(MILTER_MACROS *); +extern int milter_macros_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); +extern int milter_macros_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *); + +#define MILTER_MACROS_ALLOC_ZERO 1 /* null pointer */ +#define MILTER_MACROS_ALLOC_EMPTY 2 /* mystrdup(""); */ + + /* + * Helper to parse list of name=value default macro settings. + */ +extern struct HTABLE *milter_macro_defaults_create(const char *); + + /* + * A bunch of Milters. + */ +typedef const char *(*MILTER_MAC_LOOKUP_FN) (const char *, void *); +typedef const char *(*MILTER_ADD_HEADER_FN) (void *, const char *, const char *, const char *); +typedef const char *(*MILTER_EDIT_HEADER_FN) (void *, ssize_t, const char *, const char *, const char *); +typedef const char *(*MILTER_DEL_HEADER_FN) (void *, ssize_t, const char *); +typedef const char *(*MILTER_EDIT_FROM_FN) (void *, const char *, const char *); +typedef const char *(*MILTER_EDIT_RCPT_FN) (void *, const char *); +typedef const char *(*MILTER_EDIT_RCPT_PAR_FN) (void *, const char *, const char *); +typedef const char *(*MILTER_EDIT_BODY_FN) (void *, int, int, VSTRING *); + +typedef struct MILTERS { + MILTER *milter_list; /* linked list of Milters */ + MILTER_MAC_LOOKUP_FN mac_lookup; + void *mac_context; /* macro lookup context */ + struct MILTER_MACROS *macros; + struct HTABLE *macro_defaults; + void *chg_context; /* context for queue file changes */ + MILTER_ADD_HEADER_FN add_header; + MILTER_EDIT_HEADER_FN upd_header; + MILTER_DEL_HEADER_FN del_header; + MILTER_EDIT_HEADER_FN ins_header; + MILTER_EDIT_FROM_FN chg_from; + MILTER_EDIT_RCPT_FN add_rcpt; + MILTER_EDIT_RCPT_PAR_FN add_rcpt_par; + MILTER_EDIT_RCPT_FN del_rcpt; + MILTER_EDIT_BODY_FN repl_body; +} MILTERS; + +#define milter_create(milter_names, conn_timeout, cmd_timeout, msg_timeout, \ + protocol, def_action, conn_macros, helo_macros, \ + mail_macros, rcpt_macros, data_macros, eoh_macros, \ + eod_macros, unk_macros, macro_deflts) \ + milter_new(milter_names, conn_timeout, cmd_timeout, msg_timeout, \ + protocol, def_action, milter_macros_create(conn_macros, \ + helo_macros, mail_macros, rcpt_macros, data_macros, \ + eoh_macros, eod_macros, unk_macros), \ + milter_macro_defaults_create(macro_deflts)) + +extern MILTERS *milter_new(const char *, int, int, int, const char *, + const char *, MILTER_MACROS *, + struct HTABLE *); +extern void milter_macro_callback(MILTERS *, MILTER_MAC_LOOKUP_FN, void *); +extern void milter_edit_callback(MILTERS *milters, MILTER_ADD_HEADER_FN, + MILTER_EDIT_HEADER_FN, MILTER_EDIT_HEADER_FN, + MILTER_DEL_HEADER_FN, MILTER_EDIT_FROM_FN, + MILTER_EDIT_RCPT_FN, MILTER_EDIT_RCPT_PAR_FN, + MILTER_EDIT_RCPT_FN, MILTER_EDIT_BODY_FN, + void *); +extern const char *milter_conn_event(MILTERS *, const char *, const char *, const char *, unsigned); +extern const char *milter_helo_event(MILTERS *, const char *, int); +extern const char *milter_mail_event(MILTERS *, const char **); +extern const char *milter_rcpt_event(MILTERS *, int, const char **); +extern const char *milter_data_event(MILTERS *); +extern const char *milter_message(MILTERS *, VSTREAM *, off_t, ARGV *); +extern const char *milter_unknown_event(MILTERS *, const char *); +extern const char *milter_other_event(MILTERS *); +extern void milter_abort(MILTERS *); +extern void milter_disc_event(MILTERS *); +extern int milter_dummy(MILTERS *, VSTREAM *); +extern int milter_send(MILTERS *, VSTREAM *); +extern MILTERS *milter_receive(VSTREAM *, int); +extern void milter_free(MILTERS *); + + /* + * Milter body edit commands. + */ +#define MILTER_BODY_START 1 /* start message body */ +#define MILTER_BODY_LINE 2 /* message body line */ +#define MILTER_BODY_END 3 /* end message body */ + + /* + * Sendmail 8 macro names. We support forms with and without the {}. + */ +#define S8_MAC__ "{_}" /* sender host, see client_resolve */ +#define S8_MAC_J "{j}" /* myhostname */ +#define S8_MAC_V "{v}" /* mail_name + mail_version */ + +#define S8_MAC_DAEMON_NAME "{daemon_name}" +#define S8_MAC_IF_NAME "{if_name}" +#define S8_MAC_IF_ADDR "{if_addr}" + +#define S8_MAC_CLIENT_ADDR "{client_addr}" +#define S8_MAC_CLIENT_CONN "{client_connections}" +#define S8_MAC_CLIENT_NAME "{client_name}" +#define S8_MAC_CLIENT_PORT "{client_port}" +#define S8_MAC_CLIENT_PTR "{client_ptr}" +#define S8_MAC_CLIENT_RES "{client_resolve}" + +#define S8_MAC_DAEMON_ADDR "{daemon_addr}" +#define S8_MAC_DAEMON_PORT "{daemon_port}" + +#define S8_MAC_TLS_VERSION "{tls_version}" +#define S8_MAC_CIPHER "{cipher}" +#define S8_MAC_CIPHER_BITS "{cipher_bits}" +#define S8_MAC_CERT_SUBJECT "{cert_subject}" +#define S8_MAC_CERT_ISSUER "{cert_issuer}" + +#define S8_MAC_I "{i}" /* queue ID */ +#define S8_MAC_AUTH_TYPE "{auth_type}" /* SASL method */ +#define S8_MAC_AUTH_AUTHEN "{auth_authen}" /* SASL username */ +#define S8_MAC_AUTH_AUTHOR "{auth_author}" /* SASL sender */ + +#define S8_MAC_MAIL_MAILER "{mail_mailer}" /* sender transport */ +#define S8_MAC_MAIL_HOST "{mail_host}" /* sender nexthop */ +#define S8_MAC_MAIL_ADDR "{mail_addr}" /* sender address */ + +#define S8_MAC_RCPT_MAILER "{rcpt_mailer}" /* recip transport */ +#define S8_MAC_RCPT_HOST "{rcpt_host}" /* recip nexthop */ +#define S8_MAC_RCPT_ADDR "{rcpt_addr}" /* recip address */ + +#define S8_RCPT_MAILER_ERROR "error" /* see MILTER_FLAG_WANT_RCPT_REJ */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/milter/milter8.c b/src/milter/milter8.c new file mode 100644 index 0000000..ab2bc86 --- /dev/null +++ b/src/milter/milter8.c @@ -0,0 +1,2911 @@ +/*++ +/* NAME +/* milter8 3 +/* SUMMARY +/* MTA-side Sendmail 8 Milter protocol +/* SYNOPSIS +/* #include +/* +/* MILTER *milter8_create(name, conn_timeout, cmd_timeout, msg_timeout, +/* protocol, def_action, parent) +/* const char *name; +/* int conn_timeout; +/* int cmd_timeout; +/* int msg_timeout; +/* const char *protocol; +/* const char *def_action; +/* MILTERS *parent; +/* +/* MILTER *milter8_receive(stream) +/* VSTREAM *stream; +/* DESCRIPTION +/* This module implements the MTA side of the Sendmail 8 mail +/* filter protocol. +/* +/* milter8_create() creates a MILTER data structure with virtual +/* functions that implement a client for the Sendmail 8 Milter +/* protocol. These virtual functions are then invoked via the +/* milter(3) interface. The *timeout, protocol and def_action +/* arguments come directly from milter_create(). The parent +/* argument specifies a context for content editing. +/* +/* milter8_receive() receives a mail filter definition from the +/* specified stream. The result is zero in case of success. +/* +/* Arguments: +/* .IP name +/* The Milter application endpoint, either inet:host:port or +/* unix:/pathname. +/* DIAGNOSTICS +/* Panic: interface violation. Fatal errors: out of memory. +/* CONFIGURATION PARAMETERS +/* milter8_protocol, protocol version and extensions +/* SEE ALSO +/* milter(3) generic Milter interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include /* offsetof() */ +#include +#include +#include +#include /* INT_MAX */ + +#ifndef SHUT_RDWR +#define SHUT_RDWR 2 +#endif + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* Postfix Milter library. */ + +#include + +/* Application-specific. */ + + /* + * Use our own protocol definitions, so that Postfix can be built even when + * libmilter is not installed. This means that we must specify the libmilter + * protocol version in main.cf, and that we must send only the commands that + * are supported for that protocol version. + */ + + /* + * Commands from MTA to filter. + */ +#define SMFIC_ABORT 'A' /* Abort */ +#define SMFIC_BODY 'B' /* Body chunk */ +#define SMFIC_CONNECT 'C' /* Connection information */ +#define SMFIC_MACRO 'D' /* Define macro */ +#define SMFIC_BODYEOB 'E' /* final body chunk (End) */ +#define SMFIC_HELO 'H' /* HELO/EHLO */ +#define SMFIC_HEADER 'L' /* Header */ +#define SMFIC_MAIL 'M' /* MAIL from */ +#define SMFIC_EOH 'N' /* EOH */ +#define SMFIC_OPTNEG 'O' /* Option negotiation */ +#define SMFIC_QUIT 'Q' /* QUIT */ +#define SMFIC_RCPT 'R' /* RCPT to */ +#define SMFIC_DATA 'T' /* DATA */ +#define SMFIC_UNKNOWN 'U' /* Any unknown command */ + /* Introduced with Sendmail 8.14. */ +#define SMFIC_QUIT_NC 'K' /* Quit + new connection */ + +static const NAME_CODE smfic_table[] = { + "SMFIC_ABORT", SMFIC_ABORT, + "SMFIC_BODY", SMFIC_BODY, + "SMFIC_CONNECT", SMFIC_CONNECT, + "SMFIC_MACRO", SMFIC_MACRO, + "SMFIC_BODYEOB", SMFIC_BODYEOB, + "SMFIC_HELO", SMFIC_HELO, + "SMFIC_HEADER", SMFIC_HEADER, + "SMFIC_MAIL", SMFIC_MAIL, + "SMFIC_EOH", SMFIC_EOH, + "SMFIC_OPTNEG", SMFIC_OPTNEG, + "SMFIC_QUIT", SMFIC_QUIT, + "SMFIC_RCPT", SMFIC_RCPT, + "SMFIC_DATA", SMFIC_DATA, + "SMFIC_UNKNOWN", SMFIC_UNKNOWN, + /* Introduced with Sendmail 8.14. */ + "SMFIC_QUIT_NC", SMFIC_QUIT_NC, + 0, 0, +}; + + /* + * Responses from filter to MTA. + */ +#define SMFIR_ADDRCPT '+' /* add recipient */ +#define SMFIR_DELRCPT '-' /* remove recipient */ +#define SMFIR_ACCEPT 'a' /* accept */ +#define SMFIR_REPLBODY 'b' /* replace body (chunk) */ +#define SMFIR_CONTINUE 'c' /* continue */ +#define SMFIR_DISCARD 'd' /* discard */ +#define SMFIR_CONN_FAIL 'f' /* cause a connection failure */ +#define SMFIR_CHGHEADER 'm' /* change header */ +#define SMFIR_PROGRESS 'p' /* progress */ +#define SMFIR_REJECT 'r' /* reject */ +#define SMFIR_TEMPFAIL 't' /* tempfail */ +#define SMFIR_SHUTDOWN '4' /* 421: shutdown (internal to MTA) */ +#define SMFIR_ADDHEADER 'h' /* add header */ +#define SMFIR_INSHEADER 'i' /* insert header */ +#define SMFIR_REPLYCODE 'y' /* reply code etc */ +#define SMFIR_QUARANTINE 'q' /* quarantine */ + /* Introduced with Sendmail 8.14. */ +#define SMFIR_SKIP 's' /* skip further events of this type */ +#define SMFIR_CHGFROM 'e' /* change sender (incl. ESMTP args) */ +#define SMFIR_ADDRCPT_PAR '2' /* add recipient (incl. ESMTP args) */ +#define SMFIR_SETSYMLIST 'l' /* set list of symbols (macros) */ + +static const NAME_CODE smfir_table[] = { + "SMFIR_ADDRCPT", SMFIR_ADDRCPT, + "SMFIR_DELRCPT", SMFIR_DELRCPT, + "SMFIR_ACCEPT", SMFIR_ACCEPT, + "SMFIR_REPLBODY", SMFIR_REPLBODY, + "SMFIR_CONTINUE", SMFIR_CONTINUE, + "SMFIR_DISCARD", SMFIR_DISCARD, + "SMFIR_CONN_FAIL", SMFIR_CONN_FAIL, + "SMFIR_CHGHEADER", SMFIR_CHGHEADER, + "SMFIR_PROGRESS", SMFIR_PROGRESS, + "SMFIR_REJECT", SMFIR_REJECT, + "SMFIR_TEMPFAIL", SMFIR_TEMPFAIL, + "SMFIR_SHUTDOWN", SMFIR_SHUTDOWN, + "SMFIR_ADDHEADER", SMFIR_ADDHEADER, + "SMFIR_INSHEADER", SMFIR_INSHEADER, + "SMFIR_REPLYCODE", SMFIR_REPLYCODE, + "SMFIR_QUARANTINE", SMFIR_QUARANTINE, + /* Introduced with Sendmail 8.14. */ + "SMFIR_SKIP", SMFIR_SKIP, + "SMFIR_CHGFROM", SMFIR_CHGFROM, + "SMFIR_ADDRCPT_PAR", SMFIR_ADDRCPT_PAR, + "SMFIR_SETSYMLIST", SMFIR_SETSYMLIST, + 0, 0, +}; + + /* + * Commands that the filter does not want to receive, and replies that the + * filter will not send. Plus some other random stuff. + */ +#define SMFIP_NOCONNECT (1L<<0) /* filter does not want connect info */ +#define SMFIP_NOHELO (1L<<1) /* filter does not want HELO info */ +#define SMFIP_NOMAIL (1L<<2) /* filter does not want MAIL info */ +#define SMFIP_NORCPT (1L<<3) /* filter does not want RCPT info */ +#define SMFIP_NOBODY (1L<<4) /* filter does not want body */ +#define SMFIP_NOHDRS (1L<<5) /* filter does not want headers */ +#define SMFIP_NOEOH (1L<<6) /* filter does not want EOH */ +#define SMFIP_NR_HDR (1L<<7) /* filter won't reply for header */ +#define SMFIP_NOHREPL SMFIP_NR_HDR +#define SMFIP_NOUNKNOWN (1L<<8) /* filter does not want unknown cmd */ +#define SMFIP_NODATA (1L<<9) /* filter does not want DATA */ + /* Introduced with Sendmail 8.14. */ +#define SMFIP_SKIP (1L<<10)/* MTA supports SMFIR_SKIP */ +#define SMFIP_RCPT_REJ (1L<<11)/* filter wants rejected RCPTs */ +#define SMFIP_NR_CONN (1L<<12)/* filter won't reply for connect */ +#define SMFIP_NR_HELO (1L<<13)/* filter won't reply for HELO */ +#define SMFIP_NR_MAIL (1L<<14)/* filter won't reply for MAIL */ +#define SMFIP_NR_RCPT (1L<<15)/* filter won't reply for RCPT */ +#define SMFIP_NR_DATA (1L<<16)/* filter won't reply for DATA */ +#define SMFIP_NR_UNKN (1L<<17)/* filter won't reply for UNKNOWN */ +#define SMFIP_NR_EOH (1L<<18)/* filter won't reply for eoh */ +#define SMFIP_NR_BODY (1L<<19)/* filter won't reply for body chunk */ +#define SMFIP_HDR_LEADSPC (1L<<20)/* header value has leading space */ + +#define SMFIP_NOSEND_MASK \ + (SMFIP_NOCONNECT | SMFIP_NOHELO | SMFIP_NOMAIL | SMFIP_NORCPT \ + | SMFIP_NOBODY | SMFIP_NOHDRS | SMFIP_NOEOH | SMFIP_NOUNKNOWN \ + | SMFIP_NODATA) + +#define SMFIP_NOREPLY_MASK \ + (SMFIP_NR_CONN | SMFIP_NR_HELO | SMFIP_NR_MAIL | SMFIP_NR_RCPT \ + | SMFIP_NR_DATA | SMFIP_NR_UNKN | SMFIP_NR_HDR | SMFIP_NR_EOH | \ + SMFIP_NR_BODY) + +static const NAME_MASK smfip_table[] = { + "SMFIP_NOCONNECT", SMFIP_NOCONNECT, + "SMFIP_NOHELO", SMFIP_NOHELO, + "SMFIP_NOMAIL", SMFIP_NOMAIL, + "SMFIP_NORCPT", SMFIP_NORCPT, + "SMFIP_NOBODY", SMFIP_NOBODY, + "SMFIP_NOHDRS", SMFIP_NOHDRS, + "SMFIP_NOEOH", SMFIP_NOEOH, + "SMFIP_NR_HDR", SMFIP_NR_HDR, + "SMFIP_NOUNKNOWN", SMFIP_NOUNKNOWN, + "SMFIP_NODATA", SMFIP_NODATA, + /* Introduced with Sendmail 8.14. */ + "SMFIP_SKIP", SMFIP_SKIP, + "SMFIP_RCPT_REJ", SMFIP_RCPT_REJ, + "SMFIP_NR_CONN", SMFIP_NR_CONN, + "SMFIP_NR_HELO", SMFIP_NR_HELO, + "SMFIP_NR_MAIL", SMFIP_NR_MAIL, + "SMFIP_NR_RCPT", SMFIP_NR_RCPT, + "SMFIP_NR_DATA", SMFIP_NR_DATA, + "SMFIP_NR_UNKN", SMFIP_NR_UNKN, + "SMFIP_NR_EOH", SMFIP_NR_EOH, + "SMFIP_NR_BODY", SMFIP_NR_BODY, + "SMFIP_HDR_LEADSPC", SMFIP_HDR_LEADSPC, + 0, 0, +}; + + /* + * Options that the filter may send at initial handshake time, and message + * modifications that the filter may request at the end of the message body. + */ +#define SMFIF_ADDHDRS (1L<<0) /* filter may add headers */ +#define SMFIF_CHGBODY (1L<<1) /* filter may replace body */ +#define SMFIF_ADDRCPT (1L<<2) /* filter may add recipients */ +#define SMFIF_DELRCPT (1L<<3) /* filter may delete recipients */ +#define SMFIF_CHGHDRS (1L<<4) /* filter may change/delete headers */ +#define SMFIF_QUARANTINE (1L<<5) /* filter may quarantine envelope */ + /* Introduced with Sendmail 8.14. */ +#define SMFIF_CHGFROM (1L<<6) /* filter may replace sender */ +#define SMFIF_ADDRCPT_PAR (1L<<7) /* filter may add recipients + args */ +#define SMFIF_SETSYMLIST (1L<<8) /* filter may send macro names */ + +static const NAME_MASK smfif_table[] = { + "SMFIF_ADDHDRS", SMFIF_ADDHDRS, + "SMFIF_CHGBODY", SMFIF_CHGBODY, + "SMFIF_ADDRCPT", SMFIF_ADDRCPT, + "SMFIF_DELRCPT", SMFIF_DELRCPT, + "SMFIF_CHGHDRS", SMFIF_CHGHDRS, + "SMFIF_QUARANTINE", SMFIF_QUARANTINE, + /* Introduced with Sendmail 8.14. */ + "SMFIF_CHGFROM", SMFIF_CHGFROM, + "SMFIF_ADDRCPT_PAR", SMFIF_ADDRCPT_PAR, + "SMFIF_SETSYMLIST", SMFIF_SETSYMLIST, + 0, 0, +}; + + /* + * Network protocol families, used when sending CONNECT information. + */ +#define SMFIA_UNKNOWN 'U' /* unknown */ +#define SMFIA_UNIX 'L' /* unix/local */ +#define SMFIA_INET '4' /* inet */ +#define SMFIA_INET6 '6' /* inet6 */ + + /* + * External macro class numbers, to identify the optional macro name lists + * that may be sent after the initial negotiation header. + */ +#define SMFIM_CONNECT 0 /* macros for connect */ +#define SMFIM_HELO 1 /* macros for HELO */ +#define SMFIM_ENVFROM 2 /* macros for MAIL */ +#define SMFIM_ENVRCPT 3 /* macros for RCPT */ +#define SMFIM_DATA 4 /* macros for DATA */ +#define SMFIM_EOM 5 /* macros for end-of-message */ +#define SMFIM_EOH 6 /* macros for end-of-header */ + +static const NAME_CODE smfim_table[] = { + "SMFIM_CONNECT", SMFIM_CONNECT, + "SMFIM_HELO", SMFIM_HELO, + "SMFIM_ENVFROM", SMFIM_ENVFROM, + "SMFIM_ENVRCPT", SMFIM_ENVRCPT, + "SMFIM_DATA", SMFIM_DATA, + "SMFIM_EOM", SMFIM_EOM, + "SMFIM_EOH", SMFIM_EOH, + 0, 0, +}; + + /* + * Mapping from external macro class numbers to our internal MILTER_MACROS + * structure members, without using a switch statement. + */ +static const size_t milter8_macro_offsets[] = { + offsetof(MILTER_MACROS, conn_macros), /* SMFIM_CONNECT */ + offsetof(MILTER_MACROS, helo_macros), /* SMFIM_HELO */ + offsetof(MILTER_MACROS, mail_macros), /* SMFIM_ENVFROM */ + offsetof(MILTER_MACROS, rcpt_macros), /* SMFIM_ENVRCPT */ + offsetof(MILTER_MACROS, data_macros), /* SMFIM_DATA */ + offsetof(MILTER_MACROS, eod_macros),/* Note: SMFIM_EOM < SMFIM_EOH */ + offsetof(MILTER_MACROS, eoh_macros),/* Note: SMFIM_EOH > SMFIM_EOM */ +}; + +#define MILTER8_MACRO_PTR(__macros, __class) \ + ((char **) (((char *) (__macros)) + milter8_macro_offsets[(__class)])) + + /* + * How much buffer space is available for sending body content. + */ +#define MILTER_CHUNK_SIZE 65535 /* body chunk size */ + +/*#define msg_verbose 2*/ + + /* + * Sendmail 8 mail filter client. + */ +typedef struct { + MILTER m; /* parent class */ + int conn_timeout; /* connect timeout */ + int cmd_timeout; /* per-command timeout */ + int msg_timeout; /* content inspection timeout */ + char *protocol; /* protocol version/extension */ + char *def_action; /* action if unavailable */ + int version; /* application protocol version */ + int rq_mask; /* application requests (SMFIF_*) */ + int ev_mask; /* application events (SMFIP_*) */ + int np_mask; /* events outside my protocol version */ + VSTRING *buf; /* I/O buffer */ + VSTRING *body; /* I/O buffer */ + VSTREAM *fp; /* stream or null (closed) */ + + /* + * Following fields must be reset after successful CONNECT, to avoid + * leakage from one use to another. + */ + int state; /* MILTER8_STAT_mumble */ + char *def_reply; /* error response or null */ + int skip_event_type; /* skip operations of this type */ +} MILTER8; + + /* + * XXX Sendmail 8 libmilter automatically closes the MTA-to-filter socket + * when it finds out that the SMTP client has disconnected. Because of this + * behavior, Postfix has to open a new MTA-to-filter socket each time an + * SMTP client connects. + */ +#define LIBMILTER_AUTO_DISCONNECT + + /* + * Milter internal state. For the external representation we use SMTP + * replies (4XX X.Y.Z text, 5XX X.Y.Z text) and one-letter strings + * (H=quarantine, D=discard, S=shutdown). + */ +#define MILTER8_STAT_ERROR 1 /* error, must be non-zero */ +#define MILTER8_STAT_CLOSED 2 /* no connection */ +#define MILTER8_STAT_READY 3 /* wait for connect event */ +#define MILTER8_STAT_ENVELOPE 4 /* in envelope */ +#define MILTER8_STAT_MESSAGE 5 /* in message */ +#define MILTER8_STAT_ACCEPT_CON 6 /* accept all commands */ +#define MILTER8_STAT_ACCEPT_MSG 7 /* accept one message */ +#define MILTER8_STAT_REJECT_CON 8 /* reject all commands */ + + /* + * Protocol formatting requests. Note: the terms "long" and "short" refer to + * the data types manipulated by htonl(), htons() and friends. These types + * are network specific, not host platform specific. + */ +#define MILTER8_DATA_END 0 /* no more arguments */ +#define MILTER8_DATA_HLONG 1 /* host long */ +#define MILTER8_DATA_BUFFER 2 /* network-formatted buffer */ +#define MILTER8_DATA_STRING 3 /* null-terminated string */ +#define MILTER8_DATA_NSHORT 4 /* network short */ +#define MILTER8_DATA_ARGV 5 /* array of null-terminated strings */ +#define MILTER8_DATA_OCTET 6 /* byte */ +#define MILTER8_DATA_MORE 7 /* more arguments in next call */ + + /* + * We don't accept insane amounts of data. + */ +#define XXX_MAX_DATA (INT_MAX / 2) +#define XXX_TIMEOUT 10 + + /* + * We implement the protocol up to and including version 6, and configure in + * main.cf what protocol version we will use. The version is the first data + * item in the SMFIC_OPTNEG packet. + * + * We must send only events that are defined for the specified protocol + * version. Libmilter may disconnect when we send unexpected events. + * + * The following events are supported in all our milter protocol versions. + */ +#define MILTER8_V2_PROTO_MASK \ + (SMFIP_NOCONNECT | SMFIP_NOHELO | SMFIP_NOMAIL | SMFIP_NORCPT | \ + SMFIP_NOBODY | SMFIP_NOHDRS | SMFIP_NOEOH) + + /* + * Events supported by later versions. + */ +#define MILTER8_V3_PROTO_MASK (MILTER8_V2_PROTO_MASK | SMFIP_NOUNKNOWN) +#define MILTER8_V4_PROTO_MASK (MILTER8_V3_PROTO_MASK | SMFIP_NODATA) +#define MILTER8_V6_PROTO_MASK \ + (MILTER8_V4_PROTO_MASK | SMFIP_SKIP | SMFIP_RCPT_REJ \ + | SMFIP_NOREPLY_MASK | SMFIP_HDR_LEADSPC) + + /* + * What events we can send to the milter application. The milter8_protocol + * parameter can specify a protocol version as well as protocol extensions + * such as "no_header_reply", a feature that speeds up the protocol by not + * sending a filter reply for every individual message header. + * + * This looks unclean because the user can specify multiple protocol versions, + * but that is taken care of by the table that follows this one. + * + * XXX Is this still needed? Sendmail 8.14 provides a proper way to negotiate + * what replies the mail filter will send. + * + * XXX Keep this table in reverse numerical order. This is needed by the code + * that implements compatibility with older Milter protocol versions. + */ +static const NAME_CODE milter8_event_masks[] = { + "6", MILTER8_V6_PROTO_MASK, + "4", MILTER8_V4_PROTO_MASK, + "3", MILTER8_V3_PROTO_MASK, + "2", MILTER8_V2_PROTO_MASK, + "no_header_reply", SMFIP_NOHREPL, + 0, -1, +}; + + /* + * The following table lets us use the same milter8_protocol parameter + * setting to derive the protocol version number. In this case we ignore + * protocol extensions such as "no_header_reply", and require that exactly + * one version number is specified. + */ +static const NAME_CODE milter8_versions[] = { + "2", 2, + "3", 3, + "4", 4, + "6", 6, + "no_header_reply", 0, + 0, -1, +}; + +/* SLMs. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* milter8_def_reply - set persistent response */ + +static const char *milter8_def_reply(MILTER8 *milter, const char *reply) +{ + if (milter->def_reply) + myfree(milter->def_reply); + milter->def_reply = reply ? mystrdup(reply) : 0; + return (milter->def_reply); +} + +/* milter8_conf_error - local/remote configuration error */ + +static int milter8_conf_error(MILTER8 *milter) +{ + const char *reply; + + /* + * XXX When the cleanup server closes its end of the Milter socket while + * editing a queue file, the SMTP server is left out of sync with the + * Milter. Sending an ABORT to the Milters will not restore + * synchronization, because there may be any number of Milter replies + * already in flight. Workaround: poison the socket and force the SMTP + * server to abandon it. + */ + if (milter->fp != 0) { + (void) shutdown(vstream_fileno(milter->fp), SHUT_RDWR); + (void) vstream_fclose(milter->fp); + milter->fp = 0; + } + if (strcasecmp(milter->def_action, "accept") == 0) { + reply = 0; + } else if (strcasecmp(milter->def_action, "quarantine") == 0) { + reply = "H"; + } else { + reply = "451 4.3.5 Server configuration problem - try again later"; + } + milter8_def_reply(milter, reply); + return (milter->state = MILTER8_STAT_ERROR); +} + +/* milter8_comm_error - read/write/format communication error */ + +static int milter8_comm_error(MILTER8 *milter) +{ + const char *reply; + + /* + * XXX When the cleanup server closes its end of the Milter socket while + * editing a queue file, the SMTP server is left out of sync with the + * Milter. Sending an ABORT to the Milters will not restore + * synchronization, because there may be any number of Milter replies + * already in flight. Workaround: poison the socket and force the SMTP + * server to abandon it. + */ + if (milter->fp != 0) { + (void) shutdown(vstream_fileno(milter->fp), SHUT_RDWR); + (void) vstream_fclose(milter->fp); + milter->fp = 0; + } + if (strcasecmp(milter->def_action, "accept") == 0) { + reply = 0; + } else if (strcasecmp(milter->def_action, "reject") == 0) { + reply = "550 5.5.0 Service unavailable"; + } else if (strcasecmp(milter->def_action, "tempfail") == 0) { + reply = "451 4.7.1 Service unavailable - try again later"; + } else if (strcasecmp(milter->def_action, "quarantine") == 0) { + reply = "H"; + } else { + msg_warn("milter %s: unrecognized default action: %s", + milter->m.name, milter->def_action); + reply = "451 4.3.5 Server configuration problem - try again later"; + } + milter8_def_reply(milter, reply); + return (milter->state = MILTER8_STAT_ERROR); +} + +/* milter8_close_stream - close stream to milter application */ + +static void milter8_close_stream(MILTER8 *milter) +{ + if (milter->fp != 0) { + (void) vstream_fclose(milter->fp); + milter->fp = 0; + } + milter->state = MILTER8_STAT_CLOSED; +} + +/* milter8_read_resp - receive command code now, receive data later */ + +static int milter8_read_resp(MILTER8 *milter, int event, unsigned char *command, + ssize_t *data_len) +{ + UINT32_TYPE len; + ssize_t pkt_len; + const char *smfic_name; + int cmd; + + /* + * Receive the packet length. + */ + if ((vstream_fread(milter->fp, (void *) &len, UINT32_SIZE)) + != UINT32_SIZE) { + smfic_name = str_name_code(smfic_table, event); + msg_warn("milter %s: can't read %s reply packet header: %m", + milter->m.name, smfic_name != 0 ? + smfic_name : "(unknown MTA event)"); + return (milter8_comm_error(milter)); + } else if ((pkt_len = ntohl(len)) < 1) { + msg_warn("milter %s: bad packet length: %ld", + milter->m.name, (long) pkt_len); + return (milter8_comm_error(milter)); + } else if (pkt_len > XXX_MAX_DATA) { + msg_warn("milter %s: unreasonable packet length: %ld > %ld", + milter->m.name, (long) pkt_len, (long) XXX_MAX_DATA); + return (milter8_comm_error(milter)); + } + + /* + * Receive the command code. + */ + else if ((cmd = VSTREAM_GETC(milter->fp)) == VSTREAM_EOF) { + msg_warn("milter %s: EOF while reading command code: %m", + milter->m.name); + return (milter8_comm_error(milter)); + } + + /* + * All is well. + */ + else { + *command = cmd; + *data_len = pkt_len - 1; + return (0); + } +} + +static int milter8_read_data(MILTER8 *milter, ssize_t *data_len,...); + +/* vmilter8_read_data - read command data */ + +static int vmilter8_read_data(MILTER8 *milter, ssize_t *data_len, va_list ap) +{ + const char *myname = "milter8_read_data"; + int arg_type; + UINT32_TYPE net_long; + UINT32_TYPE *host_long_ptr; + VSTRING *buf; + int ch; + + while ((arg_type = va_arg(ap, int)) > 0 && arg_type != MILTER8_DATA_MORE) { + switch (arg_type) { + + /* + * Host order long. + */ + case MILTER8_DATA_HLONG: + if (*data_len < UINT32_SIZE) { + msg_warn("milter %s: input packet too short for network long", + milter->m.name); + return (milter8_comm_error(milter)); + } + host_long_ptr = va_arg(ap, UINT32_TYPE *); + if (vstream_fread(milter->fp, (void *) &net_long, UINT32_SIZE) + != UINT32_SIZE) { + msg_warn("milter %s: EOF while reading network long: %m", + milter->m.name); + return (milter8_comm_error(milter)); + } + *data_len -= UINT32_SIZE; + *host_long_ptr = ntohl(net_long); + break; + + /* + * Raw on-the-wire format, without explicit null terminator. + */ + case MILTER8_DATA_BUFFER: + if (*data_len < 0) { + msg_warn("milter %s: no data in input packet", milter->m.name); + return (milter8_comm_error(milter)); + } + buf = va_arg(ap, VSTRING *); + if (vstream_fread_buf(milter->fp, buf, *data_len) + != *data_len) { + msg_warn("milter %s: EOF while reading data: %m", milter->m.name); + return (milter8_comm_error(milter)); + } + *data_len = 0; + break; + + /* + * Pointer to null-terminated string. + */ + case MILTER8_DATA_STRING: + if (*data_len < 1) { + msg_warn("milter %s: packet too short for string", + milter->m.name); + return (milter8_comm_error(milter)); + } + buf = va_arg(ap, VSTRING *); + VSTRING_RESET(buf); + for (;;) { + if ((ch = VSTREAM_GETC(milter->fp)) == VSTREAM_EOF) { + msg_warn("%s: milter %s: EOF while reading string: %m", + myname, milter->m.name); + return (milter8_comm_error(milter)); + } + *data_len -= 1; + if (ch == 0) + break; + VSTRING_ADDCH(buf, ch); + if (*data_len <= 0) { + msg_warn("%s: milter %s: missing string null termimator", + myname, milter->m.name); + return (milter8_comm_error(milter)); + } + } + VSTRING_TERMINATE(buf); + break; + + /* + * Error. + */ + default: + msg_panic("%s: unknown argument type: %d", myname, arg_type); + } + } + + /* + * Sanity checks. We may have excess data when the sender is confused. We + * may have a negative count when we're confused ourselves. + */ + if (arg_type != MILTER8_DATA_MORE && *data_len > 0) { + msg_warn("%s: left-over data %ld bytes", myname, (long) *data_len); + return (milter8_comm_error(milter)); + } + if (*data_len < 0) + msg_panic("%s: bad left-over data count %ld", + myname, (long) *data_len); + return (0); +} + +/* milter8_read_data - read command data */ + +static int milter8_read_data(MILTER8 *milter, ssize_t *data_len,...) +{ + va_list ap; + int ret; + + va_start(ap, data_len); + ret = vmilter8_read_data(milter, data_len, ap); + va_end(ap); + return (ret); +} + +/* vmilter8_size_data - compute command data length */ + +static ssize_t vmilter8_size_data(va_list ap) +{ + const char *myname = "vmilter8_size_data"; + ssize_t data_len; + int arg_type; + VSTRING *buf; + const char *str; + const char **cpp; + + /* + * Compute data size. + */ + for (data_len = 0; (arg_type = va_arg(ap, int)) > 0; /* void */ ) { + switch (arg_type) { + + /* + * Host order long. + */ + case MILTER8_DATA_HLONG: + (void) va_arg(ap, UINT32_TYPE); + data_len += UINT32_SIZE; + break; + + /* + * Raw on-the-wire format. + */ + case MILTER8_DATA_BUFFER: + buf = va_arg(ap, VSTRING *); + data_len += LEN(buf); + break; + + /* + * Pointer to null-terminated string. + */ + case MILTER8_DATA_STRING: + str = va_arg(ap, char *); + data_len += strlen(str) + 1; + break; + + /* + * Array of pointers to null-terminated strings. + */ + case MILTER8_DATA_ARGV: + for (cpp = va_arg(ap, const char **); *cpp; cpp++) + data_len += strlen(*cpp) + 1; + break; + + /* + * Network order short, promoted to int. + */ + case MILTER8_DATA_NSHORT: + (void) va_arg(ap, unsigned); + data_len += UINT16_SIZE; + break; + + /* + * Octet, promoted to int. + */ + case MILTER8_DATA_OCTET: + (void) va_arg(ap, unsigned); + data_len += 1; + break; + + /* + * Error. + */ + default: + msg_panic("%s: bad argument type: %d", myname, arg_type); + } + } + va_end(ap); + return (data_len); +} + +/* vmilter8_write_cmd - write command to Sendmail 8 Milter */ + +static int vmilter8_write_cmd(MILTER8 *milter, int command, ssize_t data_len, + va_list ap) +{ + const char *myname = "vmilter8_write_cmd"; + int arg_type; + UINT32_TYPE pkt_len; + UINT32_TYPE host_long; + UINT32_TYPE net_long; + UINT16_TYPE net_short; + VSTRING *buf; + const char *str; + const char **cpp; + char ch; + + /* + * Deliver the packet. + */ + if ((pkt_len = 1 + data_len) < 1) + msg_panic("%s: bad packet length %d", myname, pkt_len); + pkt_len = htonl(pkt_len); + (void) vstream_fwrite(milter->fp, (void *) &pkt_len, UINT32_SIZE); + (void) VSTREAM_PUTC(command, milter->fp); + while ((arg_type = va_arg(ap, int)) > 0) { + switch (arg_type) { + + /* + * Network long. + */ + case MILTER8_DATA_HLONG: + host_long = va_arg(ap, UINT32_TYPE); + net_long = htonl(host_long); + (void) vstream_fwrite(milter->fp, (void *) &net_long, UINT32_SIZE); + break; + + /* + * Raw on-the-wire format. + */ + case MILTER8_DATA_BUFFER: + buf = va_arg(ap, VSTRING *); + (void) vstream_fwrite(milter->fp, STR(buf), LEN(buf)); + break; + + /* + * Pointer to null-terminated string. + */ + case MILTER8_DATA_STRING: + str = va_arg(ap, char *); + (void) vstream_fwrite(milter->fp, str, strlen(str) + 1); + break; + + /* + * Octet, promoted to int. + */ + case MILTER8_DATA_OCTET: + ch = va_arg(ap, unsigned); + (void) vstream_fwrite(milter->fp, &ch, 1); + break; + + /* + * Array of pointers to null-terminated strings. + */ + case MILTER8_DATA_ARGV: + for (cpp = va_arg(ap, const char **); *cpp; cpp++) + (void) vstream_fwrite(milter->fp, *cpp, strlen(*cpp) + 1); + break; + + /* + * Network order short, promoted to int. + */ + case MILTER8_DATA_NSHORT: + net_short = va_arg(ap, unsigned); + (void) vstream_fwrite(milter->fp, (void *) &net_short, UINT16_SIZE); + break; + + /* + * Error. + */ + default: + msg_panic("%s: bad argument type: %d", myname, arg_type); + } + + /* + * Report errors immediately. + */ + if (vstream_ferror(milter->fp)) { + msg_warn("milter %s: error writing command: %m", milter->m.name); + milter8_comm_error(milter); + break; + } + } + va_end(ap); + return (milter->state == MILTER8_STAT_ERROR); +} + +/* milter8_write_cmd - write command to Sendmail 8 Milter */ + +static int milter8_write_cmd(MILTER8 *milter, int command,...) +{ + va_list ap; + va_list ap2; + ssize_t data_len; + int err; + + /* + * Initialize argument lists. + */ + va_start(ap, command); + VA_COPY(ap2, ap); + + /* + * Size the command data. + */ + data_len = vmilter8_size_data(ap); + va_end(ap); + + /* + * Send the command and data. + */ + err = vmilter8_write_cmd(milter, command, data_len, ap2); + va_end(ap2); + + return (err); +} + +/* milter8_event - report event and receive reply */ + +static const char *milter8_event(MILTER8 *milter, int event, + int skip_event_flag, + int skip_reply, + ARGV *macros,...) +{ + const char *myname = "milter8_event"; + va_list ap; + va_list ap2; + ssize_t data_len; + int err; + unsigned char cmd; + ssize_t data_size; + const char *smfic_name; + const char *smfir_name; + MILTERS *parent = milter->m.parent; + UINT32_TYPE index; + const char *edit_resp = 0; + const char *retval = 0; + VSTRING *body_line_buf = 0; + int done = 0; + int body_edit_lockout = 0; + +#define DONT_SKIP_REPLY 0 + + /* + * Sanity check. + */ + if (milter->fp == 0 || milter->def_reply != 0) { + msg_warn("%s: attempt to send event %s to milter %s after error", + myname, + (smfic_name = str_name_code(smfic_table, event)) != 0 ? + smfic_name : "(unknown MTA event)", milter->m.name); + return (milter->def_reply); + } + + /* + * Skip this event if it doesn't exist in the protocol that I announced. + */ + if ((skip_event_flag & milter->np_mask) != 0) { + if (msg_verbose) + msg_info("skipping non-protocol event %s for milter %s", + (smfic_name = str_name_code(smfic_table, event)) != 0 ? + smfic_name : "(unknown MTA event)", milter->m.name); + return (milter->def_reply); + } + + /* + * Skip further events of this type if the filter told us so. + */ + if (milter->skip_event_type != 0) { + if (event == milter->skip_event_type) { + if (msg_verbose) + msg_info("skipping event %s after SMFIR_SKIP from milter %s", + (smfic_name = str_name_code(smfic_table, event)) != 0 ? + smfic_name : "(unknown MTA event)", milter->m.name); + return (milter->def_reply); + } else { + milter->skip_event_type = 0; + } + } + + /* + * Send the macros for this event, even when we're not reporting the + * event itself. This does not introduce a performance problem because + * we're sending macros and event parameters in one VSTREAM transaction. + * + * XXX Is this still necessary? + */ + if (msg_verbose) { + VSTRING *buf = vstring_alloc(100); + + if (macros) { + if (macros->argc > 0) { + char **cpp; + + for (cpp = macros->argv; *cpp && cpp[1]; cpp += 2) + vstring_sprintf_append(buf, " %s=%s", *cpp, cpp[1]); + } + } + msg_info("event: %s; macros:%s", + (smfic_name = str_name_code(smfic_table, event)) != 0 ? + smfic_name : "(unknown MTA event)", *STR(buf) ? + STR(buf) : " (none)"); + vstring_free(buf); + } + if (macros) { + if (milter8_write_cmd(milter, SMFIC_MACRO, + MILTER8_DATA_OCTET, event, + MILTER8_DATA_ARGV, macros->argv, + MILTER8_DATA_END) != 0) + return (milter->def_reply); + } + + /* + * Skip this event if the Milter told us not to send it. + */ + if ((skip_event_flag & milter->ev_mask) != 0) { + if (msg_verbose) + msg_info("skipping event %s for milter %s", + (smfic_name = str_name_code(smfic_table, event)) != 0 ? + smfic_name : "(unknown MTA event)", milter->m.name); + return (milter->def_reply); + } + + /* + * Initialize argument lists. + */ + va_start(ap, macros); + VA_COPY(ap2, ap); + + /* + * Compute the command data size. This is necessary because the protocol + * sends length before content. + */ + data_len = vmilter8_size_data(ap); + va_end(ap); + + /* + * Send the command and data. + */ + err = vmilter8_write_cmd(milter, event, data_len, ap2); + va_end(ap2); + + /* + * C99 requires that we finalize argument lists before returning. + */ + if (err != 0) + return (milter->def_reply); + + /* + * Special feature: don't wait for one reply per header. This allows us + * to send multiple headers in one VSTREAM transaction, and improves + * over-all performance. + */ + if (skip_reply) { + if (msg_verbose) + msg_info("skipping reply for event %s from milter %s", + (smfic_name = str_name_code(smfic_table, event)) != 0 ? + smfic_name : "(unknown MTA event)", milter->m.name); + return (milter->def_reply); + } + + /* + * Receive the reply or replies. + * + * Intercept all loop exits so that we can do post header/body edit + * processing. + * + * XXX Bound the loop iteration count. + * + * In the end-of-body stage, the Milter may reply with one or more queue + * file edit requests before it replies with its final decision: accept, + * reject, etc. After a local queue file edit error (file too big, media + * write error), do not close the Milter socket in the cleanup server. + * Instead skip all further Milter replies until the final decision. This + * way the Postfix SMTP server stays in sync with the Milter, and Postfix + * doesn't have to lose the ability to handle multiple deliveries within + * the same SMTP session. This requires that the Postfix SMTP server uses + * something other than CLEANUP_STAT_WRITE when it loses contact with the + * cleanup server. + */ +#define IN_CONNECT_EVENT(e) ((e) == SMFIC_CONNECT || (e) == SMFIC_HELO) + + /* + * XXX Don't evaluate this macro's argument multiple times. Since we use + * "continue" the macro can't be enclosed in do .. while (0). + */ +#define MILTER8_EVENT_BREAK(s) { \ + retval = (s); \ + done = 1; \ + continue; \ + } + + while (done == 0) { + char *cp; + char *rp; + char ch; + char *next; + + if (milter8_read_resp(milter, event, &cmd, &data_size) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + if (msg_verbose) + msg_info("reply: %s data %ld bytes", + (smfir_name = str_name_code(smfir_table, cmd)) != 0 ? + smfir_name : "unknown", (long) data_size); + + /* + * Handle unfinished message body replacement first. + * + * XXX When SMFIR_REPLBODY is followed by some different request, we + * assume that the body replacement operation is complete. The queue + * file editing implementation currently does not support sending + * part 1 of the body replacement text, doing some other queue file + * updates, and then sending part 2 of the body replacement text. To + * avoid loss of data, we log an error when SMFIR_REPLBODY requests + * are alternated with other requests. + */ + if (body_line_buf != 0 && cmd != SMFIR_REPLBODY) { + /* In case the last body replacement line didn't end in CRLF. */ + if (edit_resp == 0 && LEN(body_line_buf) > 0) + edit_resp = parent->repl_body(parent->chg_context, + MILTER_BODY_LINE, + REC_TYPE_NORM, + body_line_buf); + if (edit_resp == 0) + edit_resp = parent->repl_body(parent->chg_context, + MILTER_BODY_END, + /* unused*/ 0, + (VSTRING *) 0); + body_edit_lockout = 1; + vstring_free(body_line_buf); + body_line_buf = 0; + } + switch (cmd) { + + /* + * Still working on it. + */ + case SMFIR_PROGRESS: + if (data_size != 0) + break; + continue; + + /* + * Decision: continue processing. + */ + case SMFIR_CONTINUE: + if (data_size != 0) + break; + MILTER8_EVENT_BREAK(milter->def_reply); + + /* + * Decision: accept this message, or accept all further commands + * in this SMTP connection. This decision is final (i.e. Sendmail + * 8 changes receiver state). + */ + case SMFIR_ACCEPT: + if (data_size != 0) + break; + if (IN_CONNECT_EVENT(event)) { +#ifdef LIBMILTER_AUTO_DISCONNECT + milter8_close_stream(milter); +#endif + /* No more events for this SMTP connection. */ + milter->state = MILTER8_STAT_ACCEPT_CON; + } else { + /* No more events for this message. */ + milter->state = MILTER8_STAT_ACCEPT_MSG; + } + MILTER8_EVENT_BREAK(milter->def_reply); + + /* + * Decision: accept and silently discard this message. According + * to the milter API documentation there will be no action when + * this is requested by a connection-level function. This + * decision is final (i.e. Sendmail 8 changes receiver state). + */ + case SMFIR_DISCARD: + if (data_size != 0) + break; + if (IN_CONNECT_EVENT(event)) { + msg_warn("milter %s: DISCARD action is not allowed " + "for connect or helo", milter->m.name); + MILTER8_EVENT_BREAK(milter->def_reply); + } else { + /* No more events for this message. */ + milter->state = MILTER8_STAT_ACCEPT_MSG; + MILTER8_EVENT_BREAK("D"); + } + + /* + * Decision: reject connection, message or recipient. This + * decision is final (i.e. Sendmail 8 changes receiver state). + */ + case SMFIR_REJECT: + if (data_size != 0) + break; + if (IN_CONNECT_EVENT(event)) { +#ifdef LIBMILTER_AUTO_DISCONNECT + milter8_close_stream(milter); +#endif + milter->state = MILTER8_STAT_REJECT_CON; + MILTER8_EVENT_BREAK(milter8_def_reply(milter, "550 5.7.1 Command rejected")); + } else { + MILTER8_EVENT_BREAK("550 5.7.1 Command rejected"); + } + + /* + * Decision: tempfail. This decision is final (i.e. Sendmail 8 + * changes receiver state). + */ + case SMFIR_TEMPFAIL: + if (data_size != 0) + break; + if (IN_CONNECT_EVENT(event)) { +#ifdef LIBMILTER_AUTO_DISCONNECT + milter8_close_stream(milter); +#endif + milter->state = MILTER8_STAT_REJECT_CON; + MILTER8_EVENT_BREAK(milter8_def_reply(milter, + "451 4.7.1 Service unavailable - try again later")); + } else { + MILTER8_EVENT_BREAK("451 4.7.1 Service unavailable - try again later"); + } + + /* + * Decision: disconnect. This decision is final (i.e. Sendmail 8 + * changes receiver state). + */ + case SMFIR_SHUTDOWN: + if (data_size != 0) + break; +#ifdef LIBMILTER_AUTO_DISCONNECT + milter8_close_stream(milter); +#endif + milter->state = MILTER8_STAT_REJECT_CON; + MILTER8_EVENT_BREAK(milter8_def_reply(milter, "S")); + + /* + * Decision: "ddd d.d+.d+ text". This decision is final (i.e. + * Sendmail 8 changes receiver state). Note: the reply may be in + * multi-line SMTP format. + * + * XXX Sendmail compatibility: sendmail 8 uses the reply as a format + * string; therefore any '%' characters in the reply are doubled. + * Postfix doesn't use replies as format strings; we replace '%%' + * by '%', and remove single (i.e. invalid) '%' characters. + */ + case SMFIR_REPLYCODE: + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_BUFFER, milter->buf, + MILTER8_DATA_END) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + /* XXX Enforce this for each line of a multi-line reply. */ + if ((STR(milter->buf)[0] != '4' && STR(milter->buf)[0] != '5') + || !ISDIGIT(STR(milter->buf)[1]) + || !ISDIGIT(STR(milter->buf)[2]) + || (STR(milter->buf)[3] != ' ' && STR(milter->buf)[3] != '-') + || (ISDIGIT(STR(milter->buf)[4]) + && (STR(milter->buf)[4] != STR(milter->buf)[0]))) { + msg_warn("milter %s: malformed reply: %s", + milter->m.name, STR(milter->buf)); + milter8_conf_error(milter); + MILTER8_EVENT_BREAK(milter->def_reply); + } + if ((rp = cp = strchr(STR(milter->buf), '%')) != 0) { + for (;;) { + if ((ch = *cp++) == '%') + ch = *cp++; + *rp++ = ch; + if (ch == 0) + break; + } + } + if (var_soft_bounce) { + for (cp = STR(milter->buf); /* void */ ; cp = next) { + if (cp[0] == '5') { + cp[0] = '4'; + if (cp[4] == '5') + cp[4] = '4'; + } + if ((next = strstr(cp, "\r\n")) == 0) + break; + next += 2; + } + } + if (IN_CONNECT_EVENT(event)) { +#ifdef LIBMILTER_AUTO_DISCONNECT + milter8_close_stream(milter); +#endif + milter->state = MILTER8_STAT_REJECT_CON; + MILTER8_EVENT_BREAK(milter8_def_reply(milter, STR(milter->buf))); + } else { + MILTER8_EVENT_BREAK(STR(milter->buf)); + } + + /* + * Decision: quarantine. In Sendmail 8.13 this does not imply a + * transition in the receiver state (reply, reject, tempfail, + * accept, discard). We should not transition, either, otherwise + * we get out of sync. + */ + case SMFIR_QUARANTINE: + /* XXX What to do with the "reason" text? */ + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_BUFFER, milter->buf, + MILTER8_DATA_END) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + milter8_def_reply(milter, "H"); + continue; + + /* + * Decision: skip further events of this type. + */ + case SMFIR_SKIP: + if (data_size != 0) + break; + milter->skip_event_type = event; + MILTER8_EVENT_BREAK(milter->def_reply); + + /* + * Modification request or error. + */ + default: + if (event == SMFIC_BODYEOB) { + switch (cmd) { + +#define MILTER8_HDR_SPACE(m) (((m)->ev_mask & SMFIP_HDR_LEADSPC) ? "" : " ") + + /* + * Modification request: replace, insert or delete + * header. Index 1 means the first instance. + */ + case SMFIR_CHGHEADER: + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_HLONG, &index, + MILTER8_DATA_STRING, milter->buf, + MILTER8_DATA_STRING, milter->body, + MILTER8_DATA_END) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + /* Skip to the next request after previous edit error. */ + if (edit_resp) + continue; + /* XXX Sendmail 8 compatibility. */ + if (index == 0) + index = 1; + if ((ssize_t) index < 1) { + msg_warn("milter %s: bad change header index: %ld", + milter->m.name, (long) index); + milter8_conf_error(milter); + MILTER8_EVENT_BREAK(milter->def_reply); + } + if (LEN(milter->buf) == 0) { + msg_warn("milter %s: null change header name", + milter->m.name); + milter8_conf_error(milter); + MILTER8_EVENT_BREAK(milter->def_reply); + } + if (STR(milter->body)[0]) + edit_resp = parent->upd_header(parent->chg_context, + (ssize_t) index, + STR(milter->buf), + MILTER8_HDR_SPACE(milter), + STR(milter->body)); + else + edit_resp = parent->del_header(parent->chg_context, + (ssize_t) index, + STR(milter->buf)); + continue; + + /* + * Modification request: append header. + */ + case SMFIR_ADDHEADER: + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_STRING, milter->buf, + MILTER8_DATA_STRING, milter->body, + MILTER8_DATA_END) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + /* Skip to the next request after previous edit error. */ + if (edit_resp) + continue; + edit_resp = parent->add_header(parent->chg_context, + STR(milter->buf), + MILTER8_HDR_SPACE(milter), + STR(milter->body)); + continue; + + /* + * Modification request: insert header. With Sendmail 8, + * index 0 means the top-most header. We use 1-based + * indexing for consistency with header change + * operations. + */ + case SMFIR_INSHEADER: + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_HLONG, &index, + MILTER8_DATA_STRING, milter->buf, + MILTER8_DATA_STRING, milter->body, + MILTER8_DATA_END) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + /* Skip to the next request after previous edit error. */ + if (edit_resp) + continue; + if ((ssize_t) index + 1 < 1) { + msg_warn("milter %s: bad insert header index: %ld", + milter->m.name, (long) index); + milter8_conf_error(milter); + MILTER8_EVENT_BREAK(milter->def_reply); + } + edit_resp = parent->ins_header(parent->chg_context, + (ssize_t) index + 1, + STR(milter->buf), + MILTER8_HDR_SPACE(milter), + STR(milter->body)); + continue; + + /* + * Modification request: replace sender, with optional + * ESMTP args. + */ + case SMFIR_CHGFROM: + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_STRING, milter->buf, + MILTER8_DATA_MORE) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + if (data_size > 0) { + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_STRING, milter->body, + MILTER8_DATA_END) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + } else { + VSTRING_RESET(milter->body); + VSTRING_TERMINATE(milter->body); + } + /* Skip to the next request after previous edit error. */ + if (edit_resp) + continue; + edit_resp = parent->chg_from(parent->chg_context, + STR(milter->buf), + STR(milter->body)); + continue; + + /* + * Modification request: append recipient. + */ + case SMFIR_ADDRCPT: + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_STRING, milter->buf, + MILTER8_DATA_END) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + /* Skip to the next request after previous edit error. */ + if (edit_resp) + continue; + edit_resp = parent->add_rcpt(parent->chg_context, + STR(milter->buf)); + continue; + + /* + * Modification request: append recipient, with optional + * ESMTP args. + */ + case SMFIR_ADDRCPT_PAR: + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_STRING, milter->buf, + MILTER8_DATA_MORE) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + if (data_size > 0) { + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_STRING, milter->body, + MILTER8_DATA_END) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + } else { + VSTRING_RESET(milter->body); + VSTRING_TERMINATE(milter->body); + } + /* Skip to the next request after previous edit error. */ + if (edit_resp) + continue; + edit_resp = parent->add_rcpt_par(parent->chg_context, + STR(milter->buf), + STR(milter->body)); + continue; + + /* + * Modification request: delete (expansion of) recipient. + */ + case SMFIR_DELRCPT: + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_STRING, milter->buf, + MILTER8_DATA_END) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + /* Skip to the next request after previous edit error. */ + if (edit_resp) + continue; + edit_resp = parent->del_rcpt(parent->chg_context, + STR(milter->buf)); + continue; + + /* + * Modification request: replace the message body, and + * update the message size. + */ + case SMFIR_REPLBODY: + if (body_edit_lockout) { + msg_warn("milter %s: body replacement requests can't " + "currently be mixed with other requests", + milter->m.name); + milter8_conf_error(milter); + MILTER8_EVENT_BREAK(milter->def_reply); + } + if (milter8_read_data(milter, &data_size, + MILTER8_DATA_BUFFER, milter->body, + MILTER8_DATA_END) != 0) + MILTER8_EVENT_BREAK(milter->def_reply); + /* Skip to the next request after previous edit error. */ + if (edit_resp) + continue; + /* Start body replacement. */ + if (body_line_buf == 0) { + body_line_buf = vstring_alloc(var_line_limit); + edit_resp = parent->repl_body(parent->chg_context, + MILTER_BODY_START, + /* unused */ 0, + (VSTRING *) 0); + } + /* Extract lines from the on-the-wire CRLF format. */ + for (cp = STR(milter->body); edit_resp == 0 + && cp < vstring_end(milter->body); cp++) { + ch = *(unsigned char *) cp; + if (ch == '\n') { + if (LEN(body_line_buf) > 0 + && vstring_end(body_line_buf)[-1] == '\r') + vstring_truncate(body_line_buf, + LEN(body_line_buf) - 1); + edit_resp = parent->repl_body(parent->chg_context, + MILTER_BODY_LINE, + REC_TYPE_NORM, + body_line_buf); + VSTRING_RESET(body_line_buf); + } else { + /* Preserves \r if not followed by \n. */ + if (LEN(body_line_buf) == var_line_limit) { + edit_resp = parent->repl_body(parent->chg_context, + MILTER_BODY_LINE, + REC_TYPE_CONT, + body_line_buf); + VSTRING_RESET(body_line_buf); + } + VSTRING_ADDCH(body_line_buf, ch); + } + } + continue; + } + } + msg_warn("milter %s: unexpected filter response %s after event %s", + milter->m.name, + (smfir_name = str_name_code(smfir_table, cmd)) != 0 ? + smfir_name : "(unknown filter reply)", + (smfic_name = str_name_code(smfic_table, event)) != 0 ? + smfic_name : "(unknown MTA event)"); + milter8_comm_error(milter); + MILTER8_EVENT_BREAK(milter->def_reply); + } + + /* + * Get here when the reply was followed by data bytes that weren't + * supposed to be there. + */ + msg_warn("milter %s: reply %s was followed by %ld data bytes", + milter->m.name, (smfir_name = str_name_code(smfir_table, cmd)) != 0 ? + smfir_name : "unknown", (long) data_len); + milter8_comm_error(milter); + MILTER8_EVENT_BREAK(milter->def_reply); + } + + /* + * Clean up after aborted message body replacement. + */ + if (body_line_buf) + vstring_free(body_line_buf); + + /* + * XXX Some cleanup clients ask the cleanup server to bounce mail for + * them. In that case we must override a hard reject retval result after + * queue file update failure. This is not a big problem; the odds are + * small that a Milter application sends a hard reject after replacing + * the message body. + */ + if (edit_resp && (retval == 0 || strchr("DS4", retval[0]) == 0)) + retval = edit_resp; + return (retval); +} + +/* milter8_connect - connect to filter */ + +static void milter8_connect(MILTER8 *milter) +{ + const char *myname = "milter8_connect"; + ssize_t data_len; + unsigned char cmd; + char *transport; + char *endpoint; + int (*connect_fn) (const char *, int, int); + int fd; + const UINT32_TYPE my_actions = (SMFIF_ADDHDRS | SMFIF_ADDRCPT + | SMFIF_DELRCPT | SMFIF_CHGHDRS + | SMFIF_CHGBODY + | SMFIF_QUARANTINE + | SMFIF_CHGFROM + | SMFIF_ADDRCPT_PAR + | SMFIF_SETSYMLIST + ); + UINT32_TYPE my_version = 0; + UINT32_TYPE my_events = 0; + char *saved_version; + char *cp; + char *name; + + /* + * Sanity check. + */ + if (milter->fp != 0) + msg_panic("%s: milter %s: socket is not closed", + myname, milter->m.name); + + /* + * For user friendliness reasons the milter_protocol configuration + * parameter can specify both the protocol version and protocol + * extensions (e.g., don't reply for each individual message header). + * + * The protocol version is sent as is to the milter application. + * + * The version and extensions determine what events we can send to the + * milter application. + * + * We don't announce support for events that aren't defined for my protocol + * version. Today's libmilter implementations don't seem to care, but we + * don't want to take the risk that a future version will be more picky. + */ + cp = saved_version = mystrdup(milter->protocol); + while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) { + int mask; + int vers; + + if ((mask = name_code(milter8_event_masks, + NAME_CODE_FLAG_NONE, name)) == -1 + || (vers = name_code(milter8_versions, + NAME_CODE_FLAG_NONE, name)) == -1 + || (vers != 0 && my_version != 0)) { + msg_warn("milter %s: bad protocol information: %s", + milter->m.name, name); + milter8_conf_error(milter); + return; + } + if (vers != 0) + my_version = vers; + my_events |= mask; + } + myfree(saved_version); + if (my_events == 0 || my_version == 0) { + msg_warn("milter %s: no protocol version information", milter->m.name); + milter8_conf_error(milter); + return; + } + + /* + * Don't send events that aren't defined for my protocol version. + */ + milter->np_mask = (SMFIP_NOSEND_MASK & ~my_events); + if (msg_verbose) + msg_info("%s: non-protocol events for protocol version %d: %s", + myname, my_version, + str_name_mask_opt(milter->buf, "non-protocol event mask", + smfip_table, milter->np_mask, NAME_MASK_NUMBER)); + + /* + * Parse the Milter application endpoint. + */ +#define FREE_TRANSPORT_AND_BAIL_OUT(milter, milter_error) do { \ + myfree(transport); \ + milter_error(milter); \ + return; \ + } while (0); + + transport = mystrdup(milter->m.name); + if ((endpoint = split_at(transport, ':')) == 0 + || *endpoint == 0 || *transport == 0) { + msg_warn("Milter service needs transport:endpoint instead of \"%s\"", + milter->m.name); + FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_conf_error); + } + if (msg_verbose) + msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint); + if (strcmp(transport, "inet") == 0) { + connect_fn = inet_connect; + } else if (strcmp(transport, "unix") == 0) { + connect_fn = unix_connect; + } else if (strcmp(transport, "local") == 0) { + connect_fn = LOCAL_CONNECT; + } else { + msg_warn("invalid transport name: %s in Milter service: %s", + transport, milter->m.name); + FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_conf_error); + } + + /* + * Connect to the Milter application. + */ + if ((fd = connect_fn(endpoint, BLOCKING, milter->conn_timeout)) < 0) { + msg_warn("connect to Milter service %s: %m", milter->m.name); + FREE_TRANSPORT_AND_BAIL_OUT(milter, milter8_comm_error); + } + myfree(transport); + milter->fp = vstream_fdopen(fd, O_RDWR); + vstream_control(milter->fp, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_TIMEOUT(milter->cmd_timeout), + CA_VSTREAM_CTL_END); + /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ + if (connect_fn == inet_connect) + vstream_tweak_tcp(milter->fp); + + /* + * Open the negotiations by sending what actions the Milter may request + * and what events the Milter can receive. + */ + if (msg_verbose) { + msg_info("%s: my_version=0x%lx", myname, (long) my_version); + msg_info("%s: my_actions=0x%lx %s", myname, (long) my_actions, + str_name_mask_opt(milter->buf, "request mask", + smfif_table, my_actions, NAME_MASK_NUMBER)); + msg_info("%s: my_events=0x%lx %s", myname, (long) my_events, + str_name_mask_opt(milter->buf, "event mask", + smfip_table, my_events, NAME_MASK_NUMBER)); + } + errno = 0; + if (milter8_write_cmd(milter, SMFIC_OPTNEG, + MILTER8_DATA_HLONG, my_version, + MILTER8_DATA_HLONG, my_actions, + MILTER8_DATA_HLONG, my_events, + MILTER8_DATA_END) != 0) { + msg_warn("milter %s: write error in initial handshake", + milter->m.name); + /* milter8_write_cmd() called milter8_comm_error() */ + return; + } + + /* + * Receive the filter's response and verify that we are compatible. + */ + if (milter8_read_resp(milter, SMFIC_OPTNEG, &cmd, &data_len) != 0) { + msg_warn("milter %s: read error in initial handshake", milter->m.name); + /* milter8_read_resp() called milter8_comm_error() */ + return; + } + if (cmd != SMFIC_OPTNEG) { + msg_warn("milter %s: unexpected reply \"%c\" in initial handshake", + milter->m.name, cmd); + (void) milter8_comm_error(milter); + return; + } + if (milter8_read_data(milter, &data_len, + MILTER8_DATA_HLONG, &milter->version, + MILTER8_DATA_HLONG, &milter->rq_mask, + MILTER8_DATA_HLONG, &milter->ev_mask, + MILTER8_DATA_MORE) != 0) { + msg_warn("milter %s: read error in initial handshake", milter->m.name); + /* milter8_read_data() called milter8_comm_error() */ + return; + } + if (milter->version > my_version) { + msg_warn("milter %s: protocol version %d conflict" + " with MTA protocol version %d", + milter->m.name, milter->version, my_version); + (void) milter8_comm_error(milter); + return; + } + if ((milter->rq_mask & my_actions) != milter->rq_mask) { + msg_warn("milter %s: request mask 0x%x conflict" + " with MTA request mask 0x%lx", + milter->m.name, milter->rq_mask, (long) my_actions); + (void) milter8_comm_error(milter); + return; + } + if (milter->ev_mask & SMFIP_RCPT_REJ) + milter->m.flags |= MILTER_FLAG_WANT_RCPT_REJ; + + /* + * Allow the remote application to run an older protocol version, but + * don't them send events that their protocol version doesn't support. + * Based on a suggestion by Kouhei Sutou. + * + * XXX When the Milter sends a protocol version that we don't have + * information for, use the information for the next-lower protocol + * version instead. This code assumes that the milter8_event_masks table + * is organized in reverse numerical order. + */ + if (milter->version < my_version) { + const NAME_CODE *np; + int version; + + for (np = milter8_event_masks; /* see below */ ; np++) { + if (np->name == 0) { + msg_warn("milter %s: unexpected protocol version %d", + milter->m.name, milter->version); + break; + } + if ((version = atoi(np->name)) > 0 && version <= milter->version) { + milter->np_mask |= (SMFIP_NOSEND_MASK & ~np->code); + if (msg_verbose) + msg_info("%s: non-protocol events for milter %s" + " protocol version %d: %s", + myname, milter->m.name, milter->version, + str_name_mask_opt(milter->buf, + "non-protocol event mask", + smfip_table, milter->np_mask, + NAME_MASK_NUMBER)); + break; + } + } + } + + /* + * Initial negotiations completed. + */ + if (msg_verbose) { + if ((milter->ev_mask & my_events) != milter->ev_mask) + msg_info("milter %s: event mask 0x%x includes features not" + " offered in MTA event mask 0x%lx", + milter->m.name, milter->ev_mask, (long) my_events); + msg_info("%s: milter %s version %d", + myname, milter->m.name, milter->version); + msg_info("%s: events %s", myname, + str_name_mask_opt(milter->buf, "event mask", + smfip_table, milter->ev_mask, NAME_MASK_NUMBER)); + msg_info("%s: requests %s", myname, + str_name_mask_opt(milter->buf, "request mask", + smfif_table, milter->rq_mask, NAME_MASK_NUMBER)); + } + milter->state = MILTER8_STAT_READY; + milter8_def_reply(milter, 0); + milter->skip_event_type = 0; + + /* + * Secondary negotiations: override lists of macro names. + */ + if (data_len > 0) { + VSTRING *buf = vstring_alloc(100); + UINT32_TYPE mac_type; + const char *smfim_name; + char **mac_value_ptr; + + milter->m.macros = milter_macros_alloc(MILTER_MACROS_ALLOC_EMPTY); + + while (data_len > 0 + && milter8_read_data(milter, &data_len, + MILTER8_DATA_HLONG, &mac_type, + MILTER8_DATA_STRING, buf, + MILTER8_DATA_MORE) == 0) { + smfim_name = str_name_code(smfim_table, mac_type); + if (smfim_name == 0) { + msg_warn("milter %s: ignoring unknown macro type %u", + milter->m.name, (unsigned) mac_type); + } else { + if (msg_verbose) + msg_info("override %s macro list with \"%s\"", + smfim_name, STR(buf)); + mac_value_ptr = MILTER8_MACRO_PTR(milter->m.macros, mac_type); + myfree(*mac_value_ptr); + *mac_value_ptr = mystrdup(STR(buf)); + } + } + /* milter8_read_data() calls milter8_comm_error() after error. */ + vstring_free(buf); + /* At this point the filter state is either READY or ERROR. */ + } +} + +/* milter8_conn_event - report connect event to Sendmail 8 milter */ + +static const char *milter8_conn_event(MILTER *m, + const char *client_name, + const char *client_addr, + const char *client_port, + unsigned addr_family, + ARGV *macros) +{ + const char *myname = "milter8_conn_event"; + MILTER8 *milter = (MILTER8 *) m; + int port; + int skip_reply; + const char *sm_name; + char *ptr = 0; + const char *resp; + + /* + * Need a global definition for "unknown" host name or address that is + * shared by smtpd, cleanup and libmilter. + */ +#define XXX_UNKNOWN "unknown" +#define STR_EQ(x,y) (strcmp((x), (y)) == 0) +#define STR_NE(x,y) (strcmp((x), (y)) != 0) + + /* + * Report the event. + */ + switch (milter->state) { + case MILTER8_STAT_ERROR: + if (msg_verbose) + msg_info("%s: skip milter %s", myname, milter->m.name); + return (milter->def_reply); + case MILTER8_STAT_READY: + if (msg_verbose) + msg_info("%s: milter %s: connect %s/%s", + myname, milter->m.name, client_name, client_addr); + if (client_port == 0) { + port = 0; + } else if (!alldig(client_port) || (port = atoi(client_port)) < 0 + || port > 65535) { + msg_warn("milter %s: bad client port number %s", + milter->m.name, client_port); + port = 0; + } + milter->state = MILTER8_STAT_ENVELOPE; + skip_reply = ((milter->ev_mask & SMFIP_NR_CONN) != 0); + /* Transform unknown hostname from Postfix to Sendmail form. */ + sm_name = (STR_NE(client_name, XXX_UNKNOWN) ? client_name : + STR_EQ(client_addr, XXX_UNKNOWN) ? client_name : + (ptr = concatenate("[", client_addr, "]", (char *) 0))); + switch (addr_family) { + case AF_INET: + resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT, + skip_reply, macros, + MILTER8_DATA_STRING, sm_name, + MILTER8_DATA_OCTET, SMFIA_INET, + MILTER8_DATA_NSHORT, htons(port), + MILTER8_DATA_STRING, client_addr, + MILTER8_DATA_END); + break; +#ifdef HAS_IPV6 + case AF_INET6: + resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT, + skip_reply, macros, + MILTER8_DATA_STRING, sm_name, + MILTER8_DATA_OCTET, SMFIA_INET6, + MILTER8_DATA_NSHORT, htons(port), + MILTER8_DATA_STRING, client_addr, + MILTER8_DATA_END); + break; +#endif + case AF_UNIX: + resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT, + skip_reply, macros, + MILTER8_DATA_STRING, sm_name, + MILTER8_DATA_OCTET, SMFIA_UNIX, + MILTER8_DATA_NSHORT, htons(0), + MILTER8_DATA_STRING, client_addr, + MILTER8_DATA_END); + break; + default: + resp = milter8_event(milter, SMFIC_CONNECT, SMFIP_NOCONNECT, + skip_reply, macros, + MILTER8_DATA_STRING, sm_name, + MILTER8_DATA_OCTET, SMFIA_UNKNOWN, + MILTER8_DATA_END); + break; + } + if (ptr != 0) + myfree(ptr); + return (resp); + default: + msg_panic("%s: milter %s: bad state %d", + myname, milter->m.name, milter->state); + } +} + +/* milter8_helo_event - report HELO/EHLO command to Sendmail 8 milter */ + +static const char *milter8_helo_event(MILTER *m, const char *helo_name, + int unused_esmtp, + ARGV *macros) +{ + const char *myname = "milter8_helo_event"; + MILTER8 *milter = (MILTER8 *) m; + int skip_reply; + + /* + * Report the event. + */ + switch (milter->state) { + case MILTER8_STAT_ERROR: + case MILTER8_STAT_ACCEPT_CON: + case MILTER8_STAT_REJECT_CON: + if (msg_verbose) + msg_info("%s: skip milter %s", myname, milter->m.name); + return (milter->def_reply); + case MILTER8_STAT_ENVELOPE: + case MILTER8_STAT_ACCEPT_MSG: + /* With HELO after MAIL, smtpd(8) calls milter8_abort() next. */ + if (msg_verbose) + msg_info("%s: milter %s: helo %s", + myname, milter->m.name, helo_name); + skip_reply = ((milter->ev_mask & SMFIP_NR_HELO) != 0); + return (milter8_event(milter, SMFIC_HELO, SMFIP_NOHELO, + skip_reply, macros, + MILTER8_DATA_STRING, helo_name, + MILTER8_DATA_END)); + default: + msg_panic("%s: milter %s: bad state %d", + myname, milter->m.name, milter->state); + } +} + +/* milter8_mail_event - report MAIL command to Sendmail 8 milter */ + +static const char *milter8_mail_event(MILTER *m, const char **argv, + ARGV *macros) +{ + const char *myname = "milter8_mail_event"; + MILTER8 *milter = (MILTER8 *) m; + const char **cpp; + int skip_reply; + + /* + * Report the event. + */ + switch (milter->state) { + case MILTER8_STAT_ERROR: + case MILTER8_STAT_ACCEPT_CON: + case MILTER8_STAT_REJECT_CON: + if (msg_verbose) + msg_info("%s: skip milter %s", myname, milter->m.name); + return (milter->def_reply); + case MILTER8_STAT_ENVELOPE: + if (msg_verbose) { + VSTRING *buf = vstring_alloc(100); + + for (cpp = argv; *cpp; cpp++) + vstring_sprintf_append(buf, " %s", *cpp); + msg_info("%s: milter %s: mail%s", + myname, milter->m.name, STR(buf)); + vstring_free(buf); + } + skip_reply = ((milter->ev_mask & SMFIP_NR_MAIL) != 0); + return (milter8_event(milter, SMFIC_MAIL, SMFIP_NOMAIL, + skip_reply, macros, + MILTER8_DATA_ARGV, argv, + MILTER8_DATA_END)); + default: + msg_panic("%s: milter %s: bad state %d", + myname, milter->m.name, milter->state); + } +} + +/* milter8_rcpt_event - report RCPT command to Sendmail 8 milter */ + +static const char *milter8_rcpt_event(MILTER *m, const char **argv, + ARGV *macros) +{ + const char *myname = "milter8_rcpt_event"; + MILTER8 *milter = (MILTER8 *) m; + const char **cpp; + int skip_reply; + + /* + * Report the event. + */ + switch (milter->state) { + case MILTER8_STAT_ERROR: + case MILTER8_STAT_ACCEPT_CON: + case MILTER8_STAT_REJECT_CON: + case MILTER8_STAT_ACCEPT_MSG: + if (msg_verbose) + msg_info("%s: skip milter %s", myname, milter->m.name); + return (milter->def_reply); + case MILTER8_STAT_ENVELOPE: + if (msg_verbose) { + VSTRING *buf = vstring_alloc(100); + + for (cpp = argv; *cpp; cpp++) + vstring_sprintf_append(buf, " %s", *cpp); + msg_info("%s: milter %s: rcpt%s", + myname, milter->m.name, STR(buf)); + vstring_free(buf); + } + skip_reply = ((milter->ev_mask & SMFIP_NR_RCPT) != 0); + return (milter8_event(milter, SMFIC_RCPT, SMFIP_NORCPT, + skip_reply, macros, + MILTER8_DATA_ARGV, argv, + MILTER8_DATA_END)); + default: + msg_panic("%s: milter %s: bad state %d", + myname, milter->m.name, milter->state); + } +} + +/* milter8_data_event - report DATA command to Sendmail 8 milter */ + +static const char *milter8_data_event(MILTER *m, ARGV *macros) +{ + const char *myname = "milter8_data_event"; + MILTER8 *milter = (MILTER8 *) m; + int skip_reply; + + /* + * Report the event. + */ + switch (milter->state) { + case MILTER8_STAT_ERROR: + case MILTER8_STAT_ACCEPT_CON: + case MILTER8_STAT_REJECT_CON: + case MILTER8_STAT_ACCEPT_MSG: + if (msg_verbose) + msg_info("%s: skip milter %s", myname, milter->m.name); + return (milter->def_reply); + case MILTER8_STAT_ENVELOPE: + if (msg_verbose) + msg_info("%s: milter %s: data command", myname, milter->m.name); + skip_reply = ((milter->ev_mask & SMFIP_NR_DATA) != 0); + return (milter8_event(milter, SMFIC_DATA, SMFIP_NODATA, + skip_reply, macros, + MILTER8_DATA_END)); + default: + msg_panic("%s: milter %s: bad state %d", + myname, milter->m.name, milter->state); + } +} + +/* milter8_unknown_event - report unknown SMTP command to Sendmail 8 milter */ + +static const char *milter8_unknown_event(MILTER *m, const char *command, + ARGV *macros) +{ + const char *myname = "milter8_unknown_event"; + MILTER8 *milter = (MILTER8 *) m; + int skip_reply; + + /* + * Report the event. + */ + switch (milter->state) { + case MILTER8_STAT_ERROR: + case MILTER8_STAT_ACCEPT_CON: + case MILTER8_STAT_REJECT_CON: + case MILTER8_STAT_ACCEPT_MSG: + if (msg_verbose) + msg_info("%s: skip milter %s", myname, milter->m.name); + return (milter->def_reply); + case MILTER8_STAT_ENVELOPE: + if (msg_verbose) + msg_info("%s: milter %s: unknown command: %s", + myname, milter->m.name, command); + /* XXX Sendmail doesn't send macros (checked with 8.6.13). */ + skip_reply = ((milter->ev_mask & SMFIP_NR_UNKN) != 0); + return (milter8_event(milter, SMFIC_UNKNOWN, SMFIP_NOUNKNOWN, + skip_reply, macros, + MILTER8_DATA_STRING, command, + MILTER8_DATA_END)); + default: + msg_panic("%s: milter %s: bad state %d", + myname, milter->m.name, milter->state); + } +} + +/* milter8_other_event - reply for other event */ + +static const char *milter8_other_event(MILTER *m) +{ + const char *myname = "milter8_other_event"; + MILTER8 *milter = (MILTER8 *) m; + + /* + * Return the default reply. + */ + if (msg_verbose) + msg_info("%s: milter %s", myname, milter->m.name); + return (milter->def_reply); +} + +/* milter8_abort - cancel one milter's message receiving state */ + +static void milter8_abort(MILTER *m) +{ + const char *myname = "milter8_abort"; + MILTER8 *milter = (MILTER8 *) m; + + /* + * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds + * out that the SMTP client has disconnected. Because of this, Postfix + * has to open a new MTA-to-filter socket for each SMTP client. + */ + switch (milter->state) { + case MILTER8_STAT_CLOSED: + case MILTER8_STAT_READY: + return; + case MILTER8_STAT_ERROR: + case MILTER8_STAT_ACCEPT_CON: + case MILTER8_STAT_REJECT_CON: + if (msg_verbose) + msg_info("%s: skip milter %s", myname, milter->m.name); + break; + case MILTER8_STAT_ENVELOPE: + case MILTER8_STAT_MESSAGE: + case MILTER8_STAT_ACCEPT_MSG: + if (msg_verbose) + msg_info("%s: abort milter %s", myname, milter->m.name); + (void) milter8_write_cmd(milter, SMFIC_ABORT, MILTER8_DATA_END); + if (milter->state != MILTER8_STAT_ERROR) + milter->state = MILTER8_STAT_ENVELOPE; + break; + default: + msg_panic("%s: milter %s: bad state %d", + myname, milter->m.name, milter->state); + } +} + +/* milter8_disc_event - report client disconnect event */ + +static void milter8_disc_event(MILTER *m) +{ + const char *myname = "milter8_disc_event"; + MILTER8 *milter = (MILTER8 *) m; + + /* + * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds + * out that the SMTP client has disconnected. Because of this, Postfix + * has to open a new MTA-to-filter socket for each SMTP client. + */ + switch (milter->state) { + case MILTER8_STAT_CLOSED: + case MILTER8_STAT_READY: + return; + case MILTER8_STAT_ERROR: +#ifdef LIBMILTER_AUTO_DISCONNECT + case MILTER8_STAT_ACCEPT_CON: + case MILTER8_STAT_REJECT_CON: +#endif + if (msg_verbose) + msg_info("%s: skip quit milter %s", myname, milter->m.name); + break; + case MILTER8_STAT_ENVELOPE: + case MILTER8_STAT_MESSAGE: +#ifndef LIBMILTER_AUTO_DISCONNECT + case MILTER8_STAT_ACCEPT_CON: + case MILTER8_STAT_REJECT_CON: +#endif + case MILTER8_STAT_ACCEPT_MSG: + if (msg_verbose) + msg_info("%s: quit milter %s", myname, milter->m.name); + (void) milter8_write_cmd(milter, SMFIC_QUIT, MILTER8_DATA_END); + break; + } +#ifdef LIBMILTER_AUTO_DISCONNECT + milter8_close_stream(milter); +#else + if (milter->state != MILTER8_STAT_ERROR) + milter->state = MILTER8_STAT_READY; +#endif + milter8_def_reply(milter, 0); +} + + /* + * Structure to ship context across the MIME_STATE engine. + */ +typedef struct { + MILTER8 *milter; /* milter client */ + ARGV *eoh_macros; /* end-of-header macros */ + ARGV *eod_macros; /* end-of-body macros */ + ARGV *auto_hdrs; /* auto-generated headers */ + int auto_done; /* good enough for now */ + int first_header; /* first header */ + int first_body; /* first body line */ + const char *resp; /* milter application response */ +} MILTER_MSG_CONTEXT; + +/* milter8_header - milter8_message call-back for message header */ + +static void milter8_header(void *ptr, int unused_header_class, + const HEADER_OPTS *header_info, + VSTRING *buf, off_t unused_offset) +{ + const char *myname = "milter8_header"; + MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr; + MILTER8 *milter = msg_ctx->milter; + char *cp; + int skip_reply; + char **cpp; + unsigned done; + + /* + * XXX Workaround: mime_state_update() may invoke multiple call-backs + * before returning to the caller. + */ +#define MILTER8_MESSAGE_DONE(milter, msg_ctx) \ + ((milter)->state != MILTER8_STAT_MESSAGE || (msg_ctx)->resp != 0) + + if (MILTER8_MESSAGE_DONE(milter, msg_ctx)) + return; + + /* + * XXX Sendmail compatibility. Don't expose our first (received) header + * to mail filter applications. See also cleanup_milter.c for code to + * ensure that header replace requests are relative to the message + * content as received, that is, without our own first (received) header, + * while header insert requests are relative to the message as delivered, + * that is, including our own first (received) header. + * + * XXX But this breaks when they delete our own Received: header with + * header_checks before it reaches the queue file. Even then we must not + * expose the first header to mail filter applications, otherwise the + * dk-filter signature will be inserted at the wrong position. It should + * precede the headers that it signs. + * + * XXX Sendmail compatibility. It eats the first space (not tab) after the + * header label and ":". + */ + for (cpp = msg_ctx->auto_hdrs->argv, done = 1; *cpp; cpp++, done <<= 1) + if ((msg_ctx->auto_done & done) == 0 && strcmp(*cpp, STR(buf)) == 0) { + msg_ctx->auto_done |= done; + return; + } + + /* + * Sendmail 8 sends multi-line headers as text separated by newline. + * + * We destroy the header buffer to split it into label and value. Changing + * the buffer is explicitly allowed by the mime_state(3) interface. + */ + if (msg_verbose > 1) + msg_info("%s: header milter %s: %.100s", + myname, milter->m.name, STR(buf)); + cp = STR(buf) + (header_info ? strlen(header_info->name) : + is_header(STR(buf))); + /* XXX Following matches is_header.c */ + while (*cp == ' ' || *cp == '\t') + *cp++ = 0; + if (*cp != ':') + msg_panic("%s: header label not followed by ':'", myname); + *cp++ = 0; + /* XXX Sendmail by default eats one space (not tab) after the colon. */ + if ((milter->ev_mask & SMFIP_HDR_LEADSPC) == 0 && *cp == ' ') + cp++; + skip_reply = ((milter->ev_mask & SMFIP_NOHREPL) != 0); + msg_ctx->resp = + milter8_event(milter, SMFIC_HEADER, SMFIP_NOHDRS, + skip_reply, msg_ctx->eoh_macros, + MILTER8_DATA_STRING, STR(buf), + MILTER8_DATA_STRING, cp, + MILTER8_DATA_END); +} + +/* milter8_eoh - milter8_message call-back for end-of-header */ + +static void milter8_eoh(void *ptr) +{ + const char *myname = "milter8_eoh"; + MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr; + MILTER8 *milter = msg_ctx->milter; + int skip_reply; + + if (MILTER8_MESSAGE_DONE(milter, msg_ctx)) + return; + if (msg_verbose) + msg_info("%s: eoh milter %s", myname, milter->m.name); + skip_reply = ((milter->ev_mask & SMFIP_NR_EOH) != 0); + msg_ctx->resp = + milter8_event(milter, SMFIC_EOH, SMFIP_NOEOH, + skip_reply, msg_ctx->eoh_macros, + MILTER8_DATA_END); +} + +/* milter8_body - milter8_message call-back for body content */ + +static void milter8_body(void *ptr, int rec_type, + const char *buf, ssize_t len, + off_t offset) +{ + const char *myname = "milter8_body"; + MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr; + MILTER8 *milter = msg_ctx->milter; + ssize_t todo = len; + const char *bp = buf; + ssize_t space; + ssize_t count; + int skip_reply; + + if (MILTER8_MESSAGE_DONE(milter, msg_ctx)) + return; + + /* + * XXX Sendmail compatibility: don't expose our first body line. + */ + if (msg_ctx->first_body) { + msg_ctx->first_body = 0; + return; + } + + /* + * XXX I thought I was going to delegate all the on-the-wire formatting + * to a common lower layer, but unfortunately it's not practical. If we + * were to do MILTER_CHUNK_SIZE buffering in a common lower layer, then + * we would have to pass along call-backs and state, so that the + * call-back can invoke milter8_event() with the right arguments when the + * MILTER_CHUNK_SIZE buffer reaches capacity. That's just too ugly. + * + * To recover the cost of making an extra copy of body content from Milter + * buffer to VSTREAM buffer, we could make vstream_fwrite() a little + * smarter so that it does large transfers directly from the user buffer + * instead of copying the data one block at a time into a VSTREAM buffer. + */ + if (msg_verbose > 1) + msg_info("%s: body milter %s: %.100s", myname, milter->m.name, buf); + skip_reply = ((milter->ev_mask & SMFIP_NR_BODY) != 0); + /* To append \r\n, simply redirect input to another buffer. */ + if (rec_type == REC_TYPE_NORM && todo == 0) { + bp = "\r\n"; + todo = 2; + rec_type = REC_TYPE_EOF; + } + while (todo > 0) { + /* Append one REC_TYPE_NORM or REC_TYPE_CONT to body chunk buffer. */ + space = MILTER_CHUNK_SIZE - LEN(milter->body); + if (space <= 0) + msg_panic("%s: bad buffer size: %ld", + myname, (long) LEN(milter->body)); + count = (todo > space ? space : todo); + vstring_memcat(milter->body, bp, count); + bp += count; + todo -= count; + /* Flush body chunk buffer when full. See also milter8_eob(). */ + if (LEN(milter->body) == MILTER_CHUNK_SIZE) { + msg_ctx->resp = + milter8_event(milter, SMFIC_BODY, SMFIP_NOBODY, + skip_reply, msg_ctx->eod_macros, + MILTER8_DATA_BUFFER, milter->body, + MILTER8_DATA_END); + if (MILTER8_MESSAGE_DONE(milter, msg_ctx)) + break; + VSTRING_RESET(milter->body); + } + /* To append \r\n, simply redirect input to another buffer. */ + if (rec_type == REC_TYPE_NORM && todo == 0) { + bp = "\r\n"; + todo = 2; + rec_type = REC_TYPE_EOF; + } + } +} + +/* milter8_eob - milter8_message call-back for end-of-body */ + +static void milter8_eob(void *ptr) +{ + const char *myname = "milter8_eob"; + MILTER_MSG_CONTEXT *msg_ctx = (MILTER_MSG_CONTEXT *) ptr; + MILTER8 *milter = msg_ctx->milter; + int skip_reply; + + if (MILTER8_MESSAGE_DONE(milter, msg_ctx)) + return; + if (msg_verbose) + msg_info("%s: eob milter %s", myname, milter->m.name); + + /* + * Flush partial body chunk buffer. See also milter8_body(). + * + * XXX Sendmail 8 libmilter accepts SMFIC_EOB+data, and delivers it to the + * application as two events: SMFIC_BODY+data followed by SMFIC_EOB. This + * breaks with the PMilter 0.95 protocol re-implementation, which + * delivers the SMFIC_EOB event and ignores the data. To avoid such + * compatibility problems we separate the events in the client. With + * this, we also prepare for a future where different event types can + * have different macro lists. + */ + if (LEN(milter->body) > 0) { + skip_reply = ((milter->ev_mask & SMFIP_NR_BODY) != 0); + msg_ctx->resp = + milter8_event(milter, SMFIC_BODY, SMFIP_NOBODY, + skip_reply, msg_ctx->eod_macros, + MILTER8_DATA_BUFFER, milter->body, + MILTER8_DATA_END); + if (MILTER8_MESSAGE_DONE(milter, msg_ctx)) + return; + } + msg_ctx->resp = + milter8_event(msg_ctx->milter, SMFIC_BODYEOB, 0, + DONT_SKIP_REPLY, msg_ctx->eod_macros, + MILTER8_DATA_END); +} + +/* milter8_message - send message content and receive reply */ + +static const char *milter8_message(MILTER *m, VSTREAM *qfile, + off_t data_offset, + ARGV *eoh_macros, + ARGV *eod_macros, + ARGV *auto_hdrs) +{ + const char *myname = "milter8_message"; + MILTER8 *milter = (MILTER8 *) m; + MIME_STATE *mime_state; + int rec_type; + const MIME_STATE_DETAIL *detail; + int mime_errs = 0; + MILTER_MSG_CONTEXT msg_ctx; + VSTRING *buf; + int saved_errno; + + switch (milter->state) { + case MILTER8_STAT_ERROR: + case MILTER8_STAT_ACCEPT_CON: + case MILTER8_STAT_REJECT_CON: + case MILTER8_STAT_ACCEPT_MSG: + if (msg_verbose) + msg_info("%s: skip message to milter %s", myname, milter->m.name); + return (milter->def_reply); + case MILTER8_STAT_ENVELOPE: + if (msg_verbose) + msg_info("%s: message to milter %s", myname, milter->m.name); + if (vstream_fseek(qfile, data_offset, SEEK_SET) < 0) { + saved_errno = errno; + msg_warn("%s: vstream_fseek %s: %m", myname, VSTREAM_PATH(qfile)); + /* XXX This should be available from cleanup_strerror.c. */ + return (saved_errno == EFBIG ? + "552 5.3.4 Message file too big" : + "451 4.3.0 Queue file write error"); + } + msg_ctx.milter = milter; + msg_ctx.eoh_macros = eoh_macros; + msg_ctx.eod_macros = eod_macros; + msg_ctx.auto_hdrs = auto_hdrs; + msg_ctx.auto_done = 0; + msg_ctx.first_header = 1; + msg_ctx.first_body = 1; + msg_ctx.resp = 0; + mime_state = + mime_state_alloc(MIME_OPT_DISABLE_MIME, + (milter->ev_mask & SMFIP_NOHDRS) ? + (MIME_STATE_HEAD_OUT) 0 : milter8_header, + (milter->ev_mask & SMFIP_NOEOH) ? + (MIME_STATE_ANY_END) 0 : milter8_eoh, + (milter->ev_mask & SMFIP_NOBODY) ? + (MIME_STATE_BODY_OUT) 0 : milter8_body, + milter8_eob, + (MIME_STATE_ERR_PRINT) 0, + (void *) &msg_ctx); + buf = vstring_alloc(100); + milter->state = MILTER8_STAT_MESSAGE; + VSTRING_RESET(milter->body); + vstream_control(milter->fp, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_TIMEOUT(milter->msg_timeout), + CA_VSTREAM_CTL_END); + + /* + * XXX When the message (not MIME body part) does not end in CRLF + * (i.e. the last record was REC_TYPE_CONT), do we send a CRLF + * terminator before triggering the end-of-body condition? + */ + for (;;) { + if ((rec_type = rec_get(qfile, buf, 0)) < 0) { + msg_warn("%s: error reading %s: %m", + myname, VSTREAM_PATH(qfile)); + msg_ctx.resp = "450 4.3.0 Queue file write error"; + break; + } + /* Invoke the appropriate call-back routine. */ + mime_errs = mime_state_update(mime_state, rec_type, + STR(buf), LEN(buf)); + if (mime_errs) { + detail = mime_state_detail(mime_errs); + msg_warn("%s: MIME problem %s in %s", + myname, detail->text, VSTREAM_PATH(qfile)); + msg_ctx.resp = "450 4.3.0 Queue file write error"; + break; + } + if (MILTER8_MESSAGE_DONE(milter, &msg_ctx)) + break; + if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT) + break; + } + mime_state_free(mime_state); + vstring_free(buf); + if (milter->fp) + vstream_control(milter->fp, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_TIMEOUT(milter->cmd_timeout), + CA_VSTREAM_CTL_END); + if (milter->state == MILTER8_STAT_MESSAGE + || milter->state == MILTER8_STAT_ACCEPT_MSG) + milter->state = MILTER8_STAT_ENVELOPE; + return (msg_ctx.resp); + default: + msg_panic("%s: milter %s: bad state %d", + myname, milter->m.name, milter->state); + } +} + + /* + * Preliminary protocol to send/receive milter instances. This needs to be + * extended with type information once we support multiple milter protocols. + */ +#define MAIL_ATTR_MILT_NAME "milter_name" +#define MAIL_ATTR_MILT_VERS "milter_version" +#define MAIL_ATTR_MILT_ACTS "milter_actions" +#define MAIL_ATTR_MILT_EVTS "milter_events" +#define MAIL_ATTR_MILT_NPTS "milter_non_events" +#define MAIL_ATTR_MILT_STAT "milter_state" +#define MAIL_ATTR_MILT_CONN "milter_conn_timeout" +#define MAIL_ATTR_MILT_CMD "milter_cmd_timeout" +#define MAIL_ATTR_MILT_MSG "milter_msg_timeout" +#define MAIL_ATTR_MILT_ACT "milter_action" +#define MAIL_ATTR_MILT_MAC "milter_macro_list" + +/* milter8_active - report if this milter still wants events */ + +static int milter8_active(MILTER *m) +{ + MILTER8 *milter = (MILTER8 *) m; + + return (milter->fp != 0 + && (milter->state == MILTER8_STAT_ENVELOPE + || milter->state == MILTER8_STAT_READY)); +} + +/* milter8_send - send milter instance */ + +static int milter8_send(MILTER *m, VSTREAM *stream) +{ + const char *myname = "milter8_send"; + MILTER8 *milter = (MILTER8 *) m; + + if (msg_verbose) + msg_info("%s: milter %s", myname, milter->m.name); + + /* + * The next read on this Milter socket happens in a different process. It + * will not automatically flush the output buffer in this process. + */ + if (milter->fp) + vstream_fflush(milter->fp); + + if (attr_print(stream, ATTR_FLAG_MORE, + SEND_ATTR_STR(MAIL_ATTR_MILT_NAME, milter->m.name), + SEND_ATTR_INT(MAIL_ATTR_MILT_VERS, milter->version), + SEND_ATTR_INT(MAIL_ATTR_MILT_ACTS, milter->rq_mask), + SEND_ATTR_INT(MAIL_ATTR_MILT_EVTS, milter->ev_mask), + SEND_ATTR_INT(MAIL_ATTR_MILT_NPTS, milter->np_mask), + SEND_ATTR_INT(MAIL_ATTR_MILT_STAT, milter->state), + SEND_ATTR_INT(MAIL_ATTR_MILT_CONN, milter->conn_timeout), + SEND_ATTR_INT(MAIL_ATTR_MILT_CMD, milter->cmd_timeout), + SEND_ATTR_INT(MAIL_ATTR_MILT_MSG, milter->msg_timeout), + SEND_ATTR_STR(MAIL_ATTR_MILT_ACT, milter->def_action), + SEND_ATTR_INT(MAIL_ATTR_MILT_MAC, milter->m.macros != 0), + ATTR_TYPE_END) != 0 + || (milter->m.macros != 0 + && attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(milter_macros_print, + (const void *) milter->m.macros), + ATTR_TYPE_END) != 0) + || (milter->m.macros == 0 + && attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_END) != 0) + || vstream_fflush(stream) != 0) { + return (-1); +#ifdef CANT_WRITE_BEFORE_SENDING_FD + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_DUMMY, milter->buf), + ATTR_TYPE_END) != 1) { + return (-1); +#endif + } else if (LOCAL_SEND_FD(vstream_fileno(stream), + vstream_fileno(milter->fp)) < 0) { + return (-1); +#ifdef MUST_READ_AFTER_SENDING_FD + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_DUMMY, milter->buf), + ATTR_TYPE_END) != 1) { + return (-1); +#endif + } else { + return (0); + } +} + +static MILTER8 *milter8_alloc(const char *, int, int, int, const char *, + const char *, MILTERS *); + +/* milter8_receive - receive milter instance */ + +MILTER *milter8_receive(VSTREAM *stream, MILTERS *parent) +{ + const char *myname = "milter8_receive"; + static VSTRING *name_buf; + static VSTRING *act_buf; + MILTER8 *milter; + int version; + int rq_mask; + int ev_mask; + int np_mask; + int state; + int conn_timeout; + int cmd_timeout; + int msg_timeout; + int fd; + int has_macros; + MILTER_MACROS *macros = 0; + +#define FREE_MACROS_AND_RETURN(x) do { \ + if (macros) \ + milter_macros_free(macros); \ + return (x); \ + } while (0) + + if (name_buf == 0) { + name_buf = vstring_alloc(10); + act_buf = vstring_alloc(10); + } + if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, + RECV_ATTR_STR(MAIL_ATTR_MILT_NAME, name_buf), + RECV_ATTR_INT(MAIL_ATTR_MILT_VERS, &version), + RECV_ATTR_INT(MAIL_ATTR_MILT_ACTS, &rq_mask), + RECV_ATTR_INT(MAIL_ATTR_MILT_EVTS, &ev_mask), + RECV_ATTR_INT(MAIL_ATTR_MILT_NPTS, &np_mask), + RECV_ATTR_INT(MAIL_ATTR_MILT_STAT, &state), + RECV_ATTR_INT(MAIL_ATTR_MILT_CONN, &conn_timeout), + RECV_ATTR_INT(MAIL_ATTR_MILT_CMD, &cmd_timeout), + RECV_ATTR_INT(MAIL_ATTR_MILT_MSG, &msg_timeout), + RECV_ATTR_STR(MAIL_ATTR_MILT_ACT, act_buf), + RECV_ATTR_INT(MAIL_ATTR_MILT_MAC, &has_macros), + ATTR_TYPE_END) < 10 + || (has_macros != 0 + && attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_FUNC(milter_macros_scan, + (void *) (macros = + milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO))), + ATTR_TYPE_END) < 1) + || (has_macros == 0 + && attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_END) < 0)) { + FREE_MACROS_AND_RETURN(0); +#ifdef CANT_WRITE_BEFORE_SENDING_FD + } else if (attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""), + ATTR_TYPE_END) != 0 + || vstream_fflush(stream) != 0) { + FREE_MACROS_AND_RETURN(0); +#endif + } else if ((fd = LOCAL_RECV_FD(vstream_fileno(stream))) < 0) { + FREE_MACROS_AND_RETURN(0); + } else { +#ifdef MUST_READ_AFTER_SENDING_FD + (void) attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""), + ATTR_TYPE_END); +#endif +#define NO_PROTOCOL ((char *) 0) + + if (msg_verbose) + msg_info("%s: milter %s", myname, STR(name_buf)); + + milter = milter8_alloc(STR(name_buf), conn_timeout, cmd_timeout, + msg_timeout, NO_PROTOCOL, STR(act_buf), parent); + milter->fp = vstream_fdopen(fd, O_RDWR); + milter->m.macros = macros; + vstream_control(milter->fp, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END); + /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ + vstream_tweak_sock(milter->fp); + milter->version = version; + milter->rq_mask = rq_mask; + milter->ev_mask = ev_mask; + milter->np_mask = np_mask; + milter->state = state; + return (&milter->m); + } +} + +/* milter8_free - destroy Milter instance */ + +static void milter8_free(MILTER *m) +{ + MILTER8 *milter = (MILTER8 *) m; + + if (msg_verbose) + msg_info("free milter %s", milter->m.name); + if (milter->fp) + (void) vstream_fclose(milter->fp); + myfree(milter->m.name); + vstring_free(milter->buf); + vstring_free(milter->body); + if (milter->protocol) + myfree(milter->protocol); + myfree(milter->def_action); + if (milter->def_reply) + myfree(milter->def_reply); + if (milter->m.macros) + milter_macros_free(milter->m.macros); + myfree((void *) milter); +} + +/* milter8_alloc - create MTA-side Sendmail 8 Milter instance */ + +static MILTER8 *milter8_alloc(const char *name, int conn_timeout, + int cmd_timeout, int msg_timeout, + const char *protocol, + const char *def_action, + MILTERS *parent) +{ + MILTER8 *milter; + + /* + * Fill in the structure. Note: all strings must be copied. + * + * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds + * out that the SMTP client has disconnected. Because of this, Postfix + * has to open a new MTA-to-filter socket for each SMTP client. + */ + milter = (MILTER8 *) mymalloc(sizeof(*milter)); + milter->m.name = mystrdup(name); + milter->m.flags = 0; + milter->m.next = 0; + milter->m.parent = parent; + milter->m.macros = 0; +#ifdef LIBMILTER_AUTO_DISCONNECT + milter->m.connect_on_demand = (void (*) (struct MILTER *)) milter8_connect; +#else + milter->m.connect_on_demand = 0; +#endif + milter->m.conn_event = milter8_conn_event; + milter->m.helo_event = milter8_helo_event; + milter->m.mail_event = milter8_mail_event; + milter->m.rcpt_event = milter8_rcpt_event; + milter->m.data_event = milter8_data_event; /* may be null */ + milter->m.message = milter8_message; + milter->m.unknown_event = milter8_unknown_event; /* may be null */ + milter->m.other_event = milter8_other_event; + milter->m.abort = milter8_abort; + milter->m.disc_event = milter8_disc_event; + milter->m.active = milter8_active; + milter->m.send = milter8_send; + milter->m.free = milter8_free; + milter->fp = 0; + milter->buf = vstring_alloc(100); + milter->body = vstring_alloc(100); + milter->version = 0; + milter->rq_mask = 0; + milter->ev_mask = 0; + milter->state = MILTER8_STAT_CLOSED; + milter->conn_timeout = conn_timeout; + milter->cmd_timeout = cmd_timeout; + milter->msg_timeout = msg_timeout; + milter->protocol = (protocol ? mystrdup(protocol) : 0); + milter->def_action = mystrdup(def_action); + milter->def_reply = 0; + milter->skip_event_type = 0; + + return (milter); +} + +/* milter8_create - create MTA-side Sendmail 8 Milter instance */ + +MILTER *milter8_create(const char *name, int conn_timeout, int cmd_timeout, + int msg_timeout, const char *protocol, + const char *def_action, MILTERS *parent) +{ + MILTER8 *milter; + + /* + * Fill in the structure. + */ + milter = milter8_alloc(name, conn_timeout, cmd_timeout, msg_timeout, + protocol, def_action, parent); + + /* + * XXX Sendmail 8 libmilter closes the MTA-to-filter socket when it finds + * out that the SMTP client has disconnected. Because of this, Postfix + * has to open a new MTA-to-filter socket for each SMTP client. + */ +#ifndef LIBMILTER_AUTO_DISCONNECT + milter8_connect(milter); +#endif + return (&milter->m); +} diff --git a/src/milter/milter_macros.c b/src/milter/milter_macros.c new file mode 100644 index 0000000..27f5509 --- /dev/null +++ b/src/milter/milter_macros.c @@ -0,0 +1,303 @@ +/*++ +/* NAME +/* milter_macros +/* SUMMARY +/* manipulate MILTER_MACROS structures +/* SYNOPSIS +/* #include +/* +/* MILTER_MACROS *milter_macros_create(conn_macros, helo_macros, +/* mail_macros, rcpt_macros, +/* data_macros, eoh_macros, +/* eod_macros, unk_macros) +/* const char *conn_macros; +/* const char *helo_macros; +/* const char *mail_macros; +/* const char *rcpt_macrps; +/* const char *data_macros; +/* const char *eoh_macros; +/* const char *eod_macros; +/* const char *unk_macros; +/* +/* MILTER_MACROS *milter_macros_alloc(init_mode) +/* int init_mode; +/* +/* void milter_macros_free(mp) +/* MILTER_MACROS *mp; +/* +/* int milter_macros_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_COMMON_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* int milter_macros_scan(scan_fn, fp, flags, ptr) +/* ATTR_SCAN_COMMON_FN scan_fn; +/* VSTREAM *fp; +/* int flags; +/* void *ptr; +/* DESCRIPTION +/* Sendmail mail filter (Milter) applications receive sets of +/* macro name=value pairs with each SMTP or content event. +/* In Postfix, these macro names are stored in MILTER_MACROS +/* structures, as one list for each event type. By default, +/* the same structure is shared by all Milter applications; +/* it is initialized with information from main.cf. With +/* Sendmail 8.14 a Milter can override one or more lists of +/* macro names. Postfix implements this by giving the Milter +/* its own MILTER_MACROS structure and by storing the per-Milter +/* information there. +/* +/* This module maintains per-event macro name lists as +/* mystrdup()'ed values. The user is explicitly allowed to +/* update these values directly, as long as the result is +/* compatible with mystrdup(). +/* +/* milter_macros_create() creates a MILTER_MACROS structure +/* and initializes it with copies of its string arguments. +/* Null pointers are not valid as input. +/* +/* milter_macros_alloc() creates am empty MILTER_MACROS structure +/* that is initialized according to its init_mode argument. +/* .IP MILTER_MACROS_ALLOC_ZERO +/* Initialize all structure members as null pointers. This +/* mode must be used with milter_macros_scan(), because that +/* function blindly overwrites all structure members. No other +/* function except milter_macros_free() allows structure members +/* with null pointer values. +/* .IP MILTER_MACROS_ALLOC_EMPTY +/* Initialize all structure members with mystrdup(""). This +/* is not as expensive as it appears to be. +/* .PP +/* milter_macros_free() destroys a MILTER_MACROS structure and +/* frees any strings referenced by it. +/* +/* milter_macros_print() writes the contents of a MILTER_MACROS +/* structure to the named stream using the specified attribute +/* print routine. milter_macros_print() is meant to be passed +/* as a call-back to attr_print*(), thusly: +/* +/* SEND_ATTR_FUNC(milter_macros_print, (const void *) macros), +/* +/* milter_macros_scan() reads a MILTER_MACROS structure from +/* the named stream using the specified attribute scan routine. +/* No attempt is made to free the memory of existing structure +/* members. milter_macros_scan() is meant to be passed as a +/* call-back to attr_scan*(), thusly: +/* +/* RECV_ATTR_FUNC(milter_macros_scan, (void *) macros), +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + + /* + * Ad-hoc protocol to send/receive milter macro name lists. + */ +#define MAIL_ATTR_MILT_MAC_CONN "conn_macros" +#define MAIL_ATTR_MILT_MAC_HELO "helo_macros" +#define MAIL_ATTR_MILT_MAC_MAIL "mail_macros" +#define MAIL_ATTR_MILT_MAC_RCPT "rcpt_macros" +#define MAIL_ATTR_MILT_MAC_DATA "data_macros" +#define MAIL_ATTR_MILT_MAC_EOH "eoh_macros" +#define MAIL_ATTR_MILT_MAC_EOD "eod_macros" +#define MAIL_ATTR_MILT_MAC_UNK "unk_macros" + +/* milter_macros_print - write macros structure to stream */ + +int milter_macros_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp, + int flags, const void *ptr) +{ + MILTER_MACROS *mp = (MILTER_MACROS *) ptr; + int ret; + + /* + * The attribute order does not matter, except that it must be the same + * as in the milter_macros_scan() function. + */ + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_CONN, mp->conn_macros), + SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_HELO, mp->helo_macros), + SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_MAIL, mp->mail_macros), + SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_RCPT, mp->rcpt_macros), + SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_DATA, mp->data_macros), + SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_EOH, mp->eoh_macros), + SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_EOD, mp->eod_macros), + SEND_ATTR_STR(MAIL_ATTR_MILT_MAC_UNK, mp->unk_macros), + ATTR_TYPE_END); + return (ret); +} + +/* milter_macros_scan - receive macros structure from stream */ + +int milter_macros_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + MILTER_MACROS *mp = (MILTER_MACROS *) ptr; + int ret; + + /* + * We could simplify this by moving memory allocation into attr_scan*(). + */ + VSTRING *conn_macros = vstring_alloc(10); + VSTRING *helo_macros = vstring_alloc(10); + VSTRING *mail_macros = vstring_alloc(10); + VSTRING *rcpt_macros = vstring_alloc(10); + VSTRING *data_macros = vstring_alloc(10); + VSTRING *eoh_macros = vstring_alloc(10); + VSTRING *eod_macros = vstring_alloc(10); + VSTRING *unk_macros = vstring_alloc(10); + + /* + * The attribute order does not matter, except that it must be the same + * as in the milter_macros_print() function. + */ + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_CONN, conn_macros), + RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_HELO, helo_macros), + RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_MAIL, mail_macros), + RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_RCPT, rcpt_macros), + RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_DATA, data_macros), + RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_EOH, eoh_macros), + RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_EOD, eod_macros), + RECV_ATTR_STR(MAIL_ATTR_MILT_MAC_UNK, unk_macros), + ATTR_TYPE_END); + + /* + * Don't optimize for error. + */ + mp->conn_macros = vstring_export(conn_macros); + mp->helo_macros = vstring_export(helo_macros); + mp->mail_macros = vstring_export(mail_macros); + mp->rcpt_macros = vstring_export(rcpt_macros); + mp->data_macros = vstring_export(data_macros); + mp->eoh_macros = vstring_export(eoh_macros); + mp->eod_macros = vstring_export(eod_macros); + mp->unk_macros = vstring_export(unk_macros); + + return (ret == 8 ? 1 : -1); +} + +/* milter_macros_create - create and initialize macros structure */ + +MILTER_MACROS *milter_macros_create(const char *conn_macros, + const char *helo_macros, + const char *mail_macros, + const char *rcpt_macros, + const char *data_macros, + const char *eoh_macros, + const char *eod_macros, + const char *unk_macros) +{ + MILTER_MACROS *mp; + + mp = (MILTER_MACROS *) mymalloc(sizeof(*mp)); + mp->conn_macros = mystrdup(conn_macros); + mp->helo_macros = mystrdup(helo_macros); + mp->mail_macros = mystrdup(mail_macros); + mp->rcpt_macros = mystrdup(rcpt_macros); + mp->data_macros = mystrdup(data_macros); + mp->eoh_macros = mystrdup(eoh_macros); + mp->eod_macros = mystrdup(eod_macros); + mp->unk_macros = mystrdup(unk_macros); + + return (mp); +} + +/* milter_macros_alloc - allocate macros structure with simple initialization */ + +MILTER_MACROS *milter_macros_alloc(int mode) +{ + MILTER_MACROS *mp; + + /* + * This macro was originally in milter.h, but no-one else needed it. + */ +#define milter_macros_init(mp, expr) do { \ + MILTER_MACROS *__mp = (mp); \ + char *__expr = (expr); \ + __mp->conn_macros = __expr; \ + __mp->helo_macros = __expr; \ + __mp->mail_macros = __expr; \ + __mp->rcpt_macros = __expr; \ + __mp->data_macros = __expr; \ + __mp->eoh_macros = __expr; \ + __mp->eod_macros = __expr; \ + __mp->unk_macros = __expr; \ + } while (0) + + mp = (MILTER_MACROS *) mymalloc(sizeof(*mp)); + switch (mode) { + case MILTER_MACROS_ALLOC_ZERO: + milter_macros_init(mp, 0); + break; + case MILTER_MACROS_ALLOC_EMPTY: + milter_macros_init(mp, mystrdup("")); + break; + default: + msg_panic("milter_macros_alloc: unknown mode %d", mode); + } + return (mp); +} + +/* milter_macros_free - destroy memory for MILTER_MACROS structure */ + +void milter_macros_free(MILTER_MACROS *mp) +{ + + /* + * This macro was originally in milter.h, but no-one else needed it. + */ +#define milter_macros_wipe(mp) do { \ + MILTER_MACROS *__mp = mp; \ + if (__mp->conn_macros) \ + myfree(__mp->conn_macros); \ + if (__mp->helo_macros) \ + myfree(__mp->helo_macros); \ + if (__mp->mail_macros) \ + myfree(__mp->mail_macros); \ + if (__mp->rcpt_macros) \ + myfree(__mp->rcpt_macros); \ + if (__mp->data_macros) \ + myfree(__mp->data_macros); \ + if (__mp->eoh_macros) \ + myfree(__mp->eoh_macros); \ + if (__mp->eod_macros) \ + myfree(__mp->eod_macros); \ + if (__mp->unk_macros) \ + myfree(__mp->unk_macros); \ + } while (0) + + milter_macros_wipe(mp); + myfree((void *) mp); +} diff --git a/src/milter/test-list b/src/milter/test-list new file mode 100644 index 0000000..d4cef7a --- /dev/null +++ b/src/milter/test-list @@ -0,0 +1,49 @@ +# Reject with text +./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c connect -p inet:9999@0.0.0.0 +./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c helo -p inet:9999@0.0.0.0 +./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c mail -p inet:9999@0.0.0.0 +./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c rcpt -p inet:9999@0.0.0.0 +./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c header -p inet:9999@0.0.0.0 +./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c eoh -p inet:9999@0.0.0.0 +./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c body -p inet:9999@0.0.0.0 +./test-milter -C 1 -a "554 5.7.1 1% 2%% 3%%%" -c eom -p inet:9999@0.0.0.0 + +# Tempfail tests +./test-milter -C 1 -a tempfail -c connect -p inet:9999@0.0.0.0 +./test-milter -C 1 -a tempfail -c helo -p inet:9999@0.0.0.0 +./test-milter -C 1 -a tempfail -c mail -p inet:9999@0.0.0.0 +./test-milter -C 1 -a tempfail -c rcpt -p inet:9999@0.0.0.0 +./test-milter -C 1 -a tempfail -c header -p inet:9999@0.0.0.0 +./test-milter -C 1 -a tempfail -c eoh -p inet:9999@0.0.0.0 +./test-milter -C 1 -a tempfail -c body -p inet:9999@0.0.0.0 +./test-milter -C 1 -a tempfail -c eom -p inet:9999@0.0.0.0 + +# Reject tests +./test-milter -C 1 -a reject -c connect -p inet:9999@0.0.0.0 +./test-milter -C 1 -a reject -c helo -p inet:9999@0.0.0.0 +./test-milter -C 1 -a reject -c mail -p inet:9999@0.0.0.0 +./test-milter -C 1 -a reject -c rcpt -p inet:9999@0.0.0.0 +./test-milter -C 1 -a reject -c header -p inet:9999@0.0.0.0 +./test-milter -C 1 -a reject -c eoh -p inet:9999@0.0.0.0 +./test-milter -C 1 -a reject -c body -p inet:9999@0.0.0.0 +./test-milter -C 1 -a reject -c eom -p inet:9999@0.0.0.0 + +# Accept tests +./test-milter -C 1 -a accept -c connect -p inet:9999@0.0.0.0 +./test-milter -C 1 -a accept -c helo -p inet:9999@0.0.0.0 +./test-milter -C 1 -a accept -c rcpt -p inet:9999@0.0.0.0 +./test-milter -C 1 -a accept -c mail -p inet:9999@0.0.0.0 +./test-milter -C 1 -a accept -c header -p inet:9999@0.0.0.0 +./test-milter -C 1 -a accept -c eoh -p inet:9999@0.0.0.0 +./test-milter -C 1 -a accept -c body -p inet:9999@0.0.0.0 +./test-milter -C 1 -a accept -c eom -p inet:9999@0.0.0.0 + +# discard tests +./test-milter -C 1 -a discard -c connect -p inet:9999@0.0.0.0 +./test-milter -C 1 -a discard -c helo -p inet:9999@0.0.0.0 +./test-milter -C 1 -a discard -c rcpt -p inet:9999@0.0.0.0 +./test-milter -C 1 -a discard -c mail -p inet:9999@0.0.0.0 +./test-milter -C 1 -a discard -c header -p inet:9999@0.0.0.0 +./test-milter -C 1 -a discard -c eoh -p inet:9999@0.0.0.0 +./test-milter -C 1 -a discard -c body -p inet:9999@0.0.0.0 +./test-milter -C 1 -a discard -c eom -p inet:9999@0.0.0.0 diff --git a/src/milter/test-milter.c b/src/milter/test-milter.c new file mode 100644 index 0000000..0494ff0 --- /dev/null +++ b/src/milter/test-milter.c @@ -0,0 +1,840 @@ +/*++ +/* NAME +/* test-milter 1 +/* SUMMARY +/* Simple test mail filter program. +/* SYNOPSIS +/* .fi +/* \fBtest-milter\fR [\fIoptions\fR] -p \fBinet:\fIport\fB@\fIhost\fR +/* +/* \fBtest-milter\fR [\fIoptions\fR] -p \fBunix:\fIpathname\fR +/* DESCRIPTION +/* \fBtest-milter\fR is a Milter (mail filter) application that +/* exercises selected features. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments (multiple alternatives are separated by "\fB|\fR"): +/* .IP "\fB-a accept|tempfail|reject|discard|skip|\fIddd x.y.z text\fR" +/* Specifies a non-default reply for the MTA command specified +/* with \fB-c\fR. The default is \fBtempfail\fR. The \fItext\fR +/* is repeated once, to produce multi-line reply text. +/* .IP "\fB-A address\fR" +/* Add the specified recipient address (specify ESMTP parameters +/* separated by space). Multiple -A options are supported. +/* .IP "\fB-b pathname\fR" +/* Replace the message body by the content of the specified file. +/* .IP "\fB-c connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown|close|abort\fR" +/* When to send the non-default reply specified with \fB-a\fR. +/* The default protocol stage is \fBconnect\fR. +/* .IP "\fB-C\fI count\fR" +/* Terminate after \fIcount\fR connections. +/* .IP "\fB-d\fI level\fR" +/* Enable libmilter debugging at the specified level. +/* .IP "\fB-D\fI address\fR" +/* Delete the specified recipient address. Multiple -D options +/* are supported. +/* .IP "\fB-f \fIsender\fR" +/* Replace the sender by the specified address. +/* .IP "\fB-h \fI'index header-label header-value'\fR" +/* Replace the message header at the specified position. +/* .IP "\fB-i \fI'index header-label header-value'\fR" +/* Insert header at specified position. +/* .IP "\fB-l\fR" +/* Header values include leading space. Specify this option +/* before \fB-i\fR or \fB-h\fR. +/* .IP "\fB-m connect|helo|mail|rcpt|data|eoh|eom\fR" +/* The protocol stage that receives the list of macros specified +/* with \fB-M\fR. The default protocol stage is \fBconnect\fR. +/* .IP "\fB-M \fIset_macro_list\fR" +/* A non-default list of macros that the MTA should send at +/* the protocol stage specified with \fB-m\fR. +/* .IP "\fB-n connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown\fR" +/* The event that the MTA should not send. +/* .IP "\fB-N connect|helo|mail|rcpt|data|header|eoh|body|eom|unknown\fR" +/* The event for which the filter will not reply. +/* .IP "\fB-p inet:\fIport\fB@\fIhost\fB|unix:\fIpathname\fR" +/* The mail filter listen endpoint. +/* .IP "\fB-r\fR" +/* Request rejected recipients from the MTA. +/* .IP "\fB-v\fR" +/* Make the program more verbose. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libmilter/mfapi.h" +#include "libmilter/mfdef.h" + +static int conn_count; +static int verbose; + +static int test_connect_reply = SMFIS_CONTINUE; +static int test_helo_reply = SMFIS_CONTINUE; +static int test_mail_reply = SMFIS_CONTINUE; +static int test_rcpt_reply = SMFIS_CONTINUE; + +#if SMFI_VERSION > 3 +static int test_data_reply = SMFIS_CONTINUE; + +#endif +static int test_header_reply = SMFIS_CONTINUE; +static int test_eoh_reply = SMFIS_CONTINUE; +static int test_body_reply = SMFIS_CONTINUE; +static int test_eom_reply = SMFIS_CONTINUE; + +#if SMFI_VERSION > 2 +static int test_unknown_reply = SMFIS_CONTINUE; + +#endif +static int test_close_reply = SMFIS_CONTINUE; +static int test_abort_reply = SMFIS_CONTINUE; + +struct command_map { + const char *name; + int *reply; +}; + +static const struct command_map command_map[] = { + "connect", &test_connect_reply, + "helo", &test_helo_reply, + "mail", &test_mail_reply, + "rcpt", &test_rcpt_reply, + "header", &test_header_reply, + "eoh", &test_eoh_reply, + "body", &test_body_reply, + "eom", &test_eom_reply, + "abort", &test_abort_reply, + "close", &test_close_reply, +#if SMFI_VERSION > 2 + "unknown", &test_unknown_reply, +#endif +#if SMFI_VERSION > 3 + "data", &test_data_reply, +#endif + 0, 0, +}; + +static char *reply_code; +static char *reply_dsn; +static char *reply_message; + +#ifdef SMFIR_CHGFROM +static char *chg_from; + +#endif + +#ifdef SMFIR_INSHEADER +static char *ins_hdr; +static int ins_idx; +static char *ins_val; + +#endif + +#ifdef SMFIR_CHGHEADER +static char *chg_hdr; +static int chg_idx; +static char *chg_val; + +#endif + +#ifdef SMFIR_REPLBODY +static char *body_file; + +#endif + +#define MAX_RCPT 10 +int add_rcpt_count = 0; +char *add_rcpt[MAX_RCPT]; +int del_rcpt_count = 0; +char *del_rcpt[MAX_RCPT]; + +static const char *macro_names[] = { + "_", + "i", + "j", + "v", + "{auth_authen}", + "{auth_author}", + "{auth_type}", + "{cert_issuer}", + "{cert_subject}", + "{cipher}", + "{cipher_bits}", + "{client_addr}", + "{client_connections}", + "{client_name}", + "{client_port}", + "{client_ptr}", + "{client_resolve}", + "{daemon_addr}", + "{daemon_name}", + "{daemon_port}", + "{if_addr}", + "{if_name}", + "{mail_addr}", + "{mail_host}", + "{mail_mailer}", + "{rcpt_addr}", + "{rcpt_host}", + "{rcpt_mailer}", + "{tls_version}", + 0, +}; + +static int test_reply(SMFICTX *ctx, int code) +{ + const char **cpp; + const char *symval; + + for (cpp = macro_names; *cpp; cpp++) + if ((symval = smfi_getsymval(ctx, (char *) *cpp)) != 0) + printf("macro: %s=\"%s\"\n", *cpp, symval); + (void) fflush(stdout); /* In case output redirected. */ + + if (code == SMFIR_REPLYCODE) { + if (smfi_setmlreply(ctx, reply_code, reply_dsn, reply_message, reply_message, (char *) 0) == MI_FAILURE) + fprintf(stderr, "smfi_setmlreply failed\n"); + printf("test_reply %s\n\n", reply_code); + return (reply_code[0] == '4' ? SMFIS_TEMPFAIL : SMFIS_REJECT); + } else { + printf("test_reply %d\n\n", code); + return (code); + } +} + +static sfsistat test_connect(SMFICTX *ctx, char *name, struct sockaddr * sa) +{ + const char *print_addr; + char buf[BUFSIZ]; + + printf("test_connect %s ", name); + switch (sa->sa_family) { + case AF_INET: + { + struct sockaddr_in *sin = (struct sockaddr_in *) sa; + + print_addr = inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)); + if (print_addr == 0) + print_addr = strerror(errno); + printf("AF_INET (%s:%d)\n", print_addr, ntohs(sin->sin_port)); + } + break; +#ifdef HAS_IPV6 + case AF_INET6: + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) sa; + + print_addr = inet_ntop(AF_INET, &sin6->sin6_addr, buf, sizeof(buf)); + if (print_addr == 0) + print_addr = strerror(errno); + printf("AF_INET6 (%s:%d)\n", print_addr, ntohs(sin6->sin6_port)); + } + break; +#endif + case AF_UNIX: + { +#undef sun + struct sockaddr_un *sun = (struct sockaddr_un *) sa; + + printf("AF_UNIX (%s)\n", sun->sun_path); + } + break; + default: + printf(" [unknown address family]\n"); + break; + } + return (test_reply(ctx, test_connect_reply)); +} + +static sfsistat test_helo(SMFICTX *ctx, char *arg) +{ + printf("test_helo \"%s\"\n", arg ? arg : "NULL"); + return (test_reply(ctx, test_helo_reply)); +} + +static sfsistat test_mail(SMFICTX *ctx, char **argv) +{ + char **cpp; + + printf("test_mail"); + for (cpp = argv; *cpp; cpp++) + printf(" \"%s\"", *cpp); + printf("\n"); + return (test_reply(ctx, test_mail_reply)); +} + +static sfsistat test_rcpt(SMFICTX *ctx, char **argv) +{ + char **cpp; + + printf("test_rcpt"); + for (cpp = argv; *cpp; cpp++) + printf(" \"%s\"", *cpp); + printf("\n"); + return (test_reply(ctx, test_rcpt_reply)); +} + + +sfsistat test_header(SMFICTX *ctx, char *name, char *value) +{ + printf("test_header \"%s\" \"%s\"\n", name, value); + return (test_reply(ctx, test_header_reply)); +} + +static sfsistat test_eoh(SMFICTX *ctx) +{ + printf("test_eoh\n"); + return (test_reply(ctx, test_eoh_reply)); +} + +static sfsistat test_body(SMFICTX *ctx, unsigned char *data, size_t data_len) +{ + if (verbose == 0) + printf("test_body %ld bytes\n", (long) data_len); + else + printf("%.*s", (int) data_len, data); + return (test_reply(ctx, test_body_reply)); +} + +static sfsistat test_eom(SMFICTX *ctx) +{ + printf("test_eom\n"); +#ifdef SMFIR_REPLBODY + if (body_file) { + char buf[BUFSIZ + 2]; + FILE *fp; + size_t len; + int count; + + if ((fp = fopen(body_file, "r")) == 0) { + perror(body_file); + } else { + printf("replace body with content of %s\n", body_file); + for (count = 0; fgets(buf, BUFSIZ, fp) != 0; count++) { + len = strcspn(buf, "\n"); + buf[len + 0] = '\r'; + buf[len + 1] = '\n'; + if (smfi_replacebody(ctx, buf, len + 2) == MI_FAILURE) { + fprintf(stderr, "body replace failure\n"); + exit(1); + } + if (verbose) + printf("%.*s\n", (int) len, buf); + } + if (count == 0) + perror("fgets"); + (void) fclose(fp); + } + } +#endif +#ifdef SMFIR_CHGFROM + if (chg_from != 0 && smfi_chgfrom(ctx, chg_from, "whatever") == MI_FAILURE) + fprintf(stderr, "smfi_chgfrom failed\n"); +#endif +#ifdef SMFIR_INSHEADER + if (ins_hdr && smfi_insheader(ctx, ins_idx, ins_hdr, ins_val) == MI_FAILURE) + fprintf(stderr, "smfi_insheader failed\n"); +#endif +#ifdef SMFIR_CHGHEADER + if (chg_hdr && smfi_chgheader(ctx, chg_hdr, chg_idx, chg_val) == MI_FAILURE) + fprintf(stderr, "smfi_chgheader failed\n"); +#endif + { + int count; + char *args; + + for (count = 0; count < add_rcpt_count; count++) { + if ((args = strchr(add_rcpt[count], ' ')) != 0) { + *args++ = 0; + if (smfi_addrcpt_par(ctx, add_rcpt[count], args) == MI_FAILURE) + fprintf(stderr, "smfi_addrcpt_par `%s' `%s' failed\n", + add_rcpt[count], args); + } else { + if (smfi_addrcpt(ctx, add_rcpt[count]) == MI_FAILURE) + fprintf(stderr, "smfi_addrcpt `%s' failed\n", + add_rcpt[count]); + } + } + + for (count = 0; count < del_rcpt_count; count++) + if (smfi_delrcpt(ctx, del_rcpt[count]) == MI_FAILURE) + fprintf(stderr, "smfi_delrcpt `%s' failed\n", del_rcpt[count]); + } + return (test_reply(ctx, test_eom_reply)); +} + +static sfsistat test_abort(SMFICTX *ctx) +{ + printf("test_abort\n"); + return (test_reply(ctx, test_abort_reply)); +} + +static sfsistat test_close(SMFICTX *ctx) +{ + printf("test_close\n"); + if (verbose) + printf("conn_count %d\n", conn_count); + if (conn_count > 0 && --conn_count == 0) + exit(0); + return (test_reply(ctx, test_close_reply)); +} + +#if SMFI_VERSION > 3 + +static sfsistat test_data(SMFICTX *ctx) +{ + printf("test_data\n"); + return (test_reply(ctx, test_data_reply)); +} + +#endif + +#if SMFI_VERSION > 2 + +static sfsistat test_unknown(SMFICTX *ctx, const char *what) +{ + printf("test_unknown %s\n", what); + return (test_reply(ctx, test_unknown_reply)); +} + +#endif + +#if SMFI_VERSION > 5 + +static sfsistat test_negotiate(SMFICTX *, unsigned long, unsigned long, + unsigned long, unsigned long, + unsigned long *, unsigned long *, + unsigned long *, unsigned long *); + +#endif + +#ifndef SMFIF_CHGFROM +#define SMFIF_CHGFROM 0 +#endif +#ifndef SMFIP_HDR_LEADSPC +#define SMFIP_HDR_LEADSPC 0 +#define misc_mask 0 +#endif + +static struct smfiDesc smfilter = +{ + "test-milter", + SMFI_VERSION, + SMFIF_ADDRCPT | SMFIF_DELRCPT | SMFIF_ADDHDRS | SMFIF_CHGHDRS | SMFIF_CHGBODY | SMFIF_CHGFROM, + test_connect, + test_helo, + test_mail, + test_rcpt, + test_header, + test_eoh, + test_body, + test_eom, + test_abort, + test_close, +#if SMFI_VERSION > 2 + test_unknown, +#endif +#if SMFI_VERSION > 3 + test_data, +#endif +#if SMFI_VERSION > 5 + test_negotiate, +#endif +}; + +#if SMFI_VERSION > 5 + +static const char *macro_states[] = { + "connect", /* SMFIM_CONNECT */ + "helo", /* SMFIM_HELO */ + "mail", /* SMFIM_ENVFROM */ + "rcpt", /* SMFIM_ENVRCPT */ + "data", /* SMFIM_DATA */ + "eom", /* SMFIM_EOM < SMFIM_EOH */ + "eoh", /* SMFIM_EOH > SMFIM_EOM */ + 0, +}; + +static int set_macro_state; +static char *set_macro_list; + +typedef sfsistat (*FILTER_ACTION) (); + +struct noproto_map { + const char *name; + int send_mask; + int reply_mask; + int *reply; + FILTER_ACTION *action; +}; + +static const struct noproto_map noproto_map[] = { + "connect", SMFIP_NOCONNECT, SMFIP_NR_CONN, &test_connect_reply, &smfilter.xxfi_connect, + "helo", SMFIP_NOHELO, SMFIP_NR_HELO, &test_helo_reply, &smfilter.xxfi_helo, + "mail", SMFIP_NOMAIL, SMFIP_NR_MAIL, &test_mail_reply, &smfilter.xxfi_envfrom, + "rcpt", SMFIP_NORCPT, SMFIP_NR_RCPT, &test_rcpt_reply, &smfilter.xxfi_envrcpt, + "data", SMFIP_NODATA, SMFIP_NR_DATA, &test_data_reply, &smfilter.xxfi_data, + "header", SMFIP_NOHDRS, SMFIP_NR_HDR, &test_header_reply, &smfilter.xxfi_header, + "eoh", SMFIP_NOEOH, SMFIP_NR_EOH, &test_eoh_reply, &smfilter.xxfi_eoh, + "body", SMFIP_NOBODY, SMFIP_NR_BODY, &test_body_reply, &smfilter.xxfi_body, + "unknown", SMFIP_NOUNKNOWN, SMFIP_NR_UNKN, &test_connect_reply, &smfilter.xxfi_unknown, + 0, +}; + +static int nosend_mask; +static int noreply_mask; +static int misc_mask; + +static sfsistat test_negotiate(SMFICTX *ctx, + unsigned long f0, + unsigned long f1, + unsigned long f2, + unsigned long f3, + unsigned long *pf0, + unsigned long *pf1, + unsigned long *pf2, + unsigned long *pf3) +{ + if (set_macro_list) { + if (verbose) + printf("set symbol list %s to \"%s\"\n", + macro_states[set_macro_state], set_macro_list); + smfi_setsymlist(ctx, set_macro_state, set_macro_list); + } + if (verbose) + printf("negotiate f0=%lx *pf0 = %lx f1=%lx *pf1=%lx nosend=%lx noreply=%lx misc=%lx\n", + f0, *pf0, f1, *pf1, (long) nosend_mask, (long) noreply_mask, (long) misc_mask); + *pf0 = f0; + *pf1 = f1 & (nosend_mask | noreply_mask | misc_mask); + return (SMFIS_CONTINUE); +} + +#endif + +static void parse_hdr_info(const char *optarg, int *idx, + char **hdr, char **value) +{ + int len; + + len = strlen(optarg) + 1; + if ((*hdr = malloc(len)) == 0 || (*value = malloc(len)) == 0) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + if ((misc_mask & SMFIP_HDR_LEADSPC) == 0 ? + sscanf(optarg, "%d %s %[^\n]", idx, *hdr, *value) != 3 : + sscanf(optarg, "%d %[^ ]%[^\n]", idx, *hdr, *value) != 3) { + fprintf(stderr, "bad header info: %s\n", optarg); + exit(1); + } +} + +int main(int argc, char **argv) +{ + char *action = 0; + char *command = 0; + const struct command_map *cp; + int ch; + int code; + const char **cpp; + char *set_macro_state_arg = 0; + char *nosend = 0; + char *noreply = 0; + const struct noproto_map *np; + + while ((ch = getopt(argc, argv, "a:A:b:c:C:d:D:f:h:i:lm:M:n:N:p:rv")) > 0) { + switch (ch) { + case 'a': + action = optarg; + break; + case 'A': + if (add_rcpt_count >= MAX_RCPT) { + fprintf(stderr, "too many -A options\n"); + exit(1); + } + add_rcpt[add_rcpt_count++] = optarg; + break; + case 'b': +#ifdef SMFIR_REPLBODY + if (body_file) { + fprintf(stderr, "too many -b options\n"); + exit(1); + } + body_file = optarg; +#else + fprintf(stderr, "no libmilter support to replace body\n"); +#endif + break; + case 'c': + command = optarg; + break; + case 'd': + if (smfi_setdbg(atoi(optarg)) == MI_FAILURE) { + fprintf(stderr, "smfi_setdbg failed\n"); + exit(1); + } + break; + case 'D': + if (del_rcpt_count >= MAX_RCPT) { + fprintf(stderr, "too many -D options\n"); + exit(1); + } + del_rcpt[del_rcpt_count++] = optarg; + break; + case 'f': +#ifdef SMFIR_CHGFROM + if (chg_from) { + fprintf(stderr, "too many -f options\n"); + exit(1); + } + chg_from = optarg; +#else + fprintf(stderr, "no libmilter support to change sender\n"); + exit(1); +#endif + break; + case 'h': +#ifdef SMFIR_CHGHEADER + if (chg_hdr) { + fprintf(stderr, "too many -h options\n"); + exit(1); + } + parse_hdr_info(optarg, &chg_idx, &chg_hdr, &chg_val); +#else + fprintf(stderr, "no libmilter support to change header\n"); + exit(1); +#endif + break; + case 'i': +#ifdef SMFIR_INSHEADER + if (ins_hdr) { + fprintf(stderr, "too many -i options\n"); + exit(1); + } + parse_hdr_info(optarg, &ins_idx, &ins_hdr, &ins_val); +#else + fprintf(stderr, "no libmilter support to insert header\n"); + exit(1); +#endif + break; + case 'l': +#if SMFI_VERSION > 5 + if (ins_hdr || chg_hdr) { + fprintf(stderr, "specify -l before -i or -r\n"); + exit(1); + } + misc_mask |= SMFIP_HDR_LEADSPC; +#else + fprintf(stderr, "no libmilter support for leading space\n"); + exit(1); +#endif + break; + case 'm': +#if SMFI_VERSION > 5 + if (set_macro_state_arg) { + fprintf(stderr, "too many -m options\n"); + exit(1); + } + set_macro_state_arg = optarg; +#else + fprintf(stderr, "no libmilter support to specify macro list\n"); + exit(1); +#endif + break; + case 'M': +#if SMFI_VERSION > 5 + if (set_macro_list) { + fprintf(stderr, "too many -M options\n"); + exit(1); + } + set_macro_list = optarg; +#else + fprintf(stderr, "no libmilter support to specify macro list\n"); +#endif + break; + case 'n': +#if SMFI_VERSION > 5 + if (nosend) { + fprintf(stderr, "too many -n options\n"); + exit(1); + } + nosend = optarg; +#else + fprintf(stderr, "no libmilter support for negotiate callback\n"); +#endif + break; + case 'N': +#if SMFI_VERSION > 5 + if (noreply) { + fprintf(stderr, "too many -n options\n"); + exit(1); + } + noreply = optarg; +#else + fprintf(stderr, "no libmilter support for negotiate callback\n"); +#endif + break; + case 'p': + if (smfi_setconn(optarg) == MI_FAILURE) { + fprintf(stderr, "smfi_setconn failed\n"); + exit(1); + } + break; + case 'r': +#ifdef SMFIP_RCPT_REJ + misc_mask |= SMFIP_RCPT_REJ; +#else + fprintf(stderr, "no libmilter support for rejected recipients\n"); +#endif + break; + case 'v': + verbose++; + break; + case 'C': + conn_count = atoi(optarg); + break; + default: + fprintf(stderr, + "usage: %s [-dv] \n" + "\t[-a action] non-default action\n" + "\t[-b body_text] replace body\n" + "\t[-c command] non-default action trigger\n" + "\t[-h 'index label value'] replace header\n" + "\t[-i 'index label value'] insert header\n" + "\t[-m macro_state] non-default macro state\n" + "\t[-M macro_list] non-default macro list\n" + "\t[-n events] don't receive these events\n" + "\t[-N events] don't reply to these events\n" + "\t-p port milter application\n" + "\t-r request rejected recipients\n" + "\t[-C conn_count] when to exit\n", + argv[0]); + exit(1); + } + } + if (command) { + for (cp = command_map; /* see below */ ; cp++) { + if (cp->name == 0) { + fprintf(stderr, "bad -c argument: %s\n", command); + exit(1); + } + if (strcmp(command, cp->name) == 0) + break; + } + } + if (action) { + if (command == 0) + cp = command_map; + if (strcmp(action, "tempfail") == 0) { + cp->reply[0] = SMFIS_TEMPFAIL; + } else if (strcmp(action, "reject") == 0) { + cp->reply[0] = SMFIS_REJECT; + } else if (strcmp(action, "accept") == 0) { + cp->reply[0] = SMFIS_ACCEPT; + } else if (strcmp(action, "discard") == 0) { + cp->reply[0] = SMFIS_DISCARD; +#ifdef SMFIS_SKIP + } else if (strcmp(action, "skip") == 0) { + cp->reply[0] = SMFIS_SKIP; +#endif + } else if ((code = atoi(action)) >= 400 + && code <= 599 + && action[3] == ' ') { + cp->reply[0] = SMFIR_REPLYCODE; + reply_code = action; + reply_dsn = action + 3; + if (*reply_dsn != 0) { + *reply_dsn++ = 0; + reply_dsn += strspn(reply_dsn, " "); + } + if (*reply_dsn == 0) { + reply_dsn = reply_message = 0; + } else { + reply_message = reply_dsn + strcspn(reply_dsn, " "); + if (*reply_message != 0) { + *reply_message++ = 0; + reply_message += strspn(reply_message, " "); + } + if (*reply_message == 0) + reply_message = 0; + } + } else { + fprintf(stderr, "bad -a argument: %s\n", action); + exit(1); + } + if (verbose) { + printf("command %s action %d\n", cp->name, cp->reply[0]); + if (reply_code) + printf("reply code %s dsn %s message %s\n", + reply_code, reply_dsn ? reply_dsn : "(null)", + reply_message ? reply_message : "(null)"); + } + } +#if SMFI_VERSION > 5 + if (set_macro_state_arg) { + for (cpp = macro_states; /* see below */ ; cpp++) { + if (*cpp == 0) { + fprintf(stderr, "bad -m argument: %s\n", set_macro_state_arg); + exit(1); + } + if (strcmp(set_macro_state_arg, *cpp) == 0) + break; + } + set_macro_state = cpp - macro_states; + } + if (nosend) { + for (np = noproto_map; /* see below */ ; np++) { + if (np->name == 0) { + fprintf(stderr, "bad -n argument: %s\n", nosend); + exit(1); + } + if (strcmp(nosend, np->name) == 0) + break; + } + nosend_mask = np->send_mask; + np->action[0] = 0; + } + if (noreply) { + for (np = noproto_map; /* see below */ ; np++) { + if (np->name == 0) { + fprintf(stderr, "bad -N argument: %s\n", noreply); + exit(1); + } + if (strcmp(noreply, np->name) == 0) + break; + } + noreply_mask = np->reply_mask; + *np->reply = SMFIS_NOREPLY; + } +#endif + if (smfi_register(smfilter) == MI_FAILURE) { + fprintf(stderr, "smfi_register failed\n"); + exit(1); + } + return (smfi_main()); +} diff --git a/src/oqmgr/.indent.pro b/src/oqmgr/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/oqmgr/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/oqmgr/.printfck b/src/oqmgr/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/oqmgr/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/oqmgr/Makefile.in b/src/oqmgr/Makefile.in new file mode 100644 index 0000000..593042b --- /dev/null +++ b/src/oqmgr/Makefile.in @@ -0,0 +1,362 @@ +SHELL = /bin/sh +SRCS = qmgr.c qmgr_active.c qmgr_transport.c qmgr_queue.c qmgr_entry.c \ + qmgr_message.c qmgr_deliver.c qmgr_move.c \ + qmgr_defer.c qmgr_enable.c qmgr_scan.c qmgr_bounce.c qmgr_error.c \ + qmgr_feedback.c +OBJS = qmgr.o qmgr_active.o qmgr_transport.o qmgr_queue.o qmgr_entry.o \ + qmgr_message.o qmgr_deliver.o qmgr_move.o \ + qmgr_defer.o qmgr_enable.o qmgr_scan.o qmgr_bounce.o qmgr_error.o \ + qmgr_feedback.o +HDRS = qmgr.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = qmgr +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/o$(PROG) + +../../libexec/o$(PROG): $(PROG) + cp $(PROG) ../../libexec/o$(PROG) + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +qmgr.o: ../../include/argv.h +qmgr.o: ../../include/attr.h +qmgr.o: ../../include/check_arg.h +qmgr.o: ../../include/dict.h +qmgr.o: ../../include/dsn.h +qmgr.o: ../../include/events.h +qmgr.o: ../../include/flush_clnt.h +qmgr.o: ../../include/htable.h +qmgr.o: ../../include/iostuff.h +qmgr.o: ../../include/mail_conf.h +qmgr.o: ../../include/mail_flow.h +qmgr.o: ../../include/mail_params.h +qmgr.o: ../../include/mail_proto.h +qmgr.o: ../../include/mail_queue.h +qmgr.o: ../../include/mail_server.h +qmgr.o: ../../include/mail_version.h +qmgr.o: ../../include/master_proto.h +qmgr.o: ../../include/msg.h +qmgr.o: ../../include/myflock.h +qmgr.o: ../../include/mymalloc.h +qmgr.o: ../../include/nvtable.h +qmgr.o: ../../include/recipient_list.h +qmgr.o: ../../include/scan_dir.h +qmgr.o: ../../include/sys_defs.h +qmgr.o: ../../include/vbuf.h +qmgr.o: ../../include/vstream.h +qmgr.o: ../../include/vstring.h +qmgr.o: qmgr.c +qmgr.o: qmgr.h +qmgr_active.o: ../../include/abounce.h +qmgr_active.o: ../../include/attr.h +qmgr_active.o: ../../include/bounce.h +qmgr_active.o: ../../include/check_arg.h +qmgr_active.o: ../../include/defer.h +qmgr_active.o: ../../include/deliver_request.h +qmgr_active.o: ../../include/dsn.h +qmgr_active.o: ../../include/dsn_buf.h +qmgr_active.o: ../../include/dsn_mask.h +qmgr_active.o: ../../include/events.h +qmgr_active.o: ../../include/htable.h +qmgr_active.o: ../../include/info_log_addr_form.h +qmgr_active.o: ../../include/mail_open_ok.h +qmgr_active.o: ../../include/mail_params.h +qmgr_active.o: ../../include/mail_queue.h +qmgr_active.o: ../../include/msg.h +qmgr_active.o: ../../include/msg_stats.h +qmgr_active.o: ../../include/mymalloc.h +qmgr_active.o: ../../include/nvtable.h +qmgr_active.o: ../../include/qmgr_user.h +qmgr_active.o: ../../include/rec_type.h +qmgr_active.o: ../../include/recipient_list.h +qmgr_active.o: ../../include/scan_dir.h +qmgr_active.o: ../../include/sys_defs.h +qmgr_active.o: ../../include/trace.h +qmgr_active.o: ../../include/vbuf.h +qmgr_active.o: ../../include/vstream.h +qmgr_active.o: ../../include/vstring.h +qmgr_active.o: ../../include/warn_stat.h +qmgr_active.o: qmgr.h +qmgr_active.o: qmgr_active.c +qmgr_bounce.o: ../../include/attr.h +qmgr_bounce.o: ../../include/bounce.h +qmgr_bounce.o: ../../include/check_arg.h +qmgr_bounce.o: ../../include/deliver_completed.h +qmgr_bounce.o: ../../include/deliver_request.h +qmgr_bounce.o: ../../include/dsn.h +qmgr_bounce.o: ../../include/dsn_buf.h +qmgr_bounce.o: ../../include/htable.h +qmgr_bounce.o: ../../include/msg_stats.h +qmgr_bounce.o: ../../include/mymalloc.h +qmgr_bounce.o: ../../include/nvtable.h +qmgr_bounce.o: ../../include/recipient_list.h +qmgr_bounce.o: ../../include/scan_dir.h +qmgr_bounce.o: ../../include/sys_defs.h +qmgr_bounce.o: ../../include/vbuf.h +qmgr_bounce.o: ../../include/vstream.h +qmgr_bounce.o: ../../include/vstring.h +qmgr_bounce.o: qmgr.h +qmgr_bounce.o: qmgr_bounce.c +qmgr_defer.o: ../../include/attr.h +qmgr_defer.o: ../../include/bounce.h +qmgr_defer.o: ../../include/check_arg.h +qmgr_defer.o: ../../include/defer.h +qmgr_defer.o: ../../include/deliver_request.h +qmgr_defer.o: ../../include/dsn.h +qmgr_defer.o: ../../include/dsn_buf.h +qmgr_defer.o: ../../include/htable.h +qmgr_defer.o: ../../include/iostuff.h +qmgr_defer.o: ../../include/mail_proto.h +qmgr_defer.o: ../../include/msg.h +qmgr_defer.o: ../../include/msg_stats.h +qmgr_defer.o: ../../include/mymalloc.h +qmgr_defer.o: ../../include/nvtable.h +qmgr_defer.o: ../../include/recipient_list.h +qmgr_defer.o: ../../include/scan_dir.h +qmgr_defer.o: ../../include/sys_defs.h +qmgr_defer.o: ../../include/vbuf.h +qmgr_defer.o: ../../include/vstream.h +qmgr_defer.o: ../../include/vstring.h +qmgr_defer.o: qmgr.h +qmgr_defer.o: qmgr_defer.c +qmgr_deliver.o: ../../include/attr.h +qmgr_deliver.o: ../../include/check_arg.h +qmgr_deliver.o: ../../include/deliver_request.h +qmgr_deliver.o: ../../include/dsb_scan.h +qmgr_deliver.o: ../../include/dsn.h +qmgr_deliver.o: ../../include/dsn_buf.h +qmgr_deliver.o: ../../include/dsn_util.h +qmgr_deliver.o: ../../include/events.h +qmgr_deliver.o: ../../include/htable.h +qmgr_deliver.o: ../../include/iostuff.h +qmgr_deliver.o: ../../include/mail_params.h +qmgr_deliver.o: ../../include/mail_proto.h +qmgr_deliver.o: ../../include/mail_queue.h +qmgr_deliver.o: ../../include/msg.h +qmgr_deliver.o: ../../include/msg_stats.h +qmgr_deliver.o: ../../include/mymalloc.h +qmgr_deliver.o: ../../include/nvtable.h +qmgr_deliver.o: ../../include/rcpt_print.h +qmgr_deliver.o: ../../include/recipient_list.h +qmgr_deliver.o: ../../include/scan_dir.h +qmgr_deliver.o: ../../include/smtputf8.h +qmgr_deliver.o: ../../include/stringops.h +qmgr_deliver.o: ../../include/sys_defs.h +qmgr_deliver.o: ../../include/vbuf.h +qmgr_deliver.o: ../../include/verp_sender.h +qmgr_deliver.o: ../../include/vstream.h +qmgr_deliver.o: ../../include/vstring.h +qmgr_deliver.o: ../../include/vstring_vstream.h +qmgr_deliver.o: qmgr.h +qmgr_deliver.o: qmgr_deliver.c +qmgr_enable.o: ../../include/check_arg.h +qmgr_enable.o: ../../include/dsn.h +qmgr_enable.o: ../../include/msg.h +qmgr_enable.o: ../../include/recipient_list.h +qmgr_enable.o: ../../include/scan_dir.h +qmgr_enable.o: ../../include/sys_defs.h +qmgr_enable.o: ../../include/vbuf.h +qmgr_enable.o: ../../include/vstream.h +qmgr_enable.o: qmgr.h +qmgr_enable.o: qmgr_enable.c +qmgr_entry.o: ../../include/attr.h +qmgr_entry.o: ../../include/check_arg.h +qmgr_entry.o: ../../include/deliver_request.h +qmgr_entry.o: ../../include/dsn.h +qmgr_entry.o: ../../include/events.h +qmgr_entry.o: ../../include/htable.h +qmgr_entry.o: ../../include/mail_params.h +qmgr_entry.o: ../../include/msg.h +qmgr_entry.o: ../../include/msg_stats.h +qmgr_entry.o: ../../include/mymalloc.h +qmgr_entry.o: ../../include/nvtable.h +qmgr_entry.o: ../../include/recipient_list.h +qmgr_entry.o: ../../include/scan_dir.h +qmgr_entry.o: ../../include/sys_defs.h +qmgr_entry.o: ../../include/vbuf.h +qmgr_entry.o: ../../include/vstream.h +qmgr_entry.o: ../../include/vstring.h +qmgr_entry.o: qmgr.h +qmgr_entry.o: qmgr_entry.c +qmgr_error.o: ../../include/check_arg.h +qmgr_error.o: ../../include/dsn.h +qmgr_error.o: ../../include/mymalloc.h +qmgr_error.o: ../../include/recipient_list.h +qmgr_error.o: ../../include/scan_dir.h +qmgr_error.o: ../../include/stringops.h +qmgr_error.o: ../../include/sys_defs.h +qmgr_error.o: ../../include/vbuf.h +qmgr_error.o: ../../include/vstream.h +qmgr_error.o: ../../include/vstring.h +qmgr_error.o: qmgr.h +qmgr_error.o: qmgr_error.c +qmgr_feedback.o: ../../include/check_arg.h +qmgr_feedback.o: ../../include/dsn.h +qmgr_feedback.o: ../../include/mail_conf.h +qmgr_feedback.o: ../../include/mail_params.h +qmgr_feedback.o: ../../include/msg.h +qmgr_feedback.o: ../../include/mymalloc.h +qmgr_feedback.o: ../../include/name_code.h +qmgr_feedback.o: ../../include/recipient_list.h +qmgr_feedback.o: ../../include/scan_dir.h +qmgr_feedback.o: ../../include/stringops.h +qmgr_feedback.o: ../../include/sys_defs.h +qmgr_feedback.o: ../../include/vbuf.h +qmgr_feedback.o: ../../include/vstream.h +qmgr_feedback.o: ../../include/vstring.h +qmgr_feedback.o: qmgr.h +qmgr_feedback.o: qmgr_feedback.c +qmgr_message.o: ../../include/argv.h +qmgr_message.o: ../../include/attr.h +qmgr_message.o: ../../include/bounce.h +qmgr_message.o: ../../include/canon_addr.h +qmgr_message.o: ../../include/check_arg.h +qmgr_message.o: ../../include/deliver_completed.h +qmgr_message.o: ../../include/deliver_request.h +qmgr_message.o: ../../include/dict.h +qmgr_message.o: ../../include/dsn.h +qmgr_message.o: ../../include/dsn_buf.h +qmgr_message.o: ../../include/dsn_mask.h +qmgr_message.o: ../../include/htable.h +qmgr_message.o: ../../include/iostuff.h +qmgr_message.o: ../../include/mail_params.h +qmgr_message.o: ../../include/mail_proto.h +qmgr_message.o: ../../include/mail_queue.h +qmgr_message.o: ../../include/msg.h +qmgr_message.o: ../../include/msg_stats.h +qmgr_message.o: ../../include/myflock.h +qmgr_message.o: ../../include/mymalloc.h +qmgr_message.o: ../../include/nvtable.h +qmgr_message.o: ../../include/opened.h +qmgr_message.o: ../../include/qmgr_user.h +qmgr_message.o: ../../include/rec_attr_map.h +qmgr_message.o: ../../include/rec_type.h +qmgr_message.o: ../../include/recipient_list.h +qmgr_message.o: ../../include/record.h +qmgr_message.o: ../../include/resolve_clnt.h +qmgr_message.o: ../../include/rewrite_clnt.h +qmgr_message.o: ../../include/scan_dir.h +qmgr_message.o: ../../include/sent.h +qmgr_message.o: ../../include/split_addr.h +qmgr_message.o: ../../include/split_at.h +qmgr_message.o: ../../include/stringops.h +qmgr_message.o: ../../include/sys_defs.h +qmgr_message.o: ../../include/valid_hostname.h +qmgr_message.o: ../../include/vbuf.h +qmgr_message.o: ../../include/verp_sender.h +qmgr_message.o: ../../include/vstream.h +qmgr_message.o: ../../include/vstring.h +qmgr_message.o: qmgr.h +qmgr_message.o: qmgr_message.c +qmgr_move.o: ../../include/check_arg.h +qmgr_move.o: ../../include/dsn.h +qmgr_move.o: ../../include/mail_queue.h +qmgr_move.o: ../../include/mail_scan_dir.h +qmgr_move.o: ../../include/msg.h +qmgr_move.o: ../../include/recipient_list.h +qmgr_move.o: ../../include/scan_dir.h +qmgr_move.o: ../../include/sys_defs.h +qmgr_move.o: ../../include/vbuf.h +qmgr_move.o: ../../include/vstream.h +qmgr_move.o: ../../include/vstring.h +qmgr_move.o: qmgr.h +qmgr_move.o: qmgr_move.c +qmgr_queue.o: ../../include/attr.h +qmgr_queue.o: ../../include/check_arg.h +qmgr_queue.o: ../../include/dsn.h +qmgr_queue.o: ../../include/events.h +qmgr_queue.o: ../../include/htable.h +qmgr_queue.o: ../../include/iostuff.h +qmgr_queue.o: ../../include/mail_params.h +qmgr_queue.o: ../../include/mail_proto.h +qmgr_queue.o: ../../include/msg.h +qmgr_queue.o: ../../include/mymalloc.h +qmgr_queue.o: ../../include/nvtable.h +qmgr_queue.o: ../../include/recipient_list.h +qmgr_queue.o: ../../include/scan_dir.h +qmgr_queue.o: ../../include/sys_defs.h +qmgr_queue.o: ../../include/vbuf.h +qmgr_queue.o: ../../include/vstream.h +qmgr_queue.o: ../../include/vstring.h +qmgr_queue.o: qmgr.h +qmgr_queue.o: qmgr_queue.c +qmgr_scan.o: ../../include/check_arg.h +qmgr_scan.o: ../../include/dsn.h +qmgr_scan.o: ../../include/mail_scan_dir.h +qmgr_scan.o: ../../include/msg.h +qmgr_scan.o: ../../include/mymalloc.h +qmgr_scan.o: ../../include/recipient_list.h +qmgr_scan.o: ../../include/scan_dir.h +qmgr_scan.o: ../../include/sys_defs.h +qmgr_scan.o: ../../include/vbuf.h +qmgr_scan.o: ../../include/vstream.h +qmgr_scan.o: qmgr.h +qmgr_scan.o: qmgr_scan.c +qmgr_transport.o: ../../include/attr.h +qmgr_transport.o: ../../include/check_arg.h +qmgr_transport.o: ../../include/dsn.h +qmgr_transport.o: ../../include/events.h +qmgr_transport.o: ../../include/htable.h +qmgr_transport.o: ../../include/iostuff.h +qmgr_transport.o: ../../include/mail_conf.h +qmgr_transport.o: ../../include/mail_params.h +qmgr_transport.o: ../../include/mail_proto.h +qmgr_transport.o: ../../include/msg.h +qmgr_transport.o: ../../include/mymalloc.h +qmgr_transport.o: ../../include/nvtable.h +qmgr_transport.o: ../../include/recipient_list.h +qmgr_transport.o: ../../include/scan_dir.h +qmgr_transport.o: ../../include/sys_defs.h +qmgr_transport.o: ../../include/vbuf.h +qmgr_transport.o: ../../include/vstream.h +qmgr_transport.o: ../../include/vstring.h +qmgr_transport.o: qmgr.h +qmgr_transport.o: qmgr_transport.c diff --git a/src/oqmgr/qmgr.c b/src/oqmgr/qmgr.c new file mode 100644 index 0000000..02573f1 --- /dev/null +++ b/src/oqmgr/qmgr.c @@ -0,0 +1,736 @@ +/*++ +/* NAME +/* qmgr 8 +/* SUMMARY +/* old Postfix queue manager +/* SYNOPSIS +/* \fBqmgr\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBqmgr\fR(8) daemon awaits the arrival of incoming mail +/* and arranges for its delivery via Postfix delivery processes. +/* The actual mail routing strategy is delegated to the +/* \fBtrivial-rewrite\fR(8) daemon. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* Mail addressed to the local \fBdouble-bounce\fR address is +/* logged and discarded. This stops potential loops caused by +/* undeliverable bounce notifications. +/* MAIL QUEUES +/* .ad +/* .fi +/* The \fBqmgr\fR(8) daemon maintains the following queues: +/* .IP \fBincoming\fR +/* Inbound mail from the network, or mail picked up by the +/* local \fBpickup\fR(8) agent from the \fBmaildrop\fR directory. +/* .IP \fBactive\fR +/* Messages that the queue manager has opened for delivery. Only +/* a limited number of messages is allowed to enter the \fBactive\fR +/* queue (leaky bucket strategy, for a fixed delivery rate). +/* .IP \fBdeferred\fR +/* Mail that could not be delivered upon the first attempt. The queue +/* manager implements exponential backoff by doubling the time between +/* delivery attempts. +/* .IP \fBcorrupt\fR +/* Unreadable or damaged queue files are moved here for inspection. +/* .IP \fBhold\fR +/* Messages that are kept "on hold" are kept here until someone +/* sets them free. +/* DELIVERY STATUS REPORTS +/* .ad +/* .fi +/* The \fBqmgr\fR(8) daemon keeps an eye on per-message delivery status +/* reports in the following directories. Each status report file has +/* the same name as the corresponding message file: +/* .IP \fBbounce\fR +/* Per-recipient status information about why mail is bounced. +/* These files are maintained by the \fBbounce\fR(8) daemon. +/* .IP \fBdefer\fR +/* Per-recipient status information about why mail is delayed. +/* These files are maintained by the \fBdefer\fR(8) daemon. +/* .IP \fBtrace\fR +/* Per-recipient status information as requested with the +/* Postfix "\fBsendmail -v\fR" or "\fBsendmail -bv\fR" command. +/* These files are maintained by the \fBtrace\fR(8) daemon. +/* .PP +/* The \fBqmgr\fR(8) daemon is responsible for asking the +/* \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemons to +/* send delivery reports. +/* STRATEGIES +/* .ad +/* .fi +/* The queue manager implements a variety of strategies for +/* either opening queue files (input) or for message delivery (output). +/* .IP "\fBleaky bucket\fR" +/* This strategy limits the number of messages in the \fBactive\fR queue +/* and prevents the queue manager from running out of memory under +/* heavy load. +/* .IP \fBfairness\fR +/* When the \fBactive\fR queue has room, the queue manager takes one +/* message from the \fBincoming\fR queue and one from the \fBdeferred\fR +/* queue. This prevents a large mail backlog from blocking the delivery +/* of new mail. +/* .IP "\fBslow start\fR" +/* This strategy eliminates "thundering herd" problems by slowly +/* adjusting the number of parallel deliveries to the same destination. +/* .IP "\fBround robin\fR" +/* The queue manager sorts delivery requests by destination. +/* Round-robin selection prevents one destination from dominating +/* deliveries to other destinations. +/* .IP "\fBexponential backoff\fR" +/* Mail that cannot be delivered upon the first attempt is deferred. +/* The time interval between delivery attempts is doubled after each +/* attempt. +/* .IP "\fBdestination status cache\fR" +/* The queue manager avoids unnecessary delivery attempts by +/* maintaining a short-term, in-memory list of unreachable destinations. +/* TRIGGERS +/* .ad +/* .fi +/* On an idle system, the queue manager waits for the arrival of +/* trigger events, or it waits for a timer to go off. A trigger +/* is a one-byte message. +/* Depending on the message received, the queue manager performs +/* one of the following actions (the message is followed by the +/* symbolic constant used internally by the software): +/* .IP "\fBD (QMGR_REQ_SCAN_DEFERRED)\fR" +/* Start a deferred queue scan. If a deferred queue scan is already +/* in progress, that scan will be restarted as soon as it finishes. +/* .IP "\fBI (QMGR_REQ_SCAN_INCOMING)\fR" +/* Start an incoming queue scan. If an incoming queue scan is already +/* in progress, that scan will be restarted as soon as it finishes. +/* .IP "\fBA (QMGR_REQ_SCAN_ALL)\fR" +/* Ignore deferred queue file time stamps. The request affects +/* the next deferred queue scan. +/* .IP "\fBF (QMGR_REQ_FLUSH_DEAD)\fR" +/* Purge all information about dead transports and destinations. +/* .IP "\fBW (TRIGGER_REQ_WAKEUP)\fR" +/* Wakeup call, This is used by the master server to instantiate +/* servers that should not go away forever. The action is to start +/* an incoming queue scan. +/* .PP +/* The \fBqmgr\fR(8) daemon reads an entire buffer worth of triggers. +/* Multiple identical trigger requests are collapsed into one, and +/* trigger requests are sorted so that \fBA\fR and \fBF\fR precede +/* \fBD\fR and \fBI\fR. Thus, in order to force a deferred queue run, +/* one would request \fBA F D\fR; in order to notify the queue manager +/* of the arrival of new mail one would request \fBI\fR. +/* STANDARDS +/* RFC 3463 (Enhanced status codes) +/* RFC 3464 (Delivery status notifications) +/* SECURITY +/* .ad +/* .fi +/* The \fBqmgr\fR(8) daemon is not security sensitive. It reads +/* single-character messages from untrusted local users, and thus may +/* be susceptible to denial of service attacks. The \fBqmgr\fR(8) daemon +/* does not talk to the outside world, and it can be run at fixed low +/* privilege in a chrooted environment. +/* DIAGNOSTICS +/* Problems and transactions are logged to the \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8) daemon. +/* Corrupted message files are saved to the \fBcorrupt\fR queue +/* for further inspection. +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces and of other trouble. +/* BUGS +/* A single queue manager process has to compete for disk access with +/* multiple front-end processes such as \fBcleanup\fR(8). A sudden burst of +/* inbound mail can negatively impact outbound delivery rates. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are not picked up automatically, +/* as \fBqmgr\fR(8) +/* is a persistent process. Use the command "\fBpostfix reload\fR" after +/* a configuration change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* +/* In the text below, \fItransport\fR is the first field in a +/* \fBmaster.cf\fR entry. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* Available before Postfix version 2.5: +/* .IP "\fBallow_min_user (no)\fR" +/* Allow a sender or recipient address to have `-' as the first +/* character. +/* .PP +/* Available with Postfix version 2.7 and later: +/* .IP "\fBdefault_filter_nexthop (empty)\fR" +/* When a content_filter or FILTER request specifies no explicit +/* next-hop destination, use $default_filter_nexthop instead; when +/* that value is empty, use the domain in the recipient address. +/* ACTIVE QUEUE CONTROLS +/* .ad +/* .fi +/* .IP "\fBqmgr_clog_warn_time (300s)\fR" +/* The minimal delay between warnings that a specific destination is +/* clogging up the Postfix active queue. +/* .IP "\fBqmgr_message_active_limit (20000)\fR" +/* The maximal number of messages in the active queue. +/* .IP "\fBqmgr_message_recipient_limit (20000)\fR" +/* The maximal number of recipients held in memory by the Postfix +/* queue manager, and the maximal size of the short-term, +/* in-memory "dead" destination status cache. +/* DELIVERY CONCURRENCY CONTROLS +/* .ad +/* .fi +/* .IP "\fBqmgr_fudge_factor (100)\fR" +/* Obsolete feature: the percentage of delivery resources that a busy +/* mail system will use up for delivery of a large mailing list +/* message. +/* .IP "\fBinitial_destination_concurrency (5)\fR" +/* The initial per-destination concurrency level for parallel delivery +/* to the same destination. +/* .IP "\fBdefault_destination_concurrency_limit (20)\fR" +/* The default maximal number of parallel deliveries to the same +/* destination. +/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBtransport_initial_destination_concurrency ($initial_destination_concurrency)\fR" +/* A transport-specific override for the initial_destination_concurrency +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* .IP "\fBdefault_destination_concurrency_failed_cohort_limit (1)\fR" +/* How many pseudo-cohorts must suffer connection or handshake +/* failure before a specific destination is considered unavailable +/* (and further delivery is suspended). +/* .IP "\fBtransport_destination_concurrency_failed_cohort_limit ($default_destination_concurrency_failed_cohort_limit)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_failed_cohort_limit parameter value, +/* where \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .IP "\fBdefault_destination_concurrency_negative_feedback (1)\fR" +/* The per-destination amount of delivery concurrency negative +/* feedback, after a delivery completes with a connection or handshake +/* failure. +/* .IP "\fBtransport_destination_concurrency_negative_feedback ($default_destination_concurrency_negative_feedback)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_negative_feedback parameter value, +/* where \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .IP "\fBdefault_destination_concurrency_positive_feedback (1)\fR" +/* The per-destination amount of delivery concurrency positive +/* feedback, after a delivery completes without connection or handshake +/* failure. +/* .IP "\fBtransport_destination_concurrency_positive_feedback ($default_destination_concurrency_positive_feedback)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_positive_feedback parameter value, +/* where \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .IP "\fBdestination_concurrency_feedback_debug (no)\fR" +/* Make the queue manager's feedback algorithm verbose for performance +/* analysis purposes. +/* RECIPIENT SCHEDULING CONTROLS +/* .ad +/* .fi +/* .IP "\fBdefault_destination_recipient_limit (50)\fR" +/* The default maximal number of recipients per message delivery. +/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR" +/* A transport-specific override for the +/* default_destination_recipient_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* OTHER RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBminimal_backoff_time (300s)\fR" +/* The minimal time between attempts to deliver a deferred message; +/* prior to Postfix 2.4 the default value was 1000s. +/* .IP "\fBmaximal_backoff_time (4000s)\fR" +/* The maximal time between attempts to deliver a deferred message. +/* .IP "\fBmaximal_queue_lifetime (5d)\fR" +/* Consider a message as undeliverable, when delivery fails with a +/* temporary error, and the time in the queue has reached the +/* maximal_queue_lifetime limit. +/* .IP "\fBqueue_run_delay (300s)\fR" +/* The time between deferred queue scans by the queue manager; +/* prior to Postfix 2.4 the default value was 1000s. +/* .IP "\fBtransport_retry_time (60s)\fR" +/* The time between attempts by the Postfix queue manager to contact +/* a malfunctioning message delivery transport. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBbounce_queue_lifetime (5d)\fR" +/* Consider a bounce message as undeliverable, when delivery fails +/* with a temporary error, and the time in the queue has reached the +/* bounce_queue_lifetime limit. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBdefault_destination_rate_delay (0s)\fR" +/* The default amount of delay that is inserted between individual +/* message deliveries to the same destination and over the same message +/* delivery transport. +/* .IP "\fBtransport_destination_rate_delay ($default_destination_rate_delay)\fR" +/* A transport-specific override for the default_destination_rate_delay +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBdefault_transport_rate_delay (0s)\fR" +/* The default amount of delay that is inserted between individual +/* message deliveries over the same message delivery transport, +/* regardless of destination. +/* .IP "\fBtransport_transport_rate_delay ($default_transport_rate_delay)\fR" +/* A transport-specific override for the default_transport_rate_delay +/* parameter value, where the initial \fItransport\fR in the parameter +/* name is the master.cf name of the message delivery transport. +/* SAFETY CONTROLS +/* .ad +/* .fi +/* .IP "\fBqmgr_daemon_timeout (1000s)\fR" +/* How much time a Postfix queue manager process may take to handle +/* a request before it is terminated by a built-in watchdog timer. +/* .IP "\fBqmgr_ipc_timeout (60s)\fR" +/* The time limit for the queue manager to send or receive information +/* over an internal communication channel. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBaddress_verify_pending_request_limit (see 'postconf -d' output)\fR" +/* A safety limit that prevents address verification requests from +/* overwhelming the Postfix queue. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdefer_transports (empty)\fR" +/* The names of message delivery transports that should not deliver mail +/* unless someone issues "\fBsendmail -q\fR" or equivalent. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBhelpful_warnings (yes)\fR" +/* Log warnings about problematic configuration settings, and provide +/* helpful suggestions. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBconfirm_delay_cleared (no)\fR" +/* After sending a "your message is delayed" notification, inform +/* the sender when the delay clears up. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.5 and later: +/* .IP "\fBinfo_log_address_format (external)\fR" +/* The email address form that will be used in non-debug logging +/* (info, warning, etc.). +/* FILES +/* /var/spool/postfix/incoming, incoming queue +/* /var/spool/postfix/active, active queue +/* /var/spool/postfix/deferred, deferred queue +/* /var/spool/postfix/bounce, non-delivery status +/* /var/spool/postfix/defer, non-delivery status +/* /var/spool/postfix/trace, delivery status +/* SEE ALSO +/* trivial-rewrite(8), address routing +/* bounce(8), delivery status reports +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* QSHAPE_README, Postfix queue analysis +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include /* QMGR_SCAN constants */ +#include +#include + +/* Master process interface */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + + /* + * Tunables. + */ +int var_queue_run_delay; +int var_min_backoff_time; +int var_max_backoff_time; +int var_max_queue_time; +int var_dsn_queue_time; +int var_qmgr_active_limit; +int var_qmgr_rcpt_limit; +int var_init_dest_concurrency; +int var_transport_retry_time; +int var_dest_con_limit; +int var_dest_rcpt_limit; +char *var_defer_xports; +int var_qmgr_fudge; +int var_local_rcpt_lim; /* XXX */ +int var_local_con_lim; /* XXX */ +bool var_verp_bounce_off; +int var_qmgr_clog_warn_time; +char *var_conc_pos_feedback; +char *var_conc_neg_feedback; +int var_conc_cohort_limit; +int var_conc_feedback_debug; +int var_xport_rate_delay; +int var_dest_rate_delay; +char *var_def_filter_nexthop; +int var_qmgr_daemon_timeout; +int var_qmgr_ipc_timeout; +int var_dsn_delay_cleared; +int var_vrfy_pend_limit; + +static QMGR_SCAN *qmgr_scans[2]; + +#define QMGR_SCAN_IDX_INCOMING 0 +#define QMGR_SCAN_IDX_DEFERRED 1 +#define QMGR_SCAN_IDX_COUNT (sizeof(qmgr_scans) / sizeof(qmgr_scans[0])) + +/* qmgr_deferred_run_event - queue manager heartbeat */ + +static void qmgr_deferred_run_event(int unused_event, void *dummy) +{ + + /* + * This routine runs when it is time for another deferred queue scan. + * Make sure this routine gets called again in the future. + */ + qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_DEFERRED], QMGR_SCAN_START); + event_request_timer(qmgr_deferred_run_event, dummy, var_queue_run_delay); +} + +/* qmgr_trigger_event - respond to external trigger(s) */ + +static void qmgr_trigger_event(char *buf, ssize_t len, + char *unused_service, char **argv) +{ + int incoming_flag = 0; + int deferred_flag = 0; + int i; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Collapse identical requests that have arrived since we looked last + * time. There is no client feedback so there is no need to process each + * request in order. And as long as we don't have conflicting requests we + * are free to sort them into the most suitable order. + */ +#define QMGR_FLUSH_BEFORE (QMGR_FLUSH_ONCE | QMGR_FLUSH_DFXP) + + for (i = 0; i < len; i++) { + if (msg_verbose) + msg_info("request: %d (%c)", + buf[i], ISALNUM(buf[i]) ? buf[i] : '?'); + switch (buf[i]) { + case TRIGGER_REQ_WAKEUP: + case QMGR_REQ_SCAN_INCOMING: + incoming_flag |= QMGR_SCAN_START; + break; + case QMGR_REQ_SCAN_DEFERRED: + deferred_flag |= QMGR_SCAN_START; + break; + case QMGR_REQ_FLUSH_DEAD: + deferred_flag |= QMGR_FLUSH_BEFORE; + incoming_flag |= QMGR_FLUSH_BEFORE; + break; + case QMGR_REQ_SCAN_ALL: + deferred_flag |= QMGR_SCAN_ALL; + incoming_flag |= QMGR_SCAN_ALL; + break; + default: + if (msg_verbose) + msg_info("request ignored"); + break; + } + } + + /* + * Process each request type at most once. Modifiers take effect upon the + * next queue run. If no queue run is in progress, and a queue scan is + * requested, the request takes effect immediately. + */ + if (incoming_flag != 0) + qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_INCOMING], incoming_flag); + if (deferred_flag != 0) + qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_DEFERRED], deferred_flag); +} + +/* qmgr_loop - queue manager main loop */ + +static int qmgr_loop(char *unused_name, char **unused_argv) +{ + char *path; + ssize_t token_count; + int feed = 0; + int scan_idx; /* Priority order scan index */ + static int first_scan_idx = QMGR_SCAN_IDX_INCOMING; + int last_scan_idx = QMGR_SCAN_IDX_COUNT - 1; + int delay; + + /* + * This routine runs as part of the event handling loop, after the event + * manager has delivered a timer or I/O event (including the completion + * of a connection to a delivery process), or after it has waited for a + * specified amount of time. The result value of qmgr_loop() specifies + * how long the event manager should wait for the next event. + */ +#define DONT_WAIT 0 +#define WAIT_FOR_EVENT (-1) + + /* + * Attempt to drain the active queue by allocating a suitable delivery + * process and by delivering mail via it. Delivery process allocation and + * mail delivery are asynchronous. + */ + qmgr_active_drain(); + + /* + * Let some new blood into the active queue when the queue size is + * smaller than some configurable limit, and when the number of in-core + * recipients does not exceed some configurable limit. + * + * We import one message per interrupt, to optimally tune the input count + * for the number of delivery agent protocol wait states, as explained in + * qmgr_transport.c. + */ + delay = WAIT_FOR_EVENT; + for (scan_idx = 0; qmgr_message_count < var_qmgr_active_limit + && qmgr_recipient_count < var_qmgr_rcpt_limit + && scan_idx < QMGR_SCAN_IDX_COUNT; ++scan_idx) { + last_scan_idx = (scan_idx + first_scan_idx) % QMGR_SCAN_IDX_COUNT; + if ((path = qmgr_scan_next(qmgr_scans[last_scan_idx])) != 0) { + delay = DONT_WAIT; + if ((feed = qmgr_active_feed(qmgr_scans[last_scan_idx], path)) != 0) + break; + } + } + + /* + * Round-robin the queue scans. When the active queue becomes full, + * prefer new mail over deferred mail. + */ + if (qmgr_message_count < var_qmgr_active_limit + && qmgr_recipient_count < var_qmgr_rcpt_limit) { + first_scan_idx = (last_scan_idx + 1) % QMGR_SCAN_IDX_COUNT; + } else if (first_scan_idx != QMGR_SCAN_IDX_INCOMING) { + first_scan_idx = QMGR_SCAN_IDX_INCOMING; + } + + /* + * Global flow control. If enabled, slow down receiving processes that + * get ahead of the queue manager, but don't block them completely. + */ + if (var_in_flow_delay > 0) { + token_count = mail_flow_count(); + if (token_count < var_proc_limit) { + if (feed != 0 && last_scan_idx == QMGR_SCAN_IDX_INCOMING) + mail_flow_put(1); + else if (qmgr_scans[QMGR_SCAN_IDX_INCOMING]->handle == 0) + mail_flow_put(var_proc_limit - token_count); + } else if (token_count > var_proc_limit) { + mail_flow_get(token_count - var_proc_limit); + } + } + return (delay); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* qmgr_pre_init - pre-jail initialization */ + +static void qmgr_pre_init(char *unused_name, char **unused_argv) +{ + flush_init(); +} + +/* qmgr_post_init - post-jail initialization */ + +static void qmgr_post_init(char *unused_name, char **unused_argv) +{ + + /* + * Sanity check. + */ + if (var_qmgr_rcpt_limit < var_qmgr_active_limit) { + msg_warn("%s is smaller than %s - adjusting %s", + VAR_QMGR_RCPT_LIMIT, VAR_QMGR_ACT_LIMIT, VAR_QMGR_RCPT_LIMIT); + var_qmgr_rcpt_limit = var_qmgr_active_limit; + } + if (var_dsn_queue_time > var_max_queue_time) { + msg_warn("%s is larger than %s - adjusting %s", + VAR_DSN_QUEUE_TIME, VAR_MAX_QUEUE_TIME, VAR_DSN_QUEUE_TIME); + var_dsn_queue_time = var_max_queue_time; + } + + /* + * This routine runs after the skeleton code has entered the chroot jail. + * Prevent automatic process suicide after a limited number of client + * requests or after a limited amount of idle time. Move any left-over + * entries from the active queue to the incoming queue, and give them a + * time stamp into the future, in order to allow ongoing deliveries to + * finish first. Start scanning the incoming and deferred queues. + * Left-over active queue entries are moved to the incoming queue because + * the incoming queue has priority; moving left-overs to the deferred + * queue could cause anomalous delays when "postfix reload/start" are + * issued often. Override the IPC timeout (default 3600s) so that the + * queue manager can reset a broken IPC channel before the watchdog timer + * goes off. + */ + var_ipc_timeout = var_qmgr_ipc_timeout; + var_use_limit = 0; + var_idle_limit = 0; + qmgr_move(MAIL_QUEUE_ACTIVE, MAIL_QUEUE_INCOMING, event_time()); + qmgr_scans[QMGR_SCAN_IDX_INCOMING] = qmgr_scan_create(MAIL_QUEUE_INCOMING); + qmgr_scans[QMGR_SCAN_IDX_DEFERRED] = qmgr_scan_create(MAIL_QUEUE_DEFERRED); + qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_INCOMING], QMGR_SCAN_START); + qmgr_deferred_run_event(0, (void *) 0); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static const CONFIG_STR_TABLE str_table[] = { + VAR_DEFER_XPORTS, DEF_DEFER_XPORTS, &var_defer_xports, 0, 0, + VAR_CONC_POS_FDBACK, DEF_CONC_POS_FDBACK, &var_conc_pos_feedback, 1, 0, + VAR_CONC_NEG_FDBACK, DEF_CONC_NEG_FDBACK, &var_conc_neg_feedback, 1, 0, + VAR_DEF_FILTER_NEXTHOP, DEF_DEF_FILTER_NEXTHOP, &var_def_filter_nexthop, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0, + VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0, + VAR_MAX_BACKOFF_TIME, DEF_MAX_BACKOFF_TIME, &var_max_backoff_time, 1, 0, + VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 0, 8640000, + VAR_DSN_QUEUE_TIME, DEF_DSN_QUEUE_TIME, &var_dsn_queue_time, 0, 8640000, + VAR_XPORT_RETRY_TIME, DEF_XPORT_RETRY_TIME, &var_transport_retry_time, 1, 0, + VAR_QMGR_CLOG_WARN_TIME, DEF_QMGR_CLOG_WARN_TIME, &var_qmgr_clog_warn_time, 0, 0, + VAR_XPORT_RATE_DELAY, DEF_XPORT_RATE_DELAY, &var_xport_rate_delay, 0, 0, + VAR_DEST_RATE_DELAY, DEF_DEST_RATE_DELAY, &var_dest_rate_delay, 0, 0, + VAR_QMGR_DAEMON_TIMEOUT, DEF_QMGR_DAEMON_TIMEOUT, &var_qmgr_daemon_timeout, 1, 0, + VAR_QMGR_IPC_TIMEOUT, DEF_QMGR_IPC_TIMEOUT, &var_qmgr_ipc_timeout, 1, 0, + 0, + }; + static const CONFIG_INT_TABLE int_table[] = { + VAR_QMGR_ACT_LIMIT, DEF_QMGR_ACT_LIMIT, &var_qmgr_active_limit, 1, 0, + VAR_QMGR_RCPT_LIMIT, DEF_QMGR_RCPT_LIMIT, &var_qmgr_rcpt_limit, 1, 0, + VAR_INIT_DEST_CON, DEF_INIT_DEST_CON, &var_init_dest_concurrency, 1, 0, + VAR_DEST_CON_LIMIT, DEF_DEST_CON_LIMIT, &var_dest_con_limit, 0, 0, + VAR_DEST_RCPT_LIMIT, DEF_DEST_RCPT_LIMIT, &var_dest_rcpt_limit, 0, 0, + VAR_QMGR_FUDGE, DEF_QMGR_FUDGE, &var_qmgr_fudge, 10, 100, + VAR_LOCAL_RCPT_LIMIT, DEF_LOCAL_RCPT_LIMIT, &var_local_rcpt_lim, 0, 0, + VAR_LOCAL_CON_LIMIT, DEF_LOCAL_CON_LIMIT, &var_local_con_lim, 0, 0, + VAR_CONC_COHORT_LIM, DEF_CONC_COHORT_LIM, &var_conc_cohort_limit, 0, 0, + VAR_VRFY_PEND_LIMIT, DEF_VRFY_PEND_LIMIT, &var_vrfy_pend_limit, 1, 0, + 0, + }; + static const CONFIG_BOOL_TABLE bool_table[] = { + VAR_VERP_BOUNCE_OFF, DEF_VERP_BOUNCE_OFF, &var_verp_bounce_off, + VAR_CONC_FDBACK_DEBUG, DEF_CONC_FDBACK_DEBUG, &var_conc_feedback_debug, + VAR_DSN_DELAY_CLEARED, DEF_DSN_DELAY_CLEARED, &var_dsn_delay_cleared, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Use the trigger service skeleton, because no-one else should be + * monitoring our service port while this process runs, and because we do + * not talk back to the client. + */ + trigger_server_main(argc, argv, qmgr_trigger_event, + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_BOOL_TABLE(bool_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(qmgr_pre_init), + CA_MAIL_SERVER_POST_INIT(qmgr_post_init), + CA_MAIL_SERVER_LOOP(qmgr_loop), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_SOLITARY, + CA_MAIL_SERVER_WATCHDOG(&var_qmgr_daemon_timeout), + 0); +} diff --git a/src/oqmgr/qmgr.h b/src/oqmgr/qmgr.h new file mode 100644 index 0000000..cc22ca5 --- /dev/null +++ b/src/oqmgr/qmgr.h @@ -0,0 +1,432 @@ +/*++ +/* NAME +/* qmgr 3h +/* SUMMARY +/* queue manager data structures +/* SYNOPSIS +/* #include "qmgr.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include + + /* + * The queue manager is built around lots of mutually-referring structures. + * These typedefs save some typing. + */ +typedef struct QMGR_TRANSPORT QMGR_TRANSPORT; +typedef struct QMGR_QUEUE QMGR_QUEUE; +typedef struct QMGR_ENTRY QMGR_ENTRY; +typedef struct QMGR_MESSAGE QMGR_MESSAGE; +typedef struct QMGR_TRANSPORT_LIST QMGR_TRANSPORT_LIST; +typedef struct QMGR_QUEUE_LIST QMGR_QUEUE_LIST; +typedef struct QMGR_ENTRY_LIST QMGR_ENTRY_LIST; +typedef struct QMGR_SCAN QMGR_SCAN; +typedef struct QMGR_FEEDBACK QMGR_FEEDBACK; + + /* + * Hairy macros to update doubly-linked lists. + */ +#define QMGR_LIST_ROTATE(head, object) { \ + head.next->peers.prev = head.prev; \ + head.prev->peers.next = head.next; \ + head.next = object->peers.next; \ + if (object->peers.next) \ + head.next->peers.prev = 0; \ + head.prev = object; \ + object->peers.next = 0; \ +} + +#define QMGR_LIST_UNLINK(head, type, object) { \ + type next = object->peers.next; \ + type prev = object->peers.prev; \ + if (prev) prev->peers.next = next; \ + else head.next = next; \ + if (next) next->peers.prev = prev; \ + else head.prev = prev; \ + object->peers.next = object->peers.prev = 0; \ +} + +#define QMGR_LIST_APPEND(head, object) { \ + object->peers.next = head.next; \ + object->peers.prev = 0; \ + if (head.next) { \ + head.next->peers.prev = object; \ + } else { \ + head.prev = object; \ + } \ + head.next = object; \ +} + +#define QMGR_LIST_PREPEND(head, object) { \ + object->peers.prev = head.prev; \ + object->peers.next = 0; \ + if (head.prev) { \ + head.prev->peers.next = object; \ + } else { \ + head.next = object; \ + } \ + head.prev = object; \ +} + +#define QMGR_LIST_INIT(head) { \ + head.prev = 0; \ + head.next = 0; \ +} + + /* + * Transports are looked up by name (when we have resolved a message), or + * round-robin wise (when we want to distribute resources fairly). + */ +struct QMGR_TRANSPORT_LIST { + QMGR_TRANSPORT *next; + QMGR_TRANSPORT *prev; +}; + +extern struct HTABLE *qmgr_transport_byname; /* transport by name */ +extern QMGR_TRANSPORT_LIST qmgr_transport_list; /* transports, round robin */ + + /* + * Delivery agents provide feedback, as hints that Postfix should expend + * more or fewer resources on a specific destination domain. The main.cf + * file specifies how feedback affects delivery concurrency: add/subtract a + * constant, a ratio of constants, or a constant divided by the delivery + * concurrency; and it specifies how much feedback must accumulate between + * concurrency updates. + */ +struct QMGR_FEEDBACK { + int hysteresis; /* to pass, need to be this tall */ + double base; /* pre-computed from main.cf */ + int index; /* none, window, sqrt(window) */ +}; + +#define QMGR_FEEDBACK_IDX_NONE 0 /* no window dependence */ +#define QMGR_FEEDBACK_IDX_WIN 1 /* 1/window dependence */ +#if 0 +#define QMGR_FEEDBACK_IDX_SQRT_WIN 2 /* 1/sqrt(window) dependence */ +#endif + +#ifdef QMGR_FEEDBACK_IDX_SQRT_WIN +#include +#endif + +extern void qmgr_feedback_init(QMGR_FEEDBACK *, const char *, const char *, const char *, const char *); + +#ifndef QMGR_FEEDBACK_IDX_SQRT_WIN +#define QMGR_FEEDBACK_VAL(fb, win) \ + ((fb).index == QMGR_FEEDBACK_IDX_NONE ? (fb).base : (fb).base / (win)) +#else +#define QMGR_FEEDBACK_VAL(fb, win) \ + ((fb).index == QMGR_FEEDBACK_IDX_NONE ? (fb).base : \ + (fb).index == QMGR_FEEDBACK_IDX_WIN ? (fb).base / (win) : \ + (fb).base / sqrt(win)) +#endif + + /* + * Each transport (local, smtp-out, bounce) can have one queue per next hop + * name. Queues are looked up by next hop name (when we have resolved a + * message destination), or round-robin wise (when we want to deliver + * messages fairly). + */ +struct QMGR_QUEUE_LIST { + QMGR_QUEUE *next; + QMGR_QUEUE *prev; +}; + +struct QMGR_TRANSPORT { + int flags; /* blocked, etc. */ + int pending; /* incomplete DA connections */ + char *name; /* transport name */ + int dest_concurrency_limit; /* concurrency per domain */ + int init_dest_concurrency; /* init. per-domain concurrency */ + int recipient_limit; /* recipients per transaction */ + struct HTABLE *queue_byname; /* queues indexed by domain */ + QMGR_QUEUE_LIST queue_list; /* queues, round robin order */ + QMGR_TRANSPORT_LIST peers; /* linkage */ + DSN *dsn; /* why unavailable */ + QMGR_FEEDBACK pos_feedback; /* positive feedback control */ + QMGR_FEEDBACK neg_feedback; /* negative feedback control */ + int fail_cohort_limit; /* flow shutdown control */ + int xport_rate_delay; /* suspend per delivery */ + int rate_delay; /* suspend per delivery */ +}; + +#define QMGR_TRANSPORT_STAT_DEAD (1<<1) +#define QMGR_TRANSPORT_STAT_RATE_LOCK (1<<2) + +typedef void (*QMGR_TRANSPORT_ALLOC_NOTIFY) (QMGR_TRANSPORT *, VSTREAM *); +extern QMGR_TRANSPORT *qmgr_transport_select(void); +extern void qmgr_transport_alloc(QMGR_TRANSPORT *, QMGR_TRANSPORT_ALLOC_NOTIFY); +extern void qmgr_transport_throttle(QMGR_TRANSPORT *, DSN *); +extern void qmgr_transport_unthrottle(QMGR_TRANSPORT *); +extern QMGR_TRANSPORT *qmgr_transport_create(const char *); +extern QMGR_TRANSPORT *qmgr_transport_find(const char *); + +#define QMGR_TRANSPORT_THROTTLED(t) ((t)->flags & QMGR_TRANSPORT_STAT_DEAD) + + /* + * Each next hop (e.g., a domain name) has its own queue of pending message + * transactions. The "todo" queue contains messages that are to be delivered + * to this next hop. When a message is elected for transmission, it is moved + * from the "todo" queue to the "busy" queue. Messages are taken from the + * "todo" queue in sequence. An initial destination delivery concurrency > 1 + * ensures that one problematic message will not block all other traffic to + * that next hop. + */ +struct QMGR_ENTRY_LIST { + QMGR_ENTRY *next; + QMGR_ENTRY *prev; +}; + +struct QMGR_QUEUE { + int dflags; /* delivery request options */ + time_t last_done; /* last delivery completion */ + char *name; /* domain name or address */ + char *nexthop; /* domain name */ + int todo_refcount; /* queue entries (todo list) */ + int busy_refcount; /* queue entries (busy list) */ + int window; /* slow open algorithm */ + double success; /* accumulated positive feedback */ + double failure; /* accumulated negative feedback */ + double fail_cohorts; /* pseudo-cohort failure count */ + QMGR_TRANSPORT *transport; /* transport linkage */ + QMGR_ENTRY_LIST todo; /* todo queue entries */ + QMGR_ENTRY_LIST busy; /* messages on the wire */ + QMGR_QUEUE_LIST peers; /* neighbor queues */ + DSN *dsn; /* why unavailable */ + time_t clog_time_to_warn; /* time of next warning */ +}; + +#define QMGR_QUEUE_TODO 1 /* waiting for service */ +#define QMGR_QUEUE_BUSY 2 /* recipients on the wire */ + +extern int qmgr_queue_count; + +extern QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *, const char *, const char *); +extern QMGR_QUEUE *qmgr_queue_select(QMGR_TRANSPORT *); +extern void qmgr_queue_done(QMGR_QUEUE *); +extern void qmgr_queue_throttle(QMGR_QUEUE *, DSN *); +extern void qmgr_queue_unthrottle(QMGR_QUEUE *); +extern QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *, const char *); +extern void qmgr_queue_suspend(QMGR_QUEUE *, int); + + /* + * Exclusive queue states. Originally there were only two: "throttled" and + * "not throttled". It was natural to encode these in the queue window size. + * After 10 years it's not practical to rip out all the working code and + * change representations, so we just clean up the names a little. + * + * Note: only the "ready" state can reach every state (including itself); + * non-ready states can reach only the "ready" state. Other transitions are + * forbidden, because they would result in dangling event handlers. + */ +#define QMGR_QUEUE_STAT_THROTTLED 0 /* back-off timer */ +#define QMGR_QUEUE_STAT_SUSPENDED -1 /* voluntary delay timer */ +#define QMGR_QUEUE_STAT_SAVED -2 /* delayed cleanup timer */ +#define QMGR_QUEUE_STAT_BAD -3 /* can't happen */ + +#define QMGR_QUEUE_READY(q) ((q)->window > 0) +#define QMGR_QUEUE_THROTTLED(q) ((q)->window == QMGR_QUEUE_STAT_THROTTLED) +#define QMGR_QUEUE_SUSPENDED(q) ((q)->window == QMGR_QUEUE_STAT_SUSPENDED) +#define QMGR_QUEUE_SAVED(q) ((q)->window == QMGR_QUEUE_STAT_SAVED) +#define QMGR_QUEUE_BAD(q) ((q)->window <= QMGR_QUEUE_STAT_BAD) + +#define QMGR_QUEUE_STATUS(q) ( \ + QMGR_QUEUE_READY(q) ? "ready" : \ + QMGR_QUEUE_THROTTLED(q) ? "throttled" : \ + QMGR_QUEUE_SUSPENDED(q) ? "suspended" : \ + QMGR_QUEUE_SAVED(q) ? "saved" : \ + "invalid queue status" \ + ) + + /* + * Structure of one next-hop queue entry. In order to save some copying + * effort we allow multiple recipients per transaction. + */ +struct QMGR_ENTRY { + VSTREAM *stream; /* delivery process */ + QMGR_MESSAGE *message; /* message info */ + RECIPIENT_LIST rcpt_list; /* as many as it takes */ + QMGR_QUEUE *queue; /* parent linkage */ + QMGR_ENTRY_LIST peers; /* neighbor entries */ +}; + +extern QMGR_ENTRY *qmgr_entry_select(QMGR_QUEUE *); +extern void qmgr_entry_unselect(QMGR_QUEUE *, QMGR_ENTRY *); +extern void qmgr_entry_move_todo(QMGR_QUEUE *, QMGR_ENTRY *); +extern void qmgr_entry_done(QMGR_ENTRY *, int); +extern QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *, QMGR_MESSAGE *); + + /* + * All common in-core information about a message is kept here. When all + * recipients have been tried the message file is linked to the "deferred" + * queue (some hosts not reachable), to the "bounce" queue (some recipients + * were rejected), and is then removed from the "active" queue. + */ +struct QMGR_MESSAGE { + int flags; /* delivery problems */ + int qflags; /* queuing flags */ + int tflags; /* tracing flags */ + long tflags_offset; /* offset for killing */ + int rflags; /* queue file read flags */ + VSTREAM *fp; /* open queue file or null */ + int refcount; /* queue entries */ + int single_rcpt; /* send one rcpt at a time */ + struct timeval arrival_time; /* start of receive transaction */ + time_t create_time; /* queue file create time */ + struct timeval active_time; /* time of entry into active queue */ + long warn_offset; /* warning bounce flag offset */ + time_t warn_time; /* time next warning to be sent */ + long data_offset; /* data seek offset */ + char *queue_name; /* queue name */ + char *queue_id; /* queue file */ + char *encoding; /* content encoding */ + char *sender; /* complete address */ + char *dsn_envid; /* DSN envelope ID */ + int dsn_ret; /* DSN headers/full */ + int smtputf8; /* requires unicode */ + char *verp_delims; /* VERP delimiters */ + char *filter_xport; /* filtering transport */ + char *inspect_xport; /* inspecting transport */ + char *redirect_addr; /* info@spammer.tld */ + long data_size; /* data segment size */ + long cont_length; /* message content length */ + long rcpt_offset; /* more recipients here */ + char *client_name; /* client hostname */ + char *client_addr; /* client address */ + char *client_port; /* client port */ + char *client_proto; /* client protocol */ + char *client_helo; /* helo parameter */ + char *sasl_method; /* SASL method */ + char *sasl_username; /* SASL user name */ + char *sasl_sender; /* SASL sender */ + char *log_ident; /* up-stream queue ID */ + char *rewrite_context; /* address qualification */ + RECIPIENT_LIST rcpt_list; /* complete addresses */ +}; + + /* + * Flags 0-15 are reserved for qmgr_user.h. + */ +#define QMGR_READ_FLAG_SEEN_ALL_NON_RCPT (1<<16) + +#define QMGR_MESSAGE_LOCKED ((QMGR_MESSAGE *) 1) + +extern int qmgr_message_count; +extern int qmgr_recipient_count; +extern int qmgr_vrfy_pend_count; + +extern void qmgr_message_free(QMGR_MESSAGE *); +extern void qmgr_message_update_warn(QMGR_MESSAGE *); +extern void qmgr_message_kill_record(QMGR_MESSAGE *, long); +extern QMGR_MESSAGE *qmgr_message_alloc(const char *, const char *, int, mode_t); +extern QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *); + +#define QMGR_MSG_STATS(stats, message) \ + MSG_STATS_INIT2(stats, \ + incoming_arrival, message->arrival_time, \ + active_arrival, message->active_time) + + /* + * qmgr_defer.c + */ +extern void qmgr_defer_transport(QMGR_TRANSPORT *, DSN *); +extern void qmgr_defer_todo(QMGR_QUEUE *, DSN *); +extern void qmgr_defer_recipient(QMGR_MESSAGE *, RECIPIENT *, DSN *); + + /* + * qmgr_bounce.c + */ +extern void qmgr_bounce_recipient(QMGR_MESSAGE *, RECIPIENT *, DSN *); + + /* + * qmgr_deliver.c + */ +extern int qmgr_deliver_concurrency; +extern void qmgr_deliver(QMGR_TRANSPORT *, VSTREAM *); + + /* + * qmgr_active.c + */ +extern int qmgr_active_feed(QMGR_SCAN *, const char *); +extern void qmgr_active_drain(void); +extern void qmgr_active_done(QMGR_MESSAGE *); + + /* + * qmgr_move.c + */ +extern void qmgr_move(const char *, const char *, time_t); + + /* + * qmgr_enable.c + */ +extern void qmgr_enable_all(void); +extern void qmgr_enable_transport(QMGR_TRANSPORT *); +extern void qmgr_enable_queue(QMGR_QUEUE *); + + /* + * Queue scan context. + */ +struct QMGR_SCAN { + char *queue; /* queue name */ + int flags; /* private, this run */ + int nflags; /* private, next run */ + struct SCAN_DIR *handle; /* scan */ +}; + + /* + * Flags that control queue scans or destination selection. These are + * similar to the QMGR_REQ_XXX request codes. + */ +#define QMGR_SCAN_START (1<<0) /* start now/restart when done */ +#define QMGR_SCAN_ALL (1<<1) /* all queue file time stamps */ +#define QMGR_FLUSH_ONCE (1<<2) /* unthrottle once */ +#define QMGR_FLUSH_DFXP (1<<3) /* override defer_transports */ +#define QMGR_FLUSH_EACH (1<<4) /* unthrottle per message */ +#define QMGR_FORCE_EXPIRE (1<<5) /* force-defer and force-expire */ + + /* + * qmgr_scan.c + */ +extern QMGR_SCAN *qmgr_scan_create(const char *); +extern void qmgr_scan_request(QMGR_SCAN *, int); +extern char *qmgr_scan_next(QMGR_SCAN *); + + /* + * qmgr_error.c + */ +extern QMGR_TRANSPORT *qmgr_error_transport(const char *); +extern QMGR_QUEUE *qmgr_error_queue(const char *, DSN *); +extern char *qmgr_error_nexthop(DSN *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/oqmgr/qmgr_active.c b/src/oqmgr/qmgr_active.c new file mode 100644 index 0000000..b1c1a4a --- /dev/null +++ b/src/oqmgr/qmgr_active.c @@ -0,0 +1,607 @@ +/*++ +/* NAME +/* qmgr_active 3 +/* SUMMARY +/* active queue management +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_active_feed(scan_info, queue_id) +/* QMGR_SCAN *scan_info; +/* const char *queue_id; +/* +/* void qmgr_active_drain() +/* +/* int qmgr_active_done(message) +/* QMGR_MESSAGE *message; +/* DESCRIPTION +/* These functions maintain the active message queue: the set +/* of messages that the queue manager is actually working on. +/* The active queue is limited in size. Messages are drained +/* from the active queue by allocating a delivery process and +/* by delivering mail via that process. Messages leak into the +/* active queue only when the active queue is small enough. +/* Damaged message files are saved to the "corrupt" directory. +/* +/* qmgr_active_feed() inserts the named message file into +/* the active queue. Message files with the wrong name or +/* with other wrong properties are skipped but not removed. +/* The following queue flags are recognized, other flags being +/* ignored: +/* .IP QMGR_SCAN_ALL +/* Examine all queue files. Normally, deferred queue files with +/* future time stamps are ignored, and incoming queue files with +/* future time stamps are frowned upon. +/* .PP +/* qmgr_active_drain() allocates one delivery process. +/* Process allocation is asynchronous. Once the delivery +/* process is available, an attempt is made to deliver +/* a message via it. Message delivery is asynchronous, too. +/* +/* qmgr_active_done() deals with a message after delivery +/* has been tried for all in-core recipients. If the message +/* was bounced, a bounce message is sent to the sender, or +/* to the Errors-To: address if one was specified. +/* If there are more on-file recipients, a new batch of +/* in-core recipients is read from the queue file. Otherwise, +/* if a delivery agent marked the queue file as corrupt, +/* the queue file is moved to the "corrupt" queue (surprise); +/* if at least one delivery failed, the message is moved +/* to the deferred queue. The time stamps of a deferred queue +/* file are set to the nearest wakeup time of its recipient +/* sites (if delivery failed due to a problem with a next-hop +/* host), are set into the future by the amount of time the +/* message was queued (per-message exponential backoff), or are set +/* into the future by a minimal backoff time, whichever is more. +/* The minimal_backoff_time parameter specifies the minimal +/* amount of time between delivery attempts; maximal_backoff_time +/* specifies an upper limit. +/* DIAGNOSTICS +/* Fatal: queue file access failures, out of memory. +/* Panic: interface violations, internal consistency errors. +/* Warnings: corrupt message file. A corrupt message is saved +/* to the "corrupt" queue for further inspection. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef S_IRWXU /* What? no POSIX system? */ +#define S_IRWXU 0700 +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + + /* + * A bunch of call-back routines. + */ +static void qmgr_active_done_2_bounce_flush(int, void *); +static void qmgr_active_done_2_generic(QMGR_MESSAGE *); +static void qmgr_active_done_25_trace_flush(int, void *); +static void qmgr_active_done_25_generic(QMGR_MESSAGE *); +static void qmgr_active_done_3_defer_flush(int, void *); +static void qmgr_active_done_3_defer_warn(int, void *); +static void qmgr_active_done_3_generic(QMGR_MESSAGE *); + +/* qmgr_active_corrupt - move corrupted file out of the way */ + +static void qmgr_active_corrupt(const char *queue_id) +{ + const char *myname = "qmgr_active_corrupt"; + + if (mail_queue_rename(queue_id, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_CORRUPT)) { + if (errno != ENOENT) + msg_fatal("%s: save corrupt file queue %s id %s: %m", + myname, MAIL_QUEUE_ACTIVE, queue_id); + } else { + msg_warn("saving corrupt file \"%s\" from queue \"%s\" to queue \"%s\"", + queue_id, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_CORRUPT); + } +} + +/* qmgr_active_defer - defer queue file */ + +static void qmgr_active_defer(const char *queue_name, const char *queue_id, + const char *dest_queue, int delay) +{ + const char *myname = "qmgr_active_defer"; + const char *path; + struct utimbuf tbuf; + + if (msg_verbose) + msg_info("wakeup %s after %ld secs", queue_id, (long) delay); + + tbuf.actime = tbuf.modtime = event_time() + delay; + path = mail_queue_path((VSTRING *) 0, queue_name, queue_id); + if (utime(path, &tbuf) < 0 && errno != ENOENT) + msg_fatal("%s: update %s time stamps: %m", myname, path); + if (mail_queue_rename(queue_id, queue_name, dest_queue)) { + if (errno != ENOENT) + msg_fatal("%s: rename %s from %s to %s: %m", myname, + queue_id, queue_name, dest_queue); + msg_warn("%s: rename %s from %s to %s: %m", myname, + queue_id, queue_name, dest_queue); + } else if (msg_verbose) { + msg_info("%s: defer %s", myname, queue_id); + } +} + +/* qmgr_active_feed - feed one message into active queue */ + +int qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) +{ + const char *myname = "qmgr_active_feed"; + QMGR_MESSAGE *message; + struct stat st; + const char *path; + + if (strcmp(scan_info->queue, MAIL_QUEUE_ACTIVE) == 0) + msg_panic("%s: bad queue %s", myname, scan_info->queue); + if (msg_verbose) + msg_info("%s: queue %s", myname, scan_info->queue); + + /* + * Make sure this is something we are willing to open. + */ + if (mail_open_ok(scan_info->queue, queue_id, &st, &path) == MAIL_OPEN_NO) + return (0); + + if (msg_verbose) + msg_info("%s: %s", myname, path); + + /* + * Skip files that have time stamps into the future. They need to cool + * down. Incoming and deferred files can have future time stamps. + */ + if ((scan_info->flags & QMGR_SCAN_ALL) == 0 + && st.st_mtime > time((time_t *) 0) + 1) { + if (msg_verbose) + msg_info("%s: skip %s (%ld seconds)", myname, queue_id, + (long) (st.st_mtime - event_time())); + return (0); + } + + /* + * Move the message to the active queue. File access errors are fatal. + */ + if (mail_queue_rename(queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE)) { + if (errno != ENOENT) + msg_fatal("%s: %s: rename from %s to %s: %m", myname, + queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE); + msg_warn("%s: %s: rename from %s to %s: %m", myname, + queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE); + return (0); + } + + /* + * Extract envelope information: sender and recipients. At this point, + * mail addresses have been processed by the cleanup service so they + * should be in canonical form. Generate requests to deliver this + * message. + * + * Throwing away queue files seems bad, especially when they made it this + * far into the mail system. Therefore we save bad files to a separate + * directory for further inspection. + * + * After queue manager restart it is possible that a queue file is still + * being delivered. In that case (the file is locked), defer delivery by + * a minimal amount of time. + */ +#define QMGR_FLUSH_AFTER (QMGR_FLUSH_EACH | QMGR_FLUSH_DFXP) +#define MAYBE_FLUSH_AFTER(mode) \ + (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? QMGR_FLUSH_AFTER : 0) +#define MAYBE_FORCE_EXPIRE(mode) \ + (((mode) & MAIL_QUEUE_STAT_EXPIRE) ? QMGR_FORCE_EXPIRE : 0) +#define MAYBE_UPDATE_MODE(mode) \ + (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? \ + (mode) & ~MAIL_QUEUE_STAT_UNTHROTTLE : 0) + + if ((message = qmgr_message_alloc(MAIL_QUEUE_ACTIVE, queue_id, + scan_info->flags + | MAYBE_FLUSH_AFTER(st.st_mode) + | MAYBE_FORCE_EXPIRE(st.st_mode), + MAYBE_UPDATE_MODE(st.st_mode))) == 0) { + qmgr_active_corrupt(queue_id); + return (0); + } else if (message == QMGR_MESSAGE_LOCKED) { + qmgr_active_defer(MAIL_QUEUE_ACTIVE, queue_id, MAIL_QUEUE_INCOMING, 60); + return (0); + } else { + + /* + * Special case if all recipients were already delivered. Send any + * bounces and clean up. + */ + if (message->refcount == 0) + qmgr_active_done(message); + return (1); + } +} + +/* qmgr_active_done - dispose of message after recipients have been tried */ + +void qmgr_active_done(QMGR_MESSAGE *message) +{ + const char *myname = "qmgr_active_done"; + struct stat st; + + if (msg_verbose) + msg_info("%s: %s", myname, message->queue_id); + + /* + * During a previous iteration, an attempt to bounce this message may + * have failed, so there may still be a bounce log lying around. XXX By + * groping around in the bounce queue, we're trespassing on the bounce + * service's territory. But doing so is more robust than depending on the + * bounce daemon to do the lookup for us, and for us to do the deleting + * after we have received a successful status from the bounce service. + * The bounce queue directory blocks are most likely in memory anyway. If + * these lookups become a performance problem we will have to build an + * in-core cache into the bounce daemon. + * + * Don't bounce when the bounce log is empty. The bounce process obviously + * failed, and the delivery agent will have requested that the message be + * deferred. + * + * Bounces are sent asynchronously to avoid stalling while the cleanup + * daemon waits for the qmgr to accept the "new mail" trigger. + * + * See also code in cleanup_bounce.c. + */ + if (stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_BOUNCE, message->queue_id), &st) == 0) { + if (st.st_size == 0) { + if (mail_queue_remove(MAIL_QUEUE_BOUNCE, message->queue_id)) + msg_fatal("remove %s %s: %m", + MAIL_QUEUE_BOUNCE, message->queue_id); + } else { + if (msg_verbose) + msg_info("%s: bounce %s", myname, message->queue_id); + if (message->verp_delims == 0 || var_verp_bounce_off) + abounce_flush(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + qmgr_active_done_2_bounce_flush, + (void *) message); + else + abounce_flush_verp(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + message->verp_delims, + qmgr_active_done_2_bounce_flush, + (void *) message); + return; + } + } + + /* + * Asynchronous processing does not reach this point. + */ + qmgr_active_done_2_generic(message); +} + +/* qmgr_active_done_2_bounce_flush - process abounce_flush() status */ + +static void qmgr_active_done_2_bounce_flush(int status, void *context) +{ + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process abounce_flush() status and continue processing. + */ + message->flags |= status; + qmgr_active_done_2_generic(message); +} + +/* qmgr_active_done_2_generic - continue processing */ + +static void qmgr_active_done_2_generic(QMGR_MESSAGE *message) +{ + const char *path; + struct stat st; + + /* + * A delivery agent marks a queue file as corrupt by changing its + * attributes, and by pretending that delivery was deferred. + */ + if (message->flags + && mail_open_ok(MAIL_QUEUE_ACTIVE, message->queue_id, &st, &path) == MAIL_OPEN_NO) { + qmgr_active_corrupt(message->queue_id); + qmgr_message_free(message); + return; + } + + /* + * If we did not read all recipients from this file, go read some more, + * but remember whether some recipients have to be tried again. + * + * Throwing away queue files seems bad, especially when they made it this + * far into the mail system. Therefore we save bad files to a separate + * directory for further inspection by a human being. + */ + if (message->rcpt_offset > 0) { + if (qmgr_message_realloc(message) == 0) { + qmgr_active_corrupt(message->queue_id); + qmgr_message_free(message); + } else { + if (message->refcount == 0) + qmgr_active_done(message); /* recurse for consistency */ + } + return; + } + + /* + * XXX With multi-recipient mail, some recipients may have NOTIFY=SUCCESS + * and others not. Depending on what subset of recipients are delivered, + * a trace file may or may not be created. Even when the last partial + * delivery attempt had no NOTIFY=SUCCESS recipients, a trace file may + * still exist from a previous partial delivery attempt. So as long as + * any recipient has NOTIFY=SUCCESS we have to always look for the trace + * file and be prepared for the file not to exist. + * + * See also comments in bounce/bounce_notify_util.c. + */ + if ((message->tflags & (DEL_REQ_FLAG_USR_VRFY | DEL_REQ_FLAG_RECORD + | DEL_REQ_FLAG_REC_DLY_SENT)) + || (message->rflags & QMGR_READ_FLAG_NOTIFY_SUCCESS)) { + atrace_flush(message->tflags, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + qmgr_active_done_25_trace_flush, + (void *) message); + return; + } + + /* + * Asynchronous processing does not reach this point. + */ + qmgr_active_done_25_generic(message); +} + +/* qmgr_active_done_25_trace_flush - continue after atrace_flush() completion */ + +static void qmgr_active_done_25_trace_flush(int status, void *context) +{ + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process atrace_flush() status and continue processing. + */ + if (status == 0 && message->tflags_offset) + qmgr_message_kill_record(message, message->tflags_offset); + message->flags |= status; + qmgr_active_done_25_generic(message); +} + +/* qmgr_active_done_25_generic - continue processing */ + +static void qmgr_active_done_25_generic(QMGR_MESSAGE *message) +{ + const char *myname = "qmgr_active_done_25_generic"; + const char *expire_status = 0; + + /* + * If we get to this point we have tried all recipients for this message. + * If the message is too old, try to bounce it. + * + * Bounces are sent asynchronously to avoid stalling while the cleanup + * daemon waits for the qmgr to accept the "new mail" trigger. + */ + if (message->flags) { + if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) { + expire_status = "force-expired"; + } else if (event_time() >= message->create_time + + (*message->sender ? var_max_queue_time : var_dsn_queue_time)) { + expire_status = "expired"; + } else { + expire_status = 0; + } + if (expire_status != 0) { + msg_info("%s: from=<%s>, status=%s, returned to sender", + message->queue_id, info_log_addr_form_sender(message->sender), + expire_status); + if (message->verp_delims == 0 || var_verp_bounce_off) + adefer_flush(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + qmgr_active_done_3_defer_flush, + (void *) message); + else + adefer_flush_verp(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + message->verp_delims, + qmgr_active_done_3_defer_flush, + (void *) message); + return; + } else if (message->warn_time > 0 + && event_time() >= message->warn_time - 1) { + if (msg_verbose) + msg_info("%s: sending defer warning for %s", myname, message->queue_id); + adefer_warn(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + qmgr_active_done_3_defer_warn, + (void *) message); + return; + } + } + + /* + * Asynchronous processing does not reach this point. + */ + qmgr_active_done_3_generic(message); +} + +/* qmgr_active_done_3_defer_warn - continue after adefer_warn() completion */ + +static void qmgr_active_done_3_defer_warn(int status, void *context) +{ + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process adefer_warn() completion status and continue processing. + */ + if (status == 0) + qmgr_message_update_warn(message); + qmgr_active_done_3_generic(message); +} + +/* qmgr_active_done_3_defer_flush - continue after adefer_flush() completion */ + +static void qmgr_active_done_3_defer_flush(int status, void *context) +{ + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process adefer_flush() status and continue processing. + */ + message->flags = status; + qmgr_active_done_3_generic(message); +} + +/* qmgr_active_done_3_generic - continue processing */ + +static void qmgr_active_done_3_generic(QMGR_MESSAGE *message) +{ + const char *myname = "qmgr_active_done_3_generic"; + int delay; + + /* + * Some recipients need to be tried again. Move the queue file time + * stamps into the future by the amount of time that the message is + * delayed, and move the message to the deferred queue. Impose minimal + * and maximal backoff times. + * + * Since we look at actual time in queue, not time since last delivery + * attempt, backoff times will be distributed. However, we can still see + * spikes in delivery activity because the interval between deferred + * queue scans is finite. + */ + if (message->flags) { + if (message->create_time > 0) { + delay = event_time() - message->create_time; + if (delay > var_max_backoff_time) + delay = var_max_backoff_time; + if (delay < var_min_backoff_time) + delay = var_min_backoff_time; + } else { + delay = var_min_backoff_time; + } + qmgr_active_defer(message->queue_name, message->queue_id, + MAIL_QUEUE_DEFERRED, delay); + } + + /* + * All recipients done. Remove the queue file. + */ + else { + if (mail_queue_remove(message->queue_name, message->queue_id)) { + if (errno != ENOENT) + msg_fatal("%s: remove %s from %s: %m", myname, + message->queue_id, message->queue_name); + msg_warn("%s: remove %s from %s: %m", myname, + message->queue_id, message->queue_name); + } else { + /* Same format as logged by postsuper. */ + msg_info("%s: removed", message->queue_id); + } + } + + /* + * Finally, delete the in-core message structure. + */ + qmgr_message_free(message); +} + +/* qmgr_active_drain - drain active queue by allocating a delivery process */ + +void qmgr_active_drain(void) +{ + QMGR_TRANSPORT *transport; + + /* + * Allocate one delivery process for every transport with pending mail. + * The process allocation completes asynchronously. + */ + while ((transport = qmgr_transport_select()) != 0) { + if (msg_verbose) + msg_info("qmgr_active_drain: allocate %s", transport->name); + qmgr_transport_alloc(transport, qmgr_deliver); + } +} diff --git a/src/oqmgr/qmgr_bounce.c b/src/oqmgr/qmgr_bounce.c new file mode 100644 index 0000000..00ba885 --- /dev/null +++ b/src/oqmgr/qmgr_bounce.c @@ -0,0 +1,71 @@ +/*++ +/* NAME +/* qmgr_bounce +/* SUMMARY +/* deal with mail that will not be delivered +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_QUEUE *qmgr_bounce_recipient(message, recipient, dsn) +/* QMGR_MESSAGE *message; +/* RECIPIENT *recipient; +/* DSN *dsn; +/* DESCRIPTION +/* qmgr_bounce_recipient() produces a bounce log record. +/* Once the bounce record is written successfully, the recipient +/* is marked as done. When the bounce record cannot be written, +/* the message structure is updated to reflect that the mail is +/* deferred. +/* +/* Arguments: +/* .IP message +/* Open queue file with the message being bounced. +/* .IP recipient +/* The recipient that will not be delivered. +/* .IP dsn +/* Delivery status information. See dsn(3). +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_bounce_recipient - bounce one message recipient */ + +void qmgr_bounce_recipient(QMGR_MESSAGE *message, RECIPIENT *recipient, + DSN *dsn) +{ + MSG_STATS stats; + int status; + + status = bounce_append(message->tflags, message->queue_id, + QMGR_MSG_STATS(&stats, message), recipient, + "none", dsn); + + if (status == 0) + deliver_completed(message->fp, recipient->offset); + else + message->flags |= status; +} diff --git a/src/oqmgr/qmgr_defer.c b/src/oqmgr/qmgr_defer.c new file mode 100644 index 0000000..dc0319e --- /dev/null +++ b/src/oqmgr/qmgr_defer.c @@ -0,0 +1,158 @@ +/*++ +/* NAME +/* qmgr_defer +/* SUMMARY +/* deal with mail that must be delivered later +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_defer_recipient(message, recipient, dsn) +/* QMGR_MESSAGE *message; +/* RECIPIENT *recipient; +/* DSN *dsn; +/* +/* void qmgr_defer_todo(queue, dsn) +/* QMGR_QUEUE *queue; +/* DSN *dsn; +/* +/* void qmgr_defer_transport(transport, dsn) +/* QMGR_TRANSPORT *transport; +/* DSN *dsn; +/* DESCRIPTION +/* qmgr_defer_recipient() defers delivery of the named message to +/* the named recipient. It updates the message structure and writes +/* a log entry. +/* +/* qmgr_defer_todo() iterates over all "todo" deliveries queued for +/* the named site, and calls qmgr_defer_recipient() for each recipient +/* found. Side effects caused by qmgr_entry_done(), qmgr_queue_done(), +/* and by qmgr_active_done(): in-core queue entries will disappear, +/* in-core queues may disappear, in-core and on-disk messages may +/* disappear, bounces may be sent, new in-core queues, queue entries +/* and recipients may appear. +/* +/* qmgr_defer_transport() calls qmgr_defer_todo() for each queue +/* that depends on the named transport. See there for side effects. +/* +/* Arguments: +/* .IP recipient +/* A recipient address; used for logging purposes, and for updating +/* the message-specific \fIdefer\fR log. +/* .IP queue +/* Specifies a queue with delivery requests for a specific next-hop +/* host (or local user). +/* .IP transport +/* Specifies a message delivery transport. +/* .IP dsn +/* See dsn(3). +/* BUGS +/* The side effects of calling this routine are quite dramatic. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_defer_transport - defer todo entries for named transport */ + +void qmgr_defer_transport(QMGR_TRANSPORT *transport, DSN *dsn) +{ + QMGR_QUEUE *queue; + QMGR_QUEUE *next; + + if (msg_verbose) + msg_info("defer transport %s: %s %s", + transport->name, dsn->status, dsn->reason); + + /* + * Proceed carefully. Queues may disappear as a side effect. + */ + for (queue = transport->queue_list.next; queue; queue = next) { + next = queue->peers.next; + qmgr_defer_todo(queue, dsn); + } +} + +/* qmgr_defer_todo - defer all todo queue entries for specific site */ + +void qmgr_defer_todo(QMGR_QUEUE *queue, DSN *dsn) +{ + QMGR_ENTRY *entry; + QMGR_ENTRY *next; + QMGR_MESSAGE *message; + RECIPIENT *recipient; + int nrcpt; + QMGR_QUEUE *retry_queue; + + /* + * Sanity checks. + */ + if (msg_verbose) + msg_info("defer site %s: %s %s", + queue->name, dsn->status, dsn->reason); + + /* + * See if we can redirect the deliveries to the retry(8) delivery agent, + * so that they can be handled asynchronously. If the retry(8) service is + * unavailable, use the synchronous defer(8) server. With a large todo + * queue, this blocks the queue manager for a significant time. + */ + retry_queue = qmgr_error_queue(MAIL_SERVICE_RETRY, dsn); + + /* + * Proceed carefully. Queue entries may disappear as a side effect. + */ + for (entry = queue->todo.next; entry != 0; entry = next) { + next = entry->peers.next; + if (retry_queue != 0) { + qmgr_entry_move_todo(retry_queue, entry); + continue; + } + message = entry->message; + for (nrcpt = 0; nrcpt < entry->rcpt_list.len; nrcpt++) { + recipient = entry->rcpt_list.info + nrcpt; + qmgr_defer_recipient(message, recipient, dsn); + } + qmgr_entry_done(entry, QMGR_QUEUE_TODO); + } +} + +/* qmgr_defer_recipient - defer delivery of specific recipient */ + +void qmgr_defer_recipient(QMGR_MESSAGE *message, RECIPIENT *recipient, + DSN *dsn) +{ + MSG_STATS stats; + + /* + * Update the message structure and log the message disposition. + */ + message->flags |= defer_append(message->tflags, message->queue_id, + QMGR_MSG_STATS(&stats, message), recipient, + "none", dsn); +} diff --git a/src/oqmgr/qmgr_deliver.c b/src/oqmgr/qmgr_deliver.c new file mode 100644 index 0000000..100ccc7 --- /dev/null +++ b/src/oqmgr/qmgr_deliver.c @@ -0,0 +1,453 @@ +/*++ +/* NAME +/* qmgr_deliver 3 +/* SUMMARY +/* deliver one per-site queue entry to that site +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* int qmgr_deliver_concurrency; +/* +/* int qmgr_deliver(transport, fp) +/* QMGR_TRANSPORT *transport; +/* VSTREAM *fp; +/* DESCRIPTION +/* This module implements the client side of the `queue manager +/* to delivery agent' protocol. The queue manager uses +/* asynchronous I/O so that it can drive multiple delivery +/* agents in parallel. Depending on the outcome of a delivery +/* attempt, the status of messages, queues and transports is +/* updated. +/* +/* qmgr_deliver_concurrency is a global counter that says how +/* many delivery processes are in use. This can be used, for +/* example, to control the size of the `active' message queue. +/* +/* qmgr_deliver() executes when a delivery process announces its +/* availability for the named transport. It arranges for delivery +/* of a suitable queue entry. The \fIfp\fR argument specifies a +/* stream that is connected to the delivery process, or a null +/* pointer if the transport accepts no connection. Upon completion +/* of delivery (successful or not), the stream is closed, so that the +/* delivery process is released. +/* DIAGNOSTICS +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + + /* + * Important note on the _transport_rate_delay implementation: after + * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all + * code paths must directly or indirectly invoke qmgr_transport_unthrottle() + * or qmgr_transport_throttle(). Otherwise, transports with non-zero + * _transport_rate_delay will become stuck. + */ + +int qmgr_deliver_concurrency; + + /* + * Message delivery status codes. + */ +#define DELIVER_STAT_OK 0 /* all recipients delivered */ +#define DELIVER_STAT_DEFER 1 /* try some recipients later */ +#define DELIVER_STAT_CRASH 2 /* mailer internal problem */ + +/* qmgr_deliver_initial_reply - retrieve initial delivery process response */ + +static int qmgr_deliver_initial_reply(VSTREAM *stream) +{ + if (peekfd(vstream_fileno(stream)) < 0) { + msg_warn("%s: premature disconnect", VSTREAM_PATH(stream)); + return (DELIVER_STAT_CRASH); + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_DELIVER), + ATTR_TYPE_END) != 0) { + msg_warn("%s: malformed response", VSTREAM_PATH(stream)); + return (DELIVER_STAT_DEFER); + } else { + return (0); + } +} + +/* qmgr_deliver_final_reply - retrieve final delivery process response */ + +static int qmgr_deliver_final_reply(VSTREAM *stream, DSN_BUF *dsb) +{ + int stat; + + if (peekfd(vstream_fileno(stream)) < 0) { + msg_warn("%s: premature disconnect", VSTREAM_PATH(stream)); + return (DELIVER_STAT_CRASH); + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_FUNC(dsb_scan, (void *) dsb), + RECV_ATTR_INT(MAIL_ATTR_STATUS, &stat), + ATTR_TYPE_END) != 2) { + msg_warn("%s: malformed response", VSTREAM_PATH(stream)); + return (DELIVER_STAT_CRASH); + } else { + return (stat ? DELIVER_STAT_DEFER : 0); + } +} + +/* qmgr_deliver_send_request - send delivery request to delivery process */ + +static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) +{ + RECIPIENT_LIST list = entry->rcpt_list; + RECIPIENT *recipient; + QMGR_MESSAGE *message = entry->message; + VSTRING *sender_buf = 0; + MSG_STATS stats; + char *sender; + int flags; + int smtputf8 = message->smtputf8; + const char *addr; + + /* + * Todo: integrate with code up-stream that builds the delivery request. + */ + for (recipient = list.info; recipient < list.info + list.len; recipient++) + if (var_smtputf8_enable && (addr = recipient->address)[0] + && !allascii(addr) && valid_utf8_string(addr, strlen(addr))) { + smtputf8 |= SMTPUTF8_FLAG_RECIPIENT; + if (message->verp_delims) + smtputf8 |= SMTPUTF8_FLAG_SENDER; + } + + /* + * If variable envelope return path is requested, change prefix+@origin + * into prefix+user=domain@origin. Note that with VERP there is only one + * recipient per delivery. + */ + if (message->verp_delims == 0) { + sender = message->sender; + } else { + sender_buf = vstring_alloc(100); + verp_sender(sender_buf, message->verp_delims, + message->sender, list.info); + sender = vstring_str(sender_buf); + } + + flags = message->tflags + | entry->queue->dflags + | (message->inspect_xport ? DEL_REQ_FLAG_BOUNCE : DEL_REQ_FLAG_DEFLT); + (void) QMGR_MSG_STATS(&stats, message); + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags), + SEND_ATTR_STR(MAIL_ATTR_QUEUE, message->queue_name), + SEND_ATTR_STR(MAIL_ATTR_QUEUEID, message->queue_id), + SEND_ATTR_LONG(MAIL_ATTR_OFFSET, message->data_offset), + SEND_ATTR_LONG(MAIL_ATTR_SIZE, message->cont_length), + SEND_ATTR_STR(MAIL_ATTR_NEXTHOP, entry->queue->nexthop), + SEND_ATTR_STR(MAIL_ATTR_ENCODING, message->encoding), + SEND_ATTR_INT(MAIL_ATTR_SMTPUTF8, smtputf8), + SEND_ATTR_STR(MAIL_ATTR_SENDER, sender), + SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, message->dsn_envid), + SEND_ATTR_INT(MAIL_ATTR_DSN_RET, message->dsn_ret), + SEND_ATTR_FUNC(msg_stats_print, (const void *) &stats), + /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */ + SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_NAME, message->client_name), + SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_ADDR, message->client_addr), + SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_PORT, message->client_port), + SEND_ATTR_STR(MAIL_ATTR_LOG_PROTO_NAME, message->client_proto), + SEND_ATTR_STR(MAIL_ATTR_LOG_HELO_NAME, message->client_helo), + /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */ + SEND_ATTR_STR(MAIL_ATTR_SASL_METHOD, message->sasl_method), + SEND_ATTR_STR(MAIL_ATTR_SASL_USERNAME, message->sasl_username), + SEND_ATTR_STR(MAIL_ATTR_SASL_SENDER, message->sasl_sender), + /* XXX Ditto if we want to pass TLS certificate info. */ + SEND_ATTR_STR(MAIL_ATTR_LOG_IDENT, message->log_ident), + SEND_ATTR_STR(MAIL_ATTR_RWR_CONTEXT, message->rewrite_context), + SEND_ATTR_INT(MAIL_ATTR_RCPT_COUNT, list.len), + ATTR_TYPE_END); + if (sender_buf != 0) + vstring_free(sender_buf); + for (recipient = list.info; recipient < list.info + list.len; recipient++) + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(rcpt_print, (const void *) recipient), + ATTR_TYPE_END); + if (vstream_fflush(stream) != 0) { + msg_warn("write to process (%s): %m", entry->queue->transport->name); + return (-1); + } else { + if (msg_verbose) + msg_info("qmgr_deliver: site `%s'", entry->queue->name); + return (0); + } +} + +/* qmgr_deliver_abort - transport response watchdog */ + +static void qmgr_deliver_abort(int unused_event, void *context) +{ + QMGR_ENTRY *entry = (QMGR_ENTRY *) context; + QMGR_QUEUE *queue = entry->queue; + QMGR_TRANSPORT *transport = queue->transport; + QMGR_MESSAGE *message = entry->message; + + msg_fatal("%s: timeout receiving delivery status from transport: %s", + message->queue_id, transport->name); +} + +/* qmgr_deliver_update - process delivery status report */ + +static void qmgr_deliver_update(int unused_event, void *context) +{ + QMGR_ENTRY *entry = (QMGR_ENTRY *) context; + QMGR_QUEUE *queue = entry->queue; + QMGR_TRANSPORT *transport = queue->transport; + QMGR_MESSAGE *message = entry->message; + static DSN_BUF *dsb; + int status; + + /* + * Release the delivery agent from a "hot" queue entry. + */ +#define QMGR_DELIVER_RELEASE_AGENT(entry) do { \ + event_disable_readwrite(vstream_fileno(entry->stream)); \ + (void) vstream_fclose(entry->stream); \ + entry->stream = 0; \ + qmgr_deliver_concurrency--; \ + } while (0) + + if (dsb == 0) + dsb = dsb_create(); + + /* + * The message transport has responded. Stop the watchdog timer. + */ + event_cancel_timer(qmgr_deliver_abort, context); + + /* + * Retrieve the delivery agent status report. The numerical status code + * indicates if delivery should be tried again. The reason text is sent + * only when a site should be avoided for a while, so that the queue + * manager can log why it does not even try to schedule delivery to the + * affected recipients. + */ + status = qmgr_deliver_final_reply(entry->stream, dsb); + + /* + * The mail delivery process failed for some reason (although delivery + * may have been successful). Back off with this transport type for a + * while. Dispose of queue entries for this transport that await + * selection (the todo lists). Stay away from queue entries that have + * been selected (the busy lists), or we would have dangling pointers. + * The queue itself won't go away before we dispose of the current queue + * entry. + */ + if (status == DELIVER_STAT_CRASH) { + message->flags |= DELIVER_STAT_DEFER; +#if 0 + whatsup = concatenate("unknown ", transport->name, + " mail transport error", (char *) 0); + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsb->dsn, "4.3.0", whatsup)); + myfree(whatsup); +#else + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsb->dsn, "4.3.0", + "unknown mail transport error")); +#endif + msg_warn("transport %s failure -- see a previous warning/fatal/panic logfile record for the problem description", + transport->name); + + /* + * Assume the worst and write a defer logfile record for each + * recipient. This omission was already present in the first queue + * manager implementation of 199703, and was fixed 200511. + * + * To avoid the synchronous qmgr_defer_recipient() operation for each + * recipient of this queue entry, release the delivery process and + * move the entry back to the todo queue. Let qmgr_defer_transport() + * log the recipient asynchronously if possible, and get out of here. + * Note: if asynchronous logging is not possible, + * qmgr_defer_transport() eventually invokes qmgr_entry_done() and + * the entry becomes a dangling pointer. + */ + QMGR_DELIVER_RELEASE_AGENT(entry); + qmgr_entry_unselect(queue, entry); + qmgr_defer_transport(transport, &dsb->dsn); + return; + } + + /* + * This message must be tried again. + * + * If we have a problem talking to this site, back off with this site for a + * while; dispose of queue entries for this site that await selection + * (the todo list); stay away from queue entries that have been selected + * (the busy list), or we would have dangling pointers. The queue itself + * won't go away before we dispose of the current queue entry. + * + * XXX Caution: DSN_COPY() will panic on empty status or reason. + */ +#define SUSPENDED "delivery temporarily suspended: " + + if (status == DELIVER_STAT_DEFER) { + message->flags |= DELIVER_STAT_DEFER; + if (VSTRING_LEN(dsb->status)) { + /* Sanitize the DSN status/reason from the delivery agent. */ + if (!dsn_valid(vstring_str(dsb->status))) + vstring_strcpy(dsb->status, "4.0.0"); + if (VSTRING_LEN(dsb->reason) == 0) + vstring_strcpy(dsb->reason, "unknown error"); + vstring_prepend(dsb->reason, SUSPENDED, sizeof(SUSPENDED) - 1); + if (QMGR_QUEUE_READY(queue)) { + qmgr_queue_throttle(queue, DSN_FROM_DSN_BUF(dsb)); + if (QMGR_QUEUE_THROTTLED(queue)) + qmgr_defer_todo(queue, &dsb->dsn); + } + } + } + + /* + * No problems detected. Mark the transport and queue as alive. The queue + * itself won't go away before we dispose of the current queue entry. + */ + if (status != DELIVER_STAT_CRASH) { + qmgr_transport_unthrottle(transport); + if (VSTRING_LEN(dsb->reason) == 0) + qmgr_queue_unthrottle(queue); + } + + /* + * Release the delivery process, and give some other queue entry a chance + * to be delivered. When all recipients for a message have been tried, + * decide what to do next with this message: defer, bounce, delete. + */ + QMGR_DELIVER_RELEASE_AGENT(entry); + qmgr_entry_done(entry, QMGR_QUEUE_BUSY); +} + +/* qmgr_deliver - deliver one per-site queue entry */ + +void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream) +{ + QMGR_QUEUE *queue; + QMGR_ENTRY *entry; + DSN dsn; + + /* + * Find out if this delivery process is really available. Once elected, + * the delivery process is supposed to express its happiness. If there is + * a problem, wipe the pending deliveries for this transport. This + * routine runs in response to an external event, so it does not run + * while some other queue manipulation is happening. + */ + if (stream == 0 || qmgr_deliver_initial_reply(stream) != 0) { +#if 0 + whatsup = concatenate(transport->name, + " mail transport unavailable", (char *) 0); + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsn, "4.3.0", whatsup)); + myfree(whatsup); +#else + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsn, "4.3.0", + "mail transport unavailable")); +#endif + qmgr_defer_transport(transport, &dsn); + if (stream) + (void) vstream_fclose(stream); + return; + } + + /* + * Find a suitable queue entry. Things may have changed since this + * transport was allocated. If no suitable entry is found, + * unceremoniously disconnect from the delivery process. The delivery + * agent request reading routine is prepared for the queue manager to + * change its mind for no apparent reason. + */ + if ((queue = qmgr_queue_select(transport)) == 0 + || (entry = qmgr_entry_select(queue)) == 0) { + (void) vstream_fclose(stream); + return; + } + + /* + * Send the queue file info and recipient info to the delivery process. + * If there is a problem, wipe the pending deliveries for this transport. + * This routine runs in response to an external event, so it does not run + * while some other queue manipulation is happening. + */ + if (qmgr_deliver_send_request(entry, stream) < 0) { + qmgr_entry_unselect(queue, entry); +#if 0 + whatsup = concatenate(transport->name, + " mail transport unavailable", (char *) 0); + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsn, "4.3.0", whatsup)); + myfree(whatsup); +#else + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsn, "4.3.0", + "mail transport unavailable")); +#endif + qmgr_defer_transport(transport, &dsn); + /* warning: entry and queue may be dangling pointers here */ + (void) vstream_fclose(stream); + return; + } + + /* + * If we get this far, go wait for the delivery status report. + */ + qmgr_deliver_concurrency++; + entry->stream = stream; + event_enable_read(vstream_fileno(stream), + qmgr_deliver_update, (void *) entry); + + /* + * Guard against broken systems. + */ + event_request_timer(qmgr_deliver_abort, (void *) entry, var_daemon_timeout); +} diff --git a/src/oqmgr/qmgr_enable.c b/src/oqmgr/qmgr_enable.c new file mode 100644 index 0000000..a35e46e --- /dev/null +++ b/src/oqmgr/qmgr_enable.c @@ -0,0 +1,107 @@ +/*++ +/* NAME +/* qmgr_enable +/* SUMMARY +/* enable dead transports or sites +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_enable_queue(queue) +/* QMGR_QUEUE *queue; +/* +/* QMGR_QUEUE *qmgr_enable_transport(transport) +/* QMGR_TRANSPORT *transport; +/* +/* void qmgr_enable_all(void) +/* DESCRIPTION +/* This module purges dead in-core state information, effectively +/* re-enabling delivery. +/* +/* qmgr_enable_queue() enables deliveries to the named dead site. +/* Empty queues are destroyed. The existed solely to indicate that +/* a site is dead. +/* +/* qmgr_enable_transport() enables deliveries via the specified +/* transport, and calls qmgr_enable_queue() for each destination +/* on that transport. Empty queues are destroyed. +/* +/* qmgr_enable_all() enables all transports and queues. +/* See above for the side effects caused by doing this. +/* BUGS +/* The side effects of calling this module can be quite dramatic. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_enable_all - enable transports and queues */ + +void qmgr_enable_all(void) +{ + QMGR_TRANSPORT *xport; + + if (msg_verbose) + msg_info("qmgr_enable_all"); + + /* + * The number of transports does not change as a side effect, so this can + * be a straightforward loop. + */ + for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next) + qmgr_enable_transport(xport); +} + +/* qmgr_enable_transport - defer todo entries for named transport */ + +void qmgr_enable_transport(QMGR_TRANSPORT *transport) +{ + QMGR_QUEUE *queue; + QMGR_QUEUE *next; + + /* + * Proceed carefully. Queues may disappear as a side effect. + */ + if (transport->flags & QMGR_TRANSPORT_STAT_DEAD) { + if (msg_verbose) + msg_info("enable transport %s", transport->name); + qmgr_transport_unthrottle(transport); + } + for (queue = transport->queue_list.next; queue; queue = next) { + next = queue->peers.next; + qmgr_enable_queue(queue); + } +} + +/* qmgr_enable_queue - enable and possibly delete queue */ + +void qmgr_enable_queue(QMGR_QUEUE *queue) +{ + if (QMGR_QUEUE_THROTTLED(queue)) { + if (msg_verbose) + msg_info("enable site %s/%s", queue->transport->name, queue->name); + qmgr_queue_unthrottle(queue); + } + if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0) + qmgr_queue_done(queue); +} diff --git a/src/oqmgr/qmgr_entry.c b/src/oqmgr/qmgr_entry.c new file mode 100644 index 0000000..d5f3264 --- /dev/null +++ b/src/oqmgr/qmgr_entry.c @@ -0,0 +1,391 @@ +/*++ +/* NAME +/* qmgr_entry 3 +/* SUMMARY +/* per-site queue entries +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_ENTRY *qmgr_entry_create(queue, message) +/* QMGR_QUEUE *queue; +/* QMGR_MESSAGE *message; +/* +/* void qmgr_entry_done(entry, which) +/* QMGR_ENTRY *entry; +/* int which; +/* +/* QMGR_ENTRY *qmgr_entry_select(queue) +/* QMGR_QUEUE *queue; +/* +/* void qmgr_entry_unselect(queue, entry) +/* QMGR_QUEUE *queue; +/* QMGR_ENTRY *entry; +/* +/* void qmgr_entry_move_todo(dst, entry) +/* QMGR_QUEUE *dst; +/* QMGR_ENTRY *entry; +/* DESCRIPTION +/* These routines add/delete/manipulate per-site message +/* delivery requests. +/* +/* qmgr_entry_create() creates an entry for the named queue and +/* message, and appends the entry to the queue's todo list. +/* Filling in and cleaning up the recipients is the responsibility +/* of the caller. +/* +/* qmgr_entry_done() discards a per-site queue entry. The +/* \fIwhich\fR argument is either QMGR_QUEUE_BUSY for an entry +/* of the site's `busy' list (i.e. queue entries that have been +/* selected for actual delivery), or QMGR_QUEUE_TODO for an entry +/* of the site's `todo' list (i.e. queue entries awaiting selection +/* for actual delivery). +/* +/* qmgr_entry_done() triggers cleanup of the per-site queue when +/* the site has no pending deliveries, and the site is either +/* alive, or the site is dead and the number of in-core queues +/* exceeds a configurable limit (see qmgr_queue_done()). +/* +/* qmgr_entry_done() triggers special action when the last in-core +/* queue entry for a message is done with: either read more +/* recipients from the queue file, delete the queue file, or move +/* the queue file to the deferred queue; send bounce reports to the +/* message originator (see qmgr_active_done()). +/* +/* qmgr_entry_select() selects the next entry from the named +/* per-site queue's `todo' list for actual delivery. The entry is +/* moved to the queue's `busy' list: the list of messages being +/* delivered. +/* +/* qmgr_entry_unselect() takes the named entry off the named +/* per-site queue's `busy' list and moves it to the queue's +/* `todo' list. +/* +/* qmgr_entry_move_todo() moves the specified "todo" queue entry +/* to the specified "todo" queue. +/* DIAGNOSTICS +/* Panic: interface violations, internal inconsistencies. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include /* opportunistic session caching */ + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_entry_select - select queue entry for delivery */ + +QMGR_ENTRY *qmgr_entry_select(QMGR_QUEUE *queue) +{ + const char *myname = "qmgr_entry_select"; + QMGR_ENTRY *entry; + + if ((entry = queue->todo.prev) != 0) { + QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry); + queue->todo_refcount--; + QMGR_LIST_APPEND(queue->busy, entry); + queue->busy_refcount++; + + /* + * With opportunistic session caching, the delivery agent must not + * only 1) save a session upon completion, but also 2) reuse a cached + * session upon the next delivery request. In order to not miss out + * on 2), we have to make caching sticky or else we get silly + * behavior when the in-memory queue drains. Specifically, new + * connections must not be made as long as cached connections exist. + * + * Safety: don't enable opportunistic session caching unless the queue + * manager is able to schedule concurrent or back-to-back deliveries + * (we need to recognize back-to-back deliveries for transports with + * concurrency 1). + * + * If caching has previously been enabled, but is not now, fetch any + * existing entries from the cache, but don't add new ones. + */ +#define CONCURRENT_OR_BACK_TO_BACK_DELIVERY() \ + (queue->busy_refcount > 1 || BACK_TO_BACK_DELIVERY()) + +#define BACK_TO_BACK_DELIVERY() \ + (queue->last_done + 1 >= event_time()) + + /* + * Turn on session caching after we get up to speed. Don't enable + * session caching just because we have concurrent deliveries. This + * prevents unnecessary session caching when we have a burst of mail + * <= the initial concurrency limit. + */ + if ((queue->dflags & DEL_REQ_FLAG_CONN_STORE) == 0) { + if (BACK_TO_BACK_DELIVERY()) { + if (msg_verbose) + msg_info("%s: allowing on-demand session caching for %s", + myname, queue->name); + queue->dflags |= DEL_REQ_FLAG_CONN_MASK; + } + } + + /* + * Turn off session caching when concurrency drops and we're running + * out of steam. This is what prevents from turning off session + * caching too early, and from making new connections while old ones + * are still cached. + */ + else { + if (!CONCURRENT_OR_BACK_TO_BACK_DELIVERY()) { + if (msg_verbose) + msg_info("%s: disallowing on-demand session caching for %s", + myname, queue->name); + queue->dflags &= ~DEL_REQ_FLAG_CONN_STORE; + } + } + } + return (entry); +} + +/* qmgr_entry_unselect - unselect queue entry for delivery */ + +void qmgr_entry_unselect(QMGR_QUEUE *queue, QMGR_ENTRY *entry) +{ + QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry); + queue->busy_refcount--; + QMGR_LIST_APPEND(queue->todo, entry); + queue->todo_refcount++; +} + +/* qmgr_entry_move_todo - move entry between todo queues */ + +void qmgr_entry_move_todo(QMGR_QUEUE *dst, QMGR_ENTRY *entry) +{ + const char *myname = "qmgr_entry_move_todo"; + QMGR_MESSAGE *message = entry->message; + QMGR_QUEUE *src = entry->queue; + QMGR_ENTRY *new_entry; + + if (entry->stream != 0) + msg_panic("%s: queue %s entry is busy", myname, src->name); + if (QMGR_QUEUE_THROTTLED(dst)) + msg_panic("%s: destination queue %s is throttled", myname, dst->name); + if (QMGR_TRANSPORT_THROTTLED(dst->transport)) + msg_panic("%s: destination transport %s is throttled", + myname, dst->transport->name); + + /* + * Create new entry, swap the recipients between the old and new entries, + * then dispose of the old entry. This gives us any end-game actions that + * are implemented by qmgr_entry_done(), so we don't have to duplicate + * those actions here. + * + * XXX This does not enforce the per-entry recipient limit, but that is not + * a problem as long as qmgr_entry_move_todo() is called only to bounce + * or defer mail. + */ + new_entry = qmgr_entry_create(dst, message); + recipient_list_swap(&entry->rcpt_list, &new_entry->rcpt_list); + qmgr_entry_done(entry, QMGR_QUEUE_TODO); +} + +/* qmgr_entry_done - dispose of queue entry */ + +void qmgr_entry_done(QMGR_ENTRY *entry, int which) +{ + const char *myname = "qmgr_entry_done"; + QMGR_QUEUE *queue = entry->queue; + QMGR_MESSAGE *message = entry->message; + QMGR_TRANSPORT *transport = queue->transport; + + /* + * Take this entry off the in-core queue. + */ + if (entry->stream != 0) + msg_panic("%s: file is open", myname); + if (which == QMGR_QUEUE_BUSY) { + QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry); + queue->busy_refcount--; + } else if (which == QMGR_QUEUE_TODO) { + QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry); + queue->todo_refcount--; + } else { + msg_panic("%s: bad queue spec: %d", myname, which); + } + + /* + * Free the recipient list and decrease the in-core recipient count + * accordingly. + */ + qmgr_recipient_count -= entry->rcpt_list.len; + recipient_list_free(&entry->rcpt_list); + + myfree((void *) entry); + + /* + * Maintain back-to-back delivery status. + */ + if (which == QMGR_QUEUE_BUSY) + queue->last_done = event_time(); + + /* + * Suspend a rate-limited queue, so that mail trickles out. + */ + if (which == QMGR_QUEUE_BUSY && transport->rate_delay > 0) { + if (queue->window > 1) + msg_panic("%s: queue %s/%s: window %d > 1 on rate-limited service", + myname, transport->name, queue->name, queue->window); + if (QMGR_QUEUE_THROTTLED(queue)) /* XXX */ + qmgr_queue_unthrottle(queue); + if (QMGR_QUEUE_READY(queue)) + qmgr_queue_suspend(queue, transport->rate_delay); + } + + /* + * When the in-core queue for this site is empty and when this site is + * not dead, discard the in-core queue. When this site is dead, but the + * number of in-core queues exceeds some threshold, get rid of this + * in-core queue anyway, in order to avoid running out of memory. + * + * See also: qmgr_entry_move_todo(). + */ + if (queue->todo.next == 0 && queue->busy.next == 0) { + if (QMGR_QUEUE_THROTTLED(queue) && qmgr_queue_count > 2 * var_qmgr_rcpt_limit) + qmgr_queue_unthrottle(queue); + if (QMGR_QUEUE_READY(queue)) + qmgr_queue_done(queue); + } + + /* + * Update the in-core message reference count. When the in-core message + * structure has no more references, dispose of the message. + * + * When the in-core recipient count falls below a threshold, and this + * message has more recipients, read more recipients now. If we read more + * recipients as soon as the recipient count falls below the in-core + * recipient limit, we do not give other messages a chance until this + * message is delivered. That's good for mailing list deliveries, bad for + * one-to-one mail. If we wait until the in-core recipient count drops + * well below the in-core recipient limit, we give other mail a chance, + * but we also allow list deliveries to become interleaved. In the worst + * case, people near the start of a mailing list get a burst of postings + * today, while people near the end of the list get that same burst of + * postings a whole day later. + */ +#define FUDGE(x) ((x) * (var_qmgr_fudge / 100.0)) + message->refcount--; + if (message->rcpt_offset > 0 + && qmgr_recipient_count < FUDGE(var_qmgr_rcpt_limit) - 100) + qmgr_message_realloc(message); + if (message->refcount == 0) + qmgr_active_done(message); +} + +/* qmgr_entry_create - create queue todo entry */ + +QMGR_ENTRY *qmgr_entry_create(QMGR_QUEUE *queue, QMGR_MESSAGE *message) +{ + QMGR_ENTRY *entry; + + /* + * Sanity check. + */ + if (QMGR_QUEUE_THROTTLED(queue)) + msg_panic("qmgr_entry_create: dead queue: %s", queue->name); + + /* + * Create the delivery request. + */ + entry = (QMGR_ENTRY *) mymalloc(sizeof(QMGR_ENTRY)); + entry->stream = 0; + entry->message = message; + recipient_list_init(&entry->rcpt_list, RCPT_LIST_INIT_QUEUE); + message->refcount++; + entry->queue = queue; + QMGR_LIST_APPEND(queue->todo, entry); + queue->todo_refcount++; + + /* + * Warn if a destination is falling behind while the active queue + * contains a non-trivial amount of single-recipient email. When a + * destination takes up more and more space in the active queue, then + * other mail will not get through and delivery performance will suffer. + * + * XXX At this point in the code, the busy reference count is still less + * than the concurrency limit (otherwise this code would not be invoked + * in the first place) so we have to make some awkward adjustments + * below. + * + * XXX The queue length test below looks at the active queue share of an + * individual destination. This catches the case where mail for one + * destination is falling behind because it has to round-robin compete + * with many other destinations. However, Postfix will also perform + * poorly when most of the active queue is tied up by a small number of + * concurrency limited destinations. The queue length test below detects + * such conditions only indirectly. + * + * XXX This code does not detect the case that the active queue is being + * starved because incoming mail is pounding the disk. + */ + if (var_helpful_warnings && var_qmgr_clog_warn_time > 0) { + int queue_length = queue->todo_refcount + queue->busy_refcount; + time_t now; + QMGR_TRANSPORT *transport; + double active_share; + + if (queue_length > var_qmgr_active_limit / 5 + && (now = event_time()) >= queue->clog_time_to_warn) { + active_share = queue_length / (double) qmgr_message_count; + msg_warn("mail for %s is using up %d of %d active queue entries", + queue->nexthop, queue_length, qmgr_message_count); + if (active_share < 0.9) + msg_warn("this may slow down other mail deliveries"); + transport = queue->transport; + if (transport->dest_concurrency_limit > 0 + && transport->dest_concurrency_limit <= queue->busy_refcount + 1) + msg_warn("you may need to increase the main.cf %s%s from %d", + transport->name, _DEST_CON_LIMIT, + transport->dest_concurrency_limit); + else if (queue->window > var_qmgr_active_limit * active_share) + msg_warn("you may need to increase the main.cf %s from %d", + VAR_QMGR_ACT_LIMIT, var_qmgr_active_limit); + else if (queue->peers.next != queue->peers.prev) + msg_warn("you may need a separate master.cf transport for %s", + queue->nexthop); + else { + msg_warn("you may need to reduce %s connect and helo timeouts", + transport->name); + msg_warn("so that Postfix quickly skips unavailable hosts"); + msg_warn("you may need to increase the main.cf %s and %s", + VAR_MIN_BACKOFF_TIME, VAR_MAX_BACKOFF_TIME); + msg_warn("so that Postfix wastes less time on undeliverable mail"); + msg_warn("you may need to increase the master.cf %s process limit", + transport->name); + } + msg_warn("please avoid flushing the whole queue when you have"); + msg_warn("lots of deferred mail, that is bad for performance"); + msg_warn("to turn off these warnings specify: %s = 0", + VAR_QMGR_CLOG_WARN_TIME); + queue->clog_time_to_warn = now + var_qmgr_clog_warn_time; + } + } + return (entry); +} diff --git a/src/oqmgr/qmgr_error.c b/src/oqmgr/qmgr_error.c new file mode 100644 index 0000000..6541c35 --- /dev/null +++ b/src/oqmgr/qmgr_error.c @@ -0,0 +1,121 @@ +/*++ +/* NAME +/* qmgr_error 3 +/* SUMMARY +/* look up/create error/retry queue +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_TRANSPORT *qmgr_error_transport(service) +/* const char *service; +/* +/* QMGR_QUEUE *qmgr_error_queue(service, dsn) +/* const char *service; +/* DSN *dsn; +/* +/* char *qmgr_error_nexthop(dsn) +/* DSN *dsn; +/* DESCRIPTION +/* qmgr_error_transport() looks up the error transport for the +/* specified service. The result is null if the transport is +/* not available. +/* +/* qmgr_error_queue() looks up an error queue for the specified +/* service and problem. The result is null if the queue is not +/* available. +/* +/* qmgr_error_nexthop() computes the next-hop information for +/* the specified problem. The result must be passed to myfree(). +/* +/* Arguments: +/* .IP dsn +/* See dsn(3). +/* .IP service +/* One of MAIL_SERVICE_ERROR or MAIL_SERVICE_RETRY. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_error_transport - look up error transport for specified service */ + +QMGR_TRANSPORT *qmgr_error_transport(const char *service) +{ + QMGR_TRANSPORT *transport; + + /* + * Find or create retry transport. + */ + if ((transport = qmgr_transport_find(service)) == 0) + transport = qmgr_transport_create(service); + if (QMGR_TRANSPORT_THROTTLED(transport)) + return (0); + + /* + * Done. + */ + return (transport); +} + +/* qmgr_error_queue - look up error queue for specified service and problem */ + +QMGR_QUEUE *qmgr_error_queue(const char *service, DSN *dsn) +{ + QMGR_TRANSPORT *transport; + QMGR_QUEUE *queue; + char *nexthop; + + /* + * Find or create transport. + */ + if ((transport = qmgr_error_transport(service)) == 0) + return (0); + + /* + * Find or create queue. + */ + nexthop = qmgr_error_nexthop(dsn); + if ((queue = qmgr_queue_find(transport, nexthop)) == 0) + queue = qmgr_queue_create(transport, nexthop, nexthop); + myfree(nexthop); + if (QMGR_QUEUE_THROTTLED(queue)) + return (0); + + /* + * Done. + */ + return (queue); +} + +/* qmgr_error_nexthop - compute next-hop information from problem description */ + +char *qmgr_error_nexthop(DSN *dsn) +{ + char *nexthop; + + nexthop = concatenate(dsn->status, " ", dsn->reason, (char *) 0); + return (nexthop); +} diff --git a/src/oqmgr/qmgr_feedback.c b/src/oqmgr/qmgr_feedback.c new file mode 100644 index 0000000..f8019f8 --- /dev/null +++ b/src/oqmgr/qmgr_feedback.c @@ -0,0 +1,182 @@ +/*++ +/* NAME +/* qmgr_feedback 3 +/* SUMMARY +/* delivery agent feedback management +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_feedback_init(fbck_ctl, name_prefix, name_tail, +/* def_name, def_value) +/* QMGR_FEEDBACK *fbck_ctl; +/* const char *name_prefix; +/* const char *name_tail; +/* const char *def_name; +/* const char *def_value; +/* +/* double QMGR_FEEDBACK_VAL(fbck_ctl, concurrency) +/* QMGR_FEEDBACK *fbck_ctl; +/* const int concurrency; +/* DESCRIPTION +/* Upon completion of a delivery request, a delivery agent +/* provides a hint that the scheduler should dedicate fewer or +/* more resources to a specific destination. +/* +/* qmgr_feedback_init() looks up transport-dependent positive +/* or negative concurrency feedback control information from +/* main.cf, and converts it to internal form. +/* +/* QMGR_FEEDBACK_VAL() computes a concurrency adjustment based +/* on a preprocessed feedback control information and the +/* current concurrency window. This is an "unsafe" macro that +/* evaluates some arguments multiple times. +/* +/* Arguments: +/* .IP fbck_ctl +/* Pointer to QMGR_FEEDBACK structure where the result will +/* be stored. +/* .IP name_prefix +/* Mail delivery transport name, used as the initial portion +/* of a transport-dependent concurrency feedback parameter +/* name. +/* .IP name_tail +/* The second, and fixed, portion of a transport-dependent +/* concurrency feedback parameter. +/* .IP def_name +/* The name of a default feedback parameter. +/* .IP def_val +/* The value of the default feedback parameter. +/* .IP concurrency +/* Delivery concurrency for concurrency-dependent feedback calculation. +/* DIAGNOSTICS +/* Warning: configuration error or unreasonable input. The program +/* uses name_tail feedback instead. +/* Panic: consistency check failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include /* INT_MAX */ +#include /* sscanf() */ +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + + /* + * Lookup tables for main.cf feedback method names. + */ +const NAME_CODE qmgr_feedback_map[] = { + CONC_FDBACK_NAME_WIN, QMGR_FEEDBACK_IDX_WIN, +#ifdef QMGR_FEEDBACK_IDX_SQRT_WIN + CONC_FDBACK_NAME_SQRT_WIN, QMGR_FEEDBACK_IDX_SQRT_WIN, +#endif + 0, QMGR_FEEDBACK_IDX_NONE, +}; + +/* qmgr_feedback_init - initialize feedback control */ + +void qmgr_feedback_init(QMGR_FEEDBACK *fb, + const char *name_prefix, + const char *name_tail, + const char *def_name, + const char *def_val) +{ + double enum_val; + char denom_str[30 + 1]; + double denom_val; + char slash[1 + 1]; + char junk; + char *fbck_name; + char *fbck_val; + + /* + * Look up the transport-dependent feedback value. + */ + fbck_name = concatenate(name_prefix, name_tail, (char *) 0); + fbck_val = get_mail_conf_str(fbck_name, def_val, 1, 0); + + /* + * We allow users to express feedback as 1/8, as a more user-friendly + * alternative to 0.125 (or worse, having users specify the number of + * events in a feedback hysteresis cycle). + * + * We use some sscanf() fu to parse the value into numerator and optional + * "/" followed by denominator. We're doing this only a few times during + * the process life time, so we strive for convenience instead of speed. + */ +#define INCLUSIVE_BOUNDS(val, low, high) ((val) >= (low) && (val) <= (high)) + + fb->hysteresis = 1; /* legacy */ + fb->base = -1; /* assume error */ + + switch (sscanf(fbck_val, "%lf %1[/] %30s%c", + &enum_val, slash, denom_str, &junk)) { + case 1: + fb->index = QMGR_FEEDBACK_IDX_NONE; + fb->base = enum_val; + break; + case 3: + if ((fb->index = name_code(qmgr_feedback_map, NAME_CODE_FLAG_NONE, + denom_str)) != QMGR_FEEDBACK_IDX_NONE) { + fb->base = enum_val; + } else if (INCLUSIVE_BOUNDS(enum_val, 0, INT_MAX) + && sscanf(denom_str, "%lf%c", &denom_val, &junk) == 1 + && INCLUSIVE_BOUNDS(denom_val, 1.0 / INT_MAX, INT_MAX)) { + fb->base = enum_val / denom_val; + } + break; + } + + /* + * Sanity check. If input is bad, we just warn and use a reasonable + * default. + */ + if (!INCLUSIVE_BOUNDS(fb->base, 0, 1)) { + msg_warn("%s: ignoring malformed or unreasonable feedback: %s", + strcmp(fbck_val, def_val) ? fbck_name : def_name, fbck_val); + fb->index = QMGR_FEEDBACK_IDX_NONE; + fb->base = 1; + } + + /* + * Performance debugging/analysis. + */ + if (var_conc_feedback_debug) + msg_info("%s: %s feedback type %d value at %d: %g", + name_prefix, strcmp(fbck_val, def_val) ? + fbck_name : def_name, fb->index, var_init_dest_concurrency, + QMGR_FEEDBACK_VAL(*fb, var_init_dest_concurrency)); + + myfree(fbck_name); + myfree(fbck_val); +} diff --git a/src/oqmgr/qmgr_message.c b/src/oqmgr/qmgr_message.c new file mode 100644 index 0000000..b885264 --- /dev/null +++ b/src/oqmgr/qmgr_message.c @@ -0,0 +1,1497 @@ +/*++ +/* NAME +/* qmgr_message 3 +/* SUMMARY +/* in-core message structures +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* int qmgr_message_count; +/* int qmgr_recipient_count; +/* int qmgr_vrfy_pend_count; +/* +/* QMGR_MESSAGE *qmgr_message_alloc(class, name, qflags, mode) +/* const char *class; +/* const char *name; +/* int qflags; +/* mode_t mode; +/* +/* QMGR_MESSAGE *qmgr_message_realloc(message) +/* QMGR_MESSAGE *message; +/* +/* void qmgr_message_free(message) +/* QMGR_MESSAGE *message; +/* +/* void qmgr_message_update_warn(message) +/* QMGR_MESSAGE *message; +/* +/* void qmgr_message_kill_record(message, offset) +/* QMGR_MESSAGE *message; +/* long offset; +/* DESCRIPTION +/* This module performs en-gross operations on queue messages. +/* +/* qmgr_message_count is a global counter for the total number +/* of in-core message structures (i.e. the total size of the +/* `active' message queue). +/* +/* qmgr_recipient_count is a global counter for the total number +/* of in-core recipient structures (i.e. the sum of all recipients +/* in all in-core message structures). +/* +/* qmgr_vrfy_pend_count is a global counter for the total +/* number of in-core message structures that are associated +/* with an address verification request. Requests that exceed +/* the address_verify_pending_limit are deferred immediately. +/* This is a backup mechanism for a more refined enforcement +/* mechanism in the verify(8) daemon. +/* +/* qmgr_message_alloc() creates an in-core message structure +/* with sender and recipient information taken from the named queue +/* file. A null result means the queue file could not be read or +/* that the queue file contained incorrect information. A result +/* QMGR_MESSAGE_LOCKED means delivery must be deferred. The number +/* of recipients read from a queue file is limited by the global +/* var_qmgr_rcpt_limit configuration parameter. When the limit +/* is reached, the \fIrcpt_offset\fR structure member is set to +/* the position where the read was terminated. Recipients are +/* run through the resolver, and are assigned to destination +/* queues. Recipients that cannot be assigned are deferred or +/* bounced. Mail that has bounced twice is silently absorbed. +/* A non-zero mode means change the queue file permissions. +/* +/* qmgr_message_realloc() resumes reading recipients from the queue +/* file, and updates the recipient list and \fIrcpt_offset\fR message +/* structure members. A null result means that the file could not be +/* read or that the file contained incorrect information. +/* +/* qmgr_message_free() destroys an in-core message structure and makes +/* the resources available for reuse. It is an error to destroy +/* a message structure that is still referenced by queue entry structures. +/* +/* qmgr_message_update_warn() takes a closed message, opens it, updates +/* the warning field, and closes it again. +/* +/* qmgr_message_kill_record() takes a closed message, opens it, updates +/* the record type at the given offset to "killed", and closes the file. +/* A killed envelope record is ignored. Killed records are not allowed +/* inside the message content. +/* DIAGNOSTICS +/* Warnings: malformed message file. Fatal errors: out of memory. +/* SEE ALSO +/* envelope(3) message envelope parser +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include /* sscanf() */ +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Client stubs. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +int qmgr_message_count; +int qmgr_recipient_count; +int qmgr_vrfy_pend_count; + +/* qmgr_message_create - create in-core message structure */ + +static QMGR_MESSAGE *qmgr_message_create(const char *queue_name, + const char *queue_id, int qflags) +{ + QMGR_MESSAGE *message; + + message = (QMGR_MESSAGE *) mymalloc(sizeof(QMGR_MESSAGE)); + qmgr_message_count++; + message->flags = 0; + message->qflags = qflags; + message->tflags = 0; + message->tflags_offset = 0; + message->rflags = QMGR_READ_FLAG_DEFAULT; + message->fp = 0; + message->refcount = 0; + message->single_rcpt = 0; + message->arrival_time.tv_sec = message->arrival_time.tv_usec = 0; + message->create_time = 0; + GETTIMEOFDAY(&message->active_time); + message->data_offset = 0; + message->queue_id = mystrdup(queue_id); + message->queue_name = mystrdup(queue_name); + message->encoding = 0; + message->sender = 0; + message->dsn_envid = 0; + message->dsn_ret = 0; + message->smtputf8 = 0; + message->filter_xport = 0; + message->inspect_xport = 0; + message->redirect_addr = 0; + message->data_size = 0; + message->cont_length = 0; + message->warn_offset = 0; + message->warn_time = 0; + message->rcpt_offset = 0; + message->verp_delims = 0; + message->client_name = 0; + message->client_addr = 0; + message->client_port = 0; + message->client_proto = 0; + message->client_helo = 0; + message->sasl_method = 0; + message->sasl_username = 0; + message->sasl_sender = 0; + message->log_ident = 0; + message->rewrite_context = 0; + recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE); + return (message); +} + +/* qmgr_message_close - close queue file */ + +static void qmgr_message_close(QMGR_MESSAGE *message) +{ + vstream_fclose(message->fp); + message->fp = 0; +} + +/* qmgr_message_open - open queue file */ + +static int qmgr_message_open(QMGR_MESSAGE *message) +{ + + /* + * Sanity check. + */ + if (message->fp) + msg_panic("%s: queue file is open", message->queue_id); + + /* + * Open this queue file. Skip files that we cannot open. Back off when + * the system appears to be running out of resources. + */ + if ((message->fp = mail_queue_open(message->queue_name, + message->queue_id, + O_RDWR, 0)) == 0) { + if (errno != ENOENT) + msg_fatal("open %s %s: %m", message->queue_name, message->queue_id); + msg_warn("open %s %s: %m", message->queue_name, message->queue_id); + return (-1); + } + return (0); +} + +/* qmgr_message_oldstyle_scan - support for Postfix < 1.0 queue files */ + +static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message) +{ + VSTRING *buf; + long orig_offset, extra_offset; + int rec_type; + char *start; + + /* + * Initialize. No early returns or we have a memory leak. + */ + buf = vstring_alloc(100); + if ((orig_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); + + /* + * Rewind to the very beginning to make sure we see all records. + */ + if (vstream_fseek(message->fp, 0, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + + /* + * Scan through the old style queue file. Count the total number of + * recipients and find the data/extra sections offsets. Note that the new + * queue files require that data_size equals extra_offset - data_offset, + * so we set data_size to this as well and ignore the size record itself + * completely. + */ + for (;;) { + rec_type = rec_get(message->fp, buf, 0); + if (rec_type <= 0) + /* Report missing end record later. */ + break; + start = vstring_str(buf); + if (msg_verbose > 1) + msg_info("old-style scan record %c %s", rec_type, start); + if (rec_type == REC_TYPE_END) + break; + if (rec_type == REC_TYPE_MESG) { + if (message->data_offset == 0) { + if ((message->data_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); + if ((extra_offset = atol(start)) <= message->data_offset) + msg_fatal("bad extra offset %s file %s", + start, VSTREAM_PATH(message->fp)); + if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + message->data_size = extra_offset - message->data_offset; + } + continue; + } + } + + /* + * Clean up. + */ + if (vstream_fseek(message->fp, orig_offset, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + vstring_free(buf); + + /* + * Sanity checks. Verify that all required information was found, + * including the queue file end marker. + */ + if (message->data_offset == 0 || rec_type != REC_TYPE_END) + msg_fatal("%s: envelope records out of order", message->queue_id); +} + +/* qmgr_message_read - read envelope records */ + +static int qmgr_message_read(QMGR_MESSAGE *message) +{ + VSTRING *buf; + int rec_type; + long curr_offset; + long save_offset = message->rcpt_offset; /* save a flag */ + char *start; + int nrcpt = 0; + const char *error_text; + char *name; + char *value; + char *orig_rcpt = 0; + int count; + int dsn_notify = 0; + char *dsn_orcpt = 0; + int n; + int have_log_client_attr = 0; + static const char env_rec_types[] = REC_TYPE_ENVELOPE REC_TYPE_EXTRACT; + static const char extra_rec_type[] = {REC_TYPE_XTRA, 0}; + const char *expected_rec_types; + + /* + * Initialize. No early returns or we have a memory leak. + */ + buf = vstring_alloc(100); + + /* + * If we re-open this file, skip over on-file recipient records that we + * already looked at, and refill the in-core recipient address list. + */ + if (message->rcpt_offset) { + if (message->rcpt_list.len) + msg_panic("%s: recipient list not empty on recipient reload", + message->queue_id); + if (vstream_fseek(message->fp, message->rcpt_offset, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + message->rcpt_offset = 0; + } + + /* + * Read envelope records. XXX Rely on the front-end programs to enforce + * record size limits. Read up to var_qmgr_rcpt_limit recipients from the + * queue file, to protect against memory exhaustion. Recipient records + * may appear before or after the message content, so we keep reading + * from the queue file until we have enough recipients (rcpt_offset != 0) + * and until we know all the non-recipient information. + * + * When reading recipients from queue file, stop reading when we reach a + * per-message in-core recipient limit rather than a global in-core + * recipient limit. Use the global recipient limit only in order to stop + * opening queue files. The purpose is to achieve equal delay for + * messages with recipient counts up to var_qmgr_rcpt_limit recipients. + * + * If we would read recipients up to a global recipient limit, the average + * number of in-core recipients per message would asymptotically approach + * (global recipient limit)/(active queue size limit), which gives equal + * delay per recipient rather than equal delay per message. + * + * On the first open, we must examine all non-recipient records. + * + * Optimization: when we know that recipient records are not mixed with + * non-recipient records, as is typical with mailing list mail, then we + * can avoid having to examine all the queue file records before we can + * start deliveries. This avoids some file system thrashing with huge + * mailing lists. + */ + for (;;) { + expected_rec_types = env_rec_types; + if ((curr_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); + if (curr_offset == message->data_offset && curr_offset > 0) { + if (vstream_fseek(message->fp, message->data_size, SEEK_CUR) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + curr_offset += message->data_size; + expected_rec_types = extra_rec_type; + } + rec_type = rec_get_raw(message->fp, buf, 0, REC_FLAG_NONE); + start = vstring_str(buf); + if (msg_verbose > 1) + msg_info("record %c %s", rec_type, start); + if (rec_type == REC_TYPE_PTR) { + if ((rec_type = rec_goto(message->fp, start)) == REC_TYPE_ERROR) + break; + /* Need to update curr_offset after pointer jump. */ + continue; + } + if (rec_type <= 0) { + msg_warn("%s: message rejected: missing end record", + message->queue_id); + break; + } + if (strchr(expected_rec_types, rec_type) == 0) { + msg_warn("Unexpected record type '%c' at offset %ld", + rec_type, (long) curr_offset); + rec_type = REC_TYPE_ERROR; + break; + } + if (rec_type == REC_TYPE_END) { + message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT; + break; + } + + /* + * Map named attributes to pseudo record types, so that we don't have + * to pollute the queue file with records that are incompatible with + * past Postfix versions. Preferably, people should be able to back + * out from an upgrade without losing mail. + */ + if (rec_type == REC_TYPE_ATTR) { + if ((error_text = split_nameval(start, &name, &value)) != 0) { + msg_warn("%s: bad attribute record: %s: %.200s", + message->queue_id, error_text, start); + rec_type = REC_TYPE_ERROR; + break; + } + if ((n = rec_attr_map(name)) != 0) { + start = value; + rec_type = n; + } + } + + /* + * Process recipient records. + */ + if (rec_type == REC_TYPE_RCPT) { + /* See also below for code setting orig_rcpt etc. */ +#define FUDGE(x) ((x) * (var_qmgr_fudge / 100.0)) + if (message->rcpt_offset == 0) { + recipient_list_add(&message->rcpt_list, curr_offset, + dsn_orcpt ? dsn_orcpt : "", + dsn_notify ? dsn_notify : 0, + orig_rcpt ? orig_rcpt : "", start); + if (dsn_orcpt) { + myfree(dsn_orcpt); + dsn_orcpt = 0; + } + if (orig_rcpt) { + myfree(orig_rcpt); + orig_rcpt = 0; + } + if (dsn_notify) + dsn_notify = 0; + if (message->rcpt_list.len >= FUDGE(var_qmgr_rcpt_limit)) { + if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", + VSTREAM_PATH(message->fp)); + if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT) + /* We already examined all non-recipient records. */ + break; + if (message->rflags & QMGR_READ_FLAG_MIXED_RCPT_OTHER) + /* Examine all remaining non-recipient records. */ + continue; + /* Optimizations for "pure recipient" record sections. */ + if (curr_offset > message->data_offset) { + /* We already examined all non-recipient records. */ + message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT; + break; + } + + /* + * Examine non-recipient records in the extracted + * segment. Note that this skips to the message start + * record, because the handler for that record changes + * the expectations for allowed record types. + */ + if (vstream_fseek(message->fp, message->data_offset, + SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + continue; + } + } + continue; + } + if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_DRCP) { + if (message->rcpt_offset == 0) { + if (dsn_orcpt) { + myfree(dsn_orcpt); + dsn_orcpt = 0; + } + if (orig_rcpt) { + myfree(orig_rcpt); + orig_rcpt = 0; + } + if (dsn_notify) + dsn_notify = 0; + } + continue; + } + if (rec_type == REC_TYPE_DSN_ORCPT) { + /* See also above for code clearing dsn_orcpt. */ + if (dsn_orcpt != 0) { + msg_warn("%s: ignoring out-of-order DSN original recipient address <%.200s>", + message->queue_id, dsn_orcpt); + myfree(dsn_orcpt); + dsn_orcpt = 0; + } + if (message->rcpt_offset == 0) + dsn_orcpt = mystrdup(start); + continue; + } + if (rec_type == REC_TYPE_DSN_NOTIFY) { + /* See also above for code clearing dsn_notify. */ + if (dsn_notify != 0) { + msg_warn("%s: ignoring out-of-order DSN notify flags <%d>", + message->queue_id, dsn_notify); + dsn_notify = 0; + } + if (message->rcpt_offset == 0) { + if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_NOTIFY_OK(n)) + msg_warn("%s: ignoring malformed DSN notify flags <%.200s>", + message->queue_id, start); + else + dsn_notify = n; + continue; + } + } + if (rec_type == REC_TYPE_ORCP) { + /* See also above for code clearing orig_rcpt. */ + if (orig_rcpt != 0) { + msg_warn("%s: ignoring out-of-order original recipient <%.200s>", + message->queue_id, orig_rcpt); + myfree(orig_rcpt); + orig_rcpt = 0; + } + if (message->rcpt_offset == 0) + orig_rcpt = mystrdup(start); + continue; + } + + /* + * Process non-recipient records. + */ + if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT) + /* We already examined all non-recipient records. */ + continue; + if (rec_type == REC_TYPE_SIZE) { + if (message->data_offset == 0) { + if ((count = sscanf(start, "%ld %ld %d %d %ld %d", + &message->data_size, &message->data_offset, + &nrcpt, &message->rflags, + &message->cont_length, + &message->smtputf8)) >= 3) { + /* Postfix >= 1.0 (a.k.a. 20010228). */ + if (message->data_offset <= 0 || message->data_size <= 0) { + msg_warn("%s: invalid size record: %.100s", + message->queue_id, start); + rec_type = REC_TYPE_ERROR; + break; + } + if (message->rflags & ~QMGR_READ_FLAG_USER) { + msg_warn("%s: invalid flags in size record: %.100s", + message->queue_id, start); + rec_type = REC_TYPE_ERROR; + break; + } + } else if (count == 1) { + /* Postfix < 1.0 (a.k.a. 20010228). */ + qmgr_message_oldstyle_scan(message); + } else { + /* Can't happen. */ + msg_warn("%s: message rejected: weird size record", + message->queue_id); + rec_type = REC_TYPE_ERROR; + break; + } + } + /* Postfix < 2.4 compatibility. */ + if (message->cont_length == 0) { + message->cont_length = message->data_size; + } else if (message->cont_length < 0) { + msg_warn("%s: invalid size record: %.100s", + message->queue_id, start); + rec_type = REC_TYPE_ERROR; + break; + } + continue; + } + if (rec_type == REC_TYPE_TIME) { + if (message->arrival_time.tv_sec == 0) + REC_TYPE_TIME_SCAN(start, message->arrival_time); + continue; + } + if (rec_type == REC_TYPE_CTIME) { + if (message->create_time == 0) + message->create_time = atol(start); + continue; + } + if (rec_type == REC_TYPE_FILT) { + if (message->filter_xport != 0) + myfree(message->filter_xport); + message->filter_xport = mystrdup(start); + continue; + } + if (rec_type == REC_TYPE_INSP) { + if (message->inspect_xport != 0) + myfree(message->inspect_xport); + message->inspect_xport = mystrdup(start); + continue; + } + if (rec_type == REC_TYPE_RDR) { + if (message->redirect_addr != 0) + myfree(message->redirect_addr); + message->redirect_addr = mystrdup(start); + continue; + } + if (rec_type == REC_TYPE_FROM) { + if (message->sender == 0) { + message->sender = mystrdup(start); + opened(message->queue_id, message->sender, + message->cont_length, nrcpt, + "queue %s", message->queue_name); + } + continue; + } + if (rec_type == REC_TYPE_DSN_ENVID) { + /* Allow Milter override. */ + if (message->dsn_envid != 0) + myfree(message->dsn_envid); + message->dsn_envid = mystrdup(start); + } + if (rec_type == REC_TYPE_DSN_RET) { + /* Allow Milter override. */ + if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_RET_OK(n)) + msg_warn("%s: ignoring malformed DSN RET flags in queue file record:%.100s", + message->queue_id, start); + else + message->dsn_ret = n; + } + if (rec_type == REC_TYPE_ATTR) { + /* Allow extra segment to override envelope segment info. */ + if (strcmp(name, MAIL_ATTR_ENCODING) == 0) { + if (message->encoding != 0) + myfree(message->encoding); + message->encoding = mystrdup(value); + } + + /* + * Backwards compatibility. Before Postfix 2.3, the logging + * attributes were called client_name, etc. Now they are called + * log_client_name. etc., and client_name is used for the actual + * client information. To support old queue files, we accept both + * names for the purpose of logging; the new name overrides the + * old one. + * + * XXX Do not use the "legacy" client_name etc. attribute values for + * initializing the logging attributes, when this file already + * contains the "modern" log_client_name etc. logging attributes. + * Otherwise, logging attributes that are not present in the + * queue file would be set with information from the real client. + */ + else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_NAME) == 0) { + if (have_log_client_attr == 0 && message->client_name == 0) + message->client_name = mystrdup(value); + } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_ADDR) == 0) { + if (have_log_client_attr == 0 && message->client_addr == 0) + message->client_addr = mystrdup(value); + } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_PORT) == 0) { + if (have_log_client_attr == 0 && message->client_port == 0) + message->client_port = mystrdup(value); + } else if (strcmp(name, MAIL_ATTR_ACT_PROTO_NAME) == 0) { + if (have_log_client_attr == 0 && message->client_proto == 0) + message->client_proto = mystrdup(value); + } else if (strcmp(name, MAIL_ATTR_ACT_HELO_NAME) == 0) { + if (have_log_client_attr == 0 && message->client_helo == 0) + message->client_helo = mystrdup(value); + } + /* Original client attributes. */ + else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_NAME) == 0) { + if (message->client_name != 0) + myfree(message->client_name); + message->client_name = mystrdup(value); + have_log_client_attr = 1; + } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_ADDR) == 0) { + if (message->client_addr != 0) + myfree(message->client_addr); + message->client_addr = mystrdup(value); + have_log_client_attr = 1; + } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_PORT) == 0) { + if (message->client_port != 0) + myfree(message->client_port); + message->client_port = mystrdup(value); + have_log_client_attr = 1; + } else if (strcmp(name, MAIL_ATTR_LOG_PROTO_NAME) == 0) { + if (message->client_proto != 0) + myfree(message->client_proto); + message->client_proto = mystrdup(value); + have_log_client_attr = 1; + } else if (strcmp(name, MAIL_ATTR_LOG_HELO_NAME) == 0) { + if (message->client_helo != 0) + myfree(message->client_helo); + message->client_helo = mystrdup(value); + have_log_client_attr = 1; + } else if (strcmp(name, MAIL_ATTR_SASL_METHOD) == 0) { + if (message->sasl_method == 0) + message->sasl_method = mystrdup(value); + else + msg_warn("%s: ignoring multiple %s attribute: %s", + message->queue_id, MAIL_ATTR_SASL_METHOD, value); + } else if (strcmp(name, MAIL_ATTR_SASL_USERNAME) == 0) { + if (message->sasl_username == 0) + message->sasl_username = mystrdup(value); + else + msg_warn("%s: ignoring multiple %s attribute: %s", + message->queue_id, MAIL_ATTR_SASL_USERNAME, value); + } else if (strcmp(name, MAIL_ATTR_SASL_SENDER) == 0) { + if (message->sasl_sender == 0) + message->sasl_sender = mystrdup(value); + else + msg_warn("%s: ignoring multiple %s attribute: %s", + message->queue_id, MAIL_ATTR_SASL_SENDER, value); + } else if (strcmp(name, MAIL_ATTR_LOG_IDENT) == 0) { + if (message->log_ident == 0) + message->log_ident = mystrdup(value); + else + msg_warn("%s: ignoring multiple %s attribute: %s", + message->queue_id, MAIL_ATTR_LOG_IDENT, value); + } else if (strcmp(name, MAIL_ATTR_RWR_CONTEXT) == 0) { + if (message->rewrite_context == 0) + message->rewrite_context = mystrdup(value); + else + msg_warn("%s: ignoring multiple %s attribute: %s", + message->queue_id, MAIL_ATTR_RWR_CONTEXT, value); + } + + /* + * Optional tracing flags (verify, sendmail -v, sendmail -bv). + * This record is killed after a trace logfile report is sent and + * after the logfile is deleted. + */ + else if (strcmp(name, MAIL_ATTR_TRACE_FLAGS) == 0) { + if (message->tflags == 0) { + message->tflags = DEL_REQ_TRACE_FLAGS(atoi(value)); + if (message->tflags == DEL_REQ_FLAG_RECORD) + message->tflags_offset = curr_offset; + else + message->tflags_offset = 0; + if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0) + qmgr_vrfy_pend_count++; + } + } + continue; + } + if (rec_type == REC_TYPE_WARN) { + if (message->warn_offset == 0) { + message->warn_offset = curr_offset; + REC_TYPE_WARN_SCAN(start, message->warn_time); + } + continue; + } + if (rec_type == REC_TYPE_VERP) { + if (message->verp_delims == 0) { + if (message->sender == 0 || message->sender[0] == 0) { + msg_warn("%s: ignoring VERP request for null sender", + message->queue_id); + } else if (verp_delims_verify(start) != 0) { + msg_warn("%s: ignoring bad VERP request: \"%.100s\"", + message->queue_id, start); + } else { + if (msg_verbose) + msg_info("%s: enabling VERP for sender \"%.100s\"", + message->queue_id, message->sender); + message->single_rcpt = 1; + message->verp_delims = mystrdup(start); + } + } + continue; + } + } + + /* + * Grr. + */ + if (dsn_orcpt != 0) { + if (rec_type > 0) + msg_warn("%s: ignoring out-of-order DSN original recipient <%.200s>", + message->queue_id, dsn_orcpt); + myfree(dsn_orcpt); + } + if (orig_rcpt != 0) { + if (rec_type > 0) + msg_warn("%s: ignoring out-of-order original recipient <%.200s>", + message->queue_id, orig_rcpt); + myfree(orig_rcpt); + } + + /* + * After sending a "delayed" warning, request sender notification when + * message delivery is completed. While "mail delayed" notifications are + * bad enough because they multiply the amount of email traffic, "delay + * cleared" notifications are even worse because they come in a sudden + * burst when the queue drains after a network outage. + */ + if (var_dsn_delay_cleared && message->warn_time < 0) + message->tflags |= DEL_REQ_FLAG_REC_DLY_SENT; + + /* + * Avoid clumsiness elsewhere in the program. When sending data across an + * IPC channel, sending an empty string is more convenient than sending a + * null pointer. + */ + if (message->dsn_envid == 0) + message->dsn_envid = mystrdup(""); + if (message->encoding == 0) + message->encoding = mystrdup(MAIL_ATTR_ENC_NONE); + if (message->client_name == 0) + message->client_name = mystrdup(""); + if (message->client_addr == 0) + message->client_addr = mystrdup(""); + if (message->client_port == 0) + message->client_port = mystrdup(""); + if (message->client_proto == 0) + message->client_proto = mystrdup(""); + if (message->client_helo == 0) + message->client_helo = mystrdup(""); + if (message->sasl_method == 0) + message->sasl_method = mystrdup(""); + if (message->sasl_username == 0) + message->sasl_username = mystrdup(""); + if (message->sasl_sender == 0) + message->sasl_sender = mystrdup(""); + if (message->log_ident == 0) + message->log_ident = mystrdup(""); + if (message->rewrite_context == 0) + message->rewrite_context = mystrdup(MAIL_ATTR_RWR_LOCAL); + /* Postfix < 2.3 compatibility. */ + if (message->create_time == 0) + message->create_time = message->arrival_time.tv_sec; + + /* + * Clean up. + */ + vstring_free(buf); + + /* + * Sanity checks. Verify that all required information was found, + * including the queue file end marker. + */ + if (rec_type <= 0) { + /* Already logged warning. */ + } else if (message->arrival_time.tv_sec == 0) { + msg_warn("%s: message rejected: missing arrival time record", + message->queue_id); + } else if (message->sender == 0) { + msg_warn("%s: message rejected: missing sender record", + message->queue_id); + } else if (message->data_offset == 0) { + msg_warn("%s: message rejected: missing size record", + message->queue_id); + } else { + return (0); + } + message->rcpt_offset = save_offset; /* restore flag */ + return (-1); +} + +/* qmgr_message_update_warn - update the time of next delay warning */ + +void qmgr_message_update_warn(QMGR_MESSAGE *message) +{ + + /* + * After the "mail delayed" warning, optionally send a "delay cleared" + * notification. + */ + if (qmgr_message_open(message) + || vstream_fseek(message->fp, message->warn_offset, SEEK_SET) < 0 + || rec_fprintf(message->fp, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT, + REC_TYPE_WARN_ARG(-1)) < 0 + || vstream_fflush(message->fp)) + msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp)); + qmgr_message_close(message); +} + +/* qmgr_message_kill_record - mark one message record as killed */ + +void qmgr_message_kill_record(QMGR_MESSAGE *message, long offset) +{ + if (offset <= 0) + msg_panic("qmgr_message_kill_record: bad offset 0x%lx", offset); + if (qmgr_message_open(message) + || rec_put_type(message->fp, REC_TYPE_KILL, offset) < 0 + || vstream_fflush(message->fp)) + msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp)); + qmgr_message_close(message); +} + +/* qmgr_message_sort_compare - compare recipient information */ + +static int qmgr_message_sort_compare(const void *p1, const void *p2) +{ + RECIPIENT *rcpt1 = (RECIPIENT *) p1; + RECIPIENT *rcpt2 = (RECIPIENT *) p2; + QMGR_QUEUE *queue1; + QMGR_QUEUE *queue2; + char *at1; + char *at2; + int result; + + /* + * Compare most significant to least significant recipient attributes. + * The comparison function must be transitive, so NULL values need to be + * assigned an ordinal (we set NULL last). + */ + + queue1 = rcpt1->u.queue; + queue2 = rcpt2->u.queue; + if (queue1 != 0 && queue2 == 0) + return (-1); + if (queue1 == 0 && queue2 != 0) + return (1); + if (queue1 != 0 && queue2 != 0) { + + /* + * Compare message transport. + */ + if ((result = strcmp(queue1->transport->name, + queue2->transport->name)) != 0) + return (result); + + /* + * Compare queue name (nexthop or recipient@nexthop). + */ + if ((result = strcmp(queue1->name, queue2->name)) != 0) + return (result); + } + + /* + * Compare recipient domain. + */ + at1 = strrchr(rcpt1->address, '@'); + at2 = strrchr(rcpt2->address, '@'); + if (at1 == 0 && at2 != 0) + return (1); + if (at1 != 0 && at2 == 0) + return (-1); + if (at1 != 0 && at2 != 0 + && (result = strcasecmp_utf8(at1, at2)) != 0) + return (result); + + /* + * Compare recipient address. + */ + return (strcasecmp_utf8(rcpt1->address, rcpt2->address)); +} + +/* qmgr_message_sort - sort message recipient addresses by domain */ + +static void qmgr_message_sort(QMGR_MESSAGE *message) +{ + qsort((void *) message->rcpt_list.info, message->rcpt_list.len, + sizeof(message->rcpt_list.info[0]), qmgr_message_sort_compare); + if (msg_verbose) { + RECIPIENT_LIST list = message->rcpt_list; + RECIPIENT *rcpt; + + msg_info("start sorted recipient list"); + for (rcpt = list.info; rcpt < list.info + list.len; rcpt++) + msg_info("qmgr_message_sort: %s", rcpt->address); + msg_info("end sorted recipient list"); + } +} + +/* qmgr_resolve_one - resolve or skip one recipient */ + +static int qmgr_resolve_one(QMGR_MESSAGE *message, RECIPIENT *recipient, + const char *addr, RESOLVE_REPLY *reply) +{ +#define QMGR_REDIRECT(rp, tp, np) do { \ + (rp)->flags = 0; \ + vstring_strcpy((rp)->transport, (tp)); \ + vstring_strcpy((rp)->nexthop, (np)); \ + } while (0) + + if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) == 0) + resolve_clnt_query_from(message->sender, addr, reply); + else + resolve_clnt_verify_from(message->sender, addr, reply); + if (reply->flags & RESOLVE_FLAG_FAIL) { + QMGR_REDIRECT(reply, MAIL_SERVICE_RETRY, + "4.3.0 address resolver failure"); + return (0); + } else if (reply->flags & RESOLVE_FLAG_ERROR) { + QMGR_REDIRECT(reply, MAIL_SERVICE_ERROR, + "5.1.3 bad address syntax"); + return (0); + } else { + return (0); + } +} + +/* qmgr_message_resolve - resolve recipients */ + +static void qmgr_message_resolve(QMGR_MESSAGE *message) +{ + static ARGV *defer_xport_argv; + RECIPIENT_LIST list = message->rcpt_list; + RECIPIENT *recipient; + QMGR_TRANSPORT *transport = 0; + QMGR_QUEUE *queue = 0; + RESOLVE_REPLY reply; + VSTRING *queue_name; + char *at; + char **cpp; + char *nexthop; + ssize_t len; + int status; + DSN dsn; + MSG_STATS stats; + DSN *saved_dsn; + +#define STREQ(x,y) (strcmp(x,y) == 0) +#define STR vstring_str +#define LEN VSTRING_LEN + + resolve_clnt_init(&reply); + queue_name = vstring_alloc(1); + for (recipient = list.info; recipient < list.info + list.len; recipient++) { + + /* + * Redirect overrides all else. But only once (per entire message). + * For consistency with the remainder of Postfix, rewrite the address + * to canonical form before resolving it. + */ + if (message->redirect_addr) { + if (recipient > list.info) { + recipient->u.queue = 0; + continue; + } + message->rcpt_offset = 0; + rewrite_clnt_internal(REWRITE_CANON, message->redirect_addr, + reply.recipient); + RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); + if (qmgr_resolve_one(message, recipient, + recipient->address, &reply) < 0) + continue; + if (!STREQ(recipient->address, STR(reply.recipient))) + RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); + } + + /* + * Content filtering overrides the address resolver. + * + * XXX Bypass content_filter inspection for user-generated probes + * (sendmail -bv). MTA-generated probes never have the "please filter + * me" bits turned on, but we handle them here anyway for the sake of + * future proofing. + */ +#define FILTER_WITHOUT_NEXTHOP(filter, next) \ + (((next) = split_at((filter), ':')) == 0 || *(next) == 0) + +#define RCPT_WITHOUT_DOMAIN(rcpt, next) \ + ((next = strrchr(rcpt, '@')) == 0 || *++(next) == 0) + + else if (message->filter_xport + && (message->tflags & DEL_REQ_TRACE_ONLY_MASK) == 0) { + reply.flags = 0; + vstring_strcpy(reply.transport, message->filter_xport); + if (FILTER_WITHOUT_NEXTHOP(STR(reply.transport), nexthop) + && *(nexthop = var_def_filter_nexthop) == 0 + && RCPT_WITHOUT_DOMAIN(recipient->address, nexthop)) + nexthop = var_myhostname; + vstring_strcpy(reply.nexthop, nexthop); + vstring_strcpy(reply.recipient, recipient->address); + } + + /* + * Resolve the destination to (transport, nexthop, address). The + * result address may differ from the one specified by the sender. + */ + else { + if (qmgr_resolve_one(message, recipient, + recipient->address, &reply) < 0) + continue; + if (!STREQ(recipient->address, STR(reply.recipient))) + RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); + } + + /* + * Bounce null recipients. This should never happen, but is most + * likely the result of a fault in a different program, so aborting + * the queue manager process does not help. + */ + if (recipient->address[0] == 0) { + QMGR_REDIRECT(&reply, MAIL_SERVICE_ERROR, + "5.1.3 null recipient address"); + } + + /* + * Redirect a forced-to-expire message without defer log to the retry + * service, so that its defer log will contain an appropriate reason. + * Do not redirect such a message to the error service, because if + * that request fails, a defer log would be created with reason + * "bounce or trace service failure" which would make no sense. Note + * that if the bounce service fails to create a defer log, the + * message will be returned as undeliverable anyway, because it is + * expired. + */ + if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) { + QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY, + "4.7.0 message is administratively expired"); + } + + /* + * Discard mail to the local double bounce address here, so this + * system can run without a local delivery agent. They'd still have + * to configure something for mail directed to the local postmaster, + * though, but that is an RFC requirement anyway. + * + * XXX This lookup should be done in the resolver, and the mail should + * be directed to a general-purpose null delivery agent. + */ + if (reply.flags & RESOLVE_CLASS_LOCAL) { + at = strrchr(STR(reply.recipient), '@'); + len = (at ? (at - STR(reply.recipient)) + : strlen(STR(reply.recipient))); + if (strncasecmp_utf8(STR(reply.recipient), + var_double_bounce_sender, len) == 0 + && !var_double_bounce_sender[len]) { + status = sent(message->tflags, message->queue_id, + QMGR_MSG_STATS(&stats, message), recipient, + "none", DSN_SIMPLE(&dsn, "2.0.0", + "undeliverable postmaster notification discarded")); + if (status == 0) { + deliver_completed(message->fp, recipient->offset); +#if 0 + /* It's the default verification probe sender address. */ + msg_warn("%s: undeliverable postmaster notification discarded", + message->queue_id); +#endif + } else + message->flags |= status; + continue; + } + } + + /* + * Optionally defer deliveries over specific transports, unless the + * restriction is lifted temporarily. + */ + if (*var_defer_xports && (message->qflags & QMGR_FLUSH_DFXP) == 0) { + if (defer_xport_argv == 0) + defer_xport_argv = argv_split(var_defer_xports, CHARS_COMMA_SP); + for (cpp = defer_xport_argv->argv; *cpp; cpp++) + if (strcmp(*cpp, STR(reply.transport)) == 0) + break; + if (*cpp) { + QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY, + "4.3.2 deferred transport"); + } + } + + /* + * Safety: defer excess address verification requests. + */ + if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0 + && qmgr_vrfy_pend_count > var_vrfy_pend_limit) + QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY, + "4.3.2 Too many address verification requests"); + + /* + * Look up or instantiate the proper transport. + */ + if (transport == 0 || !STREQ(transport->name, STR(reply.transport))) { + if ((transport = qmgr_transport_find(STR(reply.transport))) == 0) + transport = qmgr_transport_create(STR(reply.transport)); + queue = 0; + } + + /* + * This message is being flushed. If need-be unthrottle the + * transport. + */ + if ((message->qflags & QMGR_FLUSH_EACH) != 0 + && QMGR_TRANSPORT_THROTTLED(transport)) + qmgr_transport_unthrottle(transport); + + /* + * This transport is dead. Defer delivery to this recipient. + */ + if (QMGR_TRANSPORT_THROTTLED(transport)) { + saved_dsn = transport->dsn; + if ((transport = qmgr_error_transport(MAIL_SERVICE_RETRY)) != 0) { + nexthop = qmgr_error_nexthop(saved_dsn); + vstring_strcpy(reply.nexthop, nexthop); + myfree(nexthop); + queue = 0; + } else { + qmgr_defer_recipient(message, recipient, saved_dsn); + continue; + } + } + + /* + * The nexthop destination provides the default name for the + * per-destination queue. When the delivery agent accepts only one + * recipient per delivery, give each recipient its own queue, so that + * deliveries to different recipients of the same message can happen + * in parallel, and so that we can enforce per-recipient concurrency + * limits and prevent one recipient from tying up all the delivery + * agent resources. We use recipient@nexthop as queue name rather + * than the actual recipient domain name, so that one recipient in + * multiple equivalent domains cannot evade the per-recipient + * concurrency limit. Split the address on the recipient delimiter if + * one is defined, so that extended addresses don't get extra + * delivery slots. + * + * Fold the result to lower case so that we don't have multiple queues + * for the same name. + * + * Important! All recipients in a queue must have the same nexthop + * value. It is OK to have multiple queues with the same nexthop + * value, but only when those queues are named after recipients. + * + * The single-recipient code below was written for local(8) like + * delivery agents, and assumes that all domains that deliver to the + * same (transport + nexthop) are aliases for $nexthop. Delivery + * concurrency is changed from per-domain into per-recipient, by + * changing the queue name from nexthop into localpart@nexthop. + * + * XXX This assumption is incorrect when different destinations share + * the same (transport + nexthop). In reality, such transports are + * rarely configured to use single-recipient deliveries. The fix is + * to decouple the per-destination recipient limit from the + * per-destination concurrency. + */ + vstring_strcpy(queue_name, STR(reply.nexthop)); + if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0 + && strcmp(transport->name, MAIL_SERVICE_RETRY) != 0 + && transport->recipient_limit == 1) { + /* Copy the recipient localpart. */ + at = strrchr(STR(reply.recipient), '@'); + len = (at ? (at - STR(reply.recipient)) + : strlen(STR(reply.recipient))); + vstring_strncpy(queue_name, STR(reply.recipient), len); + /* Remove the address extension from the recipient localpart. */ + if (*var_rcpt_delim && split_addr(STR(queue_name), var_rcpt_delim)) + vstring_truncate(queue_name, strlen(STR(queue_name))); + /* Assume the recipient domain is equivalent to nexthop. */ + vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop)); + } + lowercase(STR(queue_name)); + + /* + * This transport is alive. Find or instantiate a queue for this + * recipient. + */ + if (queue == 0 || !STREQ(queue->name, STR(queue_name))) { + if ((queue = qmgr_queue_find(transport, STR(queue_name))) == 0) + queue = qmgr_queue_create(transport, STR(queue_name), + STR(reply.nexthop)); + } + + /* + * This message is being flushed. If need-be unthrottle the queue. + */ + if ((message->qflags & QMGR_FLUSH_EACH) != 0 + && QMGR_QUEUE_THROTTLED(queue)) + qmgr_queue_unthrottle(queue); + + /* + * This queue is dead. Defer delivery to this recipient. + */ + if (QMGR_QUEUE_THROTTLED(queue)) { + saved_dsn = queue->dsn; + if ((queue = qmgr_error_queue(MAIL_SERVICE_RETRY, saved_dsn)) == 0) { + qmgr_defer_recipient(message, recipient, saved_dsn); + continue; + } + } + + /* + * This queue is alive. Bind this recipient to this queue instance. + */ + recipient->u.queue = queue; + } + resolve_clnt_free(&reply); + vstring_free(queue_name); +} + +/* qmgr_message_assign - assign recipients to specific delivery requests */ + +static void qmgr_message_assign(QMGR_MESSAGE *message) +{ + RECIPIENT_LIST list = message->rcpt_list; + RECIPIENT *recipient; + QMGR_ENTRY *entry = 0; + QMGR_QUEUE *queue; + + /* + * Try to bundle as many recipients in a delivery request as we can. When + * the recipient resolves to the same site and transport as the previous + * recipient, do not create a new queue entry, just move that recipient + * to the recipient list of the existing queue entry. All this provided + * that we do not exceed the transport-specific limit on the number of + * recipients per transaction. Skip recipients with a dead transport or + * destination. + */ +#define LIMIT_OK(limit, count) ((limit) == 0 || ((count) < (limit))) + + for (recipient = list.info; recipient < list.info + list.len; recipient++) { + if ((queue = recipient->u.queue) != 0) { + if (message->single_rcpt || entry == 0 || entry->queue != queue + || !LIMIT_OK(entry->queue->transport->recipient_limit, + entry->rcpt_list.len)) { + entry = qmgr_entry_create(queue, message); + } + recipient_list_add(&entry->rcpt_list, recipient->offset, + recipient->dsn_orcpt, recipient->dsn_notify, + recipient->orig_addr, recipient->address); + qmgr_recipient_count++; + } + } + recipient_list_free(&message->rcpt_list); + recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE); +} + +/* qmgr_message_free - release memory for in-core message structure */ + +void qmgr_message_free(QMGR_MESSAGE *message) +{ + if (message->refcount != 0) + msg_panic("qmgr_message_free: reference len: %d", message->refcount); + if (message->fp) + msg_panic("qmgr_message_free: queue file is open"); + myfree(message->queue_id); + myfree(message->queue_name); + if (message->dsn_envid) + myfree(message->dsn_envid); + if (message->encoding) + myfree(message->encoding); + if (message->sender) + myfree(message->sender); + if (message->verp_delims) + myfree(message->verp_delims); + if (message->filter_xport) + myfree(message->filter_xport); + if (message->inspect_xport) + myfree(message->inspect_xport); + if (message->redirect_addr) + myfree(message->redirect_addr); + if (message->client_name) + myfree(message->client_name); + if (message->client_addr) + myfree(message->client_addr); + if (message->client_port) + myfree(message->client_port); + if (message->client_proto) + myfree(message->client_proto); + if (message->client_helo) + myfree(message->client_helo); + if (message->sasl_method) + myfree(message->sasl_method); + if (message->sasl_username) + myfree(message->sasl_username); + if (message->sasl_sender) + myfree(message->sasl_sender); + if (message->log_ident) + myfree(message->log_ident); + if (message->rewrite_context) + myfree(message->rewrite_context); + recipient_list_free(&message->rcpt_list); + qmgr_message_count--; + if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0) + qmgr_vrfy_pend_count--; + myfree((void *) message); +} + +/* qmgr_message_alloc - create in-core message structure */ + +QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *queue_id, + int qflags, mode_t mode) +{ + const char *myname = "qmgr_message_alloc"; + QMGR_MESSAGE *message; + struct stat st; + + if (msg_verbose) + msg_info("%s: %s %s", myname, queue_name, queue_id); + + /* + * Create an in-core message structure. + */ + message = qmgr_message_create(queue_name, queue_id, qflags); + + /* + * Extract message envelope information: time of arrival, sender address, + * recipient addresses. Skip files with malformed envelope information. + */ +#define QMGR_LOCK_MODE (MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) + + if (qmgr_message_open(message) < 0) { + qmgr_message_free(message); + return (0); + } + if (myflock(vstream_fileno(message->fp), INTERNAL_LOCK, QMGR_LOCK_MODE) < 0) { + msg_info("%s: skipped, still being delivered", queue_id); + qmgr_message_close(message); + qmgr_message_free(message); + return (QMGR_MESSAGE_LOCKED); + } + if (qmgr_message_read(message) < 0) { + qmgr_message_close(message); + qmgr_message_free(message); + return (0); + } else { + + /* + * We have validated the queue file content, so it is safe to modify + * the file properties now. + */ + if (mode != 0 && fchmod(vstream_fileno(message->fp), mode) < 0) + msg_fatal("fchmod %s: %m", VSTREAM_PATH(message->fp)); + + /* + * If this message is forced to expire, use the existing defer + * logfile records and do not assign any deliveries, leaving the + * refcount at zero. If this message is forced to expire, but no + * defer logfile records are available, assign deliveries to the + * retry transport so that the sender will still find out what + * recipients are affected and why. Either way, do not assign normal + * deliveries because that would be undesirable especially with mail + * that was expired in the 'hold' queue. + */ + if ((message->qflags & QMGR_FORCE_EXPIRE) != 0 + && stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_DEFER, + queue_id), &st) == 0 && st.st_size > 0) { + /* Use this defer log; don't assign deliveries (refcount == 0). */ + message->flags = 1; /* simplify downstream code */ + qmgr_message_close(message); + return (message); + } + + /* + * Reset the defer log. This code should not be here, but we must + * reset the defer log *after* acquiring the exclusive lock on the + * queue file and *before* resolving new recipients. Since all those + * operations are encapsulated so nicely by this routine, the defer + * log reset has to be done here as well. + * + * Note: it is safe to remove the defer logfile from a previous queue + * run of this queue file, because the defer log contains information + * about recipients that still exist in this queue file. + */ + if (mail_queue_remove(MAIL_QUEUE_DEFER, queue_id) && errno != ENOENT) + msg_fatal("%s: %s: remove %s %s: %m", myname, + queue_id, MAIL_QUEUE_DEFER, queue_id); + qmgr_message_sort(message); + qmgr_message_resolve(message); + qmgr_message_sort(message); + qmgr_message_assign(message); + qmgr_message_close(message); + return (message); + } +} + +/* qmgr_message_realloc - refresh in-core message structure */ + +QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *message) +{ + const char *myname = "qmgr_message_realloc"; + + /* + * Sanity checks. + */ + if (message->rcpt_offset <= 0) + msg_panic("%s: invalid offset: %ld", myname, message->rcpt_offset); + if (msg_verbose) + msg_info("%s: %s %s offset %ld", myname, message->queue_name, + message->queue_id, message->rcpt_offset); + + /* + * Extract recipient addresses. Skip files with malformed envelope + * information. + */ + if (qmgr_message_open(message) < 0) + return (0); + if (qmgr_message_read(message) < 0) { + qmgr_message_close(message); + return (0); + } else { + qmgr_message_sort(message); + qmgr_message_resolve(message); + qmgr_message_sort(message); + qmgr_message_assign(message); + qmgr_message_close(message); + return (message); + } +} diff --git a/src/oqmgr/qmgr_move.c b/src/oqmgr/qmgr_move.c new file mode 100644 index 0000000..e68f803 --- /dev/null +++ b/src/oqmgr/qmgr_move.c @@ -0,0 +1,104 @@ +/*++ +/* NAME +/* qmgr_move 3 +/* SUMMARY +/* move queue entries to another queue +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_move(from, to, time_stamp) +/* const char *from; +/* const char *to; +/* time_t time_stamp; +/* DESCRIPTION +/* The \fBqmgr_move\fR routine scans the \fIfrom\fR queue for entries +/* with valid queue names and moves them to the \fIto\fR queue. +/* If \fItime_stamp\fR is non-zero, the queue file time stamps are +/* set to the specified value. +/* Entries with invalid names are left alone. No attempt is made to +/* look for other badness such as multiple links or weird file types. +/* These issues are dealt with when a queue file is actually opened. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_move - move queue entries to another queue, leave bad files alone */ + +void qmgr_move(const char *src_queue, const char *dst_queue, + time_t time_stamp) +{ + const char *myname = "qmgr_move"; + SCAN_DIR *queue_dir; + char *queue_id; + struct utimbuf tbuf; + const char *path; + + if (strcmp(src_queue, dst_queue) == 0) + msg_panic("%s: source queue %s is destination", myname, src_queue); + if (msg_verbose) + msg_info("start move queue %s -> %s", src_queue, dst_queue); + + queue_dir = scan_dir_open(src_queue); + while ((queue_id = mail_scan_dir_next(queue_dir)) != 0) { + if (mail_queue_id_ok(queue_id)) { + if (time_stamp > 0) { + tbuf.actime = tbuf.modtime = time_stamp; + path = mail_queue_path((VSTRING *) 0, src_queue, queue_id); + if (utime(path, &tbuf) < 0) { + if (errno != ENOENT) + msg_fatal("%s: update %s time stamps: %m", myname, path); + msg_warn("%s: update %s time stamps: %m", myname, path); + continue; + } + } + if (mail_queue_rename(queue_id, src_queue, dst_queue)) { + if (errno != ENOENT) + msg_fatal("%s: rename %s from %s to %s: %m", + myname, queue_id, src_queue, dst_queue); + msg_warn("%s: rename %s from %s to %s: %m", + myname, queue_id, src_queue, dst_queue); + continue; + } + if (msg_verbose) + msg_info("%s: moved %s from %s to %s", + myname, queue_id, src_queue, dst_queue); + } else { + msg_warn("%s: ignored: queue %s id %s", + myname, src_queue, queue_id); + } + } + scan_dir_close(queue_dir); + + if (msg_verbose) + msg_info("end move queue %s -> %s", src_queue, dst_queue); +} diff --git a/src/oqmgr/qmgr_queue.c b/src/oqmgr/qmgr_queue.c new file mode 100644 index 0000000..a127c6b --- /dev/null +++ b/src/oqmgr/qmgr_queue.c @@ -0,0 +1,442 @@ +/*++ +/* NAME +/* qmgr_queue 3 +/* SUMMARY +/* per-destination queues +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* int qmgr_queue_count; +/* +/* QMGR_QUEUE *qmgr_queue_create(transport, name, nexthop) +/* QMGR_TRANSPORT *transport; +/* const char *name; +/* const char *nexthop; +/* +/* void qmgr_queue_done(queue) +/* QMGR_QUEUE *queue; +/* +/* QMGR_QUEUE *qmgr_queue_find(transport, name) +/* QMGR_TRANSPORT *transport; +/* const char *name; +/* +/* QMGR_QUEUE *qmgr_queue_select(transport) +/* QMGR_TRANSPORT *transport; +/* +/* void qmgr_queue_throttle(queue, dsn) +/* QMGR_QUEUE *queue; +/* DSN *dsn; +/* +/* void qmgr_queue_unthrottle(queue) +/* QMGR_QUEUE *queue; +/* +/* void qmgr_queue_suspend(queue, delay) +/* QMGR_QUEUE *queue; +/* int delay; +/* DESCRIPTION +/* These routines add/delete/manipulate per-destination queues. +/* Each queue corresponds to a specific transport and destination. +/* Each queue has a `todo' list of delivery requests for that +/* destination, and a `busy' list of delivery requests in progress. +/* +/* qmgr_queue_count is a global counter for the total number +/* of in-core queue structures. +/* +/* qmgr_queue_create() creates an empty named queue for the named +/* transport and destination. The queue is given an initial +/* concurrency limit as specified with the +/* \fIinitial_destination_concurrency\fR configuration parameter, +/* provided that it does not exceed the transport-specific +/* concurrency limit. +/* +/* qmgr_queue_done() disposes of a per-destination queue after all +/* its entries have been taken care of. It is an error to dispose +/* of a dead queue. +/* +/* qmgr_queue_find() looks up the named queue for the named +/* transport. A null result means that the queue was not found. +/* +/* qmgr_queue_select() uses a round-robin strategy to select +/* from the named transport one per-destination queue with a +/* non-empty `todo' list. +/* +/* qmgr_queue_throttle() handles a delivery error, and decrements the +/* concurrency limit for the destination, with a lower bound of 1. +/* When the cohort failure bound is reached, qmgr_queue_throttle() +/* sets the concurrency limit to zero and starts a timer +/* to re-enable delivery to the destination after a configurable delay. +/* +/* qmgr_queue_unthrottle() undoes qmgr_queue_throttle()'s effects. +/* The concurrency limit for the destination is incremented, +/* provided that it does not exceed the destination concurrency +/* limit specified for the transport. This routine implements +/* "slow open" mode, and eliminates the "thundering herd" problem. +/* +/* qmgr_queue_suspend() suspends delivery for this destination +/* briefly. +/* DIAGNOSTICS +/* Panic: consistency check failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include /* QMGR_LOG_WINDOW */ + +/* Application-specific. */ + +#include "qmgr.h" + +int qmgr_queue_count; + +#define QMGR_ERROR_OR_RETRY_QUEUE(queue) \ + (strcmp(queue->transport->name, MAIL_SERVICE_RETRY) == 0 \ + || strcmp(queue->transport->name, MAIL_SERVICE_ERROR) == 0) + +#define QMGR_LOG_FEEDBACK(feedback) \ + if (var_conc_feedback_debug && !QMGR_ERROR_OR_RETRY_QUEUE(queue)) \ + msg_info("%s: feedback %g", myname, feedback); + +#define QMGR_LOG_WINDOW(queue) \ + if (var_conc_feedback_debug && !QMGR_ERROR_OR_RETRY_QUEUE(queue)) \ + msg_info("%s: queue %s: limit %d window %d success %g failure %g fail_cohorts %g", \ + myname, queue->name, queue->transport->dest_concurrency_limit, \ + queue->window, queue->success, queue->failure, queue->fail_cohorts); + +/* qmgr_queue_resume - resume delivery to destination */ + +static void qmgr_queue_resume(int event, void *context) +{ + QMGR_QUEUE *queue = (QMGR_QUEUE *) context; + const char *myname = "qmgr_queue_resume"; + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_SUSPENDED(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + + /* + * We can't simply force delivery on this queue: the transport's pending + * count may already be maxed out, and there may be other constraints + * that definitely should be none of our business. The best we can do is + * to play by the same rules as everyone else: let qmgr_active_drain() + * and round-robin selection take care of message selection. + */ + queue->window = 1; + + /* + * Every event handler that leaves a queue in the "ready" state should + * remove the queue when it is empty. + */ + if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0) + qmgr_queue_done(queue); +} + +/* qmgr_queue_suspend - briefly suspend a destination */ + +void qmgr_queue_suspend(QMGR_QUEUE *queue, int delay) +{ + const char *myname = "qmgr_queue_suspend"; + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + if (queue->busy_refcount > 0) + msg_panic("%s: queue is busy", myname); + + /* + * Set the queue status to "suspended". No-one is supposed to remove a + * queue in suspended state. + */ + queue->window = QMGR_QUEUE_STAT_SUSPENDED; + event_request_timer(qmgr_queue_resume, (void *) queue, delay); +} + +/* qmgr_queue_unthrottle_wrapper - in case (char *) != (struct *) */ + +static void qmgr_queue_unthrottle_wrapper(int unused_event, void *context) +{ + QMGR_QUEUE *queue = (QMGR_QUEUE *) context; + + /* + * This routine runs when a wakeup timer goes off; it does not run in the + * context of some queue manipulation. Therefore, it is safe to discard + * this in-core queue when it is empty and when this site is not dead. + */ + qmgr_queue_unthrottle(queue); + if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0) + qmgr_queue_done(queue); +} + +/* qmgr_queue_unthrottle - give this destination another chance */ + +void qmgr_queue_unthrottle(QMGR_QUEUE *queue) +{ + const char *myname = "qmgr_queue_unthrottle"; + QMGR_TRANSPORT *transport = queue->transport; + double feedback; + + if (msg_verbose) + msg_info("%s: queue %s", myname, queue->name); + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_THROTTLED(queue) && !QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + + /* + * Don't restart the negative feedback hysteresis cycle with every + * positive feedback. Restart it only when we make a positive concurrency + * adjustment (i.e. at the end of a positive feedback hysteresis cycle). + * Otherwise negative feedback would be too aggressive: negative feedback + * takes effect immediately at the start of its hysteresis cycle. + */ + queue->fail_cohorts = 0; + + /* + * Special case when this site was dead. + */ + if (QMGR_QUEUE_THROTTLED(queue)) { + event_cancel_timer(qmgr_queue_unthrottle_wrapper, (void *) queue); + if (queue->dsn == 0) + msg_panic("%s: queue %s: window 0 status 0", myname, queue->name); + dsn_free(queue->dsn); + queue->dsn = 0; + /* Back from the almost grave, best concurrency is anyone's guess. */ + if (queue->busy_refcount > 0) + queue->window = queue->busy_refcount; + else + queue->window = transport->init_dest_concurrency; + queue->success = queue->failure = 0; + QMGR_LOG_WINDOW(queue); + return; + } + + /* + * Increase the destination's concurrency limit until we reach the + * transport's concurrency limit. Allow for a margin the size of the + * initial destination concurrency, so that we're not too gentle. + * + * Why is the concurrency increment based on preferred concurrency and not + * on the number of outstanding delivery requests? The latter fluctuates + * wildly when deliveries complete in bursts (artificial benchmark + * measurements), and does not account for cached connections. + * + * Keep the window within reasonable distance from actual concurrency + * otherwise negative feedback will be ineffective. This expression + * assumes that busy_refcount changes gradually. This is invalid when + * deliveries complete in bursts (artificial benchmark measurements). + */ + if (transport->dest_concurrency_limit == 0 + || transport->dest_concurrency_limit > queue->window) + if (queue->window < queue->busy_refcount + transport->init_dest_concurrency) { + feedback = QMGR_FEEDBACK_VAL(transport->pos_feedback, queue->window); + QMGR_LOG_FEEDBACK(feedback); + queue->success += feedback; + /* Prepare for overshoot (feedback > hysteresis, rounding error). */ + while (queue->success + feedback / 2 >= transport->pos_feedback.hysteresis) { + queue->window += transport->pos_feedback.hysteresis; + queue->success -= transport->pos_feedback.hysteresis; + queue->failure = 0; + } + /* Prepare for overshoot. */ + if (transport->dest_concurrency_limit > 0 + && queue->window > transport->dest_concurrency_limit) + queue->window = transport->dest_concurrency_limit; + } + QMGR_LOG_WINDOW(queue); +} + +/* qmgr_queue_throttle - handle destination delivery failure */ + +void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) +{ + const char *myname = "qmgr_queue_throttle"; + QMGR_TRANSPORT *transport = queue->transport; + double feedback; + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + if (queue->dsn) + msg_panic("%s: queue %s: spurious reason %s", + myname, queue->name, queue->dsn->reason); + if (msg_verbose) + msg_info("%s: queue %s: %s %s", + myname, queue->name, dsn->status, dsn->reason); + + /* + * Don't restart the positive feedback hysteresis cycle with every + * negative feedback. Restart it only when we make a negative concurrency + * adjustment (i.e. at the start of a negative feedback hysteresis + * cycle). Otherwise positive feedback would be too weak (positive + * feedback does not take effect until the end of its hysteresis cycle). + */ + + /* + * This queue is declared dead after a configurable number of + * pseudo-cohort failures. + */ + if (QMGR_QUEUE_READY(queue)) { + queue->fail_cohorts += 1.0 / queue->window; + if (transport->fail_cohort_limit > 0 + && queue->fail_cohorts >= transport->fail_cohort_limit) + queue->window = QMGR_QUEUE_STAT_THROTTLED; + } + + /* + * Decrease the destination's concurrency limit until we reach 1. Base + * adjustments on the concurrency limit itself, instead of using the + * actual concurrency. The latter fluctuates wildly when deliveries + * complete in bursts (artificial benchmark measurements). + * + * Even after reaching 1, we maintain the negative hysteresis cycle so that + * negative feedback can cancel out positive feedback. + */ + if (QMGR_QUEUE_READY(queue)) { + feedback = QMGR_FEEDBACK_VAL(transport->neg_feedback, queue->window); + QMGR_LOG_FEEDBACK(feedback); + queue->failure -= feedback; + /* Prepare for overshoot (feedback > hysteresis, rounding error). */ + while (queue->failure - feedback / 2 < 0) { + queue->window -= transport->neg_feedback.hysteresis; + queue->success = 0; + queue->failure += transport->neg_feedback.hysteresis; + } + /* Prepare for overshoot. */ + if (queue->window < 1) + queue->window = 1; + } + + /* + * Special case for a site that just was declared dead. + */ + if (QMGR_QUEUE_THROTTLED(queue)) { + queue->dsn = DSN_COPY(dsn); + event_request_timer(qmgr_queue_unthrottle_wrapper, + (void *) queue, var_min_backoff_time); + queue->dflags = 0; + } + QMGR_LOG_WINDOW(queue); +} + +/* qmgr_queue_select - select in-core queue for delivery */ + +QMGR_QUEUE *qmgr_queue_select(QMGR_TRANSPORT *transport) +{ + QMGR_QUEUE *queue; + + /* + * If we find a suitable site, rotate the list to enforce round-robin + * selection. See similar selection code in qmgr_transport_select(). + */ + for (queue = transport->queue_list.next; queue; queue = queue->peers.next) { + if (queue->window > queue->busy_refcount && queue->todo.next != 0) { + QMGR_LIST_ROTATE(transport->queue_list, queue); + if (msg_verbose) + msg_info("qmgr_queue_select: %s", queue->name); + return (queue); + } + } + return (0); +} + +/* qmgr_queue_done - delete in-core queue for site */ + +void qmgr_queue_done(QMGR_QUEUE *queue) +{ + const char *myname = "qmgr_queue_done"; + QMGR_TRANSPORT *transport = queue->transport; + + /* + * Sanity checks. It is an error to delete an in-core queue with pending + * messages or timers. + */ + if (queue->busy_refcount != 0 || queue->todo_refcount != 0) + msg_panic("%s: refcount: %d", myname, + queue->busy_refcount + queue->todo_refcount); + if (queue->todo.next || queue->busy.next) + msg_panic("%s: queue not empty: %s", myname, queue->name); + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + if (queue->dsn) + msg_panic("%s: queue %s: spurious reason %s", + myname, queue->name, queue->dsn->reason); + + /* + * Clean up this in-core queue. + */ + QMGR_LIST_UNLINK(transport->queue_list, QMGR_QUEUE *, queue); + htable_delete(transport->queue_byname, queue->name, (void (*) (void *)) 0); + myfree(queue->name); + myfree(queue->nexthop); + qmgr_queue_count--; + myfree((void *) queue); +} + +/* qmgr_queue_create - create in-core queue for site */ + +QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *name, + const char *nexthop) +{ + QMGR_QUEUE *queue; + + /* + * If possible, choose an initial concurrency of > 1 so that one bad + * message or one bad network won't slow us down unnecessarily. + */ + + queue = (QMGR_QUEUE *) mymalloc(sizeof(QMGR_QUEUE)); + qmgr_queue_count++; + queue->dflags = 0; + queue->last_done = 0; + queue->name = mystrdup(name); + queue->nexthop = mystrdup(nexthop); + queue->todo_refcount = 0; + queue->busy_refcount = 0; + queue->transport = transport; + queue->window = transport->init_dest_concurrency; + queue->success = queue->failure = queue->fail_cohorts = 0; + QMGR_LIST_INIT(queue->todo); + QMGR_LIST_INIT(queue->busy); + queue->dsn = 0; + queue->clog_time_to_warn = 0; + QMGR_LIST_PREPEND(transport->queue_list, queue); + htable_enter(transport->queue_byname, name, (void *) queue); + return (queue); +} + +/* qmgr_queue_find - find in-core named queue */ + +QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *transport, const char *name) +{ + return ((QMGR_QUEUE *) htable_find(transport->queue_byname, name)); +} diff --git a/src/oqmgr/qmgr_scan.c b/src/oqmgr/qmgr_scan.c new file mode 100644 index 0000000..0665a23 --- /dev/null +++ b/src/oqmgr/qmgr_scan.c @@ -0,0 +1,185 @@ +/*++ +/* NAME +/* qmgr_scan 3 +/* SUMMARY +/* queue scanning +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_SCAN *qmgr_scan_create(queue_name) +/* const char *queue_name; +/* +/* char *qmgr_scan_next(scan_info) +/* QMGR_SCAN *scan_info; +/* +/* void qmgr_scan_request(scan_info, flags) +/* QMGR_SCAN *scan_info; +/* int flags; +/* DESCRIPTION +/* This module implements queue scans. A queue scan always runs +/* to completion, so that all files get a fair chance. The caller +/* can request that a queue scan be restarted once it completes. +/* +/* qmgr_scan_create() creates a context for scanning the named queue, +/* but does not start a queue scan. +/* +/* qmgr_scan_next() returns the base name of the next queue file. +/* A null pointer means that no file was found. qmgr_scan_next() +/* automagically restarts a queue scan when a scan request had +/* arrived while the scan was in progress. +/* +/* qmgr_scan_request() records a request for the next queue scan. The +/* flags argument is the bit-wise OR of zero or more of the following, +/* unrecognized flags being ignored: +/* .IP QMGR_FLUSH_ONCE +/* Forget state information about dead hosts or transports. +/* This request takes effect immediately. +/* .IP QMGR_FLUSH_DFXP +/* Override the defer_transports setting. This takes effect +/* immediately when a queue scan is in progress, and affects +/* the next queue scan. +/* .IP QMGR_SCAN_ALL +/* Ignore queue file time stamps. This takes effect immediately +/* when a queue scan is in progress, and affects the next queue +/* scan. +/* .IP QMGR_SCAN_START +/* Start a queue scan when none is in progress, or restart the +/* current scan upon completion. +/* DIAGNOSTICS +/* Fatal: out of memory. +/* Panic: interface violations, internal consistency errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_scan_start - start queue scan */ + +static void qmgr_scan_start(QMGR_SCAN *scan_info) +{ + const char *myname = "qmgr_scan_start"; + + /* + * Sanity check. + */ + if (scan_info->handle) + msg_panic("%s: %s queue scan in progress", + myname, scan_info->queue); + + /* + * Give the poor tester a clue. + */ + if (msg_verbose) + msg_info("%s: %sstart %s queue scan", + myname, + scan_info->nflags & QMGR_SCAN_START ? "re" : "", + scan_info->queue); + + /* + * Start or restart the scan. + */ + scan_info->flags = scan_info->nflags; + scan_info->nflags = 0; + scan_info->handle = scan_dir_open(scan_info->queue); +} + +/* qmgr_scan_request - request for future scan */ + +void qmgr_scan_request(QMGR_SCAN *scan_info, int flags) +{ + + /* + * Apply "forget all dead destinations" requests immediately. Throttle + * dead transports and queues at the earliest opportunity: preferably + * during an already ongoing queue scan, otherwise the throttling will + * have to wait until a "start scan" trigger arrives. + * + * The QMGR_FLUSH_ONCE request always comes with QMGR_FLUSH_DFXP, and + * sometimes it also comes with QMGR_SCAN_ALL. It becomes a completely + * different story when a flush request is encoded in file permissions. + */ + if (flags & QMGR_FLUSH_ONCE) + qmgr_enable_all(); + + /* + * Apply "ignore time stamp" requests also towards the scan that is + * already in progress. + */ + if (scan_info->handle != 0 && (flags & QMGR_SCAN_ALL)) + scan_info->flags |= QMGR_SCAN_ALL; + + /* + * Apply "override defer_transports" requests also towards the scan that + * is already in progress. + */ + if (scan_info->handle != 0 && (flags & QMGR_FLUSH_DFXP)) + scan_info->flags |= QMGR_FLUSH_DFXP; + + /* + * If a scan is in progress, just record the request. + */ + scan_info->nflags |= flags; + if (scan_info->handle == 0 && (flags & QMGR_SCAN_START) != 0) { + scan_info->nflags &= ~QMGR_SCAN_START; + qmgr_scan_start(scan_info); + } +} + +/* qmgr_scan_next - look for next queue file */ + +char *qmgr_scan_next(QMGR_SCAN *scan_info) +{ + char *path = 0; + + /* + * Restart the scan if we reach the end and a queue scan request has + * arrived in the mean time. + */ + if (scan_info->handle && (path = mail_scan_dir_next(scan_info->handle)) == 0) { + scan_info->handle = scan_dir_close(scan_info->handle); + if (msg_verbose && (scan_info->nflags & QMGR_SCAN_START) == 0) + msg_info("done %s queue scan", scan_info->queue); + } + if (!scan_info->handle && (scan_info->nflags & QMGR_SCAN_START)) { + qmgr_scan_start(scan_info); + path = mail_scan_dir_next(scan_info->handle); + } + return (path); +} + +/* qmgr_scan_create - create queue scan context */ + +QMGR_SCAN *qmgr_scan_create(const char *queue) +{ + QMGR_SCAN *scan_info; + + scan_info = (QMGR_SCAN *) mymalloc(sizeof(*scan_info)); + scan_info->queue = mystrdup(queue); + scan_info->flags = scan_info->nflags = 0; + scan_info->handle = 0; + return (scan_info); +} diff --git a/src/oqmgr/qmgr_transport.c b/src/oqmgr/qmgr_transport.c new file mode 100644 index 0000000..ed780db --- /dev/null +++ b/src/oqmgr/qmgr_transport.c @@ -0,0 +1,472 @@ +/*++ +/* NAME +/* qmgr_transport 3 +/* SUMMARY +/* per-transport data structures +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_TRANSPORT *qmgr_transport_create(name) +/* const char *name; +/* +/* QMGR_TRANSPORT *qmgr_transport_find(name) +/* const char *name; +/* +/* QMGR_TRANSPORT *qmgr_transport_select() +/* +/* void qmgr_transport_alloc(transport, notify) +/* QMGR_TRANSPORT *transport; +/* void (*notify)(QMGR_TRANSPORT *transport, VSTREAM *fp); +/* +/* void qmgr_transport_throttle(transport, dsn) +/* QMGR_TRANSPORT *transport; +/* DSN *dsn; +/* +/* void qmgr_transport_unthrottle(transport) +/* QMGR_TRANSPORT *transport; +/* DESCRIPTION +/* This module organizes the world by message transport type. +/* Each transport can have zero or more destination queues +/* associated with it. +/* +/* qmgr_transport_create() instantiates a data structure for the +/* named transport type. +/* +/* qmgr_transport_find() looks up an existing message transport +/* data structure. +/* +/* qmgr_transport_select() attempts to find a transport that +/* has messages pending delivery. This routine implements +/* round-robin search among transports. +/* +/* qmgr_transport_alloc() allocates a delivery process for the +/* specified transport type. Allocation is performed asynchronously. +/* When a process becomes available, the application callback routine +/* is invoked with as arguments the transport and a stream that +/* is connected to a delivery process. It is an error to call +/* qmgr_transport_alloc() while delivery process allocation for +/* the same transport is in progress. +/* +/* qmgr_transport_throttle blocks further allocation of delivery +/* processes for the named transport. Attempts to throttle a +/* throttled transport are ignored. +/* +/* qmgr_transport_unthrottle() undoes qmgr_transport_throttle(). +/* Attempts to unthrottle a non-throttled transport are ignored. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +#include /* FD_SETSIZE */ +#include /* FD_SETSIZE */ +#include /* FD_SETSIZE */ + +#ifdef USE_SYS_SELECT_H +#include /* FD_SETSIZE */ +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +HTABLE *qmgr_transport_byname; /* transport by name */ +QMGR_TRANSPORT_LIST qmgr_transport_list;/* transports, round robin */ + + /* + * A local structure to remember a delivery process allocation request. + */ +typedef struct QMGR_TRANSPORT_ALLOC QMGR_TRANSPORT_ALLOC; + +struct QMGR_TRANSPORT_ALLOC { + QMGR_TRANSPORT *transport; /* transport context */ + VSTREAM *stream; /* delivery service stream */ + QMGR_TRANSPORT_ALLOC_NOTIFY notify; /* application call-back routine */ +}; + + /* + * Connections to delivery agents are managed asynchronously. Each delivery + * agent connection goes through multiple wait states: + * + * - With Linux/Solaris and old queue manager implementations only, wait for + * the server to invoke accept(). + * + * - Wait for the delivery agent's announcement that it is ready to receive a + * delivery request. + * + * - Wait for the delivery request completion status. + * + * Older queue manager implementations had only one pending delivery agent + * connection per transport. With low-latency destinations, the output rates + * were reduced on Linux/Solaris systems that had the extra wait state. + * + * To maximize delivery agent output rates with low-latency destinations, the + * following changes were made to the queue manager by the end of the 2.4 + * development cycle: + * + * - The Linux/Solaris accept() wait state was eliminated. + * + * - A pipeline was implemented for pending delivery agent connections. The + * number of pending delivery agent connections was increased from one to + * two: the number of before-delivery wait states, plus one extra pipeline + * slot to prevent the pipeline from stalling easily. Increasing the + * pipeline much further actually hurt performance. + * + * - To reduce queue manager disk competition with delivery agents, the queue + * scanning algorithm was modified to import only one message per interrupt. + * The incoming and deferred queue scans now happen on alternate interrupts. + * + * Simplistically reasoned, a non-zero (incoming + active) queue length is + * equivalent to a time shift for mail deliveries; this is undesirable when + * delivery agents are not fully utilized. + * + * On the other hand a non-empty active queue is what allows us to do clever + * things such as queue file prefetch, concurrency windows, and connection + * caching; the idea is that such "thinking time" is affordable only after + * the output channels are maxed out. + */ +#ifndef QMGR_TRANSPORT_MAX_PEND +#define QMGR_TRANSPORT_MAX_PEND 2 +#endif + + /* + * Important note on the _transport_rate_delay implementation: after + * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all + * code paths must directly or indirectly invoke qmgr_transport_unthrottle() + * or qmgr_transport_throttle(). Otherwise, transports with non-zero + * _transport_rate_delay will become stuck. + */ + +/* qmgr_transport_unthrottle_wrapper - in case (char *) != (struct *) */ + +static void qmgr_transport_unthrottle_wrapper(int unused_event, void *context) +{ + qmgr_transport_unthrottle((QMGR_TRANSPORT *) context); +} + +/* qmgr_transport_unthrottle - open the throttle */ + +void qmgr_transport_unthrottle(QMGR_TRANSPORT *transport) +{ + const char *myname = "qmgr_transport_unthrottle"; + + /* + * This routine runs after expiration of the timer set by + * qmgr_transport_throttle(), or whenever a delivery transport has been + * used without malfunction. In either case, we enable delivery again if + * the transport was throttled. We always reset the transport rate lock. + */ + if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0) { + if (msg_verbose) + msg_info("%s: transport %s", myname, transport->name); + transport->flags &= ~QMGR_TRANSPORT_STAT_DEAD; + if (transport->dsn == 0) + msg_panic("%s: transport %s: null reason", + myname, transport->name); + dsn_free(transport->dsn); + transport->dsn = 0; + event_cancel_timer(qmgr_transport_unthrottle_wrapper, + (void *) transport); + } + if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) + transport->flags &= ~QMGR_TRANSPORT_STAT_RATE_LOCK; +} + +/* qmgr_transport_throttle - disable delivery process allocation */ + +void qmgr_transport_throttle(QMGR_TRANSPORT *transport, DSN *dsn) +{ + const char *myname = "qmgr_transport_throttle"; + + /* + * We are unable to connect to a deliver process for this type of message + * transport. Instead of hosing the system by retrying in a tight loop, + * back off and disable this transport type for a while. + */ + if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) == 0) { + if (msg_verbose) + msg_info("%s: transport %s: status: %s reason: %s", + myname, transport->name, dsn->status, dsn->reason); + transport->flags |= QMGR_TRANSPORT_STAT_DEAD; + if (transport->dsn) + msg_panic("%s: transport %s: spurious reason: %s", + myname, transport->name, transport->dsn->reason); + transport->dsn = DSN_COPY(dsn); + event_request_timer(qmgr_transport_unthrottle_wrapper, + (void *) transport, var_transport_retry_time); + } +} + +/* qmgr_transport_abort - transport connect watchdog */ + +static void qmgr_transport_abort(int unused_event, void *context) +{ + QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context; + + msg_fatal("timeout connecting to transport: %s", alloc->transport->name); +} + +/* qmgr_transport_rate_event - delivery process availability notice */ + +static void qmgr_transport_rate_event(int unused_event, void *context) +{ + QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context; + + alloc->notify(alloc->transport, alloc->stream); + myfree((void *) alloc); +} + +/* qmgr_transport_event - delivery process availability notice */ + +static void qmgr_transport_event(int unused_event, void *context) +{ + QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context; + + /* + * This routine notifies the application when the request given to + * qmgr_transport_alloc() completes. + */ + if (msg_verbose) + msg_info("transport_event: %s", alloc->transport->name); + + /* + * Connection request completed. Stop the watchdog timer. + */ + event_cancel_timer(qmgr_transport_abort, context); + + /* + * Disable further read events that end up calling this function, and + * free up this pending connection pipeline slot. + */ + if (alloc->stream) { + event_disable_readwrite(vstream_fileno(alloc->stream)); + non_blocking(vstream_fileno(alloc->stream), BLOCKING); + } + alloc->transport->pending -= 1; + + /* + * Notify the requestor. + */ + if (alloc->transport->xport_rate_delay > 0) { + if ((alloc->transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) == 0) + msg_panic("transport_event: missing rate lock for transport %s", + alloc->transport->name); + event_request_timer(qmgr_transport_rate_event, (void *) alloc, + alloc->transport->xport_rate_delay); + } else { + alloc->notify(alloc->transport, alloc->stream); + myfree((void *) alloc); + } +} + +/* qmgr_transport_select - select transport for allocation */ + +QMGR_TRANSPORT *qmgr_transport_select(void) +{ + QMGR_TRANSPORT *xport; + QMGR_QUEUE *queue; + int need; + + /* + * If we find a suitable transport, rotate the list of transports to + * effectuate round-robin selection. See similar selection code in + * qmgr_queue_select(). + * + * This function is called repeatedly until all transports have maxed out + * the number of pending delivery agent connections, until all delivery + * agent concurrency windows are maxed out, or until we run out of "todo" + * queue entries. + */ +#define MIN5af51743e4eef(x, y) ((x) < (y) ? (x) : (y)) + + for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next) { + if ((xport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0 + || (xport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) != 0 + || xport->pending >= QMGR_TRANSPORT_MAX_PEND) + continue; + need = xport->pending + 1; + for (queue = xport->queue_list.next; queue; queue = queue->peers.next) { + if (QMGR_QUEUE_READY(queue) == 0) + continue; + if ((need -= MIN5af51743e4eef(queue->window - queue->busy_refcount, + queue->todo_refcount)) <= 0) { + QMGR_LIST_ROTATE(qmgr_transport_list, xport); + if (msg_verbose) + msg_info("qmgr_transport_select: %s", xport->name); + return (xport); + } + } + } + return (0); +} + +/* qmgr_transport_alloc - allocate delivery process */ + +void qmgr_transport_alloc(QMGR_TRANSPORT *transport, QMGR_TRANSPORT_ALLOC_NOTIFY notify) +{ + QMGR_TRANSPORT_ALLOC *alloc; + + /* + * Sanity checks. + */ + if (transport->flags & QMGR_TRANSPORT_STAT_DEAD) + msg_panic("qmgr_transport: dead transport: %s", transport->name); + if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) + msg_panic("qmgr_transport: rate-locked transport: %s", transport->name); + if (transport->pending >= QMGR_TRANSPORT_MAX_PEND) + msg_panic("qmgr_transport: excess allocation: %s", transport->name); + + /* + * When this message delivery transport is rate-limited, do not select it + * again before the end of a message delivery transaction. + */ + if (transport->xport_rate_delay > 0) + transport->flags |= QMGR_TRANSPORT_STAT_RATE_LOCK; + + /* + * Connect to the well-known port for this delivery service, and wake up + * when a process announces its availability. Allow only a limited number + * of delivery process allocation attempts for this transport. In case of + * problems, back off. Do not hose the system when it is in trouble + * already. + * + * Use non-blocking connect(), so that Linux won't block the queue manager + * until the delivery agent calls accept(). + * + * When the connection to delivery agent cannot be completed, notify the + * event handler so that it can throttle the transport and defer the todo + * queues, just like it does when communication fails *after* connection + * completion. + * + * Before Postfix 2.4, the event handler was not invoked after connect() + * error, and mail was not deferred. Because of this, mail would be stuck + * in the active queue after triggering a "connection refused" condition. + */ + alloc = (QMGR_TRANSPORT_ALLOC *) mymalloc(sizeof(*alloc)); + alloc->transport = transport; + alloc->notify = notify; + transport->pending += 1; + if ((alloc->stream = mail_connect(MAIL_CLASS_PRIVATE, transport->name, + NON_BLOCKING)) == 0) { + msg_warn("connect to transport %s/%s: %m", + MAIL_CLASS_PRIVATE, transport->name); + event_request_timer(qmgr_transport_event, (void *) alloc, 0); + return; + } +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) && defined(CA_VSTREAM_CTL_DUPFD) +#ifndef THRESHOLD_FD_WORKAROUND +#define THRESHOLD_FD_WORKAROUND 128 +#endif + vstream_control(alloc->stream, + CA_VSTREAM_CTL_DUPFD(THRESHOLD_FD_WORKAROUND), + CA_VSTREAM_CTL_END); +#endif + event_enable_read(vstream_fileno(alloc->stream), qmgr_transport_event, + (void *) alloc); + + /* + * Guard against broken systems. + */ + event_request_timer(qmgr_transport_abort, (void *) alloc, + var_daemon_timeout); +} + +/* qmgr_transport_create - create transport instance */ + +QMGR_TRANSPORT *qmgr_transport_create(const char *name) +{ + QMGR_TRANSPORT *transport; + + if (htable_find(qmgr_transport_byname, name) != 0) + msg_panic("qmgr_transport_create: transport exists: %s", name); + transport = (QMGR_TRANSPORT *) mymalloc(sizeof(QMGR_TRANSPORT)); + transport->flags = 0; + transport->pending = 0; + transport->name = mystrdup(name); + + /* + * Use global configuration settings or transport-specific settings. + */ + transport->dest_concurrency_limit = + get_mail_conf_int2(name, _DEST_CON_LIMIT, + var_dest_con_limit, 0, 0); + transport->recipient_limit = + get_mail_conf_int2(name, _DEST_RCPT_LIMIT, + var_dest_rcpt_limit, 0, 0); + transport->init_dest_concurrency = + get_mail_conf_int2(name, _INIT_DEST_CON, + var_init_dest_concurrency, 1, 0); + transport->xport_rate_delay = get_mail_conf_time2(name, _XPORT_RATE_DELAY, + var_xport_rate_delay, + 's', 0, 0); + transport->rate_delay = get_mail_conf_time2(name, _DEST_RATE_DELAY, + var_dest_rate_delay, + 's', 0, 0); + + if (transport->rate_delay > 0) + transport->dest_concurrency_limit = 1; + if (transport->dest_concurrency_limit != 0 + && transport->dest_concurrency_limit < transport->init_dest_concurrency) + transport->init_dest_concurrency = transport->dest_concurrency_limit; + + transport->queue_byname = htable_create(0); + QMGR_LIST_INIT(transport->queue_list); + transport->dsn = 0; + qmgr_feedback_init(&transport->pos_feedback, name, _CONC_POS_FDBACK, + VAR_CONC_POS_FDBACK, var_conc_pos_feedback); + qmgr_feedback_init(&transport->neg_feedback, name, _CONC_NEG_FDBACK, + VAR_CONC_NEG_FDBACK, var_conc_neg_feedback); + transport->fail_cohort_limit = + get_mail_conf_int2(name, _CONC_COHORT_LIM, + var_conc_cohort_limit, 0, 0); + if (qmgr_transport_byname == 0) + qmgr_transport_byname = htable_create(10); + htable_enter(qmgr_transport_byname, name, (void *) transport); + QMGR_LIST_APPEND(qmgr_transport_list, transport); + if (msg_verbose) + msg_info("qmgr_transport_create: %s concurrency %d recipients %d", + transport->name, transport->dest_concurrency_limit, + transport->recipient_limit); + return (transport); +} + +/* qmgr_transport_find - find transport instance */ + +QMGR_TRANSPORT *qmgr_transport_find(const char *name) +{ + return ((QMGR_TRANSPORT *) htable_find(qmgr_transport_byname, name)); +} diff --git a/src/pickup/.indent.pro b/src/pickup/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/pickup/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/pickup/.printfck b/src/pickup/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/pickup/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/pickup/Makefile.in b/src/pickup/Makefile.in new file mode 100644 index 0000000..9c8766f --- /dev/null +++ b/src/pickup/Makefile.in @@ -0,0 +1,94 @@ +SHELL = /bin/sh +SRCS = pickup.c +OBJS = pickup.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = pickup +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +pickup.o: ../../include/attr.h +pickup.o: ../../include/check_arg.h +pickup.o: ../../include/cleanup_user.h +pickup.o: ../../include/htable.h +pickup.o: ../../include/info_log_addr_form.h +pickup.o: ../../include/input_transp.h +pickup.o: ../../include/iostuff.h +pickup.o: ../../include/lex_822.h +pickup.o: ../../include/mail_conf.h +pickup.o: ../../include/mail_date.h +pickup.o: ../../include/mail_open_ok.h +pickup.o: ../../include/mail_params.h +pickup.o: ../../include/mail_proto.h +pickup.o: ../../include/mail_queue.h +pickup.o: ../../include/mail_server.h +pickup.o: ../../include/mail_version.h +pickup.o: ../../include/msg.h +pickup.o: ../../include/mymalloc.h +pickup.o: ../../include/nvtable.h +pickup.o: ../../include/rec_attr_map.h +pickup.o: ../../include/rec_type.h +pickup.o: ../../include/record.h +pickup.o: ../../include/safe_open.h +pickup.o: ../../include/scan_dir.h +pickup.o: ../../include/set_ugid.h +pickup.o: ../../include/smtputf8.h +pickup.o: ../../include/stringops.h +pickup.o: ../../include/sys_defs.h +pickup.o: ../../include/vbuf.h +pickup.o: ../../include/vstream.h +pickup.o: ../../include/vstring.h +pickup.o: ../../include/watchdog.h +pickup.o: pickup.c diff --git a/src/pickup/pickup.c b/src/pickup/pickup.c new file mode 100644 index 0000000..4a77a47 --- /dev/null +++ b/src/pickup/pickup.c @@ -0,0 +1,631 @@ +/*++ +/* NAME +/* pickup 8 +/* SUMMARY +/* Postfix local mail pickup +/* SYNOPSIS +/* \fBpickup\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBpickup\fR(8) daemon waits for hints that new mail has been +/* dropped into the \fBmaildrop\fR directory, and feeds it into the +/* \fBcleanup\fR(8) daemon. +/* Ill-formatted files are deleted without notifying the originator. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* STANDARDS +/* .ad +/* .fi +/* None. The \fBpickup\fR(8) daemon does not interact with +/* the outside world. +/* SECURITY +/* .ad +/* .fi +/* The \fBpickup\fR(8) daemon is moderately security sensitive. It runs +/* with fixed low privilege and can run in a chrooted environment. +/* However, the program reads files from potentially hostile users. +/* The \fBpickup\fR(8) daemon opens no files for writing, is careful about +/* what files it opens for reading, and does not actually touch any data +/* that is sent to its public service endpoint. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* The \fBpickup\fR(8) daemon copies mail from file to the \fBcleanup\fR(8) +/* daemon. It could avoid message copying overhead by sending a file +/* descriptor instead of file data, but then the already complex +/* \fBcleanup\fR(8) daemon would have to deal with unfiltered user data. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* As the \fBpickup\fR(8) daemon is a relatively long-running process, up +/* to an hour may pass before a \fBmain.cf\fR change takes effect. +/* Use the command "\fBpostfix reload\fR" command to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* .IP "\fBcontent_filter (empty)\fR" +/* After the message is queued, send the entire message to the +/* specified \fItransport:destination\fR. +/* .IP "\fBreceive_override_options (empty)\fR" +/* Enable or disable recipient validation, built-in content +/* filtering, or address mapping. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBline_length_limit (2048)\fR" +/* Upon input, long lines are chopped up into pieces of at most +/* this length; upon delivery, long lines are reconstructed. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.5 and later: +/* .IP "\fBinfo_log_address_format (external)\fR" +/* The email address form that will be used in non-debug logging +/* (info, warning, etc.). +/* SEE ALSO +/* cleanup(8), message canonicalization +/* sendmail(1), Sendmail-compatible interface +/* postdrop(1), mail posting agent +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single-threaded server skeleton. */ + +#include + +/* Application-specific. */ + +char *var_filter_xport; +char *var_input_transp; + + /* + * Structure to bundle a bunch of information about a queue file. + */ +typedef struct { + char *id; /* queue file basename */ + struct stat st; /* queue file status */ + char *path; /* name for open/remove */ + char *sender; /* sender address */ +} PICKUP_INFO; + + /* + * What action should be taken after attempting to deliver a message: remove + * the file from the maildrop, or leave it alone. The latter is also used + * for files that are still being written to. + */ +#define REMOVE_MESSAGE_FILE 1 +#define KEEP_MESSAGE_FILE 2 + + /* + * Transparency: before mail is queued, do we allow address mapping, + * automatic bcc, header/body checks? + */ +int pickup_input_transp_mask; + +/* file_read_error - handle error while reading queue file */ + +static int file_read_error(PICKUP_INFO *info, int type) +{ + msg_warn("uid=%ld: unexpected or malformed record type %d", + (long) info->st.st_uid, type); + return (REMOVE_MESSAGE_FILE); +} + +/* cleanup_service_error_reason - handle error writing to cleanup service. */ + +static int cleanup_service_error_reason(PICKUP_INFO *info, int status, + const char *reason) +{ + + /* + * XXX If the cleanup server gave a reason, then it was already logged. + * Don't bother logging it another time. + * + * XXX Discard a message without recipient. This can happen with "postsuper + * -r" when a message is already delivered (or bounced). The Postfix + * sendmail command rejects submissions without recipients. + */ + if (reason == 0 || *reason == 0) + msg_warn("%s: error writing %s: %s", + info->path, info->id, cleanup_strerror(status)); + return ((status & (CLEANUP_STAT_BAD | CLEANUP_STAT_RCPT)) ? + REMOVE_MESSAGE_FILE : KEEP_MESSAGE_FILE); +} + +#define cleanup_service_error(info, status) \ + cleanup_service_error_reason((info), (status), (char *) 0) + +/* copy_segment - copy a record group */ + +static int copy_segment(VSTREAM *qfile, VSTREAM *cleanup, PICKUP_INFO *info, + VSTRING *buf, char *expected) +{ + int type; + int check_first = (*expected == REC_TYPE_CONTENT[0]); + int time_seen = 0; + char *attr_name; + char *attr_value; + char *saved_attr; + int skip_attr; + + /* + * Limit the input record size. All front-end programs should protect the + * mail system against unreasonable inputs. This also requires that we + * limit the size of envelope records written by the local posting agent. + * + * Records with named attributes are filtered by postdrop(1). + * + * We must allow PTR records here because of "postsuper -r". + */ + for (;;) { + if ((type = rec_get(qfile, buf, var_line_limit)) < 0 + || strchr(expected, type) == 0) + return (file_read_error(info, type)); + if (msg_verbose) + msg_info("%s: read %c %s", info->id, type, vstring_str(buf)); + if (type == *expected) + break; + if (type == REC_TYPE_FROM) { + if (info->sender == 0) + info->sender = mystrdup(vstring_str(buf)); + /* Compatibility with Postfix < 2.3. */ + if (time_seen == 0) + rec_fprintf(cleanup, REC_TYPE_TIME, "%ld", + (long) info->st.st_mtime); + } + if (type == REC_TYPE_TIME) + time_seen = 1; + + /* + * XXX Workaround: REC_TYPE_FILT (used in envelopes) == REC_TYPE_CONT + * (used in message content). + * + * As documented in postsuper(1), ignore content filter record. + */ + if (*expected != REC_TYPE_CONTENT[0]) { + if (type == REC_TYPE_FILT) + /* Discard FILTER record after "postsuper -r". */ + continue; + if (type == REC_TYPE_RDR) + /* Discard REDIRECT record after "postsuper -r". */ + continue; + } + if (*expected == REC_TYPE_EXTRACT[0]) { + if (type == REC_TYPE_RRTO) + /* Discard return-receipt record after "postsuper -r". */ + continue; + if (type == REC_TYPE_ERTO) + /* Discard errors-to record after "postsuper -r". */ + continue; + if (type == REC_TYPE_ATTR) { + saved_attr = mystrdup(vstring_str(buf)); + skip_attr = (split_nameval(saved_attr, + &attr_name, &attr_value) == 0 + && rec_attr_map(attr_name) == 0); + myfree(saved_attr); + /* Discard other/header/body action after "postsuper -r". */ + if (skip_attr) + continue; + } + } + + /* + * XXX Force an empty record when the queue file content begins with + * whitespace, so that it won't be considered as being part of our + * own Received: header. What an ugly Kluge. + */ + if (check_first + && (type == REC_TYPE_NORM || type == REC_TYPE_CONT)) { + check_first = 0; + if (VSTRING_LEN(buf) > 0 && IS_SPACE_TAB(vstring_str(buf)[0])) + rec_put(cleanup, REC_TYPE_NORM, "", 0); + } + if ((REC_PUT_BUF(cleanup, type, buf)) < 0) + return (cleanup_service_error(info, CLEANUP_STAT_WRITE)); + } + return (0); +} + +/* pickup_copy - copy message to cleanup service */ + +static int pickup_copy(VSTREAM *qfile, VSTREAM *cleanup, + PICKUP_INFO *info, VSTRING *buf) +{ + time_t now = time((time_t *) 0); + int status; + char *name; + + /* + * Protect against time-warped time stamps. Warn about mail that has been + * queued for an excessive amount of time. Allow for some time drift with + * network clients that mount the maildrop remotely - especially clients + * that can't get their daylight savings offsets right. + */ +#define DAY_SECONDS 86400 +#define HOUR_SECONDS 3600 + + if (info->st.st_mtime > now + 2 * HOUR_SECONDS) { + msg_warn("%s: message dated %ld seconds into the future", + info->id, (long) (info->st.st_mtime - now)); + info->st.st_mtime = now; + } else if (info->st.st_mtime < now - DAY_SECONDS) { + msg_warn("%s: message has been queued for %d days", + info->id, (int) ((now - info->st.st_mtime) / DAY_SECONDS)); + } + + /* + * Add content inspection transport. See also postsuper(1). + */ + if (*var_filter_xport) + rec_fprintf(cleanup, REC_TYPE_FILT, "%s", var_filter_xport); + + /* + * Copy the message envelope segment. Allow only those records that we + * expect to see in the envelope section. The envelope segment must + * contain an envelope sender address. + */ + if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_ENVELOPE)) != 0) + return (status); + if (info->sender == 0) { + msg_warn("%s: uid=%ld: no envelope sender", + info->id, (long) info->st.st_uid); + return (REMOVE_MESSAGE_FILE); + } + + /* + * For messages belonging to $mail_owner also log the maildrop queue id. + * This supports message tracking for mail requeued via "postsuper -r". + */ +#define MAIL_IS_REQUEUED(info) \ + ((info)->st.st_uid == var_owner_uid && ((info)->st.st_mode & S_IROTH) == 0) + + if (MAIL_IS_REQUEUED(info)) { + msg_info("%s: uid=%d from=<%s> orig_id=%s", info->id, + (int) info->st.st_uid, info_log_addr_form_sender(info->sender), + ((name = strrchr(info->path, '/')) != 0 ? + name + 1 : info->path)); + } else { + msg_info("%s: uid=%d from=<%s>", info->id, + (int) info->st.st_uid, info_log_addr_form_sender(info->sender)); + } + + /* + * Message content segment. Send a dummy message length. Prepend a + * Received: header to the message contents. For tracing purposes, + * include the message file ownership, without revealing the login name. + */ + rec_fputs(cleanup, REC_TYPE_MESG, ""); + rec_fprintf(cleanup, REC_TYPE_NORM, "Received: by %s (%s, from userid %ld)", + var_myhostname, var_mail_name, (long) info->st.st_uid); + rec_fprintf(cleanup, REC_TYPE_NORM, "\tid %s; %s", info->id, + mail_date(info->st.st_mtime)); + + /* + * Copy the message content segment. Allow only those records that we + * expect to see in the message content section. + */ + if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_CONTENT)) != 0) + return (status); + + /* + * Send the segment with information extracted from message headers. + * Permit a non-empty extracted segment, so that list manager software + * can to output recipients after the message, and so that sysadmins can + * re-inject messages after a change of configuration. + */ + rec_fputs(cleanup, REC_TYPE_XTRA, ""); + if ((status = copy_segment(qfile, cleanup, info, buf, REC_TYPE_EXTRACT)) != 0) + return (status); + + /* + * There are no errors. Send the end-of-data marker, and get the cleanup + * service completion status. XXX Since the pickup service is unable to + * bounce, the cleanup service can report only soft errors here. + */ + rec_fputs(cleanup, REC_TYPE_END, ""); + if (attr_scan(cleanup, ATTR_FLAG_MISSING, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + RECV_ATTR_STR(MAIL_ATTR_WHY, buf), + ATTR_TYPE_END) != 2) + return (cleanup_service_error(info, CLEANUP_STAT_WRITE)); + + /* + * Depending on the cleanup service completion status, delete the message + * file, or try again later. Bounces are dealt with by the cleanup + * service itself. The master process wakes up the cleanup service every + * now and then. + */ + if (status) { + return (cleanup_service_error_reason(info, status, vstring_str(buf))); + } else { + return (REMOVE_MESSAGE_FILE); + } +} + +/* pickup_file - initialize for file copy and cleanup */ + +static int pickup_file(PICKUP_INFO *info) +{ + VSTRING *buf = vstring_alloc(100); + int status; + VSTREAM *qfile; + VSTREAM *cleanup; + int cleanup_flags; + + /* + * Open the submitted file. If we cannot open it, and we're not having a + * file descriptor leak problem, delete the submitted file, so that we + * won't keep complaining about the same file again and again. XXX + * Perhaps we should save "bad" files elsewhere for further inspection. + * XXX How can we delete a file when open() fails with ENOENT? + */ + qfile = safe_open(info->path, O_RDONLY | O_NONBLOCK, 0, + (struct stat *) 0, -1, -1, buf); + if (qfile == 0) { + if (errno != ENOENT) + msg_warn("open input file %s: %s", info->path, vstring_str(buf)); + vstring_free(buf); + if (errno == EACCES) + msg_warn("if this file was created by Postfix < 1.1, then you may have to chmod a+r %s/%s", + var_queue_dir, info->path); + return (errno == EACCES ? KEEP_MESSAGE_FILE : REMOVE_MESSAGE_FILE); + } + + /* + * Contact the cleanup service and read the queue ID that it has + * allocated. In case of trouble, request that the cleanup service + * bounces its copy of the message. because the original input file is + * not readable by the bounce service. + * + * If mail is re-injected with "postsuper -r", disable Milter applications. + * If they were run before the mail was queued then there is no need to + * run them again. Moreover, the queue file does not contain enough + * information to reproduce the exact same SMTP events and Sendmail + * macros that Milters received when the mail originally arrived in + * Postfix. + * + * The actual message copying code is in a separate routine, so that it is + * easier to implement the many possible error exits without forgetting + * to close files, or to release memory. + */ + cleanup_flags = + input_transp_cleanup(CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_MASK_EXTERNAL, + pickup_input_transp_mask); + /* As documented in postsuper(1). */ + if (MAIL_IS_REQUEUED(info)) + cleanup_flags &= ~CLEANUP_FLAG_MILTER; + else + cleanup_flags |= smtputf8_autodetect(MAIL_SRC_MASK_SENDMAIL); + + cleanup = mail_connect_wait(MAIL_CLASS_PUBLIC, var_cleanup_service); + if (attr_scan(cleanup, ATTR_FLAG_STRICT, + RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP), + RECV_ATTR_STR(MAIL_ATTR_QUEUEID, buf), + ATTR_TYPE_END) != 1 + || attr_print(cleanup, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags), + ATTR_TYPE_END) != 0) { + status = KEEP_MESSAGE_FILE; + } else { + info->id = mystrdup(vstring_str(buf)); + status = pickup_copy(qfile, cleanup, info, buf); + } + vstream_fclose(qfile); + vstream_fclose(cleanup); + vstring_free(buf); + return (status); +} + +/* pickup_init - init info structure */ + +static void pickup_init(PICKUP_INFO *info) +{ + info->id = 0; + info->path = 0; + info->sender = 0; +} + +/* pickup_free - wipe info structure */ + +static void pickup_free(PICKUP_INFO *info) +{ +#define SAFE_FREE(x) { if (x) myfree(x); } + + SAFE_FREE(info->id); + SAFE_FREE(info->path); + SAFE_FREE(info->sender); +} + +/* pickup_service - service client */ + +static void pickup_service(char *unused_buf, ssize_t unused_len, + char *unused_service, char **argv) +{ + SCAN_DIR *scan; + char *queue_name; + PICKUP_INFO info; + const char *path; + char *id; + int file_count; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Skip over things that we don't want to open, such as files that are + * still being written, or garbage. Leave it up to the sysadmin to remove + * garbage. Keep scanning the queue directory until we stop removing + * files from it. + * + * When we find a file, stroke the watchdog so that it will not bark while + * some application is keeping us busy by injecting lots of mail into the + * maildrop directory. + */ + queue_name = MAIL_QUEUE_MAILDROP; /* XXX should be a list */ + do { + file_count = 0; + scan = scan_dir_open(queue_name); + while ((id = scan_dir_next(scan)) != 0) { + if (mail_open_ok(queue_name, id, &info.st, &path) == MAIL_OPEN_YES) { + pickup_init(&info); + info.path = mystrdup(path); + watchdog_pat(); + if (pickup_file(&info) == REMOVE_MESSAGE_FILE) { + if (REMOVE(info.path)) + msg_warn("remove %s: %m", info.path); + else + file_count++; + } + pickup_free(&info); + } + } + scan_dir_close(scan); + } while (file_count); +} + +/* post_jail_init - drop privileges */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * In case master.cf was not updated for unprivileged service. + */ + if (getuid() != var_owner_uid) + set_ugid(var_owner_uid, var_owner_gid); + + /* + * Initialize the receive transparency options: do we want unknown + * recipient checks, do we want address mapping. + */ + pickup_input_transp_mask = + input_transp_mask(VAR_INPUT_TRANSP, var_input_transp); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the multi-threaded server skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_STR_TABLE str_table[] = { + VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0, + VAR_INPUT_TRANSP, DEF_INPUT_TRANSP, &var_input_transp, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Use the multi-threaded skeleton, because no-one else should be + * monitoring our service socket while this process runs. + * + * XXX The default watchdog timeout for trigger servers is 1000s, while the + * cleanup server watchdog timeout is $daemon_timeout (i.e. several + * hours). We override the default 1000s timeout to avoid problems with + * slow mail submission. The real problem is of course that the + * single-threaded pickup server is not a good solution for mail + * submissions. + */ + trigger_server_main(argc, argv, pickup_service, + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_SOLITARY, + CA_MAIL_SERVER_WATCHDOG(&var_daemon_timeout), + 0); +} diff --git a/src/pipe/.indent.pro b/src/pipe/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/pipe/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/pipe/.printfck b/src/pipe/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/pipe/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/pipe/Makefile.in b/src/pipe/Makefile.in new file mode 100644 index 0000000..42159ca --- /dev/null +++ b/src/pipe/Makefile.in @@ -0,0 +1,107 @@ +SHELL = /bin/sh +SRCS = pipe.c +OBJS = pipe.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = pipe +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +pipe.o: ../../include/argv.h +pipe.o: ../../include/attr.h +pipe.o: ../../include/bounce.h +pipe.o: ../../include/canon_addr.h +pipe.o: ../../include/check_arg.h +pipe.o: ../../include/defer.h +pipe.o: ../../include/deliver_completed.h +pipe.o: ../../include/deliver_request.h +pipe.o: ../../include/delivered_hdr.h +pipe.o: ../../include/dict.h +pipe.o: ../../include/dsn.h +pipe.o: ../../include/dsn_buf.h +pipe.o: ../../include/dsn_util.h +pipe.o: ../../include/flush_clnt.h +pipe.o: ../../include/fold_addr.h +pipe.o: ../../include/htable.h +pipe.o: ../../include/iostuff.h +pipe.o: ../../include/mac_parse.h +pipe.o: ../../include/mail_addr.h +pipe.o: ../../include/mail_conf.h +pipe.o: ../../include/mail_copy.h +pipe.o: ../../include/mail_params.h +pipe.o: ../../include/mail_parm_split.h +pipe.o: ../../include/mail_server.h +pipe.o: ../../include/mail_version.h +pipe.o: ../../include/msg.h +pipe.o: ../../include/msg_stats.h +pipe.o: ../../include/myflock.h +pipe.o: ../../include/mymalloc.h +pipe.o: ../../include/nvtable.h +pipe.o: ../../include/off_cvt.h +pipe.o: ../../include/pipe_command.h +pipe.o: ../../include/quote_822_local.h +pipe.o: ../../include/quote_flags.h +pipe.o: ../../include/recipient_list.h +pipe.o: ../../include/sent.h +pipe.o: ../../include/set_eugid.h +pipe.o: ../../include/split_addr.h +pipe.o: ../../include/split_at.h +pipe.o: ../../include/stringops.h +pipe.o: ../../include/sys_defs.h +pipe.o: ../../include/sys_exits.h +pipe.o: ../../include/vbuf.h +pipe.o: ../../include/vstream.h +pipe.o: ../../include/vstring.h +pipe.o: pipe.c diff --git a/src/pipe/pipe.c b/src/pipe/pipe.c new file mode 100644 index 0000000..8a99430 --- /dev/null +++ b/src/pipe/pipe.c @@ -0,0 +1,1397 @@ +/*++ +/* NAME +/* pipe 8 +/* SUMMARY +/* Postfix delivery to external command +/* SYNOPSIS +/* \fBpipe\fR [generic Postfix daemon options] command_attributes... +/* DESCRIPTION +/* The \fBpipe\fR(8) daemon processes requests from the Postfix queue +/* manager to deliver messages to external commands. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* Message attributes such as sender address, recipient address and +/* next-hop host name can be specified as command-line macros that are +/* expanded before the external command is executed. +/* +/* The \fBpipe\fR(8) daemon updates queue files and marks recipients +/* as finished, or it informs the queue manager that delivery should +/* be tried again at a later time. Delivery status reports are sent +/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as +/* appropriate. +/* SINGLE-RECIPIENT DELIVERY +/* .ad +/* .fi +/* Some destinations cannot handle more than one recipient per +/* delivery request. Examples are pagers or fax machines. +/* In addition, multi-recipient delivery is undesirable when +/* prepending a \fBDelivered-to:\fR or \fBX-Original-To:\fR +/* message header. +/* +/* To prevent Postfix from sending multiple recipients per delivery +/* request, specify +/* .sp +/* .nf +/* \fItransport\fB_destination_recipient_limit = 1\fR +/* .fi +/* +/* in the Postfix \fBmain.cf\fR file, where \fItransport\fR +/* is the name in the first column of the Postfix \fBmaster.cf\fR +/* entry for the pipe-based delivery transport. +/* COMMAND ATTRIBUTE SYNTAX +/* .ad +/* .fi +/* The external command attributes are given in the \fBmaster.cf\fR +/* file at the end of a service definition. The syntax is as follows: +/* .IP "\fBchroot=\fIpathname\fR (optional)" +/* Change the process root directory and working directory to +/* the named directory. This happens before switching to the +/* privileges specified with the \fBuser\fR attribute, and +/* before executing the optional \fBdirectory=\fIpathname\fR +/* directive. Delivery is deferred in case of failure. +/* .sp +/* This feature is available as of Postfix 2.3. +/* .IP "\fBdirectory=\fIpathname\fR (optional)" +/* Change to the named directory before executing the external command. +/* The directory must be accessible for the user specified with the +/* \fBuser\fR attribute (see below). +/* The default working directory is \fB$queue_directory\fR. +/* Delivery is deferred in case of failure. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP "\fBeol=\fIstring\fR (optional, default: \fB\en\fR)" +/* The output record delimiter. Typically one would use either +/* \fB\er\en\fR or \fB\en\fR. The usual C-style backslash escape +/* sequences are recognized: \fB\ea \eb \ef \en \er \et \ev +/* \e\fIddd\fR (up to three octal digits) and \fB\e\e\fR. +/* .IP "\fBflags=BDFORXhqu.>\fR (optional)" +/* Optional message processing flags. By default, a message is +/* copied unchanged. +/* .RS +/* .IP \fBB\fR +/* Append a blank line at the end of each message. This is required +/* by some mail user agents that recognize "\fBFrom \fR" lines only +/* when preceded by a blank line. +/* .IP \fBD\fR +/* Prepend a "\fBDelivered-To: \fIrecipient\fR" message header with the +/* envelope recipient address. Note: for this to work, the +/* \fItransport\fB_destination_recipient_limit\fR must be 1 +/* (see SINGLE-RECIPIENT DELIVERY above for details). +/* .sp +/* The \fBD\fR flag also enforces loop detection (Postfix 2.5 and later): +/* if a message already contains a \fBDelivered-To:\fR header +/* with the same recipient address, then the message is +/* returned as undeliverable. The address comparison is case +/* insensitive. +/* .sp +/* This feature is available as of Postfix 2.0. +/* .IP \fBF\fR +/* Prepend a "\fBFrom \fIsender time_stamp\fR" envelope header to +/* the message content. +/* This is expected by, for example, \fBUUCP\fR software. +/* .IP \fBO\fR +/* Prepend an "\fBX-Original-To: \fIrecipient\fR" message header +/* with the recipient address as given to Postfix. Note: for this to +/* work, the \fItransport\fB_destination_recipient_limit\fR must be 1 +/* (see SINGLE-RECIPIENT DELIVERY above for details). +/* .sp +/* This feature is available as of Postfix 2.0. +/* .IP \fBR\fR +/* Prepend a \fBReturn-Path:\fR message header with the envelope sender +/* address. +/* .IP \fBX\fR +/* Indicate that the external command performs final delivery. +/* This flag affects the status reported in "success" DSN +/* (delivery status notification) messages, and changes it +/* from "relayed" into "delivered". +/* .sp +/* This feature is available as of Postfix 2.5. +/* .IP \fBh\fR +/* Fold the command-line \fB$original_recipient\fR and +/* \fB$recipient\fR address domain part +/* (text to the right of the right-most \fB@\fR character) to +/* lower case; fold the entire command-line \fB$domain\fR and +/* \fB$nexthop\fR host or domain information to lower case. +/* This is recommended for delivery via \fBUUCP\fR. +/* .IP \fBq\fR +/* Quote white space and other special characters in the command-line +/* \fB$sender\fR, \fB$original_recipient\fR and \fB$recipient\fR +/* address localparts (text to the +/* left of the right-most \fB@\fR character), according to an 8-bit +/* transparent version of RFC 822. +/* This is recommended for delivery via \fBUUCP\fR or \fBBSMTP\fR. +/* .sp +/* The result is compatible with the address parsing of command-line +/* recipients by the Postfix \fBsendmail\fR(1) mail submission command. +/* .sp +/* The \fBq\fR flag affects only entire addresses, not the partial +/* address information from the \fB$user\fR, \fB$extension\fR or +/* \fB$mailbox\fR command-line macros. +/* .IP \fBu\fR +/* Fold the command-line \fB$original_recipient\fR and +/* \fB$recipient\fR address localpart (text to +/* the left of the right-most \fB@\fR character) to lower case. +/* This is recommended for delivery via \fBUUCP\fR. +/* .IP \fB.\fR +/* Prepend "\fB.\fR" to lines starting with "\fB.\fR". This is needed +/* by, for example, \fBBSMTP\fR software. +/* .IP \fB>\fR +/* Prepend "\fB>\fR" to lines starting with "\fBFrom \fR". This is expected +/* by, for example, \fBUUCP\fR software. +/* .RE +/* .IP "\fBnull_sender\fR=\fIreplacement\fR (default: MAILER-DAEMON)" +/* Replace the null sender address (typically used for delivery +/* status notifications) with the specified text +/* when expanding the \fB$sender\fR command-line macro, and +/* when generating a From_ or Return-Path: message header. +/* +/* If the null sender replacement text is a non-empty string +/* then it is affected by the \fBq\fR flag for address quoting +/* in command-line arguments. +/* +/* The null sender replacement text may be empty; this form +/* is recommended for content filters that feed mail back into +/* Postfix. The empty sender address is not affected by the +/* \fBq\fR flag for address quoting in command-line arguments. +/* .sp +/* Caution: a null sender address is easily mis-parsed by +/* naive software. For example, when the \fBpipe\fR(8) daemon +/* executes a command such as: +/* .sp +/* .nf +/* \fIWrong\fR: command -f$sender -- $recipient +/* .fi +/* .IP +/* the command will mis-parse the -f option value when the +/* sender address is a null string. For correct parsing, +/* specify \fB$sender\fR as an argument by itself: +/* .sp +/* .nf +/* \fIRight\fR: command -f $sender -- $recipient +/* .fi +/* NOTE: DO NOT put quotes around the command, $sender, or $recipient. +/* .IP +/* This feature is available as of Postfix 2.3. +/* .IP "\fBsize\fR=\fIsize_limit\fR (optional)" +/* Don't deliver messages that exceed this size limit (in +/* bytes); return them to the sender instead. +/* .IP "\fBuser\fR=\fIusername\fR (required)" +/* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR" +/* Execute the external command with the user ID and group ID of the +/* specified \fIusername\fR. The software refuses to execute +/* commands with root privileges, or with the privileges of the +/* mail system owner. If \fIgroupname\fR is specified, the +/* corresponding group ID is used instead of the group ID of +/* \fIusername\fR. +/* .IP "\fBargv\fR=\fIcommand\fR... (required)" +/* The command to be executed. This must be specified as the +/* last command attribute. +/* The command is executed directly, i.e. without interpretation of +/* shell meta characters by a shell command interpreter. +/* .sp +/* Specify "{" and "}" around command arguments that contain +/* whitespace (Postfix 3.0 and later). Whitespace +/* after the opening "{" and before the closing "}" is ignored. +/* .sp +/* In the command argument vector, the following macros are recognized +/* and replaced with corresponding information from the Postfix queue +/* manager delivery request. +/* .sp +/* In addition to the form ${\fIname\fR}, the forms $\fIname\fR and +/* the deprecated form $(\fIname\fR) are also recognized. +/* Specify \fB$$\fR where a single \fB$\fR is wanted. +/* .RS +/* .IP \fB${client_address}\fR +/* This macro expands to the remote client network address. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${client_helo}\fR +/* This macro expands to the remote client HELO command parameter. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${client_hostname}\fR +/* This macro expands to the remote client hostname. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${client_port}\fR +/* This macro expands to the remote client TCP port number. +/* .sp +/* This feature is available as of Postfix 2.5. +/* .IP \fB${client_protocol}\fR +/* This macro expands to the remote client protocol. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${domain}\fR +/* This macro expands to the domain portion of the recipient +/* address. For example, with an address \fIuser+foo@domain\fR +/* the domain is \fIdomain\fR. +/* .sp +/* This information is modified by the \fBh\fR flag for case folding. +/* .sp +/* This feature is available as of Postfix 2.5. +/* .IP \fB${extension}\fR +/* This macro expands to the extension part of a recipient address. +/* For example, with an address \fIuser+foo@domain\fR the extension is +/* \fIfoo\fR. +/* .sp +/* A command-line argument that contains \fB${extension}\fR expands +/* into as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBu\fR flag for case folding. +/* .IP \fB${mailbox}\fR +/* This macro expands to the complete local part of a recipient address. +/* For example, with an address \fIuser+foo@domain\fR the mailbox is +/* \fIuser+foo\fR. +/* .sp +/* A command-line argument that contains \fB${mailbox}\fR +/* expands to as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBu\fR flag for case folding. +/* .IP \fB${nexthop}\fR +/* This macro expands to the next-hop hostname. +/* .sp +/* This information is modified by the \fBh\fR flag for case folding. +/* .IP \fB${original_recipient}\fR +/* This macro expands to the complete recipient address before any +/* address rewriting or aliasing. +/* .sp +/* A command-line argument that contains +/* \fB${original_recipient}\fR expands to as many +/* command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBhqu\fR flags for quoting +/* and case folding. +/* .sp +/* This feature is available as of Postfix 2.5. +/* .IP \fB${queue_id}\fR +/* This macro expands to the queue id. +/* .sp +/* This feature is available as of Postfix 2.11. +/* .IP \fB${recipient}\fR +/* This macro expands to the complete recipient address. +/* .sp +/* A command-line argument that contains \fB${recipient}\fR +/* expands to as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBhqu\fR flags for quoting +/* and case folding. +/* .IP \fB${sasl_method}\fR +/* This macro expands to the name of the SASL authentication +/* mechanism in the AUTH command when the Postfix SMTP server +/* received the message. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${sasl_sender}\fR +/* This macro expands to the SASL sender name (i.e. the original +/* submitter as per RFC 4954) in the MAIL FROM command when +/* the Postfix SMTP server received the message. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${sasl_username}\fR +/* This macro expands to the SASL user name in the AUTH command +/* when the Postfix SMTP server received the message. +/* .sp +/* This feature is available as of Postfix 2.2. +/* .IP \fB${sender}\fR +/* This macro expands to the envelope sender address. By default, +/* the null sender address expands to MAILER-DAEMON; this can +/* be changed with the \fBnull_sender\fR attribute, as described +/* above. +/* .sp +/* This information is modified by the \fBq\fR flag for quoting. +/* .IP \fB${size}\fR +/* This macro expands to Postfix's idea of the message size, which +/* is an approximation of the size of the message as delivered. +/* .IP \fB${user}\fR +/* This macro expands to the username part of a recipient address. +/* For example, with an address \fIuser+foo@domain\fR the username +/* part is \fIuser\fR. +/* .sp +/* A command-line argument that contains \fB${user}\fR expands +/* into as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBu\fR flag for case folding. +/* .RE +/* STANDARDS +/* RFC 3463 (Enhanced status codes) +/* DIAGNOSTICS +/* Command exit status codes are expected to +/* follow the conventions defined in <\fBsysexits.h\fR>. +/* Exit status 0 means normal successful completion. +/* +/* In the case of a non-zero exit status, a limited amount of +/* command output is logged, and reported in a delivery status +/* notification. When the output begins with a 4.X.X or 5.X.X +/* enhanced status code, the status code takes precedence over +/* the non-zero exit status (Postfix version 2.3 and later). +/* +/* After successful delivery (zero exit status) a limited +/* amount of command output is logged, and reported in "success" +/* delivery status notifications (Postfix 3.0 and later). +/* This command output is not examined for the presence of an +/* enhanced status code. +/* +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* Corrupted message files are marked so that the queue manager +/* can move them to the \fBcorrupt\fR queue for further inspection. +/* SECURITY +/* .fi +/* .ad +/* This program needs a dual personality 1) to access the private +/* Postfix queue and IPC mechanisms, and 2) to execute external +/* commands as the specified user. It is therefore security sensitive. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically as \fBpipe\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* In the text below, \fItransport\fR is the first field in a +/* \fBmaster.cf\fR entry. +/* .IP "\fBtransport_time_limit ($command_time_limit)\fR" +/* A transport-specific override for the command_time_limit parameter +/* value, where \fItransport\fR is the master.cf name of the message +/* delivery transport. +/* .PP +/* Implemented in the qmgr(8) daemon: +/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR" +/* A transport-specific override for the +/* default_destination_recipient_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBexport_environment (see 'postconf -d' output)\fR" +/* The list of environment variables that a Postfix process will export +/* to non-Postfix processes. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmail_owner (postfix)\fR" +/* The UNIX system account that owns the Postfix queue and most Postfix +/* daemon processes. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBrecipient_delimiter (empty)\fR" +/* The set of characters that can separate an email address +/* localpart, user name, or a .forward file name from its extension. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBpipe_delivery_status_filter ($default_delivery_status_filter)\fR" +/* Optional filter for the \fBpipe\fR(8) delivery agent to change the +/* delivery status code or explanatory text of successful or unsuccessful +/* deliveries. +/* .PP +/* Available in Postfix version 3.3 and later: +/* .IP "\fBenable_original_recipient (yes)\fR" +/* Enable support for the original recipient address after an +/* address is rewritten to a different address (for example with +/* aliasing or with canonical mapping). +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.5 and later: +/* .IP "\fBinfo_log_address_format (external)\fR" +/* The email address form that will be used in non-debug logging +/* (info, warning, etc.). +/* SEE ALSO +/* qmgr(8), queue manager +/* bounce(8), delivery status reports +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single server skeleton. */ + +#include + +/* Application-specific. */ + + /* + * The mini symbol table name and keys used for expanding macros in + * command-line arguments. + * + * XXX Update the parse_callback() routine when something gets added here, + * even when the macro is not recipient dependent. + */ +#define PIPE_DICT_TABLE "pipe_command" /* table name */ +#define PIPE_DICT_NEXTHOP "nexthop" /* key */ +#define PIPE_DICT_RCPT "recipient" /* key */ +#define PIPE_DICT_ORIG_RCPT "original_recipient" /* key */ +#define PIPE_DICT_SENDER "sender"/* key */ +#define PIPE_DICT_USER "user" /* key */ +#define PIPE_DICT_EXTENSION "extension" /* key */ +#define PIPE_DICT_MAILBOX "mailbox" /* key */ +#define PIPE_DICT_DOMAIN "domain"/* key */ +#define PIPE_DICT_SIZE "size" /* key */ +#define PIPE_DICT_CLIENT_ADDR "client_address" /* key */ +#define PIPE_DICT_CLIENT_NAME "client_hostname" /* key */ +#define PIPE_DICT_CLIENT_PORT "client_port" /* key */ +#define PIPE_DICT_CLIENT_PROTO "client_protocol" /* key */ +#define PIPE_DICT_CLIENT_HELO "client_helo" /* key */ +#define PIPE_DICT_SASL_METHOD "sasl_method" /* key */ +#define PIPE_DICT_SASL_USERNAME "sasl_username" /* key */ +#define PIPE_DICT_SASL_SENDER "sasl_sender" /* key */ +#define PIPE_DICT_QUEUE_ID "queue_id" /* key */ + + /* + * Flags used to pass back the type of special parameter found by + * parse_callback. + */ +#define PIPE_FLAG_RCPT (1<<0) +#define PIPE_FLAG_USER (1<<1) +#define PIPE_FLAG_EXTENSION (1<<2) +#define PIPE_FLAG_MAILBOX (1<<3) +#define PIPE_FLAG_DOMAIN (1<<4) +#define PIPE_FLAG_ORIG_RCPT (1<<5) + + /* + * Additional flags. These are colocated with mail_copy() flags. Allow some + * space for extension of the mail_copy() interface. + */ +#define PIPE_OPT_FOLD_BASE (16) +#define PIPE_OPT_FOLD_USER (FOLD_ADDR_USER << PIPE_OPT_FOLD_BASE) +#define PIPE_OPT_FOLD_HOST (FOLD_ADDR_HOST << PIPE_OPT_FOLD_BASE) +#define PIPE_OPT_QUOTE_LOCAL (1 << (PIPE_OPT_FOLD_BASE + 2)) +#define PIPE_OPT_FINAL_DELIVERY (1 << (PIPE_OPT_FOLD_BASE + 3)) + +#define PIPE_OPT_FOLD_ALL (FOLD_ADDR_ALL << PIPE_OPT_FOLD_BASE) +#define PIPE_OPT_FOLD_FLAGS(f) \ + (((f) & PIPE_OPT_FOLD_ALL) >> PIPE_OPT_FOLD_BASE) + + /* + * Tunable parameters. Values are taken from the config file, after + * prepending the service name to _name, and so on. + */ +int var_command_maxtime; /* You can now leave this here. */ + + /* + * Other main.cf parameters. + */ +char *var_pipe_dsn_filter; + + /* + * For convenience. Instead of passing around lists of parameters, bundle + * them up in convenient structures. + */ + + /* + * Structure for service-specific configuration parameters. + */ +typedef struct { + int time_limit; /* per-service time limit */ +} PIPE_PARAMS; + + /* + * Structure for command-line parameters. + */ +typedef struct { + char **command; /* argument vector */ + uid_t uid; /* command privileges */ + gid_t gid; /* command privileges */ + int flags; /* mail_copy() flags */ + char *exec_dir; /* working directory */ + char *chroot_dir; /* chroot directory */ + VSTRING *eol; /* output record delimiter */ + VSTRING *null_sender; /* null sender expansion */ + off_t size_limit; /* max size in bytes we will accept */ +} PIPE_ATTR; + + /* + * Structure for command-line parameter macro expansion. + */ +typedef struct { + const char *service; /* for warnings */ + int expand_flag; /* callback result */ +} PIPE_STATE; + + /* + * Silly little macros. + */ +#define STR vstring_str + +/* parse_callback - callback for mac_parse() */ + +static int parse_callback(int type, VSTRING *buf, void *context) +{ + PIPE_STATE *state = (PIPE_STATE *) context; + struct cmd_flags { + const char *name; + int flags; + }; + static struct cmd_flags cmd_flags[] = { + PIPE_DICT_NEXTHOP, 0, + PIPE_DICT_RCPT, PIPE_FLAG_RCPT, + PIPE_DICT_ORIG_RCPT, PIPE_FLAG_ORIG_RCPT, + PIPE_DICT_SENDER, 0, + PIPE_DICT_USER, PIPE_FLAG_USER, + PIPE_DICT_EXTENSION, PIPE_FLAG_EXTENSION, + PIPE_DICT_MAILBOX, PIPE_FLAG_MAILBOX, + PIPE_DICT_DOMAIN, PIPE_FLAG_DOMAIN, + PIPE_DICT_SIZE, 0, + PIPE_DICT_CLIENT_ADDR, 0, + PIPE_DICT_CLIENT_NAME, 0, + PIPE_DICT_CLIENT_PORT, 0, + PIPE_DICT_CLIENT_PROTO, 0, + PIPE_DICT_CLIENT_HELO, 0, + PIPE_DICT_SASL_METHOD, 0, + PIPE_DICT_SASL_USERNAME, 0, + PIPE_DICT_SASL_SENDER, 0, + PIPE_DICT_QUEUE_ID, 0, + 0, 0, + }; + struct cmd_flags *p; + + /* + * See if this command-line argument references a special macro. + */ + if (type == MAC_PARSE_VARNAME) { + for (p = cmd_flags; /* see below */ ; p++) { + if (p->name == 0) { + msg_warn("file %s/%s: service %s: unknown macro name: \"%s\"", + var_config_dir, MASTER_CONF_FILE, + state->service, vstring_str(buf)); + return (MAC_PARSE_ERROR); + } else if (strcmp(vstring_str(buf), p->name) == 0) { + state->expand_flag |= p->flags; + return (0); + } + } + } + return (0); +} + +/* morph_recipient - morph a recipient address */ + +static void morph_recipient(VSTRING *buf, const char *address, int flags) +{ + VSTRING *temp = vstring_alloc(100); + + /* + * Quote the recipient address as appropriate. + */ + if (flags & PIPE_OPT_QUOTE_LOCAL) + quote_822_local(temp, address); + else + vstring_strcpy(temp, address); + + /* + * Fold the recipient address as appropriate. + */ + fold_addr(buf, STR(temp), PIPE_OPT_FOLD_FLAGS(flags)); + + vstring_free(temp); +} + +/* expand_argv - expand macros in the argument vector */ + +static ARGV *expand_argv(const char *service, char **argv, + RECIPIENT_LIST *rcpt_list, int flags) +{ + VSTRING *buf = vstring_alloc(100); + ARGV *result; + char **cpp; + PIPE_STATE state; + int i; + char *ext; + char *dom; + + /* + * This appears to be simple operation (replace $name by its expansion). + * However, it becomes complex because a command-line argument that + * references $recipient must expand to as many command-line arguments as + * there are recipients (that's wat programs called by sendmail expect). + * So we parse each command-line argument, and depending on what we find, + * we either expand the argument just once, or we expand it once for each + * recipient. In either case we end up parsing the command-line argument + * twice. The amount of CPU time wasted will be negligible. + * + * Note: we can't use recursive macro expansion here, because recursion + * would screw up mail addresses that contain $ characters. + */ +#define NO 0 +#define EARLY_RETURN(x) { argv_free(result); vstring_free(buf); return (x); } + + result = argv_alloc(1); + for (cpp = argv; *cpp; cpp++) { + state.service = service; + state.expand_flag = 0; + if (mac_parse(*cpp, parse_callback, (void *) &state) & MAC_PARSE_ERROR) + EARLY_RETURN(0); + if (state.expand_flag == 0) { /* no $recipient etc. */ + argv_add(result, dict_eval(PIPE_DICT_TABLE, *cpp, NO), ARGV_END); + } else { /* contains $recipient etc. */ + for (i = 0; i < rcpt_list->len; i++) { + + /* + * This argument contains $recipient. + */ + if (state.expand_flag & PIPE_FLAG_RCPT) { + morph_recipient(buf, rcpt_list->info[i].address, flags); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_RCPT, STR(buf)); + } + + /* + * This argument contains $original_recipient. + */ + if (state.expand_flag & PIPE_FLAG_ORIG_RCPT) { + morph_recipient(buf, rcpt_list->info[i].orig_addr, flags); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_ORIG_RCPT, STR(buf)); + } + + /* + * This argument contains $user. Extract the plain user name. + * Either anything to the left of the extension delimiter or, + * in absence of the latter, anything to the left of the + * rightmost @. + * + * Beware: if the user name is blank (e.g. +user@host), the + * argument is suppressed. This is necessary to allow for + * cyrus bulletin-board (global mailbox) delivery. XXX But, + * skipping empty user parts will also prevent other + * expansions of this specific command-line argument. + */ + if (state.expand_flag & PIPE_FLAG_USER) { + morph_recipient(buf, rcpt_list->info[i].address, + flags & PIPE_OPT_FOLD_ALL); + if (split_at_right(STR(buf), '@') == 0) + msg_warn("no @ in recipient address: %s", + rcpt_list->info[i].address); + if (*var_rcpt_delim) + split_addr(STR(buf), var_rcpt_delim); + if (*STR(buf) == 0) + continue; + dict_update(PIPE_DICT_TABLE, PIPE_DICT_USER, STR(buf)); + } + + /* + * This argument contains $extension. Extract the recipient + * extension: anything between the leftmost extension + * delimiter and the rightmost @. The extension may be blank. + */ + if (state.expand_flag & PIPE_FLAG_EXTENSION) { + morph_recipient(buf, rcpt_list->info[i].address, + flags & PIPE_OPT_FOLD_ALL); + if (split_at_right(STR(buf), '@') == 0) + msg_warn("no @ in recipient address: %s", + rcpt_list->info[i].address); + if (*var_rcpt_delim == 0 + || (ext = split_addr(STR(buf), var_rcpt_delim)) == 0) + ext = ""; /* insert null arg */ + dict_update(PIPE_DICT_TABLE, PIPE_DICT_EXTENSION, ext); + } + + /* + * This argument contains $mailbox. Extract the mailbox name: + * anything to the left of the rightmost @. + */ + if (state.expand_flag & PIPE_FLAG_MAILBOX) { + morph_recipient(buf, rcpt_list->info[i].address, + flags & PIPE_OPT_FOLD_ALL); + if (split_at_right(STR(buf), '@') == 0) + msg_warn("no @ in recipient address: %s", + rcpt_list->info[i].address); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_MAILBOX, STR(buf)); + } + + /* + * This argument contains $domain. Extract the domain name: + * anything to the right of the rightmost @. + */ + if (state.expand_flag & PIPE_FLAG_DOMAIN) { + morph_recipient(buf, rcpt_list->info[i].address, + flags & PIPE_OPT_FOLD_ALL); + dom = split_at_right(STR(buf), '@'); + if (dom == 0) { + msg_warn("no @ in recipient address: %s", + rcpt_list->info[i].address); + dom = ""; /* insert null arg */ + } + dict_update(PIPE_DICT_TABLE, PIPE_DICT_DOMAIN, dom); + } + + /* + * Done. + */ + argv_add(result, dict_eval(PIPE_DICT_TABLE, *cpp, NO), ARGV_END); + } + } + } + argv_terminate(result); + vstring_free(buf); + return (result); +} + +/* get_service_params - get service-name dependent config information */ + +static void get_service_params(PIPE_PARAMS *config, char *service) +{ + const char *myname = "get_service_params"; + + /* + * Figure out the command time limit for this transport. + */ + config->time_limit = + get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0); + + /* + * Give the poor tester a clue of what is going on. + */ + if (msg_verbose) + msg_info("%s: time_limit %d", myname, config->time_limit); +} + +/* get_service_attr - get command-line attributes */ + +static void get_service_attr(PIPE_ATTR *attr, char **argv) +{ + const char *myname = "get_service_attr"; + struct passwd *pwd; + struct group *grp; + char *user; /* user name */ + char *group; /* group name */ + char *size; /* max message size */ + char *cp; + + /* + * Initialize. + */ + user = 0; + group = 0; + attr->command = 0; + attr->flags = 0; + attr->exec_dir = 0; + attr->chroot_dir = 0; + attr->eol = vstring_strcpy(vstring_alloc(1), "\n"); + attr->null_sender = vstring_strcpy(vstring_alloc(1), MAIL_ADDR_MAIL_DAEMON); + attr->size_limit = 0; + + /* + * Iterate over the command-line attribute list. + */ + for ( /* void */ ; *argv != 0; argv++) { + + /* + * flags=stuff + */ + if (strncasecmp("flags=", *argv, sizeof("flags=") - 1) == 0) { + for (cp = *argv + sizeof("flags=") - 1; *cp; cp++) { + switch (*cp) { + case 'B': + attr->flags |= MAIL_COPY_BLANK; + break; + case 'D': + attr->flags |= MAIL_COPY_DELIVERED; + break; + case 'F': + attr->flags |= MAIL_COPY_FROM; + break; + case 'O': + attr->flags |= MAIL_COPY_ORIG_RCPT; + break; + case 'R': + attr->flags |= MAIL_COPY_RETURN_PATH; + break; + case 'X': + attr->flags |= PIPE_OPT_FINAL_DELIVERY; + break; + case '.': + attr->flags |= MAIL_COPY_DOT; + break; + case '>': + attr->flags |= MAIL_COPY_QUOTE; + break; + case 'h': + attr->flags |= PIPE_OPT_FOLD_HOST; + break; + case 'q': + attr->flags |= PIPE_OPT_QUOTE_LOCAL; + break; + case 'u': + attr->flags |= PIPE_OPT_FOLD_USER; + break; + default: + msg_fatal("unknown flag: %c (ignored)", *cp); + break; + } + } + } + + /* + * user=username[:groupname] + */ + else if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) { + user = *argv + sizeof("user=") - 1; + if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */ + if (*group == 0) + group = 0; + if ((pwd = getpwnam(user)) == 0) + msg_fatal("%s: unknown username: %s", myname, user); + attr->uid = pwd->pw_uid; + if (group != 0) { + if ((grp = getgrnam(group)) == 0) + msg_fatal("%s: unknown group: %s", myname, group); + attr->gid = grp->gr_gid; + } else { + attr->gid = pwd->pw_gid; + } + } + + /* + * directory=string + */ + else if (strncasecmp("directory=", *argv, sizeof("directory=") - 1) == 0) { + attr->exec_dir = mystrdup(*argv + sizeof("directory=") - 1); + } + + /* + * chroot=string + */ + else if (strncasecmp("chroot=", *argv, sizeof("chroot=") - 1) == 0) { + attr->chroot_dir = mystrdup(*argv + sizeof("chroot=") - 1); + } + + /* + * eol=string + */ + else if (strncasecmp("eol=", *argv, sizeof("eol=") - 1) == 0) { + unescape(attr->eol, *argv + sizeof("eol=") - 1); + } + + /* + * null_sender=string + */ + else if (strncasecmp("null_sender=", *argv, sizeof("null_sender=") - 1) == 0) { + vstring_strcpy(attr->null_sender, *argv + sizeof("null_sender=") - 1); + } + + /* + * size=max_message_size (in bytes) + */ + else if (strncasecmp("size=", *argv, sizeof("size=") - 1) == 0) { + size = *argv + sizeof("size=") - 1; + if ((attr->size_limit = off_cvt_string(size)) < 0) + msg_fatal("%s: bad size= value: %s", myname, size); + } + + /* + * argv=command... + */ + else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) { + *argv += sizeof("argv=") - 1; /* XXX clobbers argv */ + attr->command = argv; + break; + } + + /* + * Bad. + */ + else + msg_fatal("unknown attribute name: %s", *argv); + } + + /* + * Sanity checks. Verify that every member has an acceptable value. + */ + if (user == 0) + msg_fatal("missing user= command-line attribute"); + if (attr->command == 0) + msg_fatal("missing argv= command-line attribute"); + if (attr->uid == 0) + msg_fatal("user= command-line attribute specifies root privileges"); + if (attr->uid == var_owner_uid) + msg_fatal("user= command-line attribute specifies mail system owner %s", + var_mail_owner); + if (attr->gid == 0) + msg_fatal("user= command-line attribute specifies privileged group id 0"); + if (attr->gid == var_owner_gid) + msg_fatal("user= command-line attribute specifies mail system owner %s group id %ld", + var_mail_owner, (long) attr->gid); + if (attr->gid == var_sgid_gid) + msg_fatal("user= command-line attribute specifies mail system %s group id %ld", + var_sgid_group, (long) attr->gid); + + /* + * Give the poor tester a clue of what is going on. + */ + if (msg_verbose) + msg_info("%s: uid %ld, gid %ld, flags %d, size %ld", + myname, (long) attr->uid, (long) attr->gid, + attr->flags, (long) attr->size_limit); +} + +/* eval_command_status - do something with command completion status */ + +static int eval_command_status(int command_status, char *service, + DELIVER_REQUEST *request, PIPE_ATTR *attr, + DSN_BUF *why) +{ + RECIPIENT *rcpt; + int status; + int result = 0; + int n; + char *saved_text; + + /* + * Depending on the result, bounce or defer the message, and mark the + * recipient as done where appropriate. + */ + switch (command_status) { + case PIPE_STAT_OK: + /* Save the command output before dsb_update() clobbers it. */ + vstring_truncate(why->reason, trimblanks(STR(why->reason), + VSTRING_LEN(why->reason)) - STR(why->reason)); + if (VSTRING_LEN(why->reason) > 0) { + VSTRING_TERMINATE(why->reason); + saved_text = + vstring_export(vstring_sprintf( + vstring_alloc(VSTRING_LEN(why->reason)), + " (%.100s)", STR(why->reason))); + } else + saved_text = mystrdup(""); /* uses shared R/O storage */ + dsb_update(why, "2.0.0", (attr->flags & PIPE_OPT_FINAL_DELIVERY) ? + "delivered" : "relayed", DSB_SKIP_RMTA, DSB_SKIP_REPLY, + "delivered via %s service%s", service, saved_text); + myfree(saved_text); + (void) DSN_FROM_DSN_BUF(why); + for (n = 0; n < request->rcpt_list.len; n++) { + rcpt = request->rcpt_list.info + n; + status = sent(DEL_REQ_TRACE_FLAGS(request->flags), + request->queue_id, &request->msg_stats, rcpt, + service, &why->dsn); + if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) + deliver_completed(request->fp, rcpt->offset); + result |= status; + } + break; + case PIPE_STAT_BOUNCE: + case PIPE_STAT_DEFER: + (void) DSN_FROM_DSN_BUF(why); + for (n = 0; n < request->rcpt_list.len; n++) { + rcpt = request->rcpt_list.info + n; + /* XXX Maybe encapsulate this with ndr_append(). */ + status = (STR(why->status)[0] != '4' ? + bounce_append : defer_append) + (DEL_REQ_TRACE_FLAGS(request->flags), + request->queue_id, + &request->msg_stats, rcpt, + service, &why->dsn); + if (status == 0) + deliver_completed(request->fp, rcpt->offset); + result |= status; + } + break; + case PIPE_STAT_CORRUPT: + /* XXX DSN should we send something? */ + result |= DEL_STAT_DEFER; + break; + default: + msg_panic("eval_command_status: bad status %d", command_status); + /* NOTREACHED */ + } + + return (result); +} + +/* deliver_message - deliver message with extreme prejudice */ + +static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv) +{ + const char *myname = "deliver_message"; + static PIPE_PARAMS conf; + static PIPE_ATTR attr; + RECIPIENT_LIST *rcpt_list = &request->rcpt_list; + DSN_BUF *why = dsb_create(); + VSTRING *buf; + ARGV *expanded_argv = 0; + int deliver_status; + int command_status; + ARGV *export_env; + const char *sender; + +#define DELIVER_MSG_CLEANUP() { \ + dsb_free(why); \ + if (expanded_argv) argv_free(expanded_argv); \ + } + + if (msg_verbose) + msg_info("%s: from <%s>", myname, request->sender); + + /* + * Sanity checks. The get_service_params() and get_service_attr() + * routines also do some sanity checks. Look up service attributes and + * config information only once. This is safe since the information comes + * from a trusted source, not from the delivery request. + */ + if (request->nexthop[0] == 0) + msg_fatal("empty nexthop hostname"); + if (rcpt_list->len <= 0) + msg_fatal("recipient count: %d", rcpt_list->len); + if (attr.command == 0) { + get_service_params(&conf, service); + get_service_attr(&attr, argv); + } + + /* + * The D flag cannot be specified for multi-recipient deliveries. + */ + if ((attr.flags & MAIL_COPY_DELIVERED) && (rcpt_list->len > 1)) { + dsb_simple(why, "4.3.5", "mail system configuration error"); + deliver_status = eval_command_status(PIPE_STAT_DEFER, service, + request, &attr, why); + msg_warn("pipe flag `D' requires %s_destination_recipient_limit = 1", + service); + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + + /* + * The O flag cannot be specified for multi-recipient deliveries. + */ + if ((attr.flags & MAIL_COPY_ORIG_RCPT) && (rcpt_list->len > 1)) { + dsb_simple(why, "4.3.5", "mail system configuration error"); + deliver_status = eval_command_status(PIPE_STAT_DEFER, service, + request, &attr, why); + msg_warn("pipe flag `O' requires %s_destination_recipient_limit = 1", + service); + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + + /* + * Check that this agent accepts messages this large. + */ + if (attr.size_limit != 0 && request->data_size > attr.size_limit) { + if (msg_verbose) + msg_info("%s: too big: size_limit = %ld, request->data_size = %ld", + myname, (long) attr.size_limit, request->data_size); + dsb_simple(why, "5.2.3", "message too large"); + deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, + request, &attr, why); + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + + /* + * Don't deliver a trace-only request. + */ + if (DEL_REQ_TRACE_ONLY(request->flags)) { + RECIPIENT *rcpt; + int status; + int n; + + deliver_status = 0; + dsb_simple(why, "2.0.0", "delivers to command: %s", attr.command[0]); + (void) DSN_FROM_DSN_BUF(why); + for (n = 0; n < request->rcpt_list.len; n++) { + rcpt = request->rcpt_list.info + n; + status = sent(DEL_REQ_TRACE_FLAGS(request->flags), + request->queue_id, &request->msg_stats, + rcpt, service, &why->dsn); + if (status == 0 && (request->flags & DEL_REQ_FLAG_SUCCESS)) + deliver_completed(request->fp, rcpt->offset); + deliver_status |= status; + } + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + + /* + * Report mail delivery loops. By definition, this requires + * single-recipient delivery. Don't silently lose recipients. + */ + if (attr.flags & MAIL_COPY_DELIVERED) { + DELIVERED_HDR_INFO *info; + RECIPIENT *rcpt; + int loop_found; + + if (request->rcpt_list.len > 1) + msg_panic("%s: delivered-to enabled with multi-recipient request", + myname); + info = delivered_hdr_init(request->fp, request->data_offset, + FOLD_ADDR_ALL); + rcpt = request->rcpt_list.info; + loop_found = delivered_hdr_find(info, rcpt->address); + delivered_hdr_free(info); + if (loop_found) { + dsb_simple(why, "5.4.6", "mail forwarding loop for %s", + rcpt->address); + deliver_status = eval_command_status(PIPE_STAT_BOUNCE, service, + request, &attr, why); + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + } + + /* + * Deliver. Set the nexthop and sender variables, and expand the command + * argument vector. Recipients will be expanded on the fly. XXX Rewrite + * envelope and header addresses according to transport-specific + * rewriting rules. + */ + if (vstream_fseek(request->fp, request->data_offset, SEEK_SET) < 0) + msg_fatal("seek queue file %s: %m", VSTREAM_PATH(request->fp)); + + /* + * A non-empty null sender replacement is subject to the 'q' flag. + */ + buf = vstring_alloc(10); + sender = *request->sender ? request->sender : STR(attr.null_sender); + if (*sender && (attr.flags & PIPE_OPT_QUOTE_LOCAL)) { + quote_822_local(buf, sender); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, STR(buf)); + } else + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, sender); + if (attr.flags & PIPE_OPT_FOLD_HOST) { + casefold(buf, request->nexthop); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, STR(buf)); + } else + dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, request->nexthop); + vstring_sprintf(buf, "%ld", (long) request->data_size); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SIZE, STR(buf)); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_ADDR, + request->client_addr); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_HELO, + request->client_helo); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_NAME, + request->client_name); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PORT, + request->client_port); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_CLIENT_PROTO, + request->client_proto); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_METHOD, + request->sasl_method); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_USERNAME, + request->sasl_username); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SASL_SENDER, + request->sasl_sender); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_QUEUE_ID, + request->queue_id); + vstring_free(buf); + + if ((expanded_argv = expand_argv(service, attr.command, + rcpt_list, attr.flags)) == 0) { + dsb_simple(why, "4.3.5", "mail system configuration error"); + deliver_status = eval_command_status(PIPE_STAT_DEFER, service, + request, &attr, why); + DELIVER_MSG_CLEANUP(); + return (deliver_status); + } + export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); + + command_status = pipe_command(request->fp, why, + CA_PIPE_CMD_UID(attr.uid), + CA_PIPE_CMD_GID(attr.gid), + CA_PIPE_CMD_SENDER(sender), + CA_PIPE_CMD_COPY_FLAGS(attr.flags), + CA_PIPE_CMD_ARGV(expanded_argv->argv), + CA_PIPE_CMD_TIME_LIMIT(conf.time_limit), + CA_PIPE_CMD_EOL(STR(attr.eol)), + CA_PIPE_CMD_EXPORT(export_env->argv), + CA_PIPE_CMD_CWD(attr.exec_dir), + CA_PIPE_CMD_CHROOT(attr.chroot_dir), + CA_PIPE_CMD_ORIG_RCPT(rcpt_list->info[0].orig_addr), + CA_PIPE_CMD_DELIVERED(rcpt_list->info[0].address), + CA_PIPE_CMD_END); + argv_free(export_env); + + deliver_status = eval_command_status(command_status, service, request, + &attr, why); + + /* + * Clean up. + */ + DELIVER_MSG_CLEANUP(); + + return (deliver_status); +} + +/* pipe_service - perform service for client */ + +static void pipe_service(VSTREAM *client_stream, char *service, char **argv) +{ + DELIVER_REQUEST *request; + int status; + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * dedicated to delivery via external command. What we see below is a + * little protocol to (1) tell the queue manager that we are ready, (2) + * read a request from the queue manager, and (3) report the completion + * status of that request. All connection-management stuff is handled by + * the common code in single_server.c. + */ + if ((request = deliver_request_read(client_stream)) != 0) { + status = deliver_message(request, service, argv); + deliver_request_done(client_stream, request, status); + } +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* drop_privileges - drop privileges most of the time */ + +static void drop_privileges(char *unused_name, char **unused_argv) +{ + set_eugid(var_owner_uid, var_owner_gid); +} + +/* pre_init - initialize */ + +static void pre_init(char *unused_name, char **unused_argv) +{ + flush_init(); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_PIPE_DSN_FILTER, DEF_PIPE_DSN_FILTER, &var_pipe_dsn_filter, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, pipe_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_PRE_INIT(pre_init), + CA_MAIL_SERVER_POST_INIT(drop_privileges), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_PRIVILEGED, + CA_MAIL_SERVER_BOUNCE_INIT(VAR_PIPE_DSN_FILTER, + &var_pipe_dsn_filter), + 0); +} diff --git a/src/postalias/.indent.pro b/src/postalias/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postalias/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postalias/.printfck b/src/postalias/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/postalias/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/postalias/Makefile.in b/src/postalias/Makefile.in new file mode 100644 index 0000000..5e5b72d --- /dev/null +++ b/src/postalias/Makefile.in @@ -0,0 +1,121 @@ +SHELL = /bin/sh +SRCS = postalias.c +OBJS = postalias.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = postalias +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +update: ../../bin/$(PROG) + +tests: test1 test2 fail_test + +root_tests: + +test1: $(PROG) map.in map-abc1.ref map-ghi1.ref map-uABC1.ref + ${SHLIB_ENV} ${VALGRIND} ./$(PROG) map.in + for key in abc ghi; \ + do \ + ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -q $${key} map.in | diff map-$${key}1.ref -; \ + done + ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -f map.in + for key in ABC; \ + do \ + ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -fq $${key} map.in | diff map-u$${key}1.ref -; \ + done + rm -f map.in.db + +test2: $(PROG) map.in map-abc2.ref map-ghi2.ref map-uABC2.ref + ${SHLIB_ENV} ${VALGRIND} ./$(PROG) map.in + for key in abc ghi; \ + do \ + echo $${key} | ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -q - map.in | diff map-$${key}2.ref -; \ + done + ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -f map.in + for key in ABC; \ + do \ + echo $${key} | ${SHLIB_ENV} ${VALGRIND} ./$(PROG) -fq - map.in | diff map-u$${key}2.ref -; \ + done + rm -f map.in.db + +fail_test: $(PROG) aliases fail_test.in fail_test.ref + -(${SHLIB_ENV} sh fail_test.in 2>&1 || exit 0) | sed \ + -e 's/No error:/Unknown error:/' \ + -e 's/Success/Unknown error: 0/' > fail_test.tmp + diff fail_test.ref fail_test.tmp + rm -f fail_test.tmp + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postalias.o: ../../include/argv.h +postalias.o: ../../include/check_arg.h +postalias.o: ../../include/clean_env.h +postalias.o: ../../include/dict.h +postalias.o: ../../include/dict_proxy.h +postalias.o: ../../include/mail_conf.h +postalias.o: ../../include/mail_dict.h +postalias.o: ../../include/mail_params.h +postalias.o: ../../include/mail_parm_split.h +postalias.o: ../../include/mail_task.h +postalias.o: ../../include/mail_version.h +postalias.o: ../../include/maillog_client.h +postalias.o: ../../include/mkmap.h +postalias.o: ../../include/msg.h +postalias.o: ../../include/msg_vstream.h +postalias.o: ../../include/myflock.h +postalias.o: ../../include/mymalloc.h +postalias.o: ../../include/readlline.h +postalias.o: ../../include/resolve_clnt.h +postalias.o: ../../include/set_eugid.h +postalias.o: ../../include/split_at.h +postalias.o: ../../include/stringops.h +postalias.o: ../../include/sys_defs.h +postalias.o: ../../include/tok822.h +postalias.o: ../../include/vbuf.h +postalias.o: ../../include/vstream.h +postalias.o: ../../include/vstring.h +postalias.o: ../../include/vstring_vstream.h +postalias.o: ../../include/warn_stat.h +postalias.o: postalias.c diff --git a/src/postalias/aliases b/src/postalias/aliases new file mode 100644 index 0000000..a2a278d --- /dev/null +++ b/src/postalias/aliases @@ -0,0 +1 @@ +xx: yy diff --git a/src/postalias/fail_test.in b/src/postalias/fail_test.in new file mode 100644 index 0000000..4a68f6e --- /dev/null +++ b/src/postalias/fail_test.in @@ -0,0 +1,7 @@ +${VALGRIND} ./postalias -q xx fail:aliases +echo xx | ${VALGRIND} ./postalias -q - fail:aliases +${VALGRIND} ./postalias -d xx fail:aliases +echo xx | ${VALGRIND} ./postalias -d - fail:aliases +${VALGRIND} ./postalias -s fail:aliases +${VALGRIND} ./postalias -i fail:aliases < aliases +${VALGRIND} ./postalias fail:aliases diff --git a/src/postalias/fail_test.ref b/src/postalias/fail_test.ref new file mode 100644 index 0000000..9cb6024 --- /dev/null +++ b/src/postalias/fail_test.ref @@ -0,0 +1,7 @@ +postalias: fatal: table fail:aliases: query error: Application error +postalias: fatal: table fail:aliases: query error: Application error +postalias: fatal: table fail:aliases: delete error: Application error +postalias: fatal: table fail:aliases: delete error: Application error +postalias: fatal: table fail:aliases: sequence error: Application error +postalias: fatal: table fail:aliases: write error: Application error +postalias: fatal: table fail:aliases: write error: Application error diff --git a/src/postalias/map-abc1.ref b/src/postalias/map-abc1.ref new file mode 100644 index 0000000..142195f --- /dev/null +++ b/src/postalias/map-abc1.ref @@ -0,0 +1 @@ +DEF diff --git a/src/postalias/map-abc2.ref b/src/postalias/map-abc2.ref new file mode 100644 index 0000000..dfbdab6 --- /dev/null +++ b/src/postalias/map-abc2.ref @@ -0,0 +1 @@ +abc: DEF diff --git a/src/postalias/map-ghi1.ref b/src/postalias/map-ghi1.ref new file mode 100644 index 0000000..7beb1db --- /dev/null +++ b/src/postalias/map-ghi1.ref @@ -0,0 +1 @@ +jkl diff --git a/src/postalias/map-ghi2.ref b/src/postalias/map-ghi2.ref new file mode 100644 index 0000000..e2ca310 --- /dev/null +++ b/src/postalias/map-ghi2.ref @@ -0,0 +1 @@ +ghi: jkl diff --git a/src/postalias/map-uABC1.ref b/src/postalias/map-uABC1.ref new file mode 100644 index 0000000..142195f --- /dev/null +++ b/src/postalias/map-uABC1.ref @@ -0,0 +1 @@ +DEF diff --git a/src/postalias/map-uABC2.ref b/src/postalias/map-uABC2.ref new file mode 100644 index 0000000..929916b --- /dev/null +++ b/src/postalias/map-uABC2.ref @@ -0,0 +1 @@ +ABC: DEF diff --git a/src/postalias/map.in b/src/postalias/map.in new file mode 100644 index 0000000..203fa0c --- /dev/null +++ b/src/postalias/map.in @@ -0,0 +1,2 @@ +ABC: DEF +ghi: jkl diff --git a/src/postalias/postalias.c b/src/postalias/postalias.c new file mode 100644 index 0000000..9538883 --- /dev/null +++ b/src/postalias/postalias.c @@ -0,0 +1,906 @@ +/*++ +/* NAME +/* postalias 1 +/* SUMMARY +/* Postfix alias database maintenance +/* SYNOPSIS +/* .fi +/* \fBpostalias\fR [\fB-Nfinoprsuvw\fR] [\fB-c \fIconfig_dir\fR] +/* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR] +/* [\fIfile_type\fR:]\fIfile_name\fR ... +/* DESCRIPTION +/* The \fBpostalias\fR(1) command creates or queries one or more Postfix +/* alias databases, or updates an existing one. The input and output +/* file formats are expected to be compatible with Sendmail version 8, +/* and are expected to be suitable for use as NIS alias maps. +/* +/* If the result files do not exist they will be created with the +/* same group and other read permissions as their source file. +/* +/* While a database update is in progress, signal delivery is +/* postponed, and an exclusive, advisory, lock is placed on the +/* entire database, in order to avoid surprises in spectator +/* processes. +/* +/* The format of Postfix alias input files is described in +/* \fBaliases\fR(5). +/* +/* By default the lookup key is mapped to lowercase to make +/* the lookups case insensitive; as of Postfix 2.3 this case +/* folding happens only with tables whose lookup keys are +/* fixed-case strings such as btree:, dbm: or hash:. With +/* earlier versions, the lookup key is folded even with tables +/* where a lookup field can match both upper and lower case +/* text, such as regexp: and pcre:. This resulted in loss of +/* information with $\fInumber\fR substitutions. +/* +/* Options: +/* .IP "\fB-c \fIconfig_dir\fR" +/* Read the \fBmain.cf\fR configuration file in the named directory +/* instead of the default configuration directory. +/* .IP "\fB-d \fIkey\fR" +/* Search the specified maps for \fIkey\fR and remove one entry per map. +/* The exit status is zero when the requested information was found. +/* +/* If a key value of \fB-\fR is specified, the program reads key +/* values from the standard input stream. The exit status is zero +/* when at least one of the requested keys was found. +/* .IP \fB-f\fR +/* Do not fold the lookup key to lower case while creating or querying +/* a table. +/* +/* With Postfix version 2.3 and later, this option has no +/* effect for regular expression tables. There, case folding +/* is controlled by appending a flag to a pattern. +/* .IP \fB-i\fR +/* Incremental mode. Read entries from standard input and do not +/* truncate an existing database. By default, \fBpostalias\fR(1) creates +/* a new database from the entries in \fIfile_name\fR. +/* .IP \fB-N\fR +/* Include the terminating null character that terminates lookup keys +/* and values. By default, \fBpostalias\fR(1) does whatever +/* is the default for +/* the host operating system. +/* .IP \fB-n\fR +/* Don't include the terminating null character that terminates lookup +/* keys and values. By default, \fBpostalias\fR(1) does whatever +/* is the default for +/* the host operating system. +/* .IP \fB-o\fR +/* Do not release root privileges when processing a non-root +/* input file. By default, \fBpostalias\fR(1) drops root privileges +/* and runs as the source file owner instead. +/* .IP \fB-p\fR +/* Do not inherit the file access permissions from the input file +/* when creating a new file. Instead, create a new file with default +/* access permissions (mode 0644). +/* .IP "\fB-q \fIkey\fR" +/* Search the specified maps for \fIkey\fR and write the first value +/* found to the standard output stream. The exit status is zero +/* when the requested information was found. +/* +/* Note: this performs a single query with the key as specified, +/* and does not make iterative queries with substrings of the +/* key as described in the aliases(5) manual page. +/* +/* If a key value of \fB-\fR is specified, the program reads key +/* values from the standard input stream and writes one line of +/* \fIkey: value\fR output for each key that was found. The exit +/* status is zero when at least one of the requested keys was found. +/* .IP \fB-r\fR +/* When updating a table, do not complain about attempts to update +/* existing entries, and make those updates anyway. +/* .IP \fB-s\fR +/* Retrieve all database elements, and write one line of +/* \fIkey: value\fR output for each element. The elements are +/* printed in database order, which is not necessarily the same +/* as the original input order. +/* This feature is available in Postfix version 2.2 and later, +/* and is not available for all database types. +/* .IP \fB-u\fR +/* Disable UTF-8 support. UTF-8 support is enabled by default +/* when "smtputf8_enable = yes". It requires that keys and +/* values are valid UTF-8 strings. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. +/* .IP \fB-w\fR +/* When updating a table, do not complain about attempts to update +/* existing entries, and ignore those attempts. +/* .PP +/* Arguments: +/* .IP \fIfile_type\fR +/* The database type. To find out what types are supported, use +/* the "\fBpostconf -m\fR" command. +/* +/* The \fBpostalias\fR(1) command can query any supported file type, +/* but it can create only the following file types: +/* .RS +/* .IP \fBbtree\fR +/* The output is a btree file, named \fIfile_name\fB.db\fR. +/* This is available on systems with support for \fBdb\fR databases. +/* .IP \fBcdb\fR +/* The output is one file named \fIfile_name\fB.cdb\fR. +/* This is available on systems with support for \fBcdb\fR databases. +/* .IP \fBdbm\fR +/* The output consists of two files, named \fIfile_name\fB.pag\fR and +/* \fIfile_name\fB.dir\fR. +/* This is available on systems with support for \fBdbm\fR databases. +/* .IP \fBfail\fR +/* A table that reliably fails all requests. The lookup table +/* name is used for logging only. This table exists to simplify +/* Postfix error tests. +/* .IP \fBhash\fR +/* The output is a hashed file, named \fIfile_name\fB.db\fR. +/* This is available on systems with support for \fBdb\fR databases. +/* .IP \fBlmdb\fR +/* The output is a btree-based file, named \fIfile_name\fB.lmdb\fR. +/* \fBlmdb\fR supports concurrent writes and reads from different +/* processes, unlike other supported file-based tables. +/* This is available on systems with support for \fBlmdb\fR databases. +/* .IP \fBsdbm\fR +/* The output consists of two files, named \fIfile_name\fB.pag\fR and +/* \fIfile_name\fB.dir\fR. +/* This is available on systems with support for \fBsdbm\fR databases. +/* .PP +/* When no \fIfile_type\fR is specified, the software uses the database +/* type specified via the \fBdefault_database_type\fR configuration +/* parameter. +/* The default value for this parameter depends on the host environment. +/* .RE +/* .IP \fIfile_name\fR +/* The name of the alias database source file when creating a database. +/* DIAGNOSTICS +/* Problems are logged to the standard error stream and to +/* \fBsyslogd\fR(8) or \fBpostlogd\fR(8). No output means that +/* no problems were detected. Duplicate entries are skipped and are +/* flagged with a warning. +/* +/* \fBpostalias\fR(1) terminates with zero exit status in case of success +/* (including successful "\fBpostalias -q\fR" lookup) and terminates +/* with non-zero exit status in case of failure. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP \fBMAIL_CONFIG\fR +/* Directory with Postfix configuration files. +/* .IP \fBMAIL_VERBOSE\fR +/* Enable verbose logging for debugging purposes. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBalias_database (see 'postconf -d' output)\fR" +/* The alias databases for \fBlocal\fR(8) delivery that are updated with +/* "\fBnewaliases\fR" or with "\fBsendmail -bi\fR". +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBberkeley_db_create_buffer_size (16777216)\fR" +/* The per-table I/O buffer size for programs that create Berkeley DB +/* hash or btree tables. +/* .IP "\fBberkeley_db_read_buffer_size (131072)\fR" +/* The per-table I/O buffer size for programs that read Berkeley DB +/* hash or btree tables. +/* .IP "\fBdefault_database_type (see 'postconf -d' output)\fR" +/* The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1) +/* and \fBpostmap\fR(1) commands. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment variables that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable preliminary SMTPUTF8 support for the protocols described +/* in RFC 6531, RFC 6532, and RFC 6533. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 2.11 and later: +/* .IP "\fBlmdb_map_size (16777216)\fR" +/* The initial OpenLDAP LMDB database size limit in bytes. +/* STANDARDS +/* RFC 822 (ARPA Internet Text Messages) +/* SEE ALSO +/* aliases(5), format of alias database input file. +/* local(8), Postfix local delivery agent. +/* postconf(1), supported database types +/* postconf(5), configuration parameters +/* postmap(1), create/update/query lookup tables +/* newaliases(1), Sendmail compatibility interface. +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* DATABASE_README, Postfix lookup table overview +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#define STR vstring_str +#define LEN VSTRING_LEN + +#define POSTALIAS_FLAG_AS_OWNER (1<<0) /* open dest as owner of source */ +#define POSTALIAS_FLAG_SAVE_PERM (1<<1) /* copy access permission + * from source */ + +/* postalias - create or update alias database */ + +static void postalias(char *map_type, char *path_name, int postalias_flags, + int open_flags, int dict_flags) +{ + VSTREAM *NOCLOBBER source_fp; + VSTRING *line_buffer; + MKMAP *mkmap; + int lineno; + int last_line; + VSTRING *key_buffer; + VSTRING *value_buffer; + TOK822 *tok_list; + TOK822 *key_list; + TOK822 *colon; + TOK822 *value_list; + struct stat st; + mode_t saved_mask; + + /* + * Initialize. + */ + line_buffer = vstring_alloc(100); + key_buffer = vstring_alloc(100); + value_buffer = vstring_alloc(100); + if ((open_flags & O_TRUNC) == 0) { + /* Incremental mode. */ + source_fp = VSTREAM_IN; + vstream_control(source_fp, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END); + } else { + /* Create database. */ + if (strcmp(map_type, DICT_TYPE_PROXY) == 0) + msg_fatal("can't create maps via the proxy service"); + dict_flags |= DICT_FLAG_BULK_UPDATE; + if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", path_name); + } + if (fstat(vstream_fileno(source_fp), &st) < 0) + msg_fatal("fstat %s: %m", path_name); + + /* + * Turn off group/other read permissions as indicated in the source file. + */ + if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) + saved_mask = umask(022 | (~st.st_mode & 077)); + + /* + * If running as root, run as the owner of the source file, so that the + * result shows proper ownership, and so that a bug in postalias does not + * allow privilege escalation. + */ + if ((postalias_flags & POSTALIAS_FLAG_AS_OWNER) && getuid() == 0 + && (st.st_uid != geteuid() || st.st_gid != getegid())) + set_eugid(st.st_uid, st.st_gid); + + /* + * Open the database, create it when it does not exist, truncate it when + * it does exist, and lock out any spectators. + */ + mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags); + + /* + * And restore the umask, in case it matters. + */ + if ((postalias_flags & POSTALIAS_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) + umask(saved_mask); + + /* + * Trap "exceptions" so that we can restart a bulk-mode update after a + * recoverable error. + */ + for (;;) { + if (dict_isjmp(mkmap->dict) != 0 + && dict_setjmp(mkmap->dict) != 0 + && vstream_fseek(source_fp, SEEK_SET, 0) < 0) + msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp)); + + /* + * Add records to the database. + */ + last_line = 0; + while (readllines(line_buffer, source_fp, &last_line, &lineno)) { + + /* + * First some UTF-8 checks sans casefolding. + */ + if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE) + && !allascii(STR(line_buffer)) + && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) { + msg_warn("%s, line %d: non-UTF-8 input \"%s\"" + " -- ignoring this line", + VSTREAM_PATH(source_fp), lineno, STR(line_buffer)); + continue; + } + + /* + * Tokenize the input, so that we do the right thing when a + * quoted localpart contains special characters such as "@", ":" + * and so on. + */ + if ((tok_list = tok822_scan(STR(line_buffer), (TOK822 **) 0)) == 0) + continue; + + /* + * Enforce the key:value format. Disallow missing keys, + * multi-address keys, or missing values. In order to specify an + * empty string or value, enclose it in double quotes. + */ + if ((colon = tok822_find_type(tok_list, ':')) == 0 + || colon->prev == 0 || colon->next == 0 + || tok822_rfind_type(colon, ',')) { + msg_warn("%s, line %d: need name:value pair", + VSTREAM_PATH(source_fp), lineno); + tok822_free_tree(tok_list); + continue; + } + + /* + * Key must be local. XXX We should use the Postfix rewriting and + * resolving services to handle all address forms correctly. + * However, we can't count on the mail system being up when the + * alias database is being built, so we're guessing a bit. + */ + if (tok822_rfind_type(colon, '@') || tok822_rfind_type(colon, '%')) { + msg_warn("%s, line %d: name must be local", + VSTREAM_PATH(source_fp), lineno); + tok822_free_tree(tok_list); + continue; + } + + /* + * Split the input into key and value parts, and convert from + * token representation back to string representation. Convert + * the key to internal (unquoted) form, because the resolver + * produces addresses in internal form. Convert the value to + * external (quoted) form, because it will have to be re-parsed + * upon lookup. Discard the token representation when done. + */ + key_list = tok_list; + tok_list = 0; + value_list = tok822_cut_after(colon); + tok822_unlink(colon); + tok822_free(colon); + + tok822_internalize(key_buffer, key_list, TOK822_STR_DEFL); + tok822_free_tree(key_list); + + tok822_externalize(value_buffer, value_list, TOK822_STR_DEFL); + tok822_free_tree(value_list); + + /* + * Store the value under a case-insensitive key. + */ + mkmap_append(mkmap, STR(key_buffer), STR(value_buffer)); + if (mkmap->dict->error) + msg_fatal("table %s:%s: write error: %m", + mkmap->dict->type, mkmap->dict->name); + } + break; + } + + /* + * Update or append sendmail and NIS signatures. + */ + if ((open_flags & O_TRUNC) == 0) + mkmap->dict->flags |= DICT_FLAG_DUP_REPLACE; + + /* + * Sendmail compatibility: add the @:@ signature to indicate that the + * database is complete. This might be needed by NIS clients running + * sendmail. + */ + mkmap_append(mkmap, "@", "@"); + if (mkmap->dict->error) + msg_fatal("table %s:%s: write error: %m", + mkmap->dict->type, mkmap->dict->name); + + /* + * NIS compatibility: add time and master info. Unlike other information, + * this information MUST be written without a trailing null appended to + * key or value. + */ + mkmap->dict->flags &= ~DICT_FLAG_TRY1NULL; + mkmap->dict->flags |= DICT_FLAG_TRY0NULL; + vstring_sprintf(value_buffer, "%010ld", (long) time((time_t *) 0)); +#if (defined(HAS_NIS) || defined(HAS_NISPLUS)) + mkmap->dict->flags &= ~DICT_FLAG_FOLD_FIX; + mkmap_append(mkmap, "YP_LAST_MODIFIED", STR(value_buffer)); + mkmap_append(mkmap, "YP_MASTER_NAME", var_myhostname); +#endif + + /* + * Close the alias database, and release the lock. + */ + mkmap_close(mkmap); + + /* + * Cleanup. We're about to terminate, but it is a good sanity check. + */ + vstring_free(value_buffer); + vstring_free(key_buffer); + vstring_free(line_buffer); + if (source_fp != VSTREAM_IN) + vstream_fclose(source_fp); +} + +/* postalias_queries - apply multiple requests from stdin */ + +static int postalias_queries(VSTREAM *in, char **maps, const int map_count, + const int dict_flags) +{ + int found = 0; + VSTRING *keybuf = vstring_alloc(100); + DICT **dicts; + const char *map_name; + const char *value; + int n; + + /* + * Sanity check. + */ + if (map_count <= 0) + msg_panic("postalias_queries: bad map count"); + + /* + * Prepare to open maps lazily. + */ + dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); + for (n = 0; n < map_count; n++) + dicts[n] = 0; + + /* + * Perform all queries. Open maps on the fly, to avoid opening + * unnecessary maps. + */ + while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) { + for (n = 0; n < map_count; n++) { + if (dicts[n] == 0) + dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? + dict_open3(maps[n], map_name, O_RDONLY, dict_flags) : + dict_open3(var_db_type, maps[n], O_RDONLY, dict_flags)); + if ((value = dict_get(dicts[n], STR(keybuf))) != 0) { + if (*value == 0) { + msg_warn("table %s:%s: key %s: empty string result is not allowed", + dicts[n]->type, dicts[n]->name, STR(keybuf)); + msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", + dicts[n]->type, dicts[n]->name); + } + vstream_printf("%s: %s\n", STR(keybuf), value); + found = 1; + break; + } + if (dicts[n]->error) + msg_fatal("table %s:%s: query error: %m", + dicts[n]->type, dicts[n]->name); + } + } + if (found) + vstream_fflush(VSTREAM_OUT); + + /* + * Cleanup. + */ + for (n = 0; n < map_count; n++) + if (dicts[n]) + dict_close(dicts[n]); + myfree((void *) dicts); + vstring_free(keybuf); + + return (found); +} + +/* postalias_query - query a map and print the result to stdout */ + +static int postalias_query(const char *map_type, const char *map_name, + const char *key, int dict_flags) +{ + DICT *dict; + const char *value; + + dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags); + if ((value = dict_get(dict, key)) != 0) { + if (*value == 0) { + msg_warn("table %s:%s: key %s: empty string result is not allowed", + map_type, map_name, key); + msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", + map_type, map_name); + } + vstream_printf("%s\n", value); + } + if (dict->error) + msg_fatal("table %s:%s: query error: %m", dict->type, dict->name); + vstream_fflush(VSTREAM_OUT); + dict_close(dict); + return (value != 0); +} + +/* postalias_deletes - apply multiple requests from stdin */ + +static int postalias_deletes(VSTREAM *in, char **maps, const int map_count, + int dict_flags) +{ + int found = 0; + VSTRING *keybuf = vstring_alloc(100); + DICT **dicts; + const char *map_name; + int n; + int open_flags; + + /* + * Sanity check. + */ + if (map_count <= 0) + msg_panic("postalias_deletes: bad map count"); + + /* + * Open maps ahead of time. + */ + dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); + for (n = 0; n < map_count; n++) { + map_name = split_at(maps[n], ':'); + if (map_name && strcmp(maps[n], DICT_TYPE_PROXY) == 0) + open_flags = O_RDWR | O_CREAT; /* XXX */ + else + open_flags = O_RDWR; + dicts[n] = (map_name != 0 ? + dict_open3(maps[n], map_name, open_flags, dict_flags) : + dict_open3(var_db_type, maps[n], open_flags, dict_flags)); + } + + /* + * Perform all requests. + */ + while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) { + for (n = 0; n < map_count; n++) { + found |= (dict_del(dicts[n], STR(keybuf)) == 0); + if (dicts[n]->error) + msg_fatal("table %s:%s: delete error: %m", + dicts[n]->type, dicts[n]->name); + } + } + + /* + * Cleanup. + */ + for (n = 0; n < map_count; n++) + if (dicts[n]) + dict_close(dicts[n]); + myfree((void *) dicts); + vstring_free(keybuf); + + return (found); +} + +/* postalias_delete - delete a key value pair from a map */ + +static int postalias_delete(const char *map_type, const char *map_name, + const char *key, int dict_flags) +{ + DICT *dict; + int status; + int open_flags; + + if (strcmp(map_type, DICT_TYPE_PROXY) == 0) + open_flags = O_RDWR | O_CREAT; /* XXX */ + else + open_flags = O_RDWR; + dict = dict_open3(map_type, map_name, open_flags, dict_flags); + status = dict_del(dict, key); + if (dict->error) + msg_fatal("table %s:%s: delete error: %m", dict->type, dict->name); + dict_close(dict); + return (status == 0); +} + +/* postalias_seq - print all map entries to stdout */ + +static void postalias_seq(const char *map_type, const char *map_name, + int dict_flags) +{ + DICT *dict; + const char *key; + const char *value; + int func; + + if (strcmp(map_type, DICT_TYPE_PROXY) == 0) + msg_fatal("can't sequence maps via the proxy service"); + dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags); + for (func = DICT_SEQ_FUN_FIRST; /* void */ ; func = DICT_SEQ_FUN_NEXT) { + if (dict_seq(dict, func, &key, &value) != 0) + break; + if (*key == 0) { + msg_warn("table %s:%s: empty lookup key value is not allowed", + map_type, map_name); + } else if (*value == 0) { + msg_warn("table %s:%s: key %s: empty string result is not allowed", + map_type, map_name, key); + msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", + map_type, map_name); + } + vstream_printf("%s: %s\n", key, value); + } + if (dict->error) + msg_fatal("table %s:%s: sequence error: %m", dict->type, dict->name); + vstream_fflush(VSTREAM_OUT); + dict_close(dict); +} + +/* usage - explain */ + +static NORETURN usage(char *myname) +{ + msg_fatal("usage: %s [-Nfinoprsuvw] [-c config_dir] [-d key] [-q key] [map_type:]file...", + myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + char *path_name; + int ch; + int fd; + char *slash; + struct stat st; + int postalias_flags = POSTALIAS_FLAG_AS_OWNER | POSTALIAS_FLAG_SAVE_PERM; + int open_flags = O_RDWR | O_CREAT | O_TRUNC; + int dict_flags = (DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + char *query = 0; + char *delkey = 0; + int sequence = 0; + int found; + ARGV *import_env; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Be consistent with file permissions. + */ + umask(022); + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Process environment options as early as we can. We are not set-uid, + * and we are supposed to be running in a controlled environment. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + + /* + * Initialize. Set up logging. Read the global configuration file after + * parsing command-line arguments. + */ + if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) + argv[0] = slash + 1; + msg_vstream_init(argv[0], VSTREAM_ERR); + maillog_client_init(mail_task(argv[0]), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "Nc:d:finopq:rsuvw")) > 0) { + switch (ch) { + default: + usage(argv[0]); + break; + case 'N': + dict_flags |= DICT_FLAG_TRY1NULL; + dict_flags &= ~DICT_FLAG_TRY0NULL; + break; + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'd': + if (sequence || query || delkey) + msg_fatal("specify only one of -s -q or -d"); + delkey = optarg; + break; + case 'f': + dict_flags &= ~DICT_FLAG_FOLD_FIX; + break; + case 'i': + open_flags &= ~O_TRUNC; + break; + case 'n': + dict_flags |= DICT_FLAG_TRY0NULL; + dict_flags &= ~DICT_FLAG_TRY1NULL; + break; + case 'o': + postalias_flags &= ~POSTALIAS_FLAG_AS_OWNER; + break; + case 'p': + postalias_flags &= ~POSTALIAS_FLAG_SAVE_PERM; + break; + case 'q': + if (sequence || query || delkey) + msg_fatal("specify only one of -s -q or -d"); + query = optarg; + break; + case 'r': + dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE); + dict_flags |= DICT_FLAG_DUP_REPLACE; + break; + case 's': + if (query || delkey) + msg_fatal("specify only one of -s or -q or -d"); + sequence = 1; + break; + case 'u': + dict_flags &= ~DICT_FLAG_UTF8_REQUEST; + break; + case 'v': + msg_verbose++; + break; + case 'w': + dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_REPLACE); + dict_flags |= DICT_FLAG_DUP_IGNORE; + break; + } + } + mail_conf_read(); + /* Enforce consistent operation of different Postfix parts. */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + update_env(import_env->argv); + argv_free(import_env); + /* Re-evaluate mail_task() after reading main.cf. */ + maillog_client_init(mail_task(argv[0]), MAILLOG_CLIENT_FLAG_NONE); + mail_dict_init(); + + /* + * Use the map type specified by the user, or fall back to a default + * database type. + */ + if (delkey) { /* remove entry */ + if (optind + 1 > argc) + usage(argv[0]); + if (strcmp(delkey, "-") == 0) + exit(postalias_deletes(VSTREAM_IN, argv + optind, argc - optind, + dict_flags | DICT_FLAG_LOCK) == 0); + found = 0; + while (optind < argc) { + if ((path_name = split_at(argv[optind], ':')) != 0) { + found |= postalias_delete(argv[optind], path_name, delkey, + dict_flags | DICT_FLAG_LOCK); + } else { + found |= postalias_delete(var_db_type, argv[optind], delkey, + dict_flags | DICT_FLAG_LOCK); + } + optind++; + } + exit(found ? 0 : 1); + } else if (query) { /* query map(s) */ + if (optind + 1 > argc) + usage(argv[0]); + if (strcmp(query, "-") == 0) + exit(postalias_queries(VSTREAM_IN, argv + optind, argc - optind, + dict_flags | DICT_FLAG_LOCK) == 0); + while (optind < argc) { + if ((path_name = split_at(argv[optind], ':')) != 0) { + found = postalias_query(argv[optind], path_name, query, + dict_flags | DICT_FLAG_LOCK); + } else { + found = postalias_query(var_db_type, argv[optind], query, + dict_flags | DICT_FLAG_LOCK); + } + if (found) + exit(0); + optind++; + } + exit(1); + } else if (sequence) { + while (optind < argc) { + if ((path_name = split_at(argv[optind], ':')) != 0) { + postalias_seq(argv[optind], path_name, + dict_flags | DICT_FLAG_LOCK); + } else { + postalias_seq(var_db_type, argv[optind], + dict_flags | DICT_FLAG_LOCK); + } + exit(0); + } + exit(1); + } else { /* create/update map(s) */ + if (optind + 1 > argc) + usage(argv[0]); + while (optind < argc) { + if ((path_name = split_at(argv[optind], ':')) != 0) { + postalias(argv[optind], path_name, postalias_flags, + open_flags, dict_flags); + } else { + postalias(var_db_type, argv[optind], postalias_flags, + open_flags, dict_flags); + } + optind++; + } + exit(0); + } +} diff --git a/src/postcat/.indent.pro b/src/postcat/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postcat/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postcat/.printfck b/src/postcat/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/postcat/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/postcat/Makefile.in b/src/postcat/Makefile.in new file mode 100644 index 0000000..06c5eb8 --- /dev/null +++ b/src/postcat/Makefile.in @@ -0,0 +1,128 @@ +SHELL = /bin/sh +SRCS = postcat.c +OBJS = postcat.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = postcat +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: default_test ebh_test e_test b_test h_test eb_test eh_test bh_test + +root_tests: + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +default_test: test-queue-file default_test.ref + ./postcat test-queue-file >postcat.tmp 2>&1 + diff default_test.ref postcat.tmp + rm -f postcat.tmp + +ebh_test: test-queue-file default_test.ref + ./postcat -ebh test-queue-file >postcat.tmp 2>&1 + diff default_test.ref postcat.tmp + rm -f postcat.tmp + +e_test: test-queue-file e_test.ref + ./postcat -e test-queue-file >postcat.tmp 2>&1 + diff e_test.ref postcat.tmp + rm -f postcat.tmp + +b_test: test-queue-file b_test.ref + ./postcat -b test-queue-file >postcat.tmp 2>&1 + diff b_test.ref postcat.tmp + rm -f postcat.tmp + +h_test: test-queue-file h_test.ref + ./postcat -h test-queue-file >postcat.tmp 2>&1 + diff h_test.ref postcat.tmp + rm -f postcat.tmp + +eb_test: test-queue-file eb_test.ref + ./postcat -eb test-queue-file >postcat.tmp 2>&1 + diff eb_test.ref postcat.tmp + rm -f postcat.tmp + +eh_test: test-queue-file eh_test.ref + ./postcat -eh test-queue-file >postcat.tmp 2>&1 + diff eh_test.ref postcat.tmp + rm -f postcat.tmp + +bh_test: test-queue-file bh_test.ref + ./postcat -bh test-queue-file >postcat.tmp 2>&1 + diff bh_test.ref postcat.tmp + rm -f postcat.tmp + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postcat.o: ../../include/argv.h +postcat.o: ../../include/attr.h +postcat.o: ../../include/check_arg.h +postcat.o: ../../include/clean_env.h +postcat.o: ../../include/htable.h +postcat.o: ../../include/iostuff.h +postcat.o: ../../include/is_header.h +postcat.o: ../../include/lex_822.h +postcat.o: ../../include/mail_conf.h +postcat.o: ../../include/mail_params.h +postcat.o: ../../include/mail_parm_split.h +postcat.o: ../../include/mail_proto.h +postcat.o: ../../include/mail_queue.h +postcat.o: ../../include/mail_version.h +postcat.o: ../../include/msg.h +postcat.o: ../../include/msg_vstream.h +postcat.o: ../../include/mymalloc.h +postcat.o: ../../include/nvtable.h +postcat.o: ../../include/rec_type.h +postcat.o: ../../include/record.h +postcat.o: ../../include/stringops.h +postcat.o: ../../include/sys_defs.h +postcat.o: ../../include/vbuf.h +postcat.o: ../../include/vstream.h +postcat.o: ../../include/vstring.h +postcat.o: ../../include/vstring_vstream.h +postcat.o: ../../include/warn_stat.h +postcat.o: postcat.c diff --git a/src/postcat/b_test.ref b/src/postcat/b_test.ref new file mode 100644 index 0000000..bacff0c --- /dev/null +++ b/src/postcat/b_test.ref @@ -0,0 +1,2 @@ + +text diff --git a/src/postcat/bh_test.ref b/src/postcat/bh_test.ref new file mode 100644 index 0000000..bfe9e2d --- /dev/null +++ b/src/postcat/bh_test.ref @@ -0,0 +1,9 @@ +Received: by hades.porcupine.org (Postfix, from userid 1001) + id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) +From: me@porcupine.org +To: you@porcupine.org +Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> +Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) +Subject: hey! + +text diff --git a/src/postcat/default_test.ref b/src/postcat/default_test.ref new file mode 100644 index 0000000..eede38b --- /dev/null +++ b/src/postcat/default_test.ref @@ -0,0 +1,21 @@ +*** ENVELOPE RECORDS test-queue-file *** +message_size: 332 182 1 0 332 +message_arrival_time: Sun Jan 21 13:32:59 2007 +create_time: Sun Jan 21 13:33:08 2007 +named_attribute: rewrite_context=local +sender_fullname: Wietse Venema +sender: me@porcupine.org +*** MESSAGE CONTENTS test-queue-file *** +Received: by hades.porcupine.org (Postfix, from userid 1001) + id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) +From: me@porcupine.org +To: you@porcupine.org +Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> +Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) +Subject: hey! + +text +*** HEADER EXTRACTED test-queue-file *** +original_recipient: you@porcupine.org +recipient: you@porcupine.org +*** MESSAGE FILE END test-queue-file *** diff --git a/src/postcat/e_test.ref b/src/postcat/e_test.ref new file mode 100644 index 0000000..308b0df --- /dev/null +++ b/src/postcat/e_test.ref @@ -0,0 +1,12 @@ +*** ENVELOPE RECORDS test-queue-file *** +message_size: 332 182 1 0 332 +message_arrival_time: Sun Jan 21 13:32:59 2007 +create_time: Sun Jan 21 13:33:08 2007 +named_attribute: rewrite_context=local +sender_fullname: Wietse Venema +sender: me@porcupine.org +*** MESSAGE CONTENTS test-queue-file *** +*** HEADER EXTRACTED test-queue-file *** +original_recipient: you@porcupine.org +recipient: you@porcupine.org +*** MESSAGE FILE END test-queue-file *** diff --git a/src/postcat/eb_test.ref b/src/postcat/eb_test.ref new file mode 100644 index 0000000..b61beb2 --- /dev/null +++ b/src/postcat/eb_test.ref @@ -0,0 +1,14 @@ +*** ENVELOPE RECORDS test-queue-file *** +message_size: 332 182 1 0 332 +message_arrival_time: Sun Jan 21 13:32:59 2007 +create_time: Sun Jan 21 13:33:08 2007 +named_attribute: rewrite_context=local +sender_fullname: Wietse Venema +sender: me@porcupine.org +*** MESSAGE CONTENTS test-queue-file *** + +text +*** HEADER EXTRACTED test-queue-file *** +original_recipient: you@porcupine.org +recipient: you@porcupine.org +*** MESSAGE FILE END test-queue-file *** diff --git a/src/postcat/eh_test.ref b/src/postcat/eh_test.ref new file mode 100644 index 0000000..3142553 --- /dev/null +++ b/src/postcat/eh_test.ref @@ -0,0 +1,19 @@ +*** ENVELOPE RECORDS test-queue-file *** +message_size: 332 182 1 0 332 +message_arrival_time: Sun Jan 21 13:32:59 2007 +create_time: Sun Jan 21 13:33:08 2007 +named_attribute: rewrite_context=local +sender_fullname: Wietse Venema +sender: me@porcupine.org +*** MESSAGE CONTENTS test-queue-file *** +Received: by hades.porcupine.org (Postfix, from userid 1001) + id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) +From: me@porcupine.org +To: you@porcupine.org +Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> +Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) +Subject: hey! +*** HEADER EXTRACTED test-queue-file *** +original_recipient: you@porcupine.org +recipient: you@porcupine.org +*** MESSAGE FILE END test-queue-file *** diff --git a/src/postcat/h_test.ref b/src/postcat/h_test.ref new file mode 100644 index 0000000..e1a8025 --- /dev/null +++ b/src/postcat/h_test.ref @@ -0,0 +1,7 @@ +Received: by hades.porcupine.org (Postfix, from userid 1001) + id DE040290405; Sun, 21 Jan 2007 13:33:08 -0500 (EST) +From: me@porcupine.org +To: you@porcupine.org +Message-Id: <20060725192735.5EC2D29013F@hades.porcupine.org> +Date: Tue, 25 Jul 2006 15:27:19 -0400 (EDT) +Subject: hey! diff --git a/src/postcat/postcat.c b/src/postcat/postcat.c new file mode 100644 index 0000000..36f2740 --- /dev/null +++ b/src/postcat/postcat.c @@ -0,0 +1,598 @@ +/*++ +/* NAME +/* postcat 1 +/* SUMMARY +/* show Postfix queue file contents +/* SYNOPSIS +/* \fBpostcat\fR [\fB-bdehnoqv\fR] [\fB-c \fIconfig_dir\fR] [\fIfiles\fR...] +/* DESCRIPTION +/* The \fBpostcat\fR(1) command prints the contents of the +/* named \fIfiles\fR in human-readable form. The files are +/* expected to be in Postfix queue file format. If no \fIfiles\fR +/* are specified on the command line, the program reads from +/* standard input. +/* +/* By default, \fBpostcat\fR(1) shows the envelope and message +/* content, as if the options \fB-beh\fR were specified. To +/* view message content only, specify \fB-bh\fR (Postfix 2.7 +/* and later). +/* +/* Options: +/* .IP \fB-b\fR +/* Show body content. The \fB-b\fR option starts producing +/* output at the first non-header line, and stops when the end +/* of the message is reached. +/* .sp +/* This feature is available in Postfix 2.7 and later. +/* .IP "\fB-c \fIconfig_dir\fR" +/* The \fBmain.cf\fR configuration file is in the named directory +/* instead of the default configuration directory. +/* .IP \fB-d\fR +/* Print the decimal type of each record. +/* .IP \fB-e\fR +/* Show message envelope content. +/* .sp +/* This feature is available in Postfix 2.7 and later. +/* .IP \fB-h\fR +/* Show message header content. The \fB-h\fR option produces +/* output from the beginning of the message up to, but not +/* including, the first non-header line. +/* .sp +/* This feature is available in Postfix 2.7 and later. +/* .IP \fB-o\fR +/* Print the queue file offset of each record. +/* .IP \fB-q\fR +/* Search the Postfix queue for the named \fIfiles\fR instead +/* of taking the names literally. +/* +/* This feature is available in Postfix 2.0 and later. +/* .IP \fB-r\fR +/* Print records in file order, don't follow pointer records. +/* +/* This feature is available in Postfix 3.7 and later. +/* .IP "\fB-s \fIoffset\fR" +/* Skip to the specified queue file offset. +/* +/* This feature is available in Postfix 3.7 and later. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP \fBMAIL_CONFIG\fR +/* Directory with Postfix configuration files. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment parameters that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* FILES +/* /var/spool/postfix, Postfix queue directory +/* SEE ALSO +/* postconf(5), Postfix configuration +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* sscanf() */ + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#define PC_FLAG_SEARCH_QUEUE (1<<0) /* search queue */ +#define PC_FLAG_PRINT_OFFSET (1<<1) /* print record offsets */ +#define PC_FLAG_PRINT_ENV (1<<2) /* print envelope records */ +#define PC_FLAG_PRINT_HEADER (1<<3) /* print header records */ +#define PC_FLAG_PRINT_BODY (1<<4) /* print body records */ +#define PC_FLAG_PRINT_RTYPE_DEC (1<<5) /* print decimal record type */ +#define PC_FLAG_PRINT_RTYPE_SYM (1<<6) /* print symbolic record type */ +#define PC_FLAG_RAW (1<<7) /* don't follow pointers */ + +#define PC_MASK_PRINT_TEXT (PC_FLAG_PRINT_HEADER | PC_FLAG_PRINT_BODY) +#define PC_MASK_PRINT_ALL (PC_FLAG_PRINT_ENV | PC_MASK_PRINT_TEXT) + + /* + * State machine. + */ +#define PC_STATE_ENV 0 /* initial or extracted envelope */ +#define PC_STATE_HEADER 1 /* primary header */ +#define PC_STATE_BODY 2 /* other */ + +off_t start_offset = 0; + +#define STR vstring_str +#define LEN VSTRING_LEN + +/* postcat - visualize Postfix queue file contents */ + +static void postcat(VSTREAM *fp, VSTRING *buffer, int flags) +{ + int prev_type = 0; + int rec_type; + struct timeval tv; + time_t time; + int ch; + off_t offset; + const char *error_text; + char *attr_name; + char *attr_value; + int rec_flags = (msg_verbose ? REC_FLAG_NONE : REC_FLAG_DEFAULT); + int state; /* state machine, input type */ + int do_print; /* state machine, output control */ + long data_offset; /* state machine, read optimization */ + long data_size; /* state machine, read optimization */ + +#define TEXT_RECORD(rec_type) \ + (rec_type == REC_TYPE_CONT || rec_type == REC_TYPE_NORM) + + /* + * Skip over or absorb some bytes. + */ + if (start_offset > 0) { + if (fp == VSTREAM_IN) { + for (offset = 0; offset < start_offset; offset++) + if (VSTREAM_GETC(fp) == VSTREAM_EOF) + msg_fatal("%s: skip %ld bytes failed after %ld", + VSTREAM_PATH(fp), (long) start_offset, + (long) offset); + } else { + if (vstream_fseek(fp, start_offset, SEEK_SET) < 0) + msg_fatal("%s: seek to %ld: %m", + VSTREAM_PATH(fp), (long) start_offset); + } + } + + /* + * See if this is a plausible file. + */ + if (start_offset == 0 && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) { + if (!strchr(REC_TYPE_ENVELOPE, ch)) { + msg_warn("%s: input is not a valid queue file", VSTREAM_PATH(fp)); + return; + } + vstream_ungetc(fp, ch); + } + + /* + * Other preliminaries. + */ + if (start_offset == 0 && (flags & PC_FLAG_PRINT_ENV)) + vstream_printf("*** ENVELOPE RECORDS %s ***\n", + VSTREAM_PATH(fp)); + state = PC_STATE_ENV; + do_print = (flags & PC_FLAG_PRINT_ENV); + data_offset = data_size = -1; + + /* + * Now look at the rest. + */ + for (;;) { + if (flags & PC_FLAG_PRINT_OFFSET) + offset = vstream_ftell(fp); + rec_type = rec_get_raw(fp, buffer, 0, rec_flags); + if (rec_type == REC_TYPE_ERROR) + msg_fatal("record read error"); + if (rec_type == REC_TYPE_EOF) + break; + + /* + * First inspect records that have side effects on the (envelope, + * header, body) state machine or on the record reading order. + * + * XXX Comments marked "Optimization:" identify subtle code that will + * likely need to be revised when the queue file organization is + * changed. + */ +#define PRINT_MARKER(flags, fp, offset, type, text) do { \ + if ((flags) & PC_FLAG_PRINT_OFFSET) \ + vstream_printf("%9lu ", (unsigned long) (offset)); \ + if (flags & PC_FLAG_PRINT_RTYPE_DEC) \ + vstream_printf("%3d ", (type)); \ + vstream_printf("*** %s %s ***\n", (text), VSTREAM_PATH(fp)); \ + vstream_fflush(VSTREAM_OUT); \ +} while (0) + +#define PRINT_RECORD(flags, offset, type, value) do { \ + if ((flags) & PC_FLAG_PRINT_OFFSET) \ + vstream_printf("%9lu ", (unsigned long) (offset)); \ + if (flags & PC_FLAG_PRINT_RTYPE_DEC) \ + vstream_printf("%3d ", (type)); \ + vstream_printf("%s: %s\n", rec_type_name(rec_type), (value)); \ + vstream_fflush(VSTREAM_OUT); \ +} while (0) + + if (TEXT_RECORD(rec_type)) { + /* This is wrong when the message starts with whitespace. */ + if (state == PC_STATE_HEADER && (flags & (PC_MASK_PRINT_TEXT)) + && prev_type != REC_TYPE_CONT && TEXT_RECORD(rec_type) + && !(is_header(STR(buffer)) || IS_SPACE_TAB(STR(buffer)[0]))) { + /* Update the state machine. */ + state = PC_STATE_BODY; + do_print = (flags & PC_FLAG_PRINT_BODY); + /* Optimization: terminate if nothing left to print. */ + if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) == 0) + break; + /* Optimization: skip to extracted segment marker. */ + if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) + && data_offset >= 0 && data_size >= 0 + && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0) + msg_fatal("seek error: %m"); + } + /* Optional output happens further down below. */ + } else if (rec_type == REC_TYPE_MESG) { + /* Sanity check. */ + if (state != PC_STATE_ENV) + msg_warn("%s: out-of-order message content marker", + VSTREAM_PATH(fp)); + /* Optional output. */ + if (flags & PC_FLAG_PRINT_ENV) + PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE CONTENTS"); + /* Optimization: skip to extracted segment marker. */ + if ((flags & PC_MASK_PRINT_TEXT) == 0 + && data_offset >= 0 && data_size >= 0 + && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0) + msg_fatal("seek error: %m"); + /* Update the state machine, even when skipping. */ + state = PC_STATE_HEADER; + do_print = (flags & PC_FLAG_PRINT_HEADER); + continue; + } else if (rec_type == REC_TYPE_XTRA) { + /* Sanity check. */ + if (state != PC_STATE_HEADER && state != PC_STATE_BODY) + msg_warn("%s: out-of-order extracted segment marker", + VSTREAM_PATH(fp)); + /* Optional output (terminate preceding header/body line). */ + if (do_print && prev_type == REC_TYPE_CONT) + VSTREAM_PUTCHAR('\n'); + if (flags & PC_FLAG_PRINT_ENV) + PRINT_MARKER(flags, fp, offset, rec_type, "HEADER EXTRACTED"); + /* Update the state machine. */ + state = PC_STATE_ENV; + do_print = (flags & PC_FLAG_PRINT_ENV); + /* Optimization: terminate if nothing left to print. */ + if (do_print == 0) + break; + continue; + } else if (rec_type == REC_TYPE_END) { + /* Sanity check. */ + if (state != PC_STATE_ENV) + msg_warn("%s: out-of-order message end marker", + VSTREAM_PATH(fp)); + /* Optional output. */ + if (flags & PC_FLAG_PRINT_ENV) + PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE FILE END"); + if (flags & PC_FLAG_RAW) + continue; + /* Terminate the state machine. */ + break; + } else if (rec_type == REC_TYPE_PTR) { + /* Optional output. */ + /* This record type is exposed only with '-v'. */ + if (do_print) + PRINT_RECORD(flags, offset, rec_type, STR(buffer)); + /* Skip to the pointer's target record. */ + if ((flags & PC_FLAG_RAW) == 0 + && rec_goto(fp, STR(buffer)) == REC_TYPE_ERROR) + msg_fatal("bad pointer record, or input is not seekable"); + continue; + } else if (rec_type == REC_TYPE_SIZE) { + /* Optional output (here before we update the state machine). */ + if (do_print) + PRINT_RECORD(flags, offset, rec_type, STR(buffer)); + /* Read the message size/offset for the state machine optimizer. */ + if (data_size >= 0 || data_offset >= 0) { + msg_warn("file contains multiple size records"); + } else { + if (sscanf(STR(buffer), "%ld %ld", &data_size, &data_offset) != 2 + || data_offset <= 0 || data_size <= 0) + msg_warn("invalid size record: %.100s", STR(buffer)); + /* Optimization: skip to the message header. */ + if ((flags & PC_FLAG_PRINT_ENV) == 0) { + if (vstream_fseek(fp, data_offset, SEEK_SET) < 0) + msg_fatal("seek error: %m"); + /* Update the state machine. */ + state = PC_STATE_HEADER; + do_print = (flags & PC_FLAG_PRINT_HEADER); + } + } + continue; + } + + /* + * Don't inspect side-effect-free records that aren't printed. + */ + if (do_print == 0) + continue; + if (flags & PC_FLAG_PRINT_OFFSET) + vstream_printf("%9lu ", (unsigned long) offset); + if (flags & PC_FLAG_PRINT_RTYPE_DEC) + vstream_printf("%3d ", rec_type); + switch (rec_type) { + case REC_TYPE_TIME: + REC_TYPE_TIME_SCAN(STR(buffer), tv); + time = tv.tv_sec; + vstream_printf("%s: %s", rec_type_name(rec_type), + asctime(localtime(&time))); + break; + case REC_TYPE_WARN: + REC_TYPE_WARN_SCAN(STR(buffer), time); + vstream_printf("%s: %s", rec_type_name(rec_type), + asctime(localtime(&time))); + break; + case REC_TYPE_CONT: /* REC_TYPE_FILT collision */ + if (state == PC_STATE_ENV) + vstream_printf("%s: ", rec_type_name(rec_type)); + else if (msg_verbose) + vstream_printf("unterminated_text: "); + vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); + if (state == PC_STATE_ENV || msg_verbose + || (flags & PC_FLAG_PRINT_OFFSET) != 0) { + rec_type = 0; + VSTREAM_PUTCHAR('\n'); + } + break; + case REC_TYPE_NORM: + if (msg_verbose) + vstream_printf("%s: ", rec_type_name(rec_type)); + vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); + VSTREAM_PUTCHAR('\n'); + break; + case REC_TYPE_DTXT: + /* This record type is exposed only with '-v'. */ + vstream_printf("%s: ", rec_type_name(rec_type)); + vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); + VSTREAM_PUTCHAR('\n'); + break; + case REC_TYPE_ATTR: + error_text = split_nameval(STR(buffer), &attr_name, &attr_value); + if (error_text != 0) { + msg_warn("%s: malformed attribute: %s: %.100s", + VSTREAM_PATH(fp), error_text, STR(buffer)); + break; + } + if (strcmp(attr_name, MAIL_ATTR_CREATE_TIME) == 0) { + time = atol(attr_value); + vstream_printf("%s: %s", MAIL_ATTR_CREATE_TIME, + asctime(localtime(&time))); + } else { + vstream_printf("%s: %s=%s\n", rec_type_name(rec_type), + attr_name, attr_value); + } + break; + default: + vstream_printf("%s: %s\n", rec_type_name(rec_type), STR(buffer)); + break; + } + prev_type = rec_type; + + /* + * In case the next record is broken. + */ + vstream_fflush(VSTREAM_OUT); + } +} + +/* usage - explain and terminate */ + +static NORETURN usage(char *myname) +{ + msg_fatal("usage: %s [-b (body text)] [-c config_dir] [-d (decimal record type)] [-e (envelope records)] [-h (header text)] [-q (access queue)] [-v] [file(s)...]", + myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + VSTRING *buffer; + VSTREAM *fp; + int ch; + int fd; + struct stat st; + int flags = 0; + static char *queue_names[] = { + MAIL_QUEUE_MAILDROP, + MAIL_QUEUE_INCOMING, + MAIL_QUEUE_ACTIVE, + MAIL_QUEUE_DEFERRED, + MAIL_QUEUE_HOLD, + MAIL_QUEUE_SAVED, + 0, + }; + char **cpp; + int tries; + ARGV *import_env; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Set up logging. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "bc:dehoqrs:v")) > 0) { + switch (ch) { + case 'b': + flags |= PC_FLAG_PRINT_BODY; + break; + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'd': + flags |= PC_FLAG_PRINT_RTYPE_DEC; + break; + case 'e': + flags |= PC_FLAG_PRINT_ENV; + break; + case 'h': + flags |= PC_FLAG_PRINT_HEADER; + break; + case 'o': + flags |= PC_FLAG_PRINT_OFFSET; + break; + case 'q': + flags |= PC_FLAG_SEARCH_QUEUE; + break; + case 'r': + flags |= PC_FLAG_RAW; + break; + case 's': + if (!alldig(optarg) || (start_offset = atol(optarg)) < 0) + msg_fatal("bad offset: %s", optarg); + break; + case 'v': + msg_verbose++; + break; + default: + usage(argv[0]); + } + } + if ((flags & PC_MASK_PRINT_ALL) == 0) + flags |= PC_MASK_PRINT_ALL; + + /* + * Further initialization... + */ + mail_conf_read(); + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + update_env(import_env->argv); + argv_free(import_env); + + /* + * Initialize. + */ + buffer = vstring_alloc(10); + + /* + * If no file names are given, copy stdin. + */ + if (argc == optind) { + vstream_control(VSTREAM_IN, + CA_VSTREAM_CTL_PATH("stdin"), + CA_VSTREAM_CTL_END); + postcat(VSTREAM_IN, buffer, flags); + } + + /* + * Copy the named queue files in the specified order. + */ + else if (flags & PC_FLAG_SEARCH_QUEUE) { + if (chdir(var_queue_dir)) + msg_fatal("chdir %s: %m", var_queue_dir); + while (optind < argc) { + if (!mail_queue_id_ok(argv[optind])) + msg_fatal("bad mail queue ID: %s", argv[optind]); + for (fp = 0, tries = 0; fp == 0 && tries < 2; tries++) + for (cpp = queue_names; fp == 0 && *cpp != 0; cpp++) + fp = mail_queue_open(*cpp, argv[optind], O_RDONLY, 0); + if (fp == 0) + msg_fatal("open queue file %s: %m", argv[optind]); + postcat(fp, buffer, flags); + if (vstream_fclose(fp)) + msg_warn("close %s: %m", argv[optind]); + optind++; + } + } + + /* + * Copy the named files in the specified order. + */ + else { + while (optind < argc) { + if ((fp = vstream_fopen(argv[optind], O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", argv[optind]); + postcat(fp, buffer, flags); + if (vstream_fclose(fp)) + msg_warn("close %s: %m", argv[optind]); + optind++; + } + } + + /* + * Clean up. + */ + vstring_free(buffer); + exit(0); +} diff --git a/src/postcat/test-queue-file b/src/postcat/test-queue-file new file mode 100644 index 0000000..4979c1d Binary files /dev/null and b/src/postcat/test-queue-file differ diff --git a/src/postconf/.indent.pro b/src/postconf/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postconf/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postconf/.printfck b/src/postconf/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/postconf/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/postconf/Makefile.in b/src/postconf/Makefile.in new file mode 100644 index 0000000..60c797a --- /dev/null +++ b/src/postconf/Makefile.in @@ -0,0 +1,1304 @@ +SHELL = /bin/sh +SRCS = postconf.c postconf_builtin.c postconf_edit.c postconf_main.c \ + postconf_master.c postconf_misc.c postconf_node.c postconf_other.c \ + postconf_service.c postconf_unused.c postconf_user.c postconf_dbms.c \ + postconf_lookup.c postconf_match.c postconf_print.c +OBJS = postconf.o postconf_builtin.o postconf_edit.o postconf_main.o \ + postconf_master.o postconf_misc.o postconf_node.o postconf_other.o \ + postconf_service.o postconf_unused.o postconf_user.o postconf_dbms.o \ + postconf_lookup.o postconf_match.o postconf_print.o +HDRS = postconf.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) -DLEGACY_DBMS_SUPPORT +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +MAKES = bool_table.h bool_vars.h int_table.h int_vars.h str_table.h \ + str_vars.h time_table.h time_vars.h raw_table.h raw_vars.h \ + nint_table.h nint_vars.h nbool_table.h nbool_vars.h long_table.h \ + long_vars.h str_fn_table.h str_fn_vars.h +DB_MAKES= pcf_ldap_suffixes.h pcf_memcache_suffixes.h pcf_mysql_suffixes.h \ + pcf_pgsql_suffixes.h pcf_sqlite_suffixes.h +TEST_TMP= main.cf master.cf test*.tmp +DUMMIES = makes_dummy # for "make -j" +PROG = postconf +SAMPLES = ../../conf/main.cf.default +INC_DIR = ../../include +LIBS = ../../lib/libxsasl.a \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) +HTABLE_FIX = NORANDOMIZE=1 + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +../../conf/main.cf.default: $(PROG) Makefile + rm -f $@ + (echo "# DO NOT EDIT THIS FILE. EDIT THE MAIN.CF FILE INSTEAD. THE"; \ + echo "# TEXT HERE JUST SHOWS DEFAULT SETTINGS BUILT INTO POSTFIX."; \ + echo "#"; $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -d -c ../../conf) | \ + egrep -v '^(myhostname|mydomain|mynetworks|process_name|process_id) ' >$@ + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: test1 test2 test3 test4 test5 test6 test7 test8 test9 test10 test11 \ + test12 test13 test14 test15 test16 test17 test18 test19 test20 test21 \ + test22 test23 test24 test25 test26 test27 test28 test29 test30 test4b \ + test31 test32 test33 test34 test35 test36 test37 test39 test40 test41 \ + test42 test43 test44 test45 test46 test47 test48 test49 test50 test51 \ + test52 test53 test54 test55 test56 test57 test58 test59 test60 test61 \ + test62 test63 test64 test65 test66 test67 test68 test69 test70 + +root_tests: + +update: ../../bin/$(PROG) $(SAMPLES) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +$(MAKES): makes_dummy + +makes_dummy: $(INC_DIR)/mail_params.h ../global/mail_params.c extract.awk Makefile.in + $(AWK) -f extract.awk ../*/*.c | $(SHELL) + touch makes_dummy + +$(DB_MAKES): extract_cfg.sh Makefile.in + +pcf_ldap_suffixes.h: ../global/dict_ldap.c + sh extract_cfg.sh -d ../global/dict_ldap.c > $@ + +pcf_memcache_suffixes.h: ../global/dict_memcache.c + sh extract_cfg.sh -d ../global/dict_memcache.c > $@ + +pcf_mysql_suffixes.h: ../global/dict_mysql.c + sh extract_cfg.sh -d -s ../global/dict_mysql.c > $@ + +pcf_pgsql_suffixes.h: ../global/dict_pgsql.c + sh extract_cfg.sh -d -s ../global/dict_pgsql.c > $@ + +pcf_sqlite_suffixes.h: ../global/dict_sqlite.c + sh extract_cfg.sh -d -s ../global/dict_sqlite.c > $@ + +# Define two parameters with smtpd_restriction_classes. One will be ignored. + +test1: $(PROG) test1.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo smtpd_restriction_classes = foo bar >> main.cf + echo foo = yes >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test1.tmp 2>&1 + diff test1.ref test1.tmp + rm -f main.cf master.cf test1.tmp + +# Define two unused parameters. Expect two warnings. + +test2: $(PROG) test2.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo restriction_classes = foo bar >> main.cf + echo foo = yes >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test2.tmp 2>&1 + diff test2.ref test2.tmp + rm -f main.cf master.cf test2.tmp + +# Define one parameter in main.cf, validate it with main.cf. + +test3: $(PROG) test3.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo = yes >> main.cf + echo 'bar = $$foo' >> main.cf + echo 'always_bcc = $$bar' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test3.tmp 2>&1 + diff test3.ref test3.tmp + rm -f main.cf master.cf test3.tmp + +# Define one parameter in main.cf, validate it with master.cf. + +test4: $(PROG) test4.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo = yes >> main.cf + echo 'bar = $$foo' >> main.cf + echo smtpd unix - n n - 0 smtpd >> master.cf + echo ' -o always_bcc=$$bar' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test4.tmp 2>&1 + diff test4.ref test4.tmp + rm -f main.cf master.cf test4.tmp + +# Define one parameter in master.cf, validate it with main.cf. + +test4b: $(PROG) test4b.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'always_bcc = $$foo' >> main.cf + echo 'biff = $$bar' >> main.cf + echo 'bar = aaa' >> main.cf + echo smtpd1 unix - n n - 0 smtpd >> master.cf + echo ' -o foo=xxx -o bar=yyy -o baz=zzz' >> master.cf + echo '#smtpd2 unix - n n - 0 smtpd' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test4b.tmp 2>&1 + diff test4b.ref test4b.tmp + rm -f main.cf master.cf test4b.tmp + +# Define one user-defined parameter with name=value in master.cf, +# validate it with known_parameter=$$name in master.cf. + +test5: $(PROG) test5.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo smtpd unix - n n - 0 smtpd >> master.cf + echo ' -o bar=yes -o always_bcc=$$bar -o' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test5.tmp 2>&1 + diff test5.ref test5.tmp + rm -f main.cf master.cf test5.tmp + +# Basic functionality test: service parameters for delivery agents. + +test6: $(PROG) test6.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo whatevershebrings unix - n n - 0 pipe >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . 2>&1 | grep whatevershebrings >test6.tmp + diff test6.ref test6.tmp + rm -f main.cf master.cf test6.tmp + +# Basic functionality test: service parameters for spawn programs. + +test7: $(PROG) test7.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo whatevershebrings unix - n n - 0 spawn >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . 2>&1 | grep whatevershebrings >test7.tmp + diff test7.ref test7.tmp + rm -f main.cf master.cf test7.tmp + +test8: $(PROG) test8.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo whatevershebrings inet - n n - 0 spawn >> master.cf + echo whatevershebrings_time_limit=1 >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . 2>&1 | grep whatevershebrings >test8.tmp + diff test8.ref test8.tmp + rm -f main.cf master.cf test8.tmp + +test9: $(PROG) test9.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo inet - n n - 0 spawn >> master.cf + echo bar unix - n n - 0 spawn >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -M '*'/inet >test9.tmp 2>&1 + diff test9.ref test9.tmp + rm -f main.cf master.cf test9.tmp + +test10: $(PROG) test10.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo inet - n n - 0 spawn >> master.cf + echo bar unix - n n - 0 spawn >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -M bar/inet foo/unix >test10.tmp 2>&1 + diff test10.ref test10.tmp + rm -f main.cf master.cf test10.tmp + +test11: $(PROG) test11.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo inet - n n - 0 spawn >> master.cf + echo bar unix - n n - 0 spawn >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -M >test11.tmp 2>&1 + diff test11.ref test11.tmp + rm -f main.cf master.cf test11.tmp + +# Duplicate service entry. + +test12: $(PROG) test12.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo bar=yes >> main.cf + echo foo inet - n n - 0 spawn >> master.cf + echo ' -o always_bcc=$$bar -o' >> master.cf + echo foo inet - n n - 0 spawn >> master.cf + echo ' -o always_bcc=$$bar -o' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -M >test12.tmp 2>&1 + diff test12.ref test12.tmp + rm -f main.cf master.cf test12.tmp + +# Define parameter with restriction_classes in master.cf, validate in main.cf. + +test13: $(PROG) test13.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo bar=yes >> main.cf + echo baz=xx >> main.cf + echo foo inet - n n - 0 spawn >> master.cf + echo ' -o smtpd_restriction_classes=bar' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test13.tmp 2>&1 + diff test13.ref test13.tmp + rm -f main.cf master.cf test13.tmp + +# Define parameter with restriction_classes in main.cf, validate in master.cf. + +test14: $(PROG) test14.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo smtpd_restriction_classes=bar >> main.cf + echo foo inet - n n - 0 spawn >> master.cf + echo ' -o bar=yes -o baz=xx' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test14.tmp 2>&1 + diff test14.ref test14.tmp + rm -f main.cf master.cf test14.tmp + +# Define two parameters, one is hidden by master.cf. + +test15: $(PROG) test15.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo bar=xx >> main.cf + echo baz=yy >> main.cf + echo foo inet - n n - 0 spawn >> master.cf + echo ' -o bar=yes -o always_bcc=$$bar$$baz' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test15.tmp 2>&1 + diff test15.ref test15.tmp + rm -f main.cf master.cf test15.tmp + +# Test graceful degradation if master.cf is unavailable. + +test16: $(PROG) test16.ref + rm -f main.cf master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test16.tmp 2>&1 + diff test16.ref test16.tmp + rm -f main.cf master.cf test16.tmp + +test17: $(PROG) test17.ref + rm -f main.cf master.cf + touch -t 197101010000 main.cf + -$(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc . >test17.tmp 2>&1; exit 0 + diff test17.ref test17.tmp + rm -f main.cf master.cf test17.tmp + +# Test legacy $name in built-in defaults. + +test18: $(PROG) test18.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo virtual_maps=xxx >> main.cf + echo smtpd_client_connection_limit_exceptions=yyy >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test18.tmp 2>&1 + diff test18.ref test18.tmp + rm -f main.cf master.cf test18.tmp + +# Test $name in "raw" parameters. + +test19: $(PROG) test19.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo forward_path='$$'aaaa >> main.cf + echo default_rbl_reply='$$'bbbb >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test19.tmp 2>&1 + diff test19.ref test19.tmp + rm -f main.cf master.cf test19.tmp + +# Test master.cf line folding. + +test20: $(PROG) test20.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo inet - n n - 0 spawn >> master.cf + echo ' -o always_bcc=$$bar$$baz' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc . >test20.tmp 2>&1 + diff test20.ref test20.tmp + rm -f main.cf master.cf test20.tmp + +# Test main.cf line folding. + +test21: $(PROG) test21.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo forward_path = xxxxxxxxxxxxx xxxxxxxxxxxxxx xxxxxxxxxxxx \ + xxxxxxxxxxxxx xxxxxxxxxxxxxx >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nfc . >test21.tmp 2>&1 + diff test21.ref test21.tmp + rm -f main.cf master.cf test21.tmp + +# Like test6, but using a delivery agent that has no _time_limit magic. + +test22: $(PROG) test22.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo whatevershebrings unix - n n - 0 smtp >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . 2>&1 | grep whatevershebrings >test22.tmp + diff test22.ref test22.tmp + rm -f main.cf master.cf test22.tmp + +# Test the -C flag for each category. + +test23: $(PROG) test23.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo always_bcc = yes >> main.cf + echo name = value >> main.cf + echo whatevershebrings unix - n n - 0 smtp >> master.cf + echo ' -o always_bcc=$$name' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -nC builtin >test23.tmp 2>&1 + diff test23.ref test23.tmp + rm -f main.cf master.cf test23.tmp + +test24: $(PROG) test24.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo always_bcc = yes >> main.cf + echo name = value >> main.cf + echo whatevershebrings unix - n n - 0 smtp >> master.cf + echo ' -o always_bcc=$$name' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -nC user >test24.tmp 2>&1 + diff test24.ref test24.tmp + rm -f main.cf master.cf test24.tmp + +test25: $(PROG) test25.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo always_bcc = yes >> main.cf + echo name = value >> main.cf + echo whatevershebrings unix - n n - 0 smtp >> master.cf + echo ' -o always_bcc=$$name' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -C service 2>&1 | grep whatevershebrings >test25.tmp + diff test25.ref test25.tmp + rm -f main.cf master.cf test25.tmp + +# Test completeness of "-C all". + +test26: $(PROG) test26.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo always_bcc = yes >> main.cf + echo name = value >> main.cf + echo whatevershebrings unix - n n - 0 smtp >> master.cf + echo ' -o always_bcc=$$name' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . -C all >test26.tmp 2>&1 + diff test26.ref test26.tmp + rm -f main.cf master.cf test26.tmp + +test27: $(PROG) test27.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo always_bcc = yes >> main.cf + echo name = value >> main.cf + echo whatevershebrings unix - n n - 0 smtp >> master.cf + echo ' -o always_bcc=$$name' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c . -C all 2>&1 | grep whatevershebrings >test27.tmp + diff test27.ref test27.tmp + rm -f main.cf master.cf test27.tmp + +# Test macro expansion, type:table parsing and scoping. + +test28: $(PROG) test28.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'xx = proxy:ldap:foo' >> main.cf + echo 'foo_domain = bar' >> main.cf + echo 'header_checks = ldap:hh' >> main.cf + echo 'hh_domain = whatever' >> main.cf + echo 'zz = $$yy' >> main.cf + echo 'yy = aap' >> main.cf + echo 'db = memcache' >> main.cf + echo whatevershebrings unix - n n - 0 other >> master.cf + echo ' -o body_checks=$$db:$$zz' >> master.cf + echo 'aap_domain = whatever' >> main.cf + echo 'aa_domain = whatever' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test28.tmp 2>&1 + diff test28.ref test28.tmp + rm -f main.cf master.cf test28.tmp + +# Test the handling of known and unknown database-defined suffixes. + +test29: $(PROG) test29.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'ldapxx = proxy:ldap:ldapfoo' >> main.cf + echo 'ldapfoo_domain = bar' >> main.cf + echo 'ldapfoo_domainx = bar' >> main.cf + echo 'mysqlxx = proxy:mysql:mysqlfoo' >> main.cf + echo 'mysqlfoo_domain = bar' >> main.cf + echo 'mysqlfoo_domainx = bar' >> main.cf + echo 'pgsqlxx = proxy:pgsql:pgsqlfoo' >> main.cf + echo 'pgsqlfoo_domain = bar' >> main.cf + echo 'pgsqlfoo_domainx = bar' >> main.cf + echo 'sqlitexx = proxy:sqlite:sqlitefoo' >> main.cf + echo 'sqlitefoo_domain = bar' >> main.cf + echo 'sqlitefoo_domainx = bar' >> main.cf + echo 'memcachexx = proxy:memcache:memcachefoo' >> main.cf + echo 'memcachefoo_domain = bar' >> main.cf + echo 'memcachefoo_domainx = bar' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test29.tmp 2>&1 + diff test29.ref test29.tmp + rm -f main.cf master.cf test29.tmp + +test30: $(PROG) test30.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo p1=xx >> main.cf + echo p2=xx >> main.cf + echo p3=xx >> main.cf + echo p4=xx >> main.cf + echo whatevershebrings unix - n n - 0 other >> master.cf + echo ' -o body_checks=$$p1' >> master.cf + echo ' -o bodyx_checks=$$p2' >> master.cf + echo ' -oheader_checks=$$p3' >> master.cf + echo ' -oheaderx_checks=$$p4' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test30.tmp 2>&1 + diff test30.ref test30.tmp + rm -f main.cf master.cf test30.tmp + +# Does a non-default setting propagate to a non-default value? + +test31: $(PROG) test31.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'smtpd_helo_restrictions=whatever' >> main.cf + echo 'smtpd_sender_restrictions=$$smtpd_helo_restrictions' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nxc . >test31.tmp 2>&1 + diff test31.ref test31.tmp + rm -f main.cf master.cf test31.tmp + +# Does a non-default setting propagate to a default value? + +test32: $(PROG) test32.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'relay_domains=whatever' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -xc . fast_flush_domains >test32.tmp 2>&1 + diff test32.ref test32.tmp + rm -f main.cf master.cf test32.tmp + +# Does a default setting propagate to a non-default value? + +test33: $(PROG) test33.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination=whatever' >> main.cf + echo 'always_bcc=$$relay_domains' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -xc . always_bcc >test33.tmp 2>&1 + diff test33.ref test33.tmp + rm -f main.cf master.cf test33.tmp + +test34: $(PROG) test34.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination=whatever' >> main.cf + echo 'process_name=xxx' >> main.cf + echo 'process_id=yyy' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -xc . mydestination process_name >test34.tmp 2>&1 + diff test34.ref test34.tmp + rm -f main.cf master.cf test34.tmp + +test35: $(PROG) test35.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo whatevershebrings unix - n n - 0 other >> master.cf + echo ' -o body_checks=whatever' >> master.cf + echo ' -o process_name=aaa' >> master.cf + echo ' -o process_id=bbb' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -xc . process_name >test35.tmp 2>&1 + diff test35.ref test35.tmp + rm -f main.cf master.cf test35.tmp + +test36: $(PROG) test36.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination=$$virtual_mapx' >> main.cf + echo 'virtual_alias_maps=$$virtual_maps' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nxc . >test36.tmp 2>&1 + diff test36.ref test36.tmp + rm -f main.cf master.cf test36.tmp + +test37: $(PROG) test37.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'xxx=yyy' >> main.cf + echo 'aaa=bbb' >> main.cf + echo whatever unix - n n - 0 other >> master.cf + echo ' -o mydestination=$$xxx' >> master.cf + echo ' -o always_bcc=$$aaa' >> master.cf + echo ' -o aaa=ccc' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfxc . >test37.tmp 2>&1 + diff test37.ref test37.tmp + rm -f main.cf master.cf test37.tmp + +test39: $(PROG) test39.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet - n n - 0 other >> master.cf + echo baz unix - n n - 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc . '*'/unix >test39.tmp 2>&1 + diff test39.ref test39.tmp + rm -f main.cf master.cf test39.tmp + +test40: $(PROG) test40.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo ' -voaaa=bbb' >> master.cf + echo ' -vo ccc=$$aaa' >> master.cf + echo ' -v -oddd=$$ccc' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfxc . '*'/unix >test40.tmp 2>&1 + diff test40.ref test40.tmp + rm -f main.cf master.cf test40.tmp + +test41: $(PROG) test41.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar unix - n n - 0 other >> master.cf + echo baz unix - n n - 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Pc . bar/unix/xxx=yyy bar/unix/aaa=bbb >test41.tmp 2>&1 + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test41.tmp 2>&1 + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Pc . bar/unix/xxx=YYY bar/unix/aaa=BBB >>test41.tmp 2>&1 + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test41.tmp 2>&1 + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Pc . >>test41.tmp 2>&1 + diff test41.ref test41.tmp + rm -f main.cf master.cf test41.tmp + +test42: $(PROG) test42.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar unix - n n - 0 other >> master.cf + echo baz unix - n n - 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Pc . bar/unix/xxx=yyy bar/unix/aaa=bbb >test42.tmp 2>&1 + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test42.tmp 2>&1 + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Pc . >>test42.tmp 2>&1 + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -PXc. bar/unix/xxx bar/unix/aaa >>test42.tmp 2>&1 + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test42.tmp 2>&1 + diff test42.ref test42.tmp + rm -f main.cf master.cf test42.tmp + +test43: $(PROG) test43.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar unix - n n - 0 other >> master.cf + echo baz unix - n n - 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Fc . bar/unix/chroot=y bar/unix/command='aa -stuffobb=cc dd' >test43.tmp 2>&1 + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test43.tmp 2>&1 + diff test43.ref test43.tmp + rm -f main.cf master.cf test43.tmp + +test44: $(PROG) test44.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar unix - n n - 0 other >> master.cf + echo baz unix - n n - 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mc . bar/unix='xx inet - n n - 0 aa -stuffobb=cc dd' >test44.tmp 2>&1 + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >>test44.tmp 2>&1 + diff test44.ref test44.tmp + rm -f main.cf master.cf test44.tmp + +test45: $(PROG) test45.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar xxxx - n n - 0 other >> master.cf + echo baz unix - n n - 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test45.tmp 2>&1 || true + diff test45.ref test45.tmp + rm -f main.cf master.cf test45.tmp + +test46: $(PROG) test46.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet X n n - 0 other >> master.cf + echo baz unix - n n - 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test46.tmp 2>&1 || true + diff test46.ref test46.tmp + rm -f main.cf master.cf test46.tmp + +test47: $(PROG) test47.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet - X n - 0 other >> master.cf + echo baz unix - n n - 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test47.tmp 2>&1 || true + diff test47.ref test47.tmp + rm -f main.cf master.cf test47.tmp + +test48: $(PROG) test48.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet - n X - 0 other >> master.cf + echo baz unix - n n - 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test48.tmp 2>&1 || true + diff test48.ref test48.tmp + rm -f main.cf master.cf test48.tmp + +test49: $(PROG) test49.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet - n n X 0 other >> master.cf + echo baz unix - n n - 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test49.tmp 2>&1 || true + diff test49.ref test49.tmp + rm -f main.cf master.cf test49.tmp + +test50: $(PROG) test50.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet - n n - X other >> master.cf + echo baz unix - n n - 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test50.tmp 2>&1 || true + diff test50.ref test50.tmp + rm -f main.cf master.cf test50.tmp + +test51: $(PROG) test51.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n -? 0 other >> master.cf + echo bar inet - n n X? 0 other >> master.cf + echo baz unix - n n 0? 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test51.tmp 2>&1 || true + diff test51.ref test51.tmp + rm -f main.cf master.cf test51.tmp + +test52: $(PROG) test52.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet - n n 0 0 other >> master.cf + echo baz unix - n n 0 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -MXc. bar/inet foo/unix xxx/yyy + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test52.tmp 2>&1 || true + diff test52.ref test52.tmp + rm -f main.cf master.cf test52.tmp + +test53: $(PROG) test53.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet - n n 0 0 other >> master.cf + echo baz unix - n n 0 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -M#c. bar/inet xxx/yyy + diff test53.ref master.cf + rm -f main.cf master.cf test53.tmp + +test54: $(PROG) test54.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet - n n 0 0 other >> master.cf + echo baz unix - n n 0 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -M#c. bar/inet foo/unix + diff test54.ref master.cf + rm -f main.cf master.cf test54.tmp + +test55: $(PROG) test55.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet - n n 0 0 other >> master.cf + echo baz unix - n n 0 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -M#c. bar/inet baz/unix + diff test55.ref master.cf + rm -f main.cf master.cf test55.tmp + +test56: $(PROG) test56.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet - n n 0 0 other >> master.cf + echo " -o first" >> master.cf + echo " -o second" >> master.cf + echo baz unix - n n 0 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -M#c. bar/inet xxx/yyy + diff test56.ref master.cf + rm -f main.cf master.cf test56.tmp + +# Many more tests in util/mac_expand.in. + +test57: $(PROG) test57.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'x = $${{1} == {2}?{error}:x-value}' >> main.cf + echo 'y = y-value' >> main.cf + echo 'bar = $${x?{$$y}:$$z}' >> main.cf + echo 'baz = $${x?{$$z}:$$y}' >> main.cf + echo 'foo = $$bar$$baz' >> main.cf + echo 't1 = Postfix 2.11 $${{$${x?bug:x}} == {bug}?in}compatible' >> main.cf + echo 't2 = $$t1' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nxc. >test57.tmp 2>&1 + diff test57.ref test57.tmp + rm -f main.cf master.cf test57.tmp + +test58: $(PROG) test58.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'mydestination = foo bar pipemap:{ldap:xxx, memcache:yy}x randmap:{xx' >> main.cf + echo 'xxx_domain = foo' >> main.cf + echo 'xxx_bogus = foo' >> main.cf + echo 'yy_backup = bbb' >> main.cf + echo 'yy_bogus = bbb' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./postconf -nc. >test58.tmp 2>&1 || true + diff test58.ref test58.tmp + rm -f main.cf master.cf test58.tmp + +test59: $(PROG) test59.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo bar inet - n n 0 0 other >> master.cf + echo " -o name1=value1" >> master.cf + echo " -o { name2 = value2a value2b }" >> master.cf + echo " { arg1a arg1b }" >> master.cf + echo " { arg2a arg2b }x" >> master.cf + echo " { arg3a arg3b " >> master.cf + echo baz unix - n n 0 0 other >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Mfc. >test59.tmp 2>&1 || true + diff test59.ref test59.tmp + rm -f main.cf master.cf test59.tmp + +test60: $(PROG) test60.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo ' -o always_bcc=bar' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Fhc. >test60.tmp 2>&1 || true + diff test60.ref test60.tmp + rm -f main.cf master.cf test60.tmp + +test61: $(PROG) test61.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo ' -o always_bcc=bar' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -Phc. >test61.tmp 2>&1 || true + diff test61.ref test61.tmp + rm -f main.cf master.cf test61.tmp + +test62: $(PROG) test62.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo ' -o always_bcc=bar' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -FHc. >test62.tmp 2>&1 || true + diff test62.ref test62.tmp + rm -f main.cf master.cf test62.tmp + +test63: $(PROG) test63.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo foo unix - n n - 0 other >> master.cf + echo ' -o always_bcc=bar' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -PHc. >test63.tmp 2>&1 || true + diff test63.ref test63.tmp + rm -f main.cf master.cf test63.tmp + +# main.cf overrides built-in default. + +test64: $(PROG) test64.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'relayhost = relay-from-main.cf' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. relayhost >test64.tmp 2>&1 + diff test64.ref test64.tmp + rm -f main.cf master.cf test64.tmp + +# '-o name=value' overrides main.cf. + +test65: $(PROG) test65.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo 'relayhost = relay-from-main.cf' >> main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. -o relayhost=relay-from-cmd-line relayhost >test65.tmp 2>&1 + diff test65.ref test65.tmp + rm -f main.cf master.cf test65.tmp + +# unknown parameters in database configuration file (absolute pathname). + +test66: $(PROG) test66.ref + rm -f main.cf master.cf + touch master.cf + echo alias_maps = ldap:`pwd`/test66.cf >> main.cf + echo " " mysql:`pwd`/test66.cf >> main.cf + echo " " pgsql:`pwd`/test66.cf >> main.cf + echo " " sqlite:`pwd`/test66.cf >> main.cf + echo " " memcache:`pwd`/test66.cf >> main.cf + echo junk = junk >> test66.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. 2>test66.tmp >/dev/null + sed "s;PWD;`pwd`;" test66.ref | diff - test66.tmp + rm -f main.cf master.cf test66.tmp test66.cf + +# expand process name and service name in master.cf. + +test67: $(PROG) test67.ref + rm -f main.cf master.cf + touch master.cf + echo 'smtp inet n - n - - smtpd' >>master.cf + echo ' -o test1_process_name=$$process_name' >> master.cf + echo ' -o test1_service_name=$$service_name' >> master.cf + echo 'smtp unix n - n - - smtp' >>master.cf + echo ' -o test2_process_name=$$process_name' >> master.cf + echo ' -o test2_service_name=$$service_name' >> master.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -xMfc. >test67.tmp 2>&1 + diff test67.ref test67.tmp + rm -f main.cf master.cf test67.tmp + +test68: $(PROG) test68.ref + rm -f main.cf master.cf + touch master.cf + echo foo = ldap:`pwd` >> main.cf + echo 'alias_maps = $$foo/test68.cf' >> main.cf + echo " " mysql:`pwd`/test68.cf >> main.cf + echo " " pgsql:`pwd`/test68.cf >> main.cf + echo " " sqlite:`pwd`/test68.cf >> main.cf + echo " " memcache:`pwd`/test68.cf >> main.cf + echo junk = junk >> test68.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -c. 2>test68.tmp >/dev/null + sed "s;PWD;`pwd`;" test68.ref | diff - test68.tmp + rm -f main.cf master.cf test68.tmp test68.cf + +# See also test28 for user-defined parameters defined in main.cf. + +test69: $(PROG) test69.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo whatevershebrings unix - n n - 0 other >> master.cf + echo " -o ldap=ldap:`pwd`" >> master.cf + echo ' -o body_checks=$$ldap/test69.cf' >> master.cf + echo junk = junk >> test69.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test69.tmp 2>&1 + sed "s;PWD;`pwd`;" test69.ref | diff - test69.tmp + rm -f main.cf master.cf test69.tmp test69.cf + +# Nasty nesting. Prove that the ldap table is detected. If support for +# legacy table syntax is dropped, replace ldap:xyz with ldap:`pwd`/xyz +# and update the good/bad parameter name tests. + +test70: $(PROG) test70.ref + rm -f main.cf master.cf + touch main.cf master.cf + echo "smtpd_client_restrictions = check_sender_access {" >>main.cf + echo " pipemap:{ldap:used} { search_order = foo, bar } }" >>main.cf + echo "used_server_host = 127.0.0.1" >>main.cf + echo "unused_server_host = 127.0.0.1" >>main.cf + touch -t 197101010000 main.cf + $(HTABLE_FIX) $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -nc . >test70.tmp 2>&1 + diff test70.ref test70.tmp + rm -f main.cf master.cf test70.tmp test70.cf + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk $(MAKES) $(AUTOS) $(DUMMIES) \ + $(TEST_TMP) $(DB_MAKES) + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postconf.o: ../../include/argv.h +postconf.o: ../../include/check_arg.h +postconf.o: ../../include/compat_level.h +postconf.o: ../../include/dict.h +postconf.o: ../../include/htable.h +postconf.o: ../../include/mail_conf.h +postconf.o: ../../include/mail_dict.h +postconf.o: ../../include/mail_params.h +postconf.o: ../../include/mail_run.h +postconf.o: ../../include/mail_version.h +postconf.o: ../../include/msg.h +postconf.o: ../../include/msg_vstream.h +postconf.o: ../../include/myflock.h +postconf.o: ../../include/mymalloc.h +postconf.o: ../../include/name_code.h +postconf.o: ../../include/name_mask.h +postconf.o: ../../include/stringops.h +postconf.o: ../../include/sys_defs.h +postconf.o: ../../include/vbuf.h +postconf.o: ../../include/vstream.h +postconf.o: ../../include/vstring.h +postconf.o: ../../include/warn_stat.h +postconf.o: postconf.c +postconf.o: postconf.h +postconf_builtin.o: ../../include/argv.h +postconf_builtin.o: ../../include/attr.h +postconf_builtin.o: ../../include/check_arg.h +postconf_builtin.o: ../../include/dict.h +postconf_builtin.o: ../../include/get_hostname.h +postconf_builtin.o: ../../include/htable.h +postconf_builtin.o: ../../include/inet_proto.h +postconf_builtin.o: ../../include/iostuff.h +postconf_builtin.o: ../../include/mail_addr.h +postconf_builtin.o: ../../include/mail_conf.h +postconf_builtin.o: ../../include/mail_params.h +postconf_builtin.o: ../../include/mail_proto.h +postconf_builtin.o: ../../include/mail_version.h +postconf_builtin.o: ../../include/msg.h +postconf_builtin.o: ../../include/myflock.h +postconf_builtin.o: ../../include/mymalloc.h +postconf_builtin.o: ../../include/mynetworks.h +postconf_builtin.o: ../../include/name_code.h +postconf_builtin.o: ../../include/nvtable.h +postconf_builtin.o: ../../include/server_acl.h +postconf_builtin.o: ../../include/stringops.h +postconf_builtin.o: ../../include/sys_defs.h +postconf_builtin.o: ../../include/vbuf.h +postconf_builtin.o: ../../include/vstream.h +postconf_builtin.o: ../../include/vstring.h +postconf_builtin.o: bool_table.h +postconf_builtin.o: bool_vars.h +postconf_builtin.o: install_table.h +postconf_builtin.o: install_vars.h +postconf_builtin.o: int_table.h +postconf_builtin.o: int_vars.h +postconf_builtin.o: long_table.h +postconf_builtin.o: long_vars.h +postconf_builtin.o: nbool_table.h +postconf_builtin.o: nbool_vars.h +postconf_builtin.o: nint_table.h +postconf_builtin.o: nint_vars.h +postconf_builtin.o: postconf.h +postconf_builtin.o: postconf_builtin.c +postconf_builtin.o: raw_table.h +postconf_builtin.o: raw_vars.h +postconf_builtin.o: str_fn_table.h +postconf_builtin.o: str_fn_vars.h +postconf_builtin.o: str_table.h +postconf_builtin.o: str_vars.h +postconf_builtin.o: time_table.h +postconf_builtin.o: time_vars.h +postconf_dbms.o: ../../include/argv.h +postconf_dbms.o: ../../include/check_arg.h +postconf_dbms.o: ../../include/dict.h +postconf_dbms.o: ../../include/dict_ht.h +postconf_dbms.o: ../../include/dict_ldap.h +postconf_dbms.o: ../../include/dict_memcache.h +postconf_dbms.o: ../../include/dict_mysql.h +postconf_dbms.o: ../../include/dict_pgsql.h +postconf_dbms.o: ../../include/dict_proxy.h +postconf_dbms.o: ../../include/dict_sqlite.h +postconf_dbms.o: ../../include/htable.h +postconf_dbms.o: ../../include/mac_expand.h +postconf_dbms.o: ../../include/mac_parse.h +postconf_dbms.o: ../../include/mail_conf.h +postconf_dbms.o: ../../include/mail_params.h +postconf_dbms.o: ../../include/msg.h +postconf_dbms.o: ../../include/myflock.h +postconf_dbms.o: ../../include/mymalloc.h +postconf_dbms.o: ../../include/name_code.h +postconf_dbms.o: ../../include/split_at.h +postconf_dbms.o: ../../include/stringops.h +postconf_dbms.o: ../../include/sys_defs.h +postconf_dbms.o: ../../include/vbuf.h +postconf_dbms.o: ../../include/vstream.h +postconf_dbms.o: ../../include/vstring.h +postconf_dbms.o: pcf_ldap_suffixes.h +postconf_dbms.o: pcf_memcache_suffixes.h +postconf_dbms.o: pcf_mysql_suffixes.h +postconf_dbms.o: pcf_pgsql_suffixes.h +postconf_dbms.o: pcf_sqlite_suffixes.h +postconf_dbms.o: postconf.h +postconf_dbms.o: postconf_dbms.c +postconf_edit.o: ../../include/argv.h +postconf_edit.o: ../../include/check_arg.h +postconf_edit.o: ../../include/dict.h +postconf_edit.o: ../../include/edit_file.h +postconf_edit.o: ../../include/htable.h +postconf_edit.o: ../../include/mail_params.h +postconf_edit.o: ../../include/msg.h +postconf_edit.o: ../../include/myflock.h +postconf_edit.o: ../../include/mymalloc.h +postconf_edit.o: ../../include/name_code.h +postconf_edit.o: ../../include/readlline.h +postconf_edit.o: ../../include/split_at.h +postconf_edit.o: ../../include/stringops.h +postconf_edit.o: ../../include/sys_defs.h +postconf_edit.o: ../../include/vbuf.h +postconf_edit.o: ../../include/vstream.h +postconf_edit.o: ../../include/vstring.h +postconf_edit.o: ../../include/vstring_vstream.h +postconf_edit.o: postconf.h +postconf_edit.o: postconf_edit.c +postconf_lookup.o: ../../include/argv.h +postconf_lookup.o: ../../include/check_arg.h +postconf_lookup.o: ../../include/dict.h +postconf_lookup.o: ../../include/htable.h +postconf_lookup.o: ../../include/mac_expand.h +postconf_lookup.o: ../../include/mac_parse.h +postconf_lookup.o: ../../include/mail_conf.h +postconf_lookup.o: ../../include/msg.h +postconf_lookup.o: ../../include/myflock.h +postconf_lookup.o: ../../include/mymalloc.h +postconf_lookup.o: ../../include/name_code.h +postconf_lookup.o: ../../include/stringops.h +postconf_lookup.o: ../../include/sys_defs.h +postconf_lookup.o: ../../include/vbuf.h +postconf_lookup.o: ../../include/vstream.h +postconf_lookup.o: ../../include/vstring.h +postconf_lookup.o: postconf.h +postconf_lookup.o: postconf_lookup.c +postconf_main.o: ../../include/argv.h +postconf_main.o: ../../include/check_arg.h +postconf_main.o: ../../include/dict.h +postconf_main.o: ../../include/htable.h +postconf_main.o: ../../include/mac_expand.h +postconf_main.o: ../../include/mac_parse.h +postconf_main.o: ../../include/mail_conf.h +postconf_main.o: ../../include/mail_params.h +postconf_main.o: ../../include/msg.h +postconf_main.o: ../../include/myflock.h +postconf_main.o: ../../include/mymalloc.h +postconf_main.o: ../../include/name_code.h +postconf_main.o: ../../include/readlline.h +postconf_main.o: ../../include/stringops.h +postconf_main.o: ../../include/sys_defs.h +postconf_main.o: ../../include/vbuf.h +postconf_main.o: ../../include/vstream.h +postconf_main.o: ../../include/vstring.h +postconf_main.o: postconf.h +postconf_main.o: postconf_main.c +postconf_master.o: ../../include/argv.h +postconf_master.o: ../../include/check_arg.h +postconf_master.o: ../../include/dict.h +postconf_master.o: ../../include/htable.h +postconf_master.o: ../../include/mail_params.h +postconf_master.o: ../../include/master_proto.h +postconf_master.o: ../../include/msg.h +postconf_master.o: ../../include/myflock.h +postconf_master.o: ../../include/mymalloc.h +postconf_master.o: ../../include/name_code.h +postconf_master.o: ../../include/readlline.h +postconf_master.o: ../../include/split_at.h +postconf_master.o: ../../include/stringops.h +postconf_master.o: ../../include/sys_defs.h +postconf_master.o: ../../include/vbuf.h +postconf_master.o: ../../include/vstream.h +postconf_master.o: ../../include/vstring.h +postconf_master.o: postconf.h +postconf_master.o: postconf_master.c +postconf_match.o: ../../include/argv.h +postconf_match.o: ../../include/check_arg.h +postconf_match.o: ../../include/dict.h +postconf_match.o: ../../include/htable.h +postconf_match.o: ../../include/msg.h +postconf_match.o: ../../include/myflock.h +postconf_match.o: ../../include/mymalloc.h +postconf_match.o: ../../include/name_code.h +postconf_match.o: ../../include/split_at.h +postconf_match.o: ../../include/sys_defs.h +postconf_match.o: ../../include/vbuf.h +postconf_match.o: ../../include/vstream.h +postconf_match.o: ../../include/vstring.h +postconf_match.o: postconf.h +postconf_match.o: postconf_match.c +postconf_misc.o: ../../include/argv.h +postconf_misc.o: ../../include/check_arg.h +postconf_misc.o: ../../include/dict.h +postconf_misc.o: ../../include/htable.h +postconf_misc.o: ../../include/mail_conf.h +postconf_misc.o: ../../include/mail_params.h +postconf_misc.o: ../../include/myflock.h +postconf_misc.o: ../../include/mymalloc.h +postconf_misc.o: ../../include/name_code.h +postconf_misc.o: ../../include/safe.h +postconf_misc.o: ../../include/sys_defs.h +postconf_misc.o: ../../include/vbuf.h +postconf_misc.o: ../../include/vstream.h +postconf_misc.o: ../../include/vstring.h +postconf_misc.o: postconf.h +postconf_misc.o: postconf_misc.c +postconf_node.o: ../../include/argv.h +postconf_node.o: ../../include/check_arg.h +postconf_node.o: ../../include/dict.h +postconf_node.o: ../../include/htable.h +postconf_node.o: ../../include/msg.h +postconf_node.o: ../../include/myflock.h +postconf_node.o: ../../include/mymalloc.h +postconf_node.o: ../../include/name_code.h +postconf_node.o: ../../include/sys_defs.h +postconf_node.o: ../../include/vbuf.h +postconf_node.o: ../../include/vstream.h +postconf_node.o: ../../include/vstring.h +postconf_node.o: postconf.h +postconf_node.o: postconf_node.c +postconf_other.o: ../../include/argv.h +postconf_other.o: ../../include/check_arg.h +postconf_other.o: ../../include/dict.h +postconf_other.o: ../../include/dns.h +postconf_other.o: ../../include/htable.h +postconf_other.o: ../../include/mbox_conf.h +postconf_other.o: ../../include/msg.h +postconf_other.o: ../../include/myaddrinfo.h +postconf_other.o: ../../include/myflock.h +postconf_other.o: ../../include/name_code.h +postconf_other.o: ../../include/name_mask.h +postconf_other.o: ../../include/sock_addr.h +postconf_other.o: ../../include/sys_defs.h +postconf_other.o: ../../include/tls.h +postconf_other.o: ../../include/vbuf.h +postconf_other.o: ../../include/vstream.h +postconf_other.o: ../../include/vstring.h +postconf_other.o: ../../include/xsasl.h +postconf_other.o: postconf.h +postconf_other.o: postconf_other.c +postconf_print.o: ../../include/argv.h +postconf_print.o: ../../include/check_arg.h +postconf_print.o: ../../include/dict.h +postconf_print.o: ../../include/htable.h +postconf_print.o: ../../include/msg.h +postconf_print.o: ../../include/myflock.h +postconf_print.o: ../../include/name_code.h +postconf_print.o: ../../include/sys_defs.h +postconf_print.o: ../../include/vbuf.h +postconf_print.o: ../../include/vstream.h +postconf_print.o: ../../include/vstring.h +postconf_print.o: postconf.h +postconf_print.o: postconf_print.c +postconf_service.o: ../../include/argv.h +postconf_service.o: ../../include/check_arg.h +postconf_service.o: ../../include/dict.h +postconf_service.o: ../../include/htable.h +postconf_service.o: ../../include/mail_params.h +postconf_service.o: ../../include/msg.h +postconf_service.o: ../../include/myflock.h +postconf_service.o: ../../include/mymalloc.h +postconf_service.o: ../../include/name_code.h +postconf_service.o: ../../include/stringops.h +postconf_service.o: ../../include/sys_defs.h +postconf_service.o: ../../include/vbuf.h +postconf_service.o: ../../include/vstream.h +postconf_service.o: ../../include/vstring.h +postconf_service.o: postconf.h +postconf_service.o: postconf_service.c +postconf_unused.o: ../../include/argv.h +postconf_unused.o: ../../include/check_arg.h +postconf_unused.o: ../../include/dict.h +postconf_unused.o: ../../include/htable.h +postconf_unused.o: ../../include/mail_conf.h +postconf_unused.o: ../../include/mail_params.h +postconf_unused.o: ../../include/msg.h +postconf_unused.o: ../../include/myflock.h +postconf_unused.o: ../../include/name_code.h +postconf_unused.o: ../../include/sys_defs.h +postconf_unused.o: ../../include/vbuf.h +postconf_unused.o: ../../include/vstream.h +postconf_unused.o: ../../include/vstring.h +postconf_unused.o: postconf.h +postconf_unused.o: postconf_unused.c +postconf_user.o: ../../include/argv.h +postconf_user.o: ../../include/check_arg.h +postconf_user.o: ../../include/dict.h +postconf_user.o: ../../include/htable.h +postconf_user.o: ../../include/mac_expand.h +postconf_user.o: ../../include/mac_parse.h +postconf_user.o: ../../include/mail_conf.h +postconf_user.o: ../../include/mail_params.h +postconf_user.o: ../../include/msg.h +postconf_user.o: ../../include/myflock.h +postconf_user.o: ../../include/mymalloc.h +postconf_user.o: ../../include/name_code.h +postconf_user.o: ../../include/stringops.h +postconf_user.o: ../../include/sys_defs.h +postconf_user.o: ../../include/vbuf.h +postconf_user.o: ../../include/vstream.h +postconf_user.o: ../../include/vstring.h +postconf_user.o: postconf.h +postconf_user.o: postconf_user.c diff --git a/src/postconf/extract.awk b/src/postconf/extract.awk new file mode 100644 index 0000000..809020d --- /dev/null +++ b/src/postconf/extract.awk @@ -0,0 +1,201 @@ +# Extract initialization tables from actual source code. + +# XXX: Associated variable aliasing: +# +# Some parameters bind to different variables in different contexts, +# And other parameters map to associated variables in a many-to-1 +# fashion. This is mostly the result of the SMTP+LMTP integration +# and the overloading of parameters that have identical semantics, +# for the corresponding context. +# +# The "++table[...]" below ignores the associated variable name +# when doing duplicate elimination. Differences in the default value +# or lower/upper bounds still result in "postconf -d" duplicates, +# which are a sign of an error somewhere... +# +# XXX Work around ancient AWK implementations with a 10 file limit +# and no working close() operator (e.g. Solaris). Some systems +# have a more modern implementation that is XPG4-compatible, but it +# is too much bother to find out where each system keeps these. + +{ owned_by_library = (FILENAME ~ /\/(global|tls)\//) } + +/^(static| )*(const +)?CONFIG_INT_TABLE .*\{/,/\};/ { + if ($1 ~ /VAR/) { + if (!owned_by_library) + int_vars["int " substr($3,2,length($3)-2) ";"] = 1 + if (++itab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) { + int_table[$0] = 1 + } + } +} +/^(static| )*(const +)?CONFIG_STR_TABLE .*\{/,/\};/ { + if ($1 ~ /^VAR/) { + if (!owned_by_library) + str_vars["char *" substr($3,2,length($3)-2) ";"] = 1 + if (++stab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) { + str_table[$0] = 1 + } + } +} +/^(static| )*(const +)?CONFIG_STR_FN_TABLE .*\{/,/\};/ { + if ($1 ~ /^VAR/) { + if (!owned_by_library) + str_fn_vars["char *" substr($3,2,length($3)-2) ";"] = 1 + $2 = "pcf_" $2 + if (++stab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) { + str_fn_table[$0] = 1 + } + } +} +/^(static| )*(const +)?CONFIG_RAW_TABLE .*\{/,/\};/ { + if ($1 ~ /^VAR/) { + if (!owned_by_library) + raw_vars["char *" substr($3,2,length($3)-2) ";"] = 1 + if (++rtab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) { + raw_table[$0] = 1 + } + } +} +/^(static| )*(const +)?CONFIG_BOOL_TABLE .*\{/,/\};/ { + if ($1 ~ /^VAR/) { + if (!owned_by_library) + bool_vars["int " substr($3,2,length($3)-2) ";"] = 1 + if (++btab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) { + bool_table[$0] = 1 + } + } +} +/^(static| )*(const +)?CONFIG_TIME_TABLE .*\{/,/\};/ { + if ($1 ~ /^VAR/) { + if (!owned_by_library) + time_vars["int " substr($3,2,length($3)-2) ";"] = 1 + if (++ttab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) { + time_table[$0] = 1 + } + } +} +/^(static| )*(const +)?CONFIG_NINT_TABLE .*\{/,/\};/ { + if ($1 ~ /VAR/) { + if (!owned_by_library) + nint_vars["int " substr($3,2,length($3)-2) ";"] = 1 + if (++itab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) { + nint_table[$0] = 1 + } + } +} +/^(static| )*(const +)?CONFIG_NBOOL_TABLE .*\{/,/\};/ { + if ($1 ~ /^VAR/) { + if (!owned_by_library) + nbool_vars["int " substr($3,2,length($3)-2) ";"] = 1 + if (++btab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) { + nbool_table[$0] = 1 + } + } +} +/^(static| )*(const +)?CONFIG_LONG_TABLE .*\{/,/\};/ { + if ($1 ~ /VAR/) { + if (!owned_by_library) + long_vars["long " substr($3,2,length($3)-2) ";"] = 1 + if (++itab[$1 $2 $4 $5 $6 $7 $8 $9] == 1) { + long_table[$0] = 1 + } + } +} + +END { + # Print parameter declarations without busting old AWK's file limit. + print "cat >int_vars.h <<'EOF'" + for (key in int_vars) + print key + print "EOF" + + print "cat >str_vars.h <<'EOF'" + for (key in str_vars) + print key + print "EOF" + + print "cat >str_fn_vars.h <<'EOF'" + for (key in str_fn_vars) + print key + print "EOF" + + print "cat >raw_vars.h <<'EOF'" + for (key in raw_vars) + print key + print "EOF" + + print "cat >bool_vars.h <<'EOF'" + for (key in bool_vars) + print key + print "EOF" + + print "cat >time_vars.h <<'EOF'" + for (key in time_vars) + print key + print "EOF" + + print "cat >nint_vars.h <<'EOF'" + for (key in nint_vars) + print key + print "EOF" + + print "cat >nbool_vars.h <<'EOF'" + for (key in nbool_vars) + print key + print "EOF" + + print "cat >long_vars.h <<'EOF'" + for (key in long_vars) + print key + print "EOF" + + # Print parameter initializations without busting old AWK's file limit. + print "sed 's/[ ][ ]*/ /g' >int_table.h <<'EOF'" + for (key in int_table) + print key + print "EOF" + + print "sed 's/[ ][ ]*/ /g' >str_table.h <<'EOF'" + for (key in str_table) + print key + print "EOF" + + print "sed 's/[ ][ ]*/ /g' >str_fn_table.h <<'EOF'" + for (key in str_fn_table) + print key + print "EOF" + + print "sed 's/[ ][ ]*/ /g' >raw_table.h <<'EOF'" + for (key in raw_table) + print key + print "EOF" + + print "sed 's/[ ][ ]*/ /g' >bool_table.h <<'EOF'" + for (key in bool_table) + print key + print "EOF" + + print "sed 's/[ ][ ]*/ /g' >time_table.h <<'EOF'" + for (key in time_table) + print key + print "EOF" + + print "sed 's/[ ][ ]*/ /g' >nint_table.h <<'EOF'" + for (key in nint_table) + print key + print "EOF" + + print "sed 's/[ ][ ]*/ /g' >nbool_table.h <<'EOF'" + for (key in nbool_table) + print key + print "EOF" + + print "sed 's/[ ][ ]*/ /g' >long_table.h <<'EOF'" + for (key in long_table) + print key + print "EOF" + + # Flush output nicely. + exit(0); +} diff --git a/src/postconf/extract_cfg.sh b/src/postconf/extract_cfg.sh new file mode 100644 index 0000000..5901e95 --- /dev/null +++ b/src/postconf/extract_cfg.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +# To view the formatted manual page of this file, type: +# POSTFIXSOURCE/mantools/srctoman - extract_cfg.sh | nroff -man + +#++ +# NAME +# extract_cfg 1 +# SUMMARY +# extract database parameter names from cfg_get_xxx() calls +# SYNOPSIS +# \fBextract_cfg [-d|-s] [\fIfile...\fB]\fR +# DESCRIPTION +# The \fBextract_cfg\fR command extracts the parameter names +# from cfg_get_{str,int,bool}() calls in dict_xxx.c files. The +# output is one parameter name per line, formatted as a C string +# followed by comma. +# +# Options: +# .IP \fB-d\fR +# Add the "domain" parameter to the output. This is used by +# the LDAP, memcache, and *SQL* tables. +# .IP \fB-s\fR +# Add the legacy SQL query parameters: "select_field", "table", +# "where_field", and "additional_conditions". +# LICENSE +# .ad +# .fi +# The Secure Mailer license must be distributed with this software. +# HISTORY +# .ad +# .fi +# This command was introduced with Postfix 3.3. +# AUTHOR(S) +# Wietse Venema +# Google, Inc. +# 111 8th Avenue +# New York, NY 10011, USA +#-- + +# In case not installed. +m4 &2; exit 1;; + *) break;; + esac + shift +done + +# We use m4 macros to extract arguments from cfg_get_xxx() calls that +# may span multiple lines. We sandwich information of interest between +# control-A characters. Multiple cfg_get_xxx() calls on the same line +# should be OK, as long as the calls don't nest. + +( +cat <<'EOF' +define(`cfg_get_str',`$2 +')dnl +define(`cfg_get_int',`$2 +')dnl +define(`cfg_get_bool',`$2 +')dnl +EOF +# Convert selected C macro definitions into m4 macro definitions. +sed 's/^#define[ ]*\([DICT_MC_NAME_A-Za-z0-9_]*\)[ ]*\("[^"]*"\)/define(`\1'"'"',`\2'"'"')/' "$@" +) | m4 | awk -F '// { print $2 }' | ( +test -n "$add_domain_param" && { +cat < +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include + + /* + * Global storage. See postconf.h for description. + */ +PCF_PARAM_TABLE *pcf_param_table; +PCF_MASTER_ENT *pcf_master_table; +int pcf_cmd_mode = PCF_DEF_MODE; + + /* + * Application fingerprinting. + */ +MAIL_VERSION_STAMP_DECLARE; + + /* + * This program has so many command-line options that we have to implement a + * compatibility matrix to weed out the conflicting option combinations, and + * to alert the user about option combinations that have no effect. + */ + + /* + * Options that are mutually-exclusive. First entry must specify the major + * modes. Other entries specify conflicts between option modifiers. + */ +static const int pcf_incompat_options[] = { + /* Major modes. */ + PCF_SHOW_SASL_SERV | PCF_SHOW_SASL_CLNT | PCF_EXP_DSN_TEMPL \ + |PCF_SHOW_LOCKS | PCF_SHOW_MAPS | PCF_DUMP_DSN_TEMPL | PCF_MAIN_PARAM \ + |PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM | PCF_SHOW_TLS, + /* Modifiers. */ + PCF_SHOW_DEFS | PCF_EDIT_CONF | PCF_SHOW_NONDEF | PCF_COMMENT_OUT \ + |PCF_EDIT_EXCL, + PCF_FOLD_LINE | PCF_EDIT_CONF | PCF_COMMENT_OUT | PCF_EDIT_EXCL, + PCF_SHOW_EVAL | PCF_EDIT_CONF | PCF_COMMENT_OUT | PCF_EDIT_EXCL, + PCF_MAIN_OVER | PCF_SHOW_DEFS | PCF_EDIT_CONF | PCF_COMMENT_OUT \ + |PCF_EDIT_EXCL, + PCF_HIDE_NAME | PCF_EDIT_CONF | PCF_COMMENT_OUT | PCF_EDIT_EXCL \ + |PCF_HIDE_VALUE, + 0, +}; + + /* + * Options, and the only options that they are compatible with. There must + * be one entry for each major mode. Other entries specify compatibility + * between option modifiers. + */ +static const int pcf_compat_options[][2] = { + /* Major modes. */ + {PCF_SHOW_SASL_SERV, 0}, + {PCF_SHOW_SASL_CLNT, 0}, + {PCF_EXP_DSN_TEMPL, 0}, + {PCF_SHOW_LOCKS, 0}, + {PCF_SHOW_MAPS, 0,}, + {PCF_SHOW_TLS, 0,}, + {PCF_DUMP_DSN_TEMPL, 0}, + {PCF_MAIN_PARAM, (PCF_EDIT_CONF | PCF_EDIT_EXCL | PCF_COMMENT_OUT \ + |PCF_FOLD_LINE | PCF_HIDE_NAME | PCF_PARAM_CLASS \ + |PCF_SHOW_EVAL | PCF_SHOW_DEFS | PCF_SHOW_NONDEF \ + |PCF_MAIN_OVER | PCF_HIDE_VALUE)}, + {PCF_MASTER_ENTRY, (PCF_EDIT_CONF | PCF_EDIT_EXCL | PCF_COMMENT_OUT \ + |PCF_FOLD_LINE | PCF_MAIN_OVER | PCF_SHOW_EVAL)}, + {PCF_MASTER_FLD, (PCF_EDIT_CONF | PCF_FOLD_LINE | PCF_HIDE_NAME \ + |PCF_MAIN_OVER | PCF_SHOW_EVAL | PCF_HIDE_VALUE)}, + {PCF_MASTER_PARAM, (PCF_EDIT_CONF | PCF_EDIT_EXCL | PCF_FOLD_LINE \ + |PCF_HIDE_NAME | PCF_MAIN_OVER | PCF_SHOW_EVAL \ + |PCF_HIDE_VALUE)}, + /* Modifiers. */ + {PCF_PARAM_CLASS, (PCF_MAIN_PARAM | PCF_SHOW_DEFS | PCF_SHOW_NONDEF)}, + 0, +}; + + /* + * Compatibility to string conversion support. + */ +static const NAME_MASK pcf_compat_names[] = { + "-a", PCF_SHOW_SASL_SERV, + "-A", PCF_SHOW_SASL_CLNT, + "-b", PCF_EXP_DSN_TEMPL, + "-C", PCF_PARAM_CLASS, + "-d", PCF_SHOW_DEFS, + "-e", PCF_EDIT_CONF, + "-f", PCF_FOLD_LINE, + "-F", PCF_MASTER_FLD, + "-h", PCF_HIDE_NAME, + "-H", PCF_HIDE_VALUE, + "-l", PCF_SHOW_LOCKS, + "-m", PCF_SHOW_MAPS, + "-M", PCF_MASTER_ENTRY, + "-n", PCF_SHOW_NONDEF, + "-o", PCF_MAIN_OVER, + "-p", PCF_MAIN_PARAM, + "-P", PCF_MASTER_PARAM, + "-t", PCF_DUMP_DSN_TEMPL, + "-T", PCF_SHOW_TLS, + "-x", PCF_SHOW_EVAL, + "-X", PCF_EDIT_EXCL, + "-#", PCF_COMMENT_OUT, + 0, +}; + +/* usage - enumerate parameters without compatibility info */ + +static void usage(const char *progname) +{ + msg_fatal("usage: %s" + " [-a (server SASL types)]" + " [-A (client SASL types)]" + " [-b (bounce templates)]" + " [-c config_dir]" + " [-c param_class]" + " [-d (parameter defaults)]" + " [-e (edit configuration)]" + " [-f (fold lines)]" + " [-F (master.cf fields)]" + " [-h (no names)]" + " [-H (no values)]" + " [-l (lock types)]" + " [-m (map types)]" + " [-M (master.cf)]" + " [-n (non-default parameters)]" + " [-o name=value (override parameter value)]" + " [-p (main.cf, default)]" + " [-P (master.cf parameters)]" + " [-t (bounce templates)]" + " [-T compile-version|run-version|public-key-algorithms]" + " [-v (verbose)]" + " [-x (expand parameter values)]" + " [-X (exclude)]" + " [-# (comment-out)]" + " [name...]", progname); +} + +/* pcf_check_exclusive_options - complain about mutually-exclusive options */ + +static void pcf_check_exclusive_options(int optval) +{ + const char *myname = "pcf_check_exclusive_options"; + const int *op; + int oval; + unsigned mask; + + for (op = pcf_incompat_options; (oval = *op) != 0; op++) { + oval &= optval; + for (mask = ~0U; (mask & oval) != 0; mask >>= 1) { + if ((mask & oval) != oval) + msg_fatal("specify one of %s", + str_name_mask(myname, pcf_compat_names, oval)); + } + } +} + +/* pcf_check_compat_options - complain about incompatible options */ + +static void pcf_check_compat_options(int optval) +{ + const char *myname = "pcf_check_compat_options"; + VSTRING *buf1 = vstring_alloc(10); + VSTRING *buf2 = vstring_alloc(10); + const int (*op)[2]; + int excess; + + for (op = pcf_compat_options; op[0][0] != 0; op++) { + if ((optval & *op[0]) != 0 + && (excess = (optval & ~((*op)[0] | (*op)[1]))) != 0) + msg_fatal("with option %s, do not specify %s", + str_name_mask_opt(buf1, myname, pcf_compat_names, + (*op)[0], NAME_MASK_NUMBER), + str_name_mask_opt(buf2, myname, pcf_compat_names, + excess, NAME_MASK_NUMBER)); + } + vstring_free(buf1); + vstring_free(buf2); +} + +/* main */ + +int main(int argc, char **argv) +{ + int ch; + int fd; + struct stat st; + ARGV *ext_argv = 0; + int param_class = PCF_PARAM_MASK_CLASS; + static const NAME_MASK param_class_table[] = { + "builtin", PCF_PARAM_FLAG_BUILTIN, + "service", PCF_PARAM_FLAG_SERVICE, + "user", PCF_PARAM_FLAG_USER, + "all", PCF_PARAM_MASK_CLASS, + 0, + }; + ARGV *override_params = 0; + const char *pcf_tls_arg = 0; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Be consistent with file permissions. + */ + umask(022); + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Set up logging. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "aAbc:C:deEfFhHlmMno:pPtT:vxX#")) > 0) { + switch (ch) { + case 'a': + pcf_cmd_mode |= PCF_SHOW_SASL_SERV; + break; + case 'A': + pcf_cmd_mode |= PCF_SHOW_SASL_CLNT; + break; + case 'b': + pcf_cmd_mode |= PCF_EXP_DSN_TEMPL; + if (ext_argv) + msg_fatal("specify one of -b and -t"); + ext_argv = argv_alloc(2); + argv_add(ext_argv, "bounce", "-SVnexpand_templates", (char *) 0); + break; + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'C': + param_class = name_mask_opt("-C option", param_class_table, + optarg, NAME_MASK_ANY_CASE | NAME_MASK_FATAL); + break; + case 'd': + pcf_cmd_mode |= PCF_SHOW_DEFS; + break; + case 'e': + pcf_cmd_mode |= PCF_EDIT_CONF; + break; + case 'f': + pcf_cmd_mode |= PCF_FOLD_LINE; + break; + case 'F': + pcf_cmd_mode |= PCF_MASTER_FLD; + break; + case '#': + pcf_cmd_mode |= PCF_COMMENT_OUT; + break; + case 'h': + pcf_cmd_mode |= PCF_HIDE_NAME; + break; + case 'H': + pcf_cmd_mode |= PCF_HIDE_VALUE; + break; + case 'l': + pcf_cmd_mode |= PCF_SHOW_LOCKS; + break; + case 'm': + pcf_cmd_mode |= PCF_SHOW_MAPS; + break; + case 'M': + pcf_cmd_mode |= PCF_MASTER_ENTRY; + break; + case 'n': + pcf_cmd_mode |= PCF_SHOW_NONDEF; + break; + case 'o': + pcf_cmd_mode |= PCF_MAIN_OVER; + if (override_params == 0) + override_params = argv_alloc(2); + argv_add(override_params, optarg, (char *) 0); + break; + case 'p': + pcf_cmd_mode |= PCF_MAIN_PARAM; + break; + case 'P': + pcf_cmd_mode |= PCF_MASTER_PARAM; + break; + case 't': + pcf_cmd_mode |= PCF_DUMP_DSN_TEMPL; + if (ext_argv) + msg_fatal("specify one of -b and -t"); + ext_argv = argv_alloc(2); + argv_add(ext_argv, "bounce", "-SVndump_templates", (char *) 0); + break; + case 'T': + if (pcf_cmd_mode & PCF_SHOW_TLS) + msg_fatal("At most one -T option may be specified"); + pcf_cmd_mode |= PCF_SHOW_TLS; + pcf_tls_arg = optarg; + break; + case 'x': + pcf_cmd_mode |= PCF_SHOW_EVAL; + break; + case 'X': + /* This is irreversible, therefore require two-finger action. */ + pcf_cmd_mode |= PCF_EDIT_EXCL; + break; + case 'v': + msg_verbose++; + break; + default: + usage(argv[0]); + } + } + + /* + * For consistency with mail_params_init(). + */ + compat_level_relop_register(); + + /* + * We don't enforce import_environment consistency in this program. + * + * We don't extract import_environment from main.cf, because the postconf + * command must be able to extract parameter settings from main.cf before + * all installation parameters such as mail_owner or setgid_group have a + * legitimate value. + * + * We would need the functionality of mail_params_init() including all the + * side effects of populating the CONFIG_DICT with default values so that + * $name expansion works correctly, but excluding all the parameter value + * sanity checks so that it would not abort at installation time. + */ + + /* + * Make all options explicit, before checking their compatibility. + */ +#define PCF_MAIN_OR_MASTER \ + (PCF_MAIN_PARAM | PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM) + + if ((pcf_cmd_mode & pcf_incompat_options[0]) == 0) + pcf_cmd_mode |= PCF_MAIN_PARAM; + if ((pcf_cmd_mode & PCF_MAIN_OR_MASTER) + && argv[optind] && strchr(argv[optind], '=')) + pcf_cmd_mode |= PCF_EDIT_CONF; + + /* + * Sanity check. + */ + pcf_check_exclusive_options(pcf_cmd_mode); + pcf_check_compat_options(pcf_cmd_mode); + + if ((pcf_cmd_mode & PCF_EDIT_CONF) && argc == optind) + msg_fatal("-e requires name=value argument"); + + /* + * Display bounce template information and exit. + */ + if (ext_argv) { + if (argv[optind]) { + if (argv[optind + 1]) + msg_fatal("options -b and -t require at most one template file"); + argv_add(ext_argv, "-o", + concatenate(VAR_BOUNCE_TMPL, "=", + argv[optind], (char *) 0), + (char *) 0); + } + /* Grr... */ + argv_add(ext_argv, "-o", + concatenate(VAR_QUEUE_DIR, "=", ".", (char *) 0), + (char *) 0); + mail_conf_read(); + mail_run_replace(var_daemon_dir, ext_argv->argv); + /* NOTREACHED */ + } + + /* + * If showing map types, show them and exit + */ + if (pcf_cmd_mode & PCF_SHOW_MAPS) { + mail_conf_read(); + mail_dict_init(); + pcf_show_maps(); + } + + /* + * If showing locking methods, show them and exit + */ + else if (pcf_cmd_mode & PCF_SHOW_LOCKS) { + pcf_show_locks(); + } + + /* + * If showing master.cf entries, show them and exit + */ + else if ((pcf_cmd_mode & (PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM)) + && !(pcf_cmd_mode & (PCF_EDIT_CONF | PCF_EDIT_EXCL | PCF_COMMENT_OUT))) { + pcf_read_master(PCF_FAIL_ON_OPEN_ERROR); + pcf_read_parameters(); + if (override_params) + pcf_set_parameters(override_params->argv); + pcf_register_builtin_parameters(basename(argv[0]), getpid()); + pcf_register_service_parameters(); + pcf_register_user_parameters(); + if (pcf_cmd_mode & PCF_MASTER_FLD) + pcf_show_master_fields(VSTREAM_OUT, pcf_cmd_mode, argc - optind, + argv + optind); + else if (pcf_cmd_mode & PCF_MASTER_PARAM) + pcf_show_master_params(VSTREAM_OUT, pcf_cmd_mode, argc - optind, + argv + optind); + else + pcf_show_master_entries(VSTREAM_OUT, pcf_cmd_mode, argc - optind, + argv + optind); + pcf_flag_unused_master_parameters(); + } + + /* + * If showing SASL plug-in types, show them and exit + */ + else if (pcf_cmd_mode & PCF_SHOW_SASL_SERV) { + pcf_show_sasl(PCF_SHOW_SASL_SERV); + } else if (pcf_cmd_mode & PCF_SHOW_SASL_CLNT) { + pcf_show_sasl(PCF_SHOW_SASL_CLNT); + } + + /* + * Show TLS info and exit. + */ + else if (pcf_cmd_mode & PCF_SHOW_TLS) { + pcf_show_tls(pcf_tls_arg); + } + + /* + * Edit main.cf or master.cf. + */ + else if (pcf_cmd_mode & (PCF_EDIT_CONF | PCF_COMMENT_OUT | PCF_EDIT_EXCL)) { + if (optind == argc) + msg_fatal("missing service argument"); + if (pcf_cmd_mode & (PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM)) { + pcf_edit_master(pcf_cmd_mode, argc - optind, argv + optind); + } else { + pcf_edit_main(pcf_cmd_mode, argc - optind, argv + optind); + } + } + + /* + * If showing non-default values, read main.cf. + */ + else { + if ((pcf_cmd_mode & PCF_SHOW_DEFS) == 0) { + pcf_read_parameters(); + if (override_params) + pcf_set_parameters(override_params->argv); + } + pcf_register_builtin_parameters(basename(argv[0]), getpid()); + + /* + * Add service-dependent parameters (service names from master.cf) + * and user-defined parameters ($name macros in parameter values in + * main.cf and master.cf, but only if those names have a name=value + * in main.cf or master.cf). + */ + pcf_read_master(PCF_WARN_ON_OPEN_ERROR); + pcf_register_service_parameters(); + if ((pcf_cmd_mode & PCF_SHOW_DEFS) == 0) + pcf_register_user_parameters(); + + /* + * Show the requested values. + */ + pcf_show_parameters(VSTREAM_OUT, pcf_cmd_mode, param_class, + argv + optind); + + /* + * Flag unused parameters. This makes no sense with "postconf -d", + * because that ignores all the user-specified parameters and + * user-specified macro expansions in main.cf. + */ + if ((pcf_cmd_mode & PCF_SHOW_DEFS) == 0) { + pcf_flag_unused_main_parameters(); + pcf_flag_unused_master_parameters(); + } + } + vstream_fflush(VSTREAM_OUT); + exit(0); +} diff --git a/src/postconf/postconf.h b/src/postconf/postconf.h new file mode 100644 index 0000000..7b23380 --- /dev/null +++ b/src/postconf/postconf.h @@ -0,0 +1,327 @@ +/*++ +/* NAME +/* postconf 3h +/* SUMMARY +/* module interfaces +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * What we're supposed to be doing. + */ +#define PCF_SHOW_NONDEF (1<<0) /* show main.cf non-default settings */ +#define PCF_SHOW_DEFS (1<<1) /* show main.cf default setting */ +#define PCF_HIDE_NAME (1<<2) /* hide main.cf parameter name */ +#define PCF_SHOW_MAPS (1<<3) /* show map types */ +#define PCF_EDIT_CONF (1<<4) /* edit main.cf or master.cf */ +#define PCF_SHOW_LOCKS (1<<5) /* show mailbox lock methods */ +#define PCF_SHOW_EVAL (1<<6) /* expand main.cf right-hand sides */ +#define PCF_SHOW_SASL_SERV (1<<7) /* show server auth plugin types */ +#define PCF_SHOW_SASL_CLNT (1<<8) /* show client auth plugin types */ +#define PCF_COMMENT_OUT (1<<9) /* #-out selected main.cf entries */ +#define PCF_MASTER_ENTRY (1<<10) /* manage master.cf entries */ +#define PCF_FOLD_LINE (1<<11) /* fold long *.cf entries */ +#define PCF_EDIT_EXCL (1<<12) /* exclude main.cf entries */ +#define PCF_MASTER_FLD (1<<13) /* hierarchical pathname */ +#define PCF_MAIN_PARAM (1<<14) /* manage main.cf entries */ +#define PCF_EXP_DSN_TEMPL (1<<15) /* expand bounce templates */ +#define PCF_PARAM_CLASS (1<<16) /* select parameter class */ +#define PCF_MAIN_OVER (1<<17) /* override parameter values */ +#define PCF_DUMP_DSN_TEMPL (1<<18) /* show bounce templates */ +#define PCF_MASTER_PARAM (1<<19) /* manage master.cf -o name=value */ +#define PCF_HIDE_VALUE (1<<20) /* hide main.cf/master.cf =value */ +#define PCF_SHOW_TLS (1<<21) /* TLS support introspection */ + +#define PCF_DEF_MODE 0 + + /* + * Structure for one "valid parameter" (built-in, service-defined or valid + * user-defined). See the postconf_builtin, postconf_service and + * postconf_user modules for narrative text. + */ +typedef struct { + int flags; /* see below */ + void *param_data; /* mostly, the default value */ + const char *(*convert_fn) (void *); /* value to string */ +} PCF_PARAM_NODE; + + /* Values for flags. See the postconf_node module for narrative text. */ +#define PCF_PARAM_FLAG_RAW (1<<0) /* raw parameter value */ +#define PCF_PARAM_FLAG_BUILTIN (1<<1) /* built-in parameter name */ +#define PCF_PARAM_FLAG_SERVICE (1<<2) /* service-defined parameter name */ +#define PCF_PARAM_FLAG_USER (1<<3) /* user-defined parameter name */ +#define PCF_PARAM_FLAG_LEGACY (1<<4) /* legacy parameter name */ +#define PCF_PARAM_FLAG_READONLY (1<<5) /* legacy parameter name */ +#define PCF_PARAM_FLAG_DBMS (1<<6) /* dbms-defined parameter name */ + +#define PCF_PARAM_MASK_CLASS \ + (PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_SERVICE | PCF_PARAM_FLAG_USER) +#define PCF_PARAM_CLASS_OVERRIDE(node, class) \ + ((node)->flags = (((node)->flags & ~PCF_PARAM_MASK_CLASS) | (class))) + +#define PCF_RAW_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_RAW) +#define PCF_BUILTIN_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_BUILTIN) +#define PCF_SERVICE_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_SERVICE) +#define PCF_USER_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_USER) +#define PCF_LEGACY_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_LEGACY) +#define PCF_READONLY_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_READONLY) +#define PCF_DBMS_PARAMETER(node) ((node)->flags & PCF_PARAM_FLAG_DBMS) + + /* Values for param_data. See postconf_node module for narrative text. */ +#define PCF_PARAM_NO_DATA ((char *) 0) + + /* + * Lookup table for global "valid parameter" information. + */ +#define PCF_PARAM_TABLE HTABLE +#define PCF_PARAM_INFO HTABLE_INFO + +extern PCF_PARAM_TABLE *pcf_param_table; + + /* + * postconf_node.c. + */ +#define PCF_PARAM_TABLE_CREATE(size) htable_create(size); +#define PCF_PARAM_NODE_CAST(ptr) ((PCF_PARAM_NODE *) (ptr)) + +#define PCF_PARAM_TABLE_LIST(table) htable_list(table) +#define PCF_PARAM_INFO_NAME(ht) ((const char *) (ht)->key) +#define PCF_PARAM_INFO_NODE(ht) PCF_PARAM_NODE_CAST((ht)->value) + +#define PCF_PARAM_TABLE_FIND(table, name) \ + PCF_PARAM_NODE_CAST(htable_find((table), (name))) +#define PCF_PARAM_TABLE_LOCATE(table, name) htable_locate((table), (name)) +#define PCF_PARAM_TABLE_ENTER(table, name, flags, data, func) \ + htable_enter((table), (name), (char *) pcf_make_param_node((flags), \ + (data), (func))) + +extern PCF_PARAM_NODE *pcf_make_param_node(int, void *, const char *(*) (void *)); +extern const char *pcf_convert_param_node(int, const char *, PCF_PARAM_NODE *); +extern VSTRING *pcf_param_string_buf; + + /* + * Structure of one master.cf entry. + */ +typedef struct { + char *name_space; /* service/type, parameter name space */ + ARGV *argv; /* null, or master.cf fields */ + DICT *all_params; /* null, or all name=value entries */ + DICT *ro_params; /* read-only name=value entries */ + HTABLE *valid_names; /* null, or "valid" parameter names */ +} PCF_MASTER_ENT; + +#define PCF_MASTER_MIN_FIELDS 8 /* mandatory field minimum */ + +#define PCF_MASTER_NAME_SERVICE "service" +#define PCF_MASTER_NAME_TYPE "type" +#define PCF_MASTER_NAME_PRIVATE "private" +#define PCF_MASTER_NAME_UNPRIV "unprivileged" +#define PCF_MASTER_NAME_CHROOT "chroot" +#define PCF_MASTER_NAME_WAKEUP "wakeup" +#define PCF_MASTER_NAME_MAXPROC "process_limit" +#define PCF_MASTER_NAME_CMD "command" + +#define PCF_MASTER_FLD_SERVICE 0 /* service name */ +#define PCF_MASTER_FLD_TYPE 1 /* service type */ +#define PCF_MASTER_FLD_PRIVATE 2 /* private service */ +#define PCF_MASTER_FLD_UNPRIV 3 /* unprivileged service */ +#define PCF_MASTER_FLD_CHROOT 4 /* chrooted service */ +#define PCF_MASTER_FLD_WAKEUP 5 /* wakeup timer */ +#define PCF_MASTER_FLD_MAXPROC 6 /* process limit */ +#define PCF_MASTER_FLD_CMD 7 /* command */ + +#define PCF_MASTER_FLD_WILDC -1 /* wild-card */ +#define PCF_MASTER_FLD_NONE -2 /* not available */ + + /* + * Lookup table for master.cf entries. The table is terminated with an entry + * that has a null argv member. + */ +extern PCF_MASTER_ENT *pcf_master_table; + + /* + * Line-wrapping support. + */ +#define PCF_LINE_LIMIT 80 /* try to fold longer lines */ +#define PCF_SEPARATORS " \t\r\n" +#define PCF_INDENT_LEN 4 /* indent long text by 4 */ +#define PCF_INDENT_TEXT " " + + /* + * XXX Global so that postconf_builtin.c call-backs can see it. + */ +extern int pcf_cmd_mode; + + /* + * postconf_misc.c. + */ +extern void pcf_set_config_dir(void); + + /* + * postconf_main.c + */ +extern void pcf_read_parameters(void); +extern void pcf_set_parameters(char **); +extern void pcf_show_parameters(VSTREAM *, int, int, char **); + + /* + * postconf_edit.c + */ +extern void pcf_edit_main(int, int, char **); +extern void pcf_edit_master(int, int, char **); + + /* + * postconf_master.c. + */ +extern const char pcf_daemon_options_expecting_value[]; +extern void pcf_read_master(int); +extern void pcf_show_master_entries(VSTREAM *, int, int, char **); +extern const char *pcf_parse_master_entry(PCF_MASTER_ENT *, const char *); +extern void pcf_print_master_entry(VSTREAM *, int, PCF_MASTER_ENT *); +extern void pcf_free_master_entry(PCF_MASTER_ENT *); +extern void pcf_show_master_fields(VSTREAM *, int, int, char **); +extern void pcf_edit_master_field(PCF_MASTER_ENT *, int, const char *); +extern void pcf_show_master_params(VSTREAM *, int, int, char **); +extern void pcf_edit_master_param(PCF_MASTER_ENT *, int, const char *, const char *); + +#define PCF_WARN_ON_OPEN_ERROR 0 +#define PCF_FAIL_ON_OPEN_ERROR 1 + +#define PCF_MASTER_BLANKS " \t\r\n" /* XXX */ + + /* + * Master.cf parameter namespace management. The idea is to manage master.cf + * "-o name=value" settings with other tools than text editors. + * + * The natural choice is to use "service-name.service-type.parameter-name", but + * unfortunately the '.' may appear in service and parameter names. + * + * For example, a spawn(8) listener can have a service name 127.0.0.1:10028. + * This service name becomes part of a service-dependent parameter name + * "127.0.0.1:10028_time_limit". All those '.' characters mean we can't use + * '.' as the parameter namespace delimiter. + * + * (We could require that such service names are specified as $foo:port with + * the value of "foo" defined in main.cf or at the top of master.cf.) + * + * But it is easier if we use '/' instead. + */ +#define PCF_NAMESP_SEP_CH '/' +#define PCF_NAMESP_SEP_STR "/" + +#define PCF_LEGACY_SEP_CH '.' + + /* + * postconf_match.c. + */ +#define PCF_MATCH_WILDC_STR "*" +#define PCF_MATCH_ANY(p) ((p)[0] == PCF_MATCH_WILDC_STR[0] && (p)[1] == 0) +#define PCF_MATCH_STRING(p, s) (PCF_MATCH_ANY(p) || strcmp((p), (s)) == 0) + +extern ARGV *pcf_parse_service_pattern(const char *, int, int); +extern int pcf_parse_field_pattern(const char *); + +#define PCF_IS_MAGIC_SERVICE_PATTERN(pat) \ + (PCF_MATCH_ANY((pat)->argv[0]) || PCF_MATCH_ANY((pat)->argv[1])) +#define PCF_MATCH_SERVICE_PATTERN(pat, name, type) \ + (PCF_MATCH_STRING((pat)->argv[0], (name)) \ + && PCF_MATCH_STRING((pat)->argv[1], (type))) + +#define pcf_is_magic_field_pattern(pat) ((pat) == PCF_MASTER_FLD_WILDC) +#define pcf_str_field_pattern(pat) ((const char *) (pcf_field_name_offset[pat].name)) + +#define PCF_IS_MAGIC_PARAM_PATTERN(pat) PCF_MATCH_ANY(pat) +#define PCF_MATCH_PARAM_PATTERN(pat, name) PCF_MATCH_STRING((pat), (name)) + +/* The following is not part of the postconf_match API. */ +extern NAME_CODE pcf_field_name_offset[]; + + /* + * postconf_builtin.c. + */ +extern void pcf_register_builtin_parameters(const char *, pid_t); + + /* + * postconf_service.c. + */ +extern void pcf_register_service_parameters(void); + + /* + * Parameter context structure. + */ +typedef struct { + PCF_MASTER_ENT *local_scope; + int param_class; +} PCF_PARAM_CTX; + + /* + * postconf_user.c. + */ +extern void pcf_register_user_parameters(void); + + /* + * postconf_dbms.c + */ +extern void pcf_register_dbms_parameters(const char *, + const char *(*) (const char *, int, PCF_MASTER_ENT *), + PCF_MASTER_ENT *); + + /* + * postconf_lookup.c. + */ +extern const char *pcf_lookup_parameter_value(int, const char *, + PCF_MASTER_ENT *, + PCF_PARAM_NODE *); + +extern char *pcf_expand_parameter_value(VSTRING *, int, const char *, + PCF_MASTER_ENT *); + + /* + * postconf_print.c. + */ +extern void PRINTFLIKE(3, 4) pcf_print_line(VSTREAM *, int, const char *,...); + + /* + * postconf_unused.c. + */ +extern void pcf_flag_unused_main_parameters(void); +extern void pcf_flag_unused_master_parameters(void); + + /* + * postconf_other.c. + */ +extern void pcf_show_maps(void); +extern void pcf_show_locks(void); +extern void pcf_show_sasl(int); +extern void pcf_show_tls(const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/postconf/postconf_builtin.c b/src/postconf/postconf_builtin.c new file mode 100644 index 0000000..1fc337c --- /dev/null +++ b/src/postconf/postconf_builtin.c @@ -0,0 +1,469 @@ +/*++ +/* NAME +/* postconf_builtin 3 +/* SUMMARY +/* built-in main.cf parameter support +/* SYNOPSIS +/* #include +/* +/* void pcf_register_builtin_parameters(procname, pid) +/* const char *procname; +/* pid_t pid; +/* DESCRIPTION +/* pcf_register_builtin_parameters() initializes the global +/* main.cf parameter name space and adds all built-in parameter +/* information. +/* +/* Arguments: +/*.IP procname +/* Provides the default value for the "process_name" parameter. +/*.IP pid +/* Provides the default value for the "process_id" parameter. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +#ifdef USE_PATHS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include + + /* + * Support for built-in parameters: declarations generated by scanning + * actual C source files. + */ +#include "time_vars.h" +#include "bool_vars.h" +#include "int_vars.h" +#include "str_vars.h" +#include "raw_vars.h" +#include "nint_vars.h" +#include "nbool_vars.h" +#include "long_vars.h" + + /* + * Support for built-in parameters: manually extracted. + */ +#include "install_vars.h" + + /* + * Support for built-in parameters: lookup tables generated by scanning + * actual C source files. + */ +static const CONFIG_TIME_TABLE pcf_time_table[] = { +#include "time_table.h" + 0, +}; + +static const CONFIG_BOOL_TABLE pcf_bool_table[] = { +#include "bool_table.h" + 0, +}; + +static const CONFIG_INT_TABLE pcf_int_table[] = { +#include "int_table.h" + 0, +}; + +static const CONFIG_STR_TABLE pcf_str_table[] = { +#include "str_table.h" +#include "install_table.h" + 0, +}; + +static const CONFIG_RAW_TABLE pcf_raw_table[] = { +#include "raw_table.h" + 0, +}; + +static const CONFIG_NINT_TABLE pcf_nint_table[] = { +#include "nint_table.h" + 0, +}; + +static const CONFIG_NBOOL_TABLE pcf_nbool_table[] = { +#include "nbool_table.h" + 0, +}; + +static const CONFIG_LONG_TABLE pcf_long_table[] = { +#include "long_table.h" + 0, +}; + + /* + * Legacy parameters for backwards compatibility. + */ +static const CONFIG_STR_TABLE pcf_legacy_str_table[] = { + {"virtual_maps", ""}, + {"fallback_relay", ""}, + {"authorized_verp_clients", ""}, + {"smtpd_client_connection_limit_exceptions", ""}, + {"postscreen_dnsbl_ttl", ""}, + {"postscreen_blacklist_action", ""}, + {"postscreen_dnsbl_whitelist_threshold", ""}, + {"postscreen_whitelist_interfaces", ""}, + {"lmtp_per_record_deadline", ""}, + {"smtp_per_record_deadline", ""}, + {"smtpd_per_record_deadline", ""}, + {"tlsproxy_client_level", ""}, + {"tlsproxy_client_policy", ""}, + 0, +}; + + /* + * Parameters whose default values are normally initialized by calling a + * function. We direct the calls to our own versions of those functions + * because the run-time conditions are slightly different. + * + * Important: if the evaluation of a parameter default value has any side + * effects, then those side effects must happen only once. + */ +static const char *pcf_check_myhostname(void); +static const char *pcf_check_mydomainname(void); +static const char *pcf_mynetworks(void); + +#include "str_fn_vars.h" + +static const CONFIG_STR_FN_TABLE pcf_str_fn_table[] = { +#include "str_fn_table.h" + 0, +}; + + /* + * Parameters whose default values are normally initialized by ad-hoc code. + * The AWK script cannot identify these parameters or values, so we provide + * our own. + * + * Important: if the evaluation of a parameter default value has any side + * effects, then those side effects must happen only once. + */ +static CONFIG_STR_TABLE pcf_adhoc_procname = {VAR_PROCNAME}; +static CONFIG_STR_TABLE pcf_adhoc_servname = {VAR_SERVNAME}; +static CONFIG_INT_TABLE pcf_adhoc_pid = {VAR_PID}; + +#define STR(x) vstring_str(x) + +/* pcf_check_myhostname - lookup hostname and validate */ + +static const char *pcf_check_myhostname(void) +{ + static const char *name; + const char *dot; + const char *domain; + + /* + * Use cached result. + */ + if (name) + return (name); + + /* + * If the local machine name is not in FQDN form, try to append the + * contents of $mydomain. + */ + name = get_hostname(); + if ((dot = strchr(name, '.')) == 0) { + if ((domain = mail_conf_lookup_eval(VAR_MYDOMAIN)) == 0) + domain = DEF_MYDOMAIN; + name = concatenate(name, ".", domain, (char *) 0); + } + return (name); +} + +/* pcf_get_myhostname - look up and store my hostname */ + +static void pcf_get_myhostname(void) +{ + const char *name; + + if ((name = mail_conf_lookup_eval(VAR_MYHOSTNAME)) == 0) + name = pcf_check_myhostname(); + var_myhostname = mystrdup(name); +} + +/* pcf_check_mydomainname - lookup domain name and validate */ + +static const char *pcf_check_mydomainname(void) +{ + static const char *domain; + char *dot; + + /* + * Use cached result. + */ + if (domain) + return (domain); + + /* + * Use a default domain when the hostname is not a FQDN ("foo"). + */ + if (var_myhostname == 0) + pcf_get_myhostname(); + if ((dot = strchr(var_myhostname, '.')) == 0) + return (domain = DEF_MYDOMAIN); + return (domain = mystrdup(dot + 1)); +} + +/* pcf_mynetworks - lookup network address list */ + +static const char *pcf_mynetworks(void) +{ + static const char *networks; + VSTRING *exp_buf; + const char *junk; + + /* + * Use cached result. + */ + if (networks) + return (networks); + + exp_buf = vstring_alloc(100); + + if (var_inet_interfaces == 0) { + if ((pcf_cmd_mode & PCF_SHOW_DEFS) + || (junk = mail_conf_lookup_eval(VAR_INET_INTERFACES)) == 0) + junk = pcf_expand_parameter_value(exp_buf, pcf_cmd_mode, + DEF_INET_INTERFACES, + (PCF_MASTER_ENT *) 0); + var_inet_interfaces = mystrdup(junk); + } + if (var_mynetworks_style == 0) { + if ((pcf_cmd_mode & PCF_SHOW_DEFS) + || (junk = mail_conf_lookup_eval(VAR_MYNETWORKS_STYLE)) == 0) + junk = pcf_expand_parameter_value(exp_buf, pcf_cmd_mode, + DEF_MYNETWORKS_STYLE, + (PCF_MASTER_ENT *) 0); + var_mynetworks_style = mystrdup(junk); + } + if (var_inet_protocols == 0) { + if ((pcf_cmd_mode & PCF_SHOW_DEFS) + || (junk = mail_conf_lookup_eval(VAR_INET_PROTOCOLS)) == 0) + junk = pcf_expand_parameter_value(exp_buf, pcf_cmd_mode, + DEF_INET_PROTOCOLS, + (PCF_MASTER_ENT *) 0); + var_inet_protocols = mystrdup(junk); + (void) inet_proto_init(VAR_INET_PROTOCOLS, var_inet_protocols); + } + vstring_free(exp_buf); + return (networks = mystrdup(mynetworks())); +} + +/* pcf_conv_bool_parameter - get boolean parameter string value */ + +static const char *pcf_conv_bool_parameter(void *ptr) +{ + CONFIG_BOOL_TABLE *cbt = (CONFIG_BOOL_TABLE *) ptr; + + return (cbt->defval ? "yes" : "no"); +} + +/* pcf_conv_time_parameter - get relative time parameter string value */ + +static const char *pcf_conv_time_parameter(void *ptr) +{ + CONFIG_TIME_TABLE *ctt = (CONFIG_TIME_TABLE *) ptr; + + return (ctt->defval); +} + +/* pcf_conv_int_parameter - get integer parameter string value */ + +static const char *pcf_conv_int_parameter(void *ptr) +{ + CONFIG_INT_TABLE *cit = (CONFIG_INT_TABLE *) ptr; + + return (STR(vstring_sprintf(pcf_param_string_buf, "%d", cit->defval))); +} + +/* pcf_conv_str_parameter - get string parameter string value */ + +static const char *pcf_conv_str_parameter(void *ptr) +{ + CONFIG_STR_TABLE *cst = (CONFIG_STR_TABLE *) ptr; + + return (cst->defval); +} + +/* pcf_conv_str_fn_parameter - get string-function parameter string value */ + +static const char *pcf_conv_str_fn_parameter(void *ptr) +{ + CONFIG_STR_FN_TABLE *cft = (CONFIG_STR_FN_TABLE *) ptr; + + return (cft->defval()); +} + +/* pcf_conv_raw_parameter - get raw string parameter string value */ + +static const char *pcf_conv_raw_parameter(void *ptr) +{ + CONFIG_RAW_TABLE *rst = (CONFIG_RAW_TABLE *) ptr; + + return (rst->defval); +} + +/* pcf_conv_nint_parameter - get new integer parameter string value */ + +static const char *pcf_conv_nint_parameter(void *ptr) +{ + CONFIG_NINT_TABLE *rst = (CONFIG_NINT_TABLE *) ptr; + + return (rst->defval); +} + +/* pcf_conv_nbool_parameter - get new boolean parameter string value */ + +static const char *pcf_conv_nbool_parameter(void *ptr) +{ + CONFIG_NBOOL_TABLE *bst = (CONFIG_NBOOL_TABLE *) ptr; + + return (bst->defval); +} + +/* pcf_conv_long_parameter - get long parameter string value */ + +static const char *pcf_conv_long_parameter(void *ptr) +{ + CONFIG_LONG_TABLE *clt = (CONFIG_LONG_TABLE *) ptr; + + return (STR(vstring_sprintf(pcf_param_string_buf, "%ld", clt->defval))); +} + +/* pcf_register_builtin_parameters - add built-ins to the global name space */ + +void pcf_register_builtin_parameters(const char *procname, pid_t pid) +{ + const char *myname = "pcf_register_builtin_parameters"; + const CONFIG_TIME_TABLE *ctt; + const CONFIG_BOOL_TABLE *cbt; + const CONFIG_INT_TABLE *cit; + const CONFIG_STR_TABLE *cst; + const CONFIG_STR_FN_TABLE *cft; + const CONFIG_RAW_TABLE *rst; + const CONFIG_NINT_TABLE *nst; + const CONFIG_NBOOL_TABLE *bst; + const CONFIG_LONG_TABLE *lst; + + /* + * Sanity checks. + */ + if (pcf_param_table != 0) + msg_panic("%s: global parameter table is already initialized", myname); + + /* + * Initialize the global parameter table. + */ + pcf_param_table = PCF_PARAM_TABLE_CREATE(1000); + + /* + * Add the built-in parameters to the global name space. The class + * (built-in) is tentative; some parameters are actually service-defined, + * but they have their own default value. + */ + for (ctt = pcf_time_table; ctt->name; ctt++) + PCF_PARAM_TABLE_ENTER(pcf_param_table, ctt->name, + PCF_PARAM_FLAG_BUILTIN, (void *) ctt, + pcf_conv_time_parameter); + for (cbt = pcf_bool_table; cbt->name; cbt++) + PCF_PARAM_TABLE_ENTER(pcf_param_table, cbt->name, + PCF_PARAM_FLAG_BUILTIN, (void *) cbt, + pcf_conv_bool_parameter); + for (cit = pcf_int_table; cit->name; cit++) + PCF_PARAM_TABLE_ENTER(pcf_param_table, cit->name, + PCF_PARAM_FLAG_BUILTIN, (void *) cit, + pcf_conv_int_parameter); + for (cst = pcf_str_table; cst->name; cst++) + PCF_PARAM_TABLE_ENTER(pcf_param_table, cst->name, + PCF_PARAM_FLAG_BUILTIN, (void *) cst, + pcf_conv_str_parameter); + for (cft = pcf_str_fn_table; cft->name; cft++) + PCF_PARAM_TABLE_ENTER(pcf_param_table, cft->name, + PCF_PARAM_FLAG_BUILTIN, (void *) cft, + pcf_conv_str_fn_parameter); + for (rst = pcf_raw_table; rst->name; rst++) + PCF_PARAM_TABLE_ENTER(pcf_param_table, rst->name, + PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_RAW, + (void *) rst, pcf_conv_raw_parameter); + for (nst = pcf_nint_table; nst->name; nst++) + PCF_PARAM_TABLE_ENTER(pcf_param_table, nst->name, + PCF_PARAM_FLAG_BUILTIN, (void *) nst, + pcf_conv_nint_parameter); + for (bst = pcf_nbool_table; bst->name; bst++) + PCF_PARAM_TABLE_ENTER(pcf_param_table, bst->name, + PCF_PARAM_FLAG_BUILTIN, (void *) bst, + pcf_conv_nbool_parameter); + for (lst = pcf_long_table; lst->name; lst++) + PCF_PARAM_TABLE_ENTER(pcf_param_table, lst->name, + PCF_PARAM_FLAG_BUILTIN, (void *) lst, + pcf_conv_long_parameter); + + /* + * Register legacy parameters (used as a backwards-compatible migration + * aid). + */ + for (cst = pcf_legacy_str_table; cst->name; cst++) + PCF_PARAM_TABLE_ENTER(pcf_param_table, cst->name, + PCF_PARAM_FLAG_LEGACY, (void *) cst, + pcf_conv_str_parameter); + + /* + * Register parameters whose default value is normally initialized by + * ad-hoc code. + */ + pcf_adhoc_procname.defval = mystrdup(procname); + PCF_PARAM_TABLE_ENTER(pcf_param_table, pcf_adhoc_procname.name, + PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_READONLY, + (void *) &pcf_adhoc_procname, pcf_conv_str_parameter); + pcf_adhoc_servname.defval = mystrdup(""); + PCF_PARAM_TABLE_ENTER(pcf_param_table, pcf_adhoc_servname.name, + PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_READONLY, + (void *) &pcf_adhoc_servname, pcf_conv_str_parameter); + pcf_adhoc_pid.defval = pid; + PCF_PARAM_TABLE_ENTER(pcf_param_table, pcf_adhoc_pid.name, + PCF_PARAM_FLAG_BUILTIN | PCF_PARAM_FLAG_READONLY, + (void *) &pcf_adhoc_pid, pcf_conv_int_parameter); +} diff --git a/src/postconf/postconf_dbms.c b/src/postconf/postconf_dbms.c new file mode 100644 index 0000000..d21cad1 --- /dev/null +++ b/src/postconf/postconf_dbms.c @@ -0,0 +1,338 @@ +/*++ +/* NAME +/* postconf_dbms 3 +/* SUMMARY +/* legacy support for database-defined main.cf parameter names +/* SYNOPSIS +/* #include +/* +/* void pcf_register_dbms_parameters(param_value, flag_parameter, +/* local_scope) +/* const char *param_value; +/* const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *); +/* PCF_MASTER_ENT *local_scope; +/* DESCRIPTION +/* This module implements legacy support for database configuration +/* where main.cf parameter names are generated by prepending +/* the database name to a database-defined suffix. +/* +/* Arguments: +/* .IP param_value +/* A parameter value to be searched for "type:table" strings. +/* When a database type is found that supports legacy-style +/* configuration, the table name is combined with each of the +/* database-defined suffixes to generate candidate parameter +/* names for that database type; if the table name specifies +/* a client configuration file, that file is scanned for unused +/* parameter settings. +/* .IP flag_parameter +/* A function that takes as arguments a candidate parameter +/* name, parameter flags, and a PCF_MASTER_ENT pointer. The +/* function will flag the parameter as "used" if it has a +/* "name=value" entry in the local or global namespace. +/* .IP local_scope +/* The local namespace. +/* DIAGNOSTICS +/* No explicit diagnostics. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +#ifdef LEGACY_DBMS_SUPPORT + + /* + * The legacy database interface automagically instantiates a list of + * parameters by prepending the table name to database-specific suffixes. + */ + +/* See ldap_table(5). */ + +static const char *pcf_ldap_suffixes[] = { +#include "pcf_ldap_suffixes.h" + 0, +}; + +/* See mysql_table(5). */ + +static const char *pcf_mysql_suffixes[] = { +#include "pcf_mysql_suffixes.h" + 0, +}; + +/* See pgsql_table(5). */ + +static const char *pcf_pgsql_suffixes[] = { +#include "pcf_pgsql_suffixes.h" + 0, +}; + +/* See sqlite_table(5). */ + +static const char *pcf_sqlite_suffixes[] = { +#include "pcf_sqlite_suffixes.h" + 0, +}; + +/* See memcache_table(5). */ + +static const char *pcf_memcache_suffixes[] = { +#include "pcf_memcache_suffixes.h" + 0, +}; + + /* + * Bundle up the database types and their suffix lists. + */ +typedef struct { + const char *db_type; + const char **db_suffixes; +} PCF_DBMS_INFO; + +static const PCF_DBMS_INFO pcf_dbms_info[] = { + DICT_TYPE_LDAP, pcf_ldap_suffixes, + DICT_TYPE_MYSQL, pcf_mysql_suffixes, + DICT_TYPE_PGSQL, pcf_pgsql_suffixes, + DICT_TYPE_SQLITE, pcf_sqlite_suffixes, + DICT_TYPE_MEMCACHE, pcf_memcache_suffixes, + 0, +}; + +/* pcf_check_dbms_client - look for unused names in client configuration */ + +static void pcf_check_dbms_client(const PCF_DBMS_INFO *dp, const char *cf_file) +{ + DICT *dict; + VSTREAM *fp; + const char **cpp; + const char *name; + const char *value; + char *dict_spec; + int dir; + + /* + * We read each database client configuration file into its own + * dictionary, and nag only the first time that a file is visited. + */ + dict_spec = concatenate(dp->db_type, ":", cf_file, (char *) 0); + if ((dict = dict_handle(dict_spec)) == 0) { + struct stat st; + + /* + * Populate the dictionary with settings in this database client + * configuration file. Don't die if a file can't be opened - some + * files may contain passwords and should not be world-readable. + * Note: dict_load_fp() nags about duplicate parameter settings. + */ + dict = dict_ht_open(dict_spec, O_CREAT | O_RDWR, 0); + dict_register(dict_spec, dict); + if ((fp = vstream_fopen(cf_file, O_RDONLY, 0)) == 0) { + if (errno != EACCES) + msg_warn("open \"%s\" configuration \"%s\": %m", + dp->db_type, cf_file); + myfree(dict_spec); + return; + } + if (fstat(vstream_fileno(fp), &st) == 0 && !S_ISREG(st.st_mode)) { + msg_warn("open \"%s\" configuration \"%s\": not a regular file", + dp->db_type, cf_file); + myfree(dict_spec); + (void) vstream_fclose(fp); + return; + } + dict_load_fp(dict_spec, fp); + if (vstream_fclose(fp)) { + msg_warn("read \"%s\" configuration \"%s\": %m", + dp->db_type, cf_file); + myfree(dict_spec); + return; + } + + /* + * Remove all known database client parameters from this dictionary, + * then report the remaining ones as "unused". We use ad-hoc logging + * code, because a database client parameter namespace is unlike the + * parameter namespaces in main.cf or master.cf. + */ + for (cpp = dp->db_suffixes; *cpp; cpp++) + (void) dict_del(dict, *cpp); + for (dir = DICT_SEQ_FUN_FIRST; + dict->sequence(dict, dir, &name, &value) == DICT_STAT_SUCCESS; + dir = DICT_SEQ_FUN_NEXT) + msg_warn("%s: unused parameter: %s=%s", dict_spec, name, value); + } + myfree(dict_spec); +} + +/* pcf_register_dbms_helper - parse one possible database type:name */ + +static void pcf_register_dbms_helper(char *str_value, + const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *), + PCF_MASTER_ENT *local_scope) +{ + const PCF_DBMS_INFO *dp; + char *db_type; + char *prefix; + static VSTRING *candidate = 0; + const char **cpp; + char *err; + + /* + * Naive parsing. We don't really know if this substring specifies a + * database or some other text. + */ + while ((db_type = mystrtokq(&str_value, CHARS_COMMA_SP, CHARS_BRACE)) != 0) { + if (*db_type == CHARS_BRACE[0]) { + if ((err = extpar(&db_type, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0) { + /* XXX Encapsulate this in pcf_warn() function. */ + if (local_scope) + msg_warn("%s:%s: %s", + MASTER_CONF_FILE, local_scope->name_space, err); + else + msg_warn("%s: %s", MAIN_CONF_FILE, err); + myfree(err); + } + pcf_register_dbms_helper(db_type, flag_parameter, local_scope); + continue; + } + + /* + * Skip over "proxy:" maptypes, to emulate the proxymap(8) server's + * behavior when opening a local database configuration file. + */ + while ((prefix = split_at(db_type, ':')) != 0 + && strcmp(db_type, DICT_TYPE_PROXY) == 0) + db_type = prefix; + + if (prefix == 0) + continue; + + /* + * Look for database:prefix where the prefix is an absolute pathname. + * Then, report unknown database client configuration parameters. + * + * XXX What about a pathname beginning with '.'? This supposedly is + * relative to the queue directory, which is the default directory + * for all Postfix daemon processes. This would also have to handle + * the case that the queue is not yet created. + */ + if (*prefix == '/') { + for (dp = pcf_dbms_info; dp->db_type != 0; dp++) { + if (strcmp(db_type, dp->db_type) == 0) { + pcf_check_dbms_client(dp, prefix); + break; + } + } + continue; + } + + /* + * Look for database:prefix where the prefix is not a pathname and + * the database is a known type. Synthesize candidate parameter names + * from the user-defined prefix and from the database-defined suffix + * list, and see if those parameters have a "name=value" entry in the + * local or global namespace. + */ + if (*prefix != '.') { + if (*prefix == CHARS_BRACE[0]) { + if ((err = extpar(&prefix, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0) { + /* XXX Encapsulate this in pcf_warn() function. */ + if (local_scope) + msg_warn("%s:%s: %s", + MASTER_CONF_FILE, local_scope->name_space, + err); + else + msg_warn("%s: %s", MAIN_CONF_FILE, err); + myfree(err); + } + pcf_register_dbms_helper(prefix, flag_parameter, local_scope); + continue; + } else { + for (dp = pcf_dbms_info; dp->db_type != 0; dp++) { + if (strcmp(db_type, dp->db_type) == 0) { + for (cpp = dp->db_suffixes; *cpp; cpp++) { + vstring_sprintf(candidate ? candidate : + (candidate = vstring_alloc(30)), + "%s_%s", prefix, *cpp); + flag_parameter(STR(candidate), + PCF_PARAM_FLAG_DBMS | PCF_PARAM_FLAG_USER, + local_scope); + } + break; + } + } + } + } + } +} + +/* pcf_register_dbms_parameters - look for database_type:prefix_name */ + +void pcf_register_dbms_parameters(const char *param_value, + const char *(flag_parameter) (const char *, int, PCF_MASTER_ENT *), + PCF_MASTER_ENT *local_scope) +{ + char *bufp; + static VSTRING *buffer = 0; + + /* + * XXX This does not examine both sides of conditional macro expansion, + * and may expand the "wrong" conditional macros. This is the best we can + * do for legacy database configuration support. + */ + if (buffer == 0) + buffer = vstring_alloc(100); + bufp = pcf_expand_parameter_value(buffer, PCF_SHOW_EVAL, param_value, + local_scope); + pcf_register_dbms_helper(bufp, flag_parameter, local_scope); +} + +#endif diff --git a/src/postconf/postconf_edit.c b/src/postconf/postconf_edit.c new file mode 100644 index 0000000..2f9a607 --- /dev/null +++ b/src/postconf/postconf_edit.c @@ -0,0 +1,625 @@ +/*++ +/* NAME +/* postconf_edit 3 +/* SUMMARY +/* edit main.cf or master.cf +/* SYNOPSIS +/* #include +/* +/* void pcf_edit_main(mode, argc, argv) +/* int mode; +/* int argc; +/* char **argv; +/* +/* void pcf_edit_master(mode, argc, argv) +/* int mode; +/* int argc; +/* char **argv; +/* DESCRIPTION +/* pcf_edit_main() edits the \fBmain.cf\fR configuration file. +/* It replaces or adds parameter settings given as "\fIname=value\fR" +/* pairs given on the command line, or removes parameter +/* settings given as "\fIname\fR" on the command line. The +/* file is copied to a temporary file then renamed into place. +/* +/* pcf_edit_master() edits the \fBmaster.cf\fR configuration +/* file. The file is copied to a temporary file then renamed +/* into place. Depending on the flags in \fBmode\fR: +/* .IP PCF_MASTER_ENTRY +/* With PCF_EDIT_CONF, pcf_edit_master() replaces or adds +/* entire master.cf entries, specified on the command line as +/* "\fIname/type = name type private unprivileged chroot wakeup +/* process_limit command...\fR". +/* +/* With PCF_EDIT_EXCL or PCF_COMMENT_OUT, pcf_edit_master() +/* removes or comments out entries specified on the command +/* line as "\fIname/type\fR. +/* .IP PCF_MASTER_FLD +/* With PCF_EDIT_CONF, pcf_edit_master() replaces the value +/* of specific service attributes, specified on the command +/* line as "\fIname/type/attribute = value\fR". +/* .IP PCF_MASTER_PARAM +/* With PCF_EDIT_CONF, pcf_edit_master() replaces or adds the +/* value of service parameters, specified on the command line +/* as "\fIname/type/parameter = value\fR". +/* +/* With PCF_EDIT_EXCL, pcf_edit_master() removes service +/* parameters specified on the command line as "\fIparametername\fR". +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* FILES +/* /etc/postfix/main.cf, Postfix configuration parameters +/* /etc/postfix/main.cf.tmp, temporary name +/* /etc/postfix/master.cf, Postfix configuration parameters +/* /etc/postfix/master.cf.tmp, temporary name +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include + +#define STR(x) vstring_str(x) + +/* pcf_find_cf_info - pass-through non-content line, return content or null */ + +static char *pcf_find_cf_info(VSTRING *buf, VSTREAM *dst) +{ + char *cp; + + for (cp = STR(buf); ISSPACE(*cp) /* including newline */ ; cp++) + /* void */ ; + /* Pass-through comment, all-whitespace, or empty line. */ + if (*cp == '#' || *cp == 0) { + vstream_fputs(STR(buf), dst); + return (0); + } else { + return (cp); + } +} + +/* pcf_next_cf_line - return next content line, pass non-content */ + +static char *pcf_next_cf_line(VSTRING *buf, VSTREAM *src, VSTREAM *dst, int *lineno) +{ + char *cp; + + while (vstring_get(buf, src) != VSTREAM_EOF) { + if (lineno) + *lineno += 1; + if ((cp = pcf_find_cf_info(buf, dst)) != 0) + return (cp); + } + return (0); +} + +/* pcf_gobble_cf_line - accumulate multi-line content, pass non-content */ + +static void pcf_gobble_cf_line(VSTRING *full_entry_buf, VSTRING *line_buf, + VSTREAM *src, VSTREAM *dst, int *lineno) +{ + int ch; + + vstring_strcpy(full_entry_buf, STR(line_buf)); + for (;;) { + if ((ch = VSTREAM_GETC(src)) != VSTREAM_EOF) + vstream_ungetc(src, ch); + if ((ch != '#' && !ISSPACE(ch)) + || vstring_get(line_buf, src) == VSTREAM_EOF) + break; + lineno += 1; + if (pcf_find_cf_info(line_buf, dst)) + vstring_strcat(full_entry_buf, STR(line_buf)); + } +} + +/* pcf_edit_main - edit main.cf file */ + +void pcf_edit_main(int mode, int argc, char **argv) +{ + char *path; + EDIT_FILE *ep; + VSTREAM *src; + VSTREAM *dst; + VSTRING *buf = vstring_alloc(100); + VSTRING *key = vstring_alloc(10); + char *cp; + char *pattern; + char *edit_value; + HTABLE *table; + struct cvalue { + char *value; + int found; + }; + struct cvalue *cvalue; + HTABLE_INFO **ht_info; + HTABLE_INFO **ht; + int interesting; + const char *err; + + /* + * Store command-line parameters for quick lookup. + */ + table = htable_create(argc); + while ((cp = *argv++) != 0) { + if (strchr(cp, '\n') != 0) + msg_fatal("-e, -X, or -# accepts no multi-line input"); + while (ISSPACE(*cp)) + cp++; + if (*cp == '#') + msg_fatal("-e, -X, or -# accepts no comment input"); + if (mode & PCF_EDIT_CONF) { + if ((err = split_nameval(cp, &pattern, &edit_value)) != 0) + msg_fatal("%s: \"%s\"", err, cp); + } else if (mode & (PCF_COMMENT_OUT | PCF_EDIT_EXCL)) { + if (*cp == 0) + msg_fatal("-X or -# requires non-blank parameter names"); + if (strchr(cp, '=') != 0) + msg_fatal("-X or -# requires parameter names without value"); + pattern = cp; + trimblanks(pattern, 0); + edit_value = 0; + } else { + msg_panic("pcf_edit_main: unknown mode %d", mode); + } + if ((cvalue = htable_find(table, pattern)) != 0) { + msg_warn("ignoring earlier request: '%s = %s'", + pattern, cvalue->value); + htable_delete(table, pattern, myfree); + } + cvalue = (struct cvalue *) mymalloc(sizeof(*cvalue)); + cvalue->value = edit_value; + cvalue->found = 0; + htable_enter(table, pattern, (void *) cvalue); + } + + /* + * Open a temp file for the result. This uses a deterministic name so we + * don't leave behind thrash with random names. + */ + pcf_set_config_dir(); + path = concatenate(var_config_dir, "/", MAIN_CONF_FILE, (char *) 0); + if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644)) == 0) + msg_fatal("open %s%s: %m", path, EDIT_FILE_SUFFIX); + dst = ep->tmp_fp; + + /* + * Open the original file for input. + */ + if ((src = vstream_fopen(path, O_RDONLY, 0)) == 0) { + /* OK to delete, since we control the temp file name exclusively. */ + (void) unlink(ep->tmp_path); + msg_fatal("open %s for reading: %m", path); + } + + /* + * Copy original file to temp file, while replacing parameters on the + * fly. Issue warnings for names found multiple times. + */ +#define STR(x) vstring_str(x) + + interesting = 0; + while ((cp = pcf_next_cf_line(buf, src, dst, (int *) 0)) != 0) { + /* Copy, skip or replace continued text. */ + if (cp > STR(buf)) { + if (interesting == 0) + vstream_fputs(STR(buf), dst); + else if (mode & PCF_COMMENT_OUT) + vstream_fprintf(dst, "#%s", STR(buf)); + } + /* Copy or replace start of logical line. */ + else { + vstring_strncpy(key, cp, strcspn(cp, CHARS_SPACE "=")); + cvalue = (struct cvalue *) htable_find(table, STR(key)); + if ((interesting = !!cvalue) != 0) { + if (cvalue->found++ == 1) + msg_warn("%s: multiple entries for \"%s\"", path, STR(key)); + if (mode & PCF_EDIT_CONF) + vstream_fprintf(dst, "%s = %s\n", STR(key), cvalue->value); + else if (mode & PCF_COMMENT_OUT) + vstream_fprintf(dst, "#%s", cp); + } else { + vstream_fputs(STR(buf), dst); + } + } + } + + /* + * Generate new entries for parameters that were not found. + */ + if (mode & PCF_EDIT_CONF) { + for (ht_info = ht = htable_list(table); *ht; ht++) { + cvalue = (struct cvalue *) ht[0]->value; + if (cvalue->found == 0) + vstream_fprintf(dst, "%s = %s\n", ht[0]->key, cvalue->value); + } + myfree((void *) ht_info); + } + + /* + * When all is well, rename the temp file to the original one. + */ + if (vstream_fclose(src)) + msg_fatal("read %s: %m", path); + if (edit_file_close(ep) != 0) + msg_fatal("close %s%s: %m", path, EDIT_FILE_SUFFIX); + + /* + * Cleanup. + */ + myfree(path); + vstring_free(buf); + vstring_free(key); + htable_free(table, myfree); +} + + /* + * Data structure to hold a master.cf edit request. + */ +typedef struct { + int match_count; /* hit count */ + const char *raw_text; /* unparsed command-line argument */ + char *parsed_text; /* destructive parse */ + ARGV *service_pattern; /* service name, type, ... */ + int field_number; /* attribute field number */ + const char *param_pattern; /* parameter name */ + char *edit_value; /* value substring */ +} PCF_MASTER_EDIT_REQ; + +/* pcf_edit_master - edit master.cf file */ + +void pcf_edit_master(int mode, int argc, char **argv) +{ + const char *myname = "pcf_edit_master"; + char *path; + EDIT_FILE *ep; + VSTREAM *src; + VSTREAM *dst; + VSTRING *line_buf = vstring_alloc(100); + VSTRING *parse_buf = vstring_alloc(100); + int lineno; + PCF_MASTER_ENT *new_entry; + VSTRING *full_entry_buf = vstring_alloc(100); + char *cp; + char *pattern; + int service_name_type_matched; + const char *err; + PCF_MASTER_EDIT_REQ *edit_reqs; + PCF_MASTER_EDIT_REQ *req; + int num_reqs = argc; + const char *edit_opts = "-Me, -Fe, -Pe, -X, or -#"; + char *service_name; + char *service_type; + + /* + * Sanity check. + */ + if (num_reqs <= 0) + msg_panic("%s: empty argument list", myname); + + /* + * Preprocessing: split pattern=value, then split the pattern components. + */ + edit_reqs = (PCF_MASTER_EDIT_REQ *) mymalloc(sizeof(*edit_reqs) * num_reqs); + for (req = edit_reqs; *argv != 0; req++, argv++) { + req->match_count = 0; + req->raw_text = *argv; + cp = req->parsed_text = mystrdup(req->raw_text); + if (strchr(cp, '\n') != 0) + msg_fatal("%s accept no multi-line input", edit_opts); + while (ISSPACE(*cp)) + cp++; + if (*cp == '#') + msg_fatal("%s accept no comment input", edit_opts); + /* Separate the pattern from the value. */ + if (mode & PCF_EDIT_CONF) { + if ((err = split_nameval(cp, &pattern, &req->edit_value)) != 0) + msg_fatal("%s: \"%s\"", err, req->raw_text); +#if 0 + if ((mode & PCF_MASTER_PARAM) + && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) + msg_fatal("whitespace in parameter value: \"%s\"", + req->raw_text); +#endif + } else if (mode & (PCF_COMMENT_OUT | PCF_EDIT_EXCL)) { + if (strchr(cp, '=') != 0) + msg_fatal("-X or -# requires names without value"); + pattern = cp; + trimblanks(pattern, 0); + req->edit_value = 0; + } else { + msg_panic("%s: unknown mode %d", myname, mode); + } + +#define PCF_MASTER_MASK (PCF_MASTER_ENTRY | PCF_MASTER_FLD | PCF_MASTER_PARAM) + + /* + * Split name/type or name/type/whatever pattern into components. + */ + switch (mode & PCF_MASTER_MASK) { + case PCF_MASTER_ENTRY: + if ((req->service_pattern = + pcf_parse_service_pattern(pattern, 2, 2)) == 0) + msg_fatal("-Me, -MX or -M# requires service_name/type"); + break; + case PCF_MASTER_FLD: + if ((req->service_pattern = + pcf_parse_service_pattern(pattern, 3, 3)) == 0) + msg_fatal("-Fe or -FX requires service_name/type/field_name"); + req->field_number = + pcf_parse_field_pattern(req->service_pattern->argv[2]); + if (pcf_is_magic_field_pattern(req->field_number)) + msg_fatal("-Fe does not accept wild-card field name"); + if ((mode & PCF_EDIT_CONF) + && req->field_number < PCF_MASTER_FLD_CMD + && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) + msg_fatal("-Fe does not accept whitespace in non-command field"); + break; + case PCF_MASTER_PARAM: + if ((req->service_pattern = + pcf_parse_service_pattern(pattern, 3, 3)) == 0) + msg_fatal("-Pe or -PX requires service_name/type/parameter"); + req->param_pattern = req->service_pattern->argv[2]; + if (PCF_IS_MAGIC_PARAM_PATTERN(req->param_pattern)) + msg_fatal("-Pe does not accept wild-card parameter name"); + if ((mode & PCF_EDIT_CONF) + && req->edit_value[strcspn(req->edit_value, PCF_MASTER_BLANKS)]) + msg_fatal("-Pe does not accept whitespace in parameter value"); + break; + default: + msg_panic("%s: unknown edit mode %d", myname, mode); + } + } + + /* + * Open a temp file for the result. This uses a deterministic name so we + * don't leave behind thrash with random names. + */ + pcf_set_config_dir(); + path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0); + if ((ep = edit_file_open(path, O_CREAT | O_WRONLY, 0644)) == 0) + msg_fatal("open %s%s: %m", path, EDIT_FILE_SUFFIX); + dst = ep->tmp_fp; + + /* + * Open the original file for input. + */ + if ((src = vstream_fopen(path, O_RDONLY, 0)) == 0) { + /* OK to delete, since we control the temp file name exclusively. */ + (void) unlink(ep->tmp_path); + msg_fatal("open %s for reading: %m", path); + } + + /* + * Copy original file to temp file, while replacing service entries on + * the fly. + */ + service_name_type_matched = 0; + new_entry = 0; + lineno = 0; + while ((cp = pcf_next_cf_line(parse_buf, src, dst, &lineno)) != 0) { + vstring_strcpy(line_buf, STR(parse_buf)); + + /* + * Copy, skip or replace continued text. + */ + if (cp > STR(parse_buf)) { + if (service_name_type_matched == 0) + vstream_fputs(STR(line_buf), dst); + else if (mode & PCF_COMMENT_OUT) + vstream_fprintf(dst, "#%s", STR(line_buf)); + } + + /* + * Copy or replace (start of) logical line. + */ + else { + service_name_type_matched = 0; + + /* + * Parse out the service name and type. + */ + if ((service_name = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0 + || (service_type = mystrtok(&cp, PCF_MASTER_BLANKS)) == 0) + msg_fatal("file %s: line %d: specify service name and type " + "on the same line", path, lineno); + if (strchr(service_name, '=')) + msg_fatal("file %s: line %d: service name syntax \"%s\" is " + "unsupported with %s", path, lineno, service_name, + edit_opts); + if (service_type[strcspn(service_type, "=/")] != 0) + msg_fatal("file %s: line %d: " + "service type syntax \"%s\" is unsupported with %s", + path, lineno, service_type, edit_opts); + + /* + * Match each service pattern. + * + * Additional care is needed when a request adds or replaces an + * entire service definition, instead of a specific field or + * parameter. Given a command "postconf -M name1/type1='name2 + * type2 ...'", where name1 and name2 may differ, and likewise + * for type1 and type2: + * + * - First, if an existing service definition a) matches the service + * pattern 'name1/type1', or b) matches the name and type in the + * new service definition 'name2 type2 ...', remove the service + * definition. + * + * - Then, after an a) or b) type match, add a new service + * definition for 'name2 type2 ...', but only after the first + * match. + * + * - Finally, if a request had no a) or b) type match for any + * master.cf service definition, add a new service definition for + * 'name2 type2 ...'. + */ + for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { + PCF_MASTER_ENT *tentative_entry = 0; + int use_tentative_entry = 0; + + /* Additional care for whole service definition requests. */ + if ((mode & PCF_MASTER_ENTRY) && (mode & PCF_EDIT_CONF)) { + tentative_entry = (PCF_MASTER_ENT *) + mymalloc(sizeof(*tentative_entry)); + if ((err = pcf_parse_master_entry(tentative_entry, + req->edit_value)) != 0) + msg_fatal("%s: \"%s\"", err, req->raw_text); + } + if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, + service_name, + service_type)) { + service_name_type_matched = 1; /* Sticky flag */ + req->match_count += 1; + + /* + * Generate replacement master.cf entries. + */ + if ((mode & PCF_EDIT_CONF) + || ((mode & PCF_MASTER_PARAM) && (mode & PCF_EDIT_EXCL))) { + switch (mode & PCF_MASTER_MASK) { + + /* + * Replace master.cf entry field or parameter + * value. + */ + case PCF_MASTER_FLD: + case PCF_MASTER_PARAM: + if (new_entry == 0) { + /* Gobble up any continuation lines. */ + pcf_gobble_cf_line(full_entry_buf, line_buf, + src, dst, &lineno); + new_entry = (PCF_MASTER_ENT *) + mymalloc(sizeof(*new_entry)); + if ((err = pcf_parse_master_entry(new_entry, + STR(full_entry_buf))) != 0) + msg_fatal("file %s: line %d: %s", + path, lineno, err); + } + if (mode & PCF_MASTER_FLD) { + pcf_edit_master_field(new_entry, + req->field_number, + req->edit_value); + } else { + pcf_edit_master_param(new_entry, mode, + req->param_pattern, + req->edit_value); + } + break; + + /* + * Replace entire master.cf entry. + */ + case PCF_MASTER_ENTRY: + if (req->match_count == 1) + use_tentative_entry = 1; + break; + default: + msg_panic("%s: unknown edit mode %d", myname, mode); + } + } + } else if (tentative_entry != 0 + && PCF_MATCH_SERVICE_PATTERN(tentative_entry->argv, + service_name, + service_type)) { + service_name_type_matched = 1; /* Sticky flag */ + req->match_count += 1; + if (req->match_count == 1) + use_tentative_entry = 1; + } + if (tentative_entry != 0) { + if (use_tentative_entry) { + if (new_entry != 0) + pcf_free_master_entry(new_entry); + new_entry = tentative_entry; + } else { + pcf_free_master_entry(tentative_entry); + } + } + } + + /* + * Pass through or replace the current input line. + */ + if (new_entry) { + pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry); + pcf_free_master_entry(new_entry); + new_entry = 0; + } else if (service_name_type_matched == 0) { + vstream_fputs(STR(line_buf), dst); + } else if (mode & PCF_COMMENT_OUT) { + vstream_fprintf(dst, "#%s", STR(line_buf)); + } + } + } + + /* + * Postprocessing: when editing entire service entries, generate new + * entries for services not found. Otherwise (editing fields or + * parameters), "service not found" is a fatal error. + */ + for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { + if (req->match_count == 0) { + if ((mode & PCF_MASTER_ENTRY) && (mode & PCF_EDIT_CONF)) { + new_entry = (PCF_MASTER_ENT *) mymalloc(sizeof(*new_entry)); + if ((err = pcf_parse_master_entry(new_entry, req->edit_value)) != 0) + msg_fatal("%s: \"%s\"", err, req->raw_text); + pcf_print_master_entry(dst, PCF_FOLD_LINE, new_entry); + pcf_free_master_entry(new_entry); + } else if ((mode & PCF_MASTER_ENTRY) == 0) { + msg_warn("unmatched service_name/type: \"%s\"", req->raw_text); + } + } + } + + /* + * When all is well, rename the temp file to the original one. + */ + if (vstream_fclose(src)) + msg_fatal("read %s: %m", path); + if (edit_file_close(ep) != 0) + msg_fatal("close %s%s: %m", path, EDIT_FILE_SUFFIX); + + /* + * Cleanup. + */ + myfree(path); + vstring_free(line_buf); + vstring_free(parse_buf); + vstring_free(full_entry_buf); + for (req = edit_reqs; req < edit_reqs + num_reqs; req++) { + argv_free(req->service_pattern); + myfree(req->parsed_text); + } + myfree((void *) edit_reqs); +} diff --git a/src/postconf/postconf_lookup.c b/src/postconf/postconf_lookup.c new file mode 100644 index 0000000..5185681 --- /dev/null +++ b/src/postconf/postconf_lookup.c @@ -0,0 +1,194 @@ +/*++ +/* NAME +/* postconf_lookup 3 +/* SUMMARY +/* parameter lookup routines +/* SYNOPSIS +/* #include +/* +/* const char *pcf_lookup_parameter_value(mode, name, local_scope, node) +/* int mode; +/* const char *name; +/* PCF_MASTER_ENT *local_scope; +/* PCF_PARAM_NODE *node; +/* +/* char *pcf_expand_parameter_value(buf, mode, value, local_scope) +/* VSTRING *buf; +/* int mode; +/* const char *value; +/* PCF_MASTER_ENT *local_scope; +/* DESCRIPTION +/* These functions perform parameter value lookups. The order +/* of decreasing precedence is: +/* .IP \(bu +/* Search name=value parameter settings in master.cf. These +/* lookups are disabled with the PCF_SHOW_DEFS flag. +/* .IP \(bu +/* Search name=value parameter settings in main.cf. These +/* lookups are disabled with the PCF_SHOW_DEFS flag. +/* .IP \(bu +/* Search built-in default parameter settings. These lookups +/* are disabled with the PCF_SHOW_NONDEF flag. +/* .PP +/* pcf_lookup_parameter_value() looks up the value for the +/* named parameter, and returns null if the name was not found. +/* +/* pcf_expand_parameter_value() expands $name in the specified +/* parameter value. This function ignores the PCF_SHOW_NONDEF +/* flag. The result value is a pointer to storage in a +/* user-supplied buffer, or in a buffer that is overwritten +/* with each call. +/* +/* Arguments: +/* .IP buf +/* Pointer to user-supplied buffer; must not be null. +/* .IP mode +/* Bit-wise OR of zero or one of the following (other flags +/* are ignored): +/* .RS +/* .IP PCF_SHOW_DEFS +/* Search built-in default parameter settings only. +/* .IP PCF_SHOW_NONDEF +/* Search local (master.cf) and global (main.cf) name=value +/* parameter settings only. +/* .RE +/* .IP name +/* The name of a parameter to be looked up. +/* .IP value +/* The parameter value where $name should be expanded. +/* .IP local_scope +/* Pointer to master.cf entry with local name=value settings, +/* or a null pointer (i.e. no local parameter lookup). +/* .IP node +/* Global default value for the named parameter, or a null +/* pointer (i.e. do the global default lookup anyway). +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include + +#define STR(x) vstring_str(x) + +/* pcf_lookup_parameter_value - look up specific parameter value */ + +const char *pcf_lookup_parameter_value(int mode, const char *name, + PCF_MASTER_ENT *local_scope, + PCF_PARAM_NODE *node) +{ + const char *value = 0; + +#define LOOKUP(dict, name) ((dict) ? dict_get((dict), (name)) : 0) + + /* + * Local name=value entries in master.cf take precedence over global + * name=value entries in main.cf. Built-in defaults have the lowest + * precedence. + */ + if ((mode & PCF_SHOW_DEFS) != 0 + || ((local_scope == 0 + || ((value = LOOKUP(local_scope->ro_params, name)) == 0 + && (value = LOOKUP(local_scope->all_params, name)) == 0)) + && (value = dict_lookup(CONFIG_DICT, name)) == 0 + && (mode & PCF_SHOW_NONDEF) == 0)) { + if (node != 0 || (node = PCF_PARAM_TABLE_FIND(pcf_param_table, name)) != 0) + value = pcf_convert_param_node(PCF_SHOW_DEFS, name, node); + } + return (value); +} + + /* + * Data structure to pass private state while recursively expanding $name in + * parameter values. + */ +typedef struct { + int mode; + PCF_MASTER_ENT *local_scope; +} PCF_EVAL_CTX; + +/* pcf_lookup_parameter_value_wrapper - macro parser call-back routine */ + +static const char *pcf_lookup_parameter_value_wrapper(const char *key, + int unused_type, + void *context) +{ + PCF_EVAL_CTX *cp = (PCF_EVAL_CTX *) context; + + return (pcf_lookup_parameter_value(cp->mode, key, cp->local_scope, + (PCF_PARAM_NODE *) 0)); +} + +/* pcf_expand_parameter_value - expand $name in parameter value */ + +char *pcf_expand_parameter_value(VSTRING *buf, int mode, const char *value, + PCF_MASTER_ENT *local_scope) +{ + const char *myname = "pcf_expand_parameter_value"; + int status; + PCF_EVAL_CTX eval_ctx; + + /* + * Sanity check. + */ + if (buf == 0) + msg_panic("%s: null buffer pointer", myname); + + /* + * Expand macros recursively. + * + * When expanding $name in "postconf -n" parameter values, don't limit the + * search to only non-default parameter values. + * + * When expanding $name in "postconf -d" parameter values, do limit the + * search to only default parameter values. + */ +#define DONT_FILTER (char *) 0 + + eval_ctx.mode = (mode & ~PCF_SHOW_NONDEF); + eval_ctx.local_scope = local_scope; + status = mac_expand(buf, value, MAC_EXP_FLAG_RECURSE, DONT_FILTER, + pcf_lookup_parameter_value_wrapper, (void *) &eval_ctx); + if (status & MAC_PARSE_ERROR) + msg_fatal("macro processing error"); + if (msg_verbose > 1) { + if (strcmp(value, STR(buf)) != 0) + msg_info("%s: expand %s -> %s", myname, value, STR(buf)); + else + msg_info("%s: const %s", myname, value); + } + return (STR(buf)); +} diff --git a/src/postconf/postconf_main.c b/src/postconf/postconf_main.c new file mode 100644 index 0000000..8e6e226 --- /dev/null +++ b/src/postconf/postconf_main.c @@ -0,0 +1,220 @@ +/*++ +/* NAME +/* postconf_main 3 +/* SUMMARY +/* basic support for main.cf +/* SYNOPSIS +/* #include +/* +/* void pcf_read_parameters() +/* +/* void pcf_show_parameters(fp, mode, param_class, names) +/* VSTREAM *fp; +/* int mode; +/* int param_class; +/* char **names; +/* DESCRIPTION +/* pcf_read_parameters() reads parameters from main.cf. +/* +/* pcf_set_parameters() takes an array of \fIname=value\fR +/* pairs and overrides settings read with pcf_read_parameters(). +/* +/* pcf_show_parameters() writes main.cf parameters to the +/* specified output stream. +/* +/* Arguments: +/* .IP fp +/* Output stream. +/* .IP mode +/* Bit-wise OR of zero or more of the following: +/* .RS +/* .IP PCF_FOLD_LINE +/* Fold long lines. +/* .IP PCF_SHOW_DEFS +/* Output default parameter values. +/* .IP PCF_SHOW_NONDEF +/* Output explicit settings only. +/* .IP PCF_HIDE_NAME +/* Output parameter values without the "name =" prefix. +/* .IP PCF_SHOW_EVAL +/* Expand $name in parameter values. +/* .RE +/* .IP param_class +/* Bit-wise OR of one or more of the following: +/* .RS +/* .IP PCF_PARAM_FLAG_BUILTIN +/* Show built-in parameters. +/* .IP PCF_PARAM_FLAG_SERVICE +/* Show service-defined parameters. +/* .IP PCF_PARAM_FLAG_USER +/* Show user-defined parameters. +/* .RE +/* .IP names +/* List of zero or more parameter names. If the list is empty, +/* output all parameters. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include + +#define STR(x) vstring_str(x) + +/* pcf_read_parameters - read parameter info from file */ + +void pcf_read_parameters(void) +{ + char *path; + + /* + * A direct rip-off of mail_conf_read(). XXX Avoid code duplication by + * better code decomposition. + */ + pcf_set_config_dir(); + path = concatenate(var_config_dir, "/", MAIN_CONF_FILE, (char *) 0); + if (dict_load_file_xt(CONFIG_DICT, path) == 0) + msg_fatal("open %s: %m", path); + myfree(path); +} + +/* pcf_set_parameters - add or override name=value pairs */ + +void pcf_set_parameters(char **name_val_array) +{ + char *name, *value, *junk; + const char *err; + char **cpp; + + for (cpp = name_val_array; *cpp; cpp++) { + junk = mystrdup(*cpp); + if ((err = split_nameval(junk, &name, &value)) != 0) + msg_fatal("invalid parameter override: %s: %s", *cpp, err); + mail_conf_update(name, value); + myfree(junk); + } +} + +/* pcf_print_parameter - show specific parameter */ + +static void pcf_print_parameter(VSTREAM *fp, int mode, const char *name, + PCF_PARAM_NODE *node) +{ + static VSTRING *exp_buf = 0; + const char *value; + + if (exp_buf == 0) + exp_buf = vstring_alloc(100); + + /* + * Use the default or actual value. + */ + value = pcf_lookup_parameter_value(mode, name, (PCF_MASTER_ENT *) 0, node); + + /* + * Optionally expand $name in the parameter value. Print the result with + * or without the name= prefix. + */ + if (value != 0) { + if (mode & PCF_HIDE_VALUE) { + pcf_print_line(fp, mode, "%s\n", name); + } else { + if ((mode & PCF_SHOW_EVAL) != 0 && PCF_RAW_PARAMETER(node) == 0) + value = pcf_expand_parameter_value(exp_buf, mode, value, + (PCF_MASTER_ENT *) 0); + if ((mode & PCF_HIDE_NAME) == 0) { + pcf_print_line(fp, mode, "%s = %s\n", name, value); + } else { + pcf_print_line(fp, mode, "%s\n", value); + } + } + if (msg_verbose) + vstream_fflush(fp); + } +} + +/* pcf_comp_names - qsort helper */ + +static int pcf_comp_names(const void *a, const void *b) +{ + PCF_PARAM_INFO **ap = (PCF_PARAM_INFO **) a; + PCF_PARAM_INFO **bp = (PCF_PARAM_INFO **) b; + + return (strcmp(PCF_PARAM_INFO_NAME(ap[0]), + PCF_PARAM_INFO_NAME(bp[0]))); +} + +/* pcf_show_parameters - show parameter info */ + +void pcf_show_parameters(VSTREAM *fp, int mode, int param_class, char **names) +{ + PCF_PARAM_INFO **list; + PCF_PARAM_INFO **ht; + char **namep; + PCF_PARAM_NODE *node; + + /* + * Show all parameters. + */ + if (*names == 0) { + list = PCF_PARAM_TABLE_LIST(pcf_param_table); + qsort((void *) list, pcf_param_table->used, sizeof(*list), + pcf_comp_names); + for (ht = list; *ht; ht++) + if (param_class & PCF_PARAM_INFO_NODE(*ht)->flags) + pcf_print_parameter(fp, mode, PCF_PARAM_INFO_NAME(*ht), + PCF_PARAM_INFO_NODE(*ht)); + myfree((void *) list); + return; + } + + /* + * Show named parameters. + */ + for (namep = names; *namep; namep++) { + if ((node = PCF_PARAM_TABLE_FIND(pcf_param_table, *namep)) == 0) { + msg_warn("%s: unknown parameter", *namep); + } else { + pcf_print_parameter(fp, mode, *namep, node); + } + } +} diff --git a/src/postconf/postconf_master.c b/src/postconf/postconf_master.c new file mode 100644 index 0000000..5b876b2 --- /dev/null +++ b/src/postconf/postconf_master.c @@ -0,0 +1,1120 @@ +/*++ +/* NAME +/* postconf_master 3 +/* SUMMARY +/* support for master.cf +/* SYNOPSIS +/* #include +/* +/* const char pcf_daemon_options_expecting_value[]; +/* +/* void pcf_read_master(fail_on_open) +/* int fail_on_open; +/* +/* void pcf_show_master_entries(fp, mode, service_filters) +/* VSTREAM *fp; +/* int mode; +/* char **service_filters; +/* +/* void pcf_show_master_fields(fp, mode, n_filters, field_filters) +/* VSTREAM *fp; +/* int mode; +/* int n_filters; +/* char **field_filters; +/* +/* void pcf_edit_master_field(masterp, field, new_value) +/* PCF_MASTER_ENT *masterp; +/* int field; +/* const char *new_value; +/* +/* void pcf_show_master_params(fp, mode, argc, **param_filters) +/* VSTREAM *fp; +/* int mode; +/* int argc; +/* char **param_filters; +/* +/* void pcf_edit_master_param(masterp, mode, param_name, param_value) +/* PCF_MASTER_ENT *masterp; +/* int mode; +/* const char *param_name; +/* const char *param_value; +/* AUXILIARY FUNCTIONS +/* const char *pcf_parse_master_entry(masterp, buf) +/* PCF_MASTER_ENT *masterp; +/* const char *buf; +/* +/* void pcf_print_master_entry(fp, mode, masterp) +/* VSTREAM *fp; +/* int mode; +/* PCF_MASTER_ENT *masterp; +/* +/* void pcf_free_master_entry(masterp) +/* PCF_MASTER_ENT *masterp; +/* DESCRIPTION +/* pcf_read_master() reads entries from master.cf into memory. +/* +/* pcf_show_master_entries() writes the entries in the master.cf +/* file to the specified stream. +/* +/* pcf_show_master_fields() writes name/type/field=value records +/* to the specified stream. +/* +/* pcf_edit_master_field() updates the value of a single-column +/* or multi-column attribute. +/* +/* pcf_show_master_params() writes name/type/parameter=value +/* records to the specified stream. +/* +/* pcf_edit_master_param() updates, removes or adds the named +/* parameter in a master.cf entry (the remove request ignores +/* the parameter value). +/* +/* pcf_daemon_options_expecting_value[] is an array of master.cf +/* daemon command-line options that expect an option value. +/* +/* pcf_parse_master_entry() parses a (perhaps multi-line) +/* string that contains a complete master.cf entry, and +/* normalizes daemon command-line options to simplify further +/* handling. +/* +/* pcf_print_master_entry() prints a parsed master.cf entry. +/* +/* pcf_free_master_entry() returns storage to the heap that +/* was allocated by pcf_parse_master_entry(). +/* +/* Arguments +/* .IP fail_on_open +/* Specify FAIL_ON_OPEN if open failure is a fatal error, +/* WARN_ON_OPEN if a warning should be logged instead. +/* .IP fp +/* Output stream. +/* .IP mode +/* Bit-wise OR of flags. Flags other than the following are +/* ignored. +/* .RS +/* .IP PCF_FOLD_LINE +/* Wrap long output lines. +/* .IP PCF_SHOW_EVAL +/* Expand $name in parameter values. +/* .IP PCF_EDIT_EXCL +/* Request that pcf_edit_master_param() removes the parameter. +/* .RE +/* .IP n_filters +/* The number of command-line filters. +/* .IP field_filters +/* A list of zero or more service field patterns (name/type/field). +/* The output is formatted as "name/type/field = value". If +/* no filters are specified, pcf_show_master_fields() outputs +/* the fields of all master.cf entries in the specified order. +/* .IP param_filters +/* A list of zero or more service parameter patterns +/* (name/type/parameter). The output is formatted as +/* "name/type/parameter = value". If no filters are specified, +/* pcf_show_master_params() outputs the parameters of all +/* master.cf entries in sorted order. +/* .IP service_filters +/* A list of zero or more service patterns (name or name/type). +/* If no filters are specified, pcf_show_master_entries() +/* outputs all master.cf entries in the specified order. +/* .IP field +/* Index into parsed master.cf entry. +/* .IP new_value +/* Replacement value for the specified field. It is split in +/* whitespace in case of a multi-field attribute. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Master library. */ + +#include + +/* Application-specific. */ + +#include + +const char pcf_daemon_options_expecting_value[] = "o"; + + /* + * Data structure to capture a command-line service field filter. + */ +typedef struct { + int match_count; /* hit count */ + const char *raw_text; /* full pattern text */ + ARGV *service_pattern; /* parsed service name, type, ... */ + int field_pattern; /* parsed field pattern */ + const char *param_pattern; /* parameter pattern */ +} PCF_MASTER_FLD_REQ; + + /* + * Valid inputs. + */ +static const char *pcf_valid_master_types[] = { + MASTER_XPORT_NAME_UNIX, + MASTER_XPORT_NAME_FIFO, + MASTER_XPORT_NAME_INET, + MASTER_XPORT_NAME_PASS, + MASTER_XPORT_NAME_UXDG, + 0, +}; + +static const char pcf_valid_bool_types[] = "yn-"; + +static VSTRING *pcf_exp_buf; + +#define STR(x) vstring_str(x) + +/* pcf_extract_field - extract text from {}, trim leading/trailing blanks */ + +static void pcf_extract_field(ARGV *argv, int field, const char *parens) +{ + char *arg = argv->argv[field]; + char *err; + + if ((err = extpar(&arg, parens, EXTPAR_FLAG_STRIP)) != 0) { + msg_warn("%s: %s", MASTER_CONF_FILE, err); + myfree(err); + } + argv_replace_one(argv, field, arg); +} + +/* pcf_normalize_nameval - normalize name = value from inside {} */ + +static void pcf_normalize_nameval(ARGV *argv, int field) +{ + char *arg = argv->argv[field]; + char *name; + char *value; + const char *err; + char *normalized; + + if ((err = split_nameval(arg, &name, &value)) != 0) { + msg_warn("%s: %s: \"%s\"", MASTER_CONF_FILE, err, arg); + } else { + normalized = concatenate(name, "=", value, (char *) 0); + argv_replace_one(argv, field, normalized); + myfree(normalized); + } +} + +/* pcf_normalize_daemon_args - bring daemon arguments into canonical form */ + +static void pcf_normalize_daemon_args(ARGV *argv) +{ + int field; + char *arg; + char *cp; + char *junk; + int extract_field; + + /* + * Normalize options to simplify later processing. + */ + for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) { + arg = argv->argv[field]; + if (arg[0] != '-' || strcmp(arg, "--") == 0) + break; + for (cp = arg + 1; *cp; cp++) { + if (strchr(pcf_daemon_options_expecting_value, *cp) != 0 + && cp > arg + 1) { + /* Split "-stuffozz" into "-stuff" and "-ozz". */ + junk = concatenate("-", cp, (char *) 0); + argv_insert_one(argv, field + 1, junk); + myfree(junk); + *cp = 0; /* XXX argv_replace_one() */ + break; + } + } + if (strchr(pcf_daemon_options_expecting_value, arg[1]) == 0) + /* Option requires no value. */ + continue; + if (arg[2] != 0) { + /* Split "-oname=value" into "-o" "name=value". */ + argv_insert_one(argv, field + 1, arg + 2); + arg[2] = 0; /* XXX argv_replace_one() */ + field += 1; + extract_field = (argv->argv[field][0] == CHARS_BRACE[0]); + } else if (argv->argv[field + 1] != 0) { + /* Already in "-o" "name=value" form. */ + field += 1; + extract_field = (argv->argv[field][0] == CHARS_BRACE[0]); + } else + extract_field = 0; + /* Extract text inside {}, optionally convert to name=value. */ + if (extract_field) { + pcf_extract_field(argv, field, CHARS_BRACE); + if (argv->argv[field - 1][1] == 'o') + pcf_normalize_nameval(argv, field); + } + } + /* Normalize non-option arguments. */ + for ( /* void */ ; argv->argv[field] != 0; field++) + /* Extract text inside {}. */ + if (argv->argv[field][0] == CHARS_BRACE[0]) + pcf_extract_field(argv, field, CHARS_BRACE); +} + +/* pcf_fix_fatal - fix multiline text before release */ + +static NORETURN PRINTFLIKE(1, 2) pcf_fix_fatal(const char *fmt,...) +{ + VSTRING *buf = vstring_alloc(100); + va_list ap; + + /* + * Replace newline with whitespace. + */ + va_start(ap, fmt); + vstring_vsprintf(buf, fmt, ap); + va_end(ap); + translit(STR(buf), "\n", " "); + msg_fatal("%s", STR(buf)); + /* NOTREACHED */ +} + +/* pcf_check_master_entry - sanity check master.cf entry */ + +static void pcf_check_master_entry(ARGV *argv, const char *raw_text) +{ + const char **cpp; + char *cp; + int len; + int field; + + cp = argv->argv[PCF_MASTER_FLD_TYPE]; + for (cpp = pcf_valid_master_types; /* see below */ ; cpp++) { + if (*cpp == 0) + pcf_fix_fatal("invalid " PCF_MASTER_NAME_TYPE " field \"%s\" in \"%s\"", + cp, raw_text); + if (strcmp(*cpp, cp) == 0) + break; + } + + for (field = PCF_MASTER_FLD_PRIVATE; field <= PCF_MASTER_FLD_CHROOT; field++) { + cp = argv->argv[field]; + if (cp[1] != 0 || strchr(pcf_valid_bool_types, *cp) == 0) + pcf_fix_fatal("invalid %s field \"%s\" in \"%s\"", + pcf_str_field_pattern(field), cp, raw_text); + } + + cp = argv->argv[PCF_MASTER_FLD_WAKEUP]; + len = strlen(cp); + if (len > 0 && cp[len - 1] == '?') + len--; + if (!(cp[0] == '-' && len == 1) && strspn(cp, "0123456789") != len) + pcf_fix_fatal("invalid " PCF_MASTER_NAME_WAKEUP " field \"%s\" in \"%s\"", + cp, raw_text); + + cp = argv->argv[PCF_MASTER_FLD_MAXPROC]; + if (strcmp("-", cp) != 0 && cp[strspn(cp, "0123456789")] != 0) + pcf_fix_fatal("invalid " PCF_MASTER_NAME_MAXPROC " field \"%s\" in \"%s\"", + cp, raw_text); +} + +/* pcf_free_master_entry - destroy parsed entry */ + +void pcf_free_master_entry(PCF_MASTER_ENT *masterp) +{ + /* XX Fixme: allocation/deallocation asymmetry. */ + myfree(masterp->name_space); + argv_free(masterp->argv); + if (masterp->valid_names) + htable_free(masterp->valid_names, myfree); + if (masterp->ro_params) + dict_close(masterp->ro_params); + if (masterp->all_params) + dict_close(masterp->all_params); + myfree((void *) masterp); +} + +/* pcf_parse_master_entry - parse one master line */ + +const char *pcf_parse_master_entry(PCF_MASTER_ENT *masterp, const char *buf) +{ + ARGV *argv; + char *ro_name_space; + char *process_name; + + /* + * We can't use the master daemon's master_ent routines in their current + * form. They convert everything to internal form, and they skip disabled + * services. + * + * The postconf command needs to show default fields as "-", and needs to + * know about all service names so that it can generate service-dependent + * parameter names (transport-dependent etc.). + * + * XXX Do per-field sanity checks. + */ + argv = argv_splitq(buf, PCF_MASTER_BLANKS, CHARS_BRACE); + if (argv->argc < PCF_MASTER_MIN_FIELDS) { + argv_free(argv); /* Coverity 201311 */ + return ("bad field count"); + } + pcf_check_master_entry(argv, buf); + pcf_normalize_daemon_args(argv); + masterp->name_space = + concatenate(argv->argv[0], PCF_NAMESP_SEP_STR, argv->argv[1], (char *) 0); + ro_name_space = + concatenate("ro", PCF_NAMESP_SEP_STR, masterp->name_space, (char *) 0); + masterp->argv = argv; + masterp->valid_names = 0; + masterp->ro_params = dict_ht_open(ro_name_space, O_CREAT | O_RDWR, 0); + process_name = basename(argv->argv[PCF_MASTER_FLD_CMD]); + dict_put(masterp->ro_params, VAR_PROCNAME, process_name); + dict_put(masterp->ro_params, VAR_SERVNAME, + strcmp(process_name, argv->argv[0]) != 0 ? + argv->argv[0] : process_name); + myfree(ro_name_space); + masterp->all_params = 0; + return (0); +} + +/* pcf_read_master - read and digest the master.cf file */ + +void pcf_read_master(int fail_on_open_error) +{ + const char *myname = "pcf_read_master"; + char *path; + VSTRING *buf; + VSTREAM *fp; + const char *err; + int entry_count = 0; + int line_count; + int last_line = 0; + + /* + * Sanity check. + */ + if (pcf_master_table != 0) + msg_panic("%s: master table is already initialized", myname); + + /* + * Get the location of master.cf. + */ + if (var_config_dir == 0) + pcf_set_config_dir(); + path = concatenate(var_config_dir, "/", MASTER_CONF_FILE, (char *) 0); + + /* + * Initialize the in-memory master table. + */ + pcf_master_table = (PCF_MASTER_ENT *) mymalloc(sizeof(*pcf_master_table)); + + /* + * Skip blank lines and comment lines. Degrade gracefully if master.cf is + * not available, and master.cf is not the primary target. + */ + if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) { + if (fail_on_open_error) + msg_fatal("open %s: %m", path); + msg_warn("open %s: %m", path); + } else { + buf = vstring_alloc(100); + while (readllines(buf, fp, &last_line, &line_count) != 0) { + pcf_master_table = (PCF_MASTER_ENT *) myrealloc((void *) pcf_master_table, + (entry_count + 2) * sizeof(*pcf_master_table)); + if ((err = pcf_parse_master_entry(pcf_master_table + entry_count, + STR(buf))) != 0) + msg_fatal("file %s: line %d: %s", path, line_count, err); + entry_count += 1; + } + vstream_fclose(fp); + vstring_free(buf); + } + + /* + * Null-terminate the master table and clean up. + */ + pcf_master_table[entry_count].argv = 0; + myfree(path); +} + +/* pcf_print_master_entry - print one master line */ + +void pcf_print_master_entry(VSTREAM *fp, int mode, PCF_MASTER_ENT *masterp) +{ + char **argv = masterp->argv->argv; + const char *arg; + const char *aval; + int arg_len; + int line_len; + int field; + int in_daemon_options; + int need_parens; + static int column_goal[] = { + 0, /* service */ + 11, /* type */ + 17, /* private */ + 25, /* unpriv */ + 33, /* chroot */ + 41, /* wakeup */ + 49, /* maxproc */ + 57, /* command */ + }; + +#define ADD_TEXT(text, len) do { \ + vstream_fputs(text, fp); line_len += len; } \ + while (0) +#define ADD_SPACE ADD_TEXT(" ", 1) + + if (pcf_exp_buf == 0) + pcf_exp_buf = vstring_alloc(100); + + /* + * Show the standard fields at their preferred column position. Use at + * least one-space column separation. + */ + for (line_len = 0, field = 0; field < PCF_MASTER_MIN_FIELDS; field++) { + arg = argv[field]; + if (line_len > 0) { + do { + ADD_SPACE; + } while (line_len < column_goal[field]); + } + ADD_TEXT(arg, strlen(arg)); + } + + /* + * Format the daemon command-line options and non-option arguments. Here, + * we have no data-dependent preference for column positions, but we do + * have argument grouping preferences. + */ + in_daemon_options = 1; + for ( /* void */ ; (arg = argv[field]) != 0; field++) { + arg_len = strlen(arg); + aval = 0; + need_parens = 0; + if (in_daemon_options) { + + /* + * Try to show the generic options (-v -D) on the first line, and + * non-options on a later line. + */ + if (arg[0] != '-' || strcmp(arg, "--") == 0) { + in_daemon_options = 0; +#if 0 + if (mode & PCF_FOLD_LINE) + /* Force line wrap. */ + line_len = PCF_LINE_LIMIT; +#endif + } + + /* + * Special processing for options that require a value. + */ + else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 + && (aval = argv[field + 1]) != 0) { + + /* Force line wrap before option with value. */ + line_len = PCF_LINE_LIMIT; + + /* + * Optionally, expand $name in parameter value. + */ + if (strcmp(arg, "-o") == 0 + && (mode & PCF_SHOW_EVAL) != 0) + aval = pcf_expand_parameter_value(pcf_exp_buf, mode, + aval, masterp); + + /* + * Keep option and value on the same line. + */ + arg_len += strlen(aval) + 3; + if ((need_parens = aval[strcspn(aval, PCF_MASTER_BLANKS)]) != 0) + arg_len += 2; + } + } else { + need_parens = arg[strcspn(arg, PCF_MASTER_BLANKS)]; + } + + /* + * Insert a line break when the next item won't fit. + */ + if (line_len > PCF_INDENT_LEN) { + if ((mode & PCF_FOLD_LINE) == 0 + || line_len + 1 + arg_len < PCF_LINE_LIMIT) { + ADD_SPACE; + } else { + vstream_fputs("\n" PCF_INDENT_TEXT, fp); + line_len = PCF_INDENT_LEN; + } + } + if (in_daemon_options == 0 && need_parens) + ADD_TEXT("{", 1); + ADD_TEXT(arg, strlen(arg)); + if (in_daemon_options == 0 && need_parens) + ADD_TEXT("}", 1); + if (aval) { + ADD_TEXT(" ", 1); + if (need_parens) + ADD_TEXT("{", 1); + ADD_TEXT(aval, strlen(aval)); + if (need_parens) + ADD_TEXT("}", 1); + field += 1; + + /* Force line wrap after option with value. */ + line_len = PCF_LINE_LIMIT; + + } + } + vstream_fputs("\n", fp); + + if (msg_verbose) + vstream_fflush(fp); +} + +/* pcf_show_master_entries - show master.cf entries */ + +void pcf_show_master_entries(VSTREAM *fp, int mode, int argc, char **argv) +{ + PCF_MASTER_ENT *masterp; + PCF_MASTER_FLD_REQ *field_reqs; + PCF_MASTER_FLD_REQ *req; + + /* + * Parse the filter expressions. + */ + if (argc > 0) { + field_reqs = (PCF_MASTER_FLD_REQ *) + mymalloc(sizeof(*field_reqs) * argc); + for (req = field_reqs; req < field_reqs + argc; req++) { + req->match_count = 0; + req->raw_text = *argv++; + req->service_pattern = + pcf_parse_service_pattern(req->raw_text, 1, 2); + if (req->service_pattern == 0) + msg_fatal("-M option requires service_name[/type]"); + } + } + + /* + * Iterate over the master table. + */ + for (masterp = pcf_master_table; masterp->argv != 0; masterp++) { + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, + masterp->argv->argv[0], + masterp->argv->argv[1])) { + req->match_count++; + pcf_print_master_entry(fp, mode, masterp); + } + } + } else { + pcf_print_master_entry(fp, mode, masterp); + } + } + + /* + * Cleanup. + */ + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (req->match_count == 0) + msg_warn("unmatched request: \"%s\"", req->raw_text); + argv_free(req->service_pattern); + } + myfree((void *) field_reqs); + } +} + +/* pcf_print_master_field - scaffolding */ + +static void pcf_print_master_field(VSTREAM *fp, int mode, + PCF_MASTER_ENT *masterp, + int field) +{ + char **argv = masterp->argv->argv; + const char *arg; + const char *aval; + int arg_len; + int line_len; + int in_daemon_options; + int need_parens; + + if (pcf_exp_buf == 0) + pcf_exp_buf = vstring_alloc(100); + + /* + * Show the field value, or the first value in the case of a multi-column + * field. + */ +#define ADD_CHAR(ch) ADD_TEXT((ch), 1) + + line_len = 0; + if ((mode & PCF_HIDE_NAME) == 0) { + ADD_TEXT(argv[0], strlen(argv[0])); + ADD_CHAR(PCF_NAMESP_SEP_STR); + ADD_TEXT(argv[1], strlen(argv[1])); + ADD_CHAR(PCF_NAMESP_SEP_STR); + ADD_TEXT(pcf_str_field_pattern(field), strlen(pcf_str_field_pattern(field))); + } + if ((mode & (PCF_HIDE_NAME | PCF_HIDE_VALUE)) == 0) { + ADD_TEXT(" = ", 3); + } + if ((mode & PCF_HIDE_VALUE) == 0) { + if (line_len > 0 && line_len + strlen(argv[field]) > PCF_LINE_LIMIT) { + vstream_fputs("\n" PCF_INDENT_TEXT, fp); + line_len = PCF_INDENT_LEN; + } + ADD_TEXT(argv[field], strlen(argv[field])); + } + + /* + * Format the daemon command-line options and non-option arguments. Here, + * we have no data-dependent preference for column positions, but we do + * have argument grouping preferences. + */ + if (field == PCF_MASTER_FLD_CMD && (mode & PCF_HIDE_VALUE) == 0) { + in_daemon_options = 1; + for (field += 1; (arg = argv[field]) != 0; field++) { + arg_len = strlen(arg); + aval = 0; + need_parens = 0; + if (in_daemon_options) { + + /* + * We make no special case for generic options (-v -D) + * options. + */ + if (arg[0] != '-' || strcmp(arg, "--") == 0) { + in_daemon_options = 0; + } else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 + && (aval = argv[field + 1]) != 0) { + + /* Force line break before option with value. */ + line_len = PCF_LINE_LIMIT; + + /* + * Optionally, expand $name in parameter value. + */ + if (strcmp(arg, "-o") == 0 + && (mode & PCF_SHOW_EVAL) != 0) + aval = pcf_expand_parameter_value(pcf_exp_buf, mode, + aval, masterp); + + /* + * Keep option and value on the same line. + */ + arg_len += strlen(aval) + 1; + if ((need_parens = aval[strcspn(aval, PCF_MASTER_BLANKS)]) != 0) + arg_len += 2; + } + } else { + need_parens = arg[strcspn(arg, PCF_MASTER_BLANKS)]; + } + + /* + * Insert a line break when the next item won't fit. + */ + if (line_len > PCF_INDENT_LEN) { + if ((mode & PCF_FOLD_LINE) == 0 + || line_len + 1 + arg_len < PCF_LINE_LIMIT) { + ADD_SPACE; + } else { + vstream_fputs("\n" PCF_INDENT_TEXT, fp); + line_len = PCF_INDENT_LEN; + } + } + if (in_daemon_options == 0 && need_parens) + ADD_TEXT("{", 1); + ADD_TEXT(arg, strlen(arg)); + if (in_daemon_options == 0 && need_parens) + ADD_TEXT("}", 1); + if (aval) { + ADD_SPACE; + if (need_parens) + ADD_TEXT("{", 1); + ADD_TEXT(aval, strlen(aval)); + if (need_parens) + ADD_TEXT("}", 1); + field += 1; + + /* Force line break after option with value. */ + line_len = PCF_LINE_LIMIT; + } + } + } + vstream_fputs("\n", fp); + + if (msg_verbose) + vstream_fflush(fp); +} + +/* pcf_show_master_fields - show master.cf fields */ + +void pcf_show_master_fields(VSTREAM *fp, int mode, int argc, char **argv) +{ + const char *myname = "pcf_show_master_fields"; + PCF_MASTER_ENT *masterp; + PCF_MASTER_FLD_REQ *field_reqs; + PCF_MASTER_FLD_REQ *req; + int field; + + /* + * Parse the filter expressions. + */ + if (argc > 0) { + field_reqs = (PCF_MASTER_FLD_REQ *) + mymalloc(sizeof(*field_reqs) * argc); + for (req = field_reqs; req < field_reqs + argc; req++) { + req->match_count = 0; + req->raw_text = *argv++; + req->service_pattern = + pcf_parse_service_pattern(req->raw_text, 1, 3); + if (req->service_pattern == 0) + msg_fatal("-F option requires service_name[/type[/field]]"); + field = req->field_pattern = + pcf_parse_field_pattern(req->service_pattern->argv[2]); + if (pcf_is_magic_field_pattern(field) == 0 + && (field < 0 || field > PCF_MASTER_FLD_CMD)) + msg_panic("%s: bad attribute field index: %d", + myname, field); + } + } + + /* + * Iterate over the master table. + */ + for (masterp = pcf_master_table; masterp->argv != 0; masterp++) { + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, + masterp->argv->argv[0], + masterp->argv->argv[1])) { + req->match_count++; + field = req->field_pattern; + if (pcf_is_magic_field_pattern(field)) { + for (field = 0; field <= PCF_MASTER_FLD_CMD; field++) + pcf_print_master_field(fp, mode, masterp, field); + } else { + pcf_print_master_field(fp, mode, masterp, field); + } + } + } + } else { + for (field = 0; field <= PCF_MASTER_FLD_CMD; field++) + pcf_print_master_field(fp, mode, masterp, field); + } + } + + /* + * Cleanup. + */ + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (req->match_count == 0) + msg_warn("unmatched request: \"%s\"", req->raw_text); + argv_free(req->service_pattern); + } + myfree((void *) field_reqs); + } +} + +/* pcf_edit_master_field - replace master.cf field value. */ + +void pcf_edit_master_field(PCF_MASTER_ENT *masterp, int field, + const char *new_value) +{ + + /* + * Replace multi-column attribute. + */ + if (field == PCF_MASTER_FLD_CMD) { + argv_truncate(masterp->argv, PCF_MASTER_FLD_CMD); + argv_splitq_append(masterp->argv, new_value, PCF_MASTER_BLANKS, CHARS_BRACE); + pcf_normalize_daemon_args(masterp->argv); + } + + /* + * Replace single-column attribute. + */ + else { + argv_replace_one(masterp->argv, field, new_value); + } + + /* + * Do per-field sanity checks. + */ + pcf_check_master_entry(masterp->argv, new_value); +} + +/* pcf_print_master_param - scaffolding */ + +static void pcf_print_master_param(VSTREAM *fp, int mode, + PCF_MASTER_ENT *masterp, + const char *param_name, + const char *param_value) +{ + if (pcf_exp_buf == 0) + pcf_exp_buf = vstring_alloc(100); + + if (mode & PCF_HIDE_VALUE) { + pcf_print_line(fp, mode, "%s%c%s\n", + masterp->name_space, PCF_NAMESP_SEP_CH, + param_name); + } else { + if ((mode & PCF_SHOW_EVAL) != 0) + param_value = pcf_expand_parameter_value(pcf_exp_buf, mode, + param_value, masterp); + if ((mode & PCF_HIDE_NAME) == 0) { + pcf_print_line(fp, mode, "%s%c%s = %s\n", + masterp->name_space, PCF_NAMESP_SEP_CH, + param_name, param_value); + } else { + pcf_print_line(fp, mode, "%s\n", param_value); + } + } + if (msg_verbose) + vstream_fflush(fp); +} + +/* pcf_sort_argv_cb - sort argv call-back */ + +static int pcf_sort_argv_cb(const void *a, const void *b) +{ + return (strcmp(*(char **) a, *(char **) b)); +} + +/* pcf_show_master_any_param - show any parameter in master.cf service entry */ + +static void pcf_show_master_any_param(VSTREAM *fp, int mode, + PCF_MASTER_ENT *masterp) +{ + const char *myname = "pcf_show_master_any_param"; + ARGV *argv = argv_alloc(10); + DICT *dict = masterp->all_params; + const char *param_name; + const char *param_value; + int param_count = 0; + int how; + char **cpp; + + /* + * Print parameters in sorted order. The number of parameters per + * master.cf entry is small, so we optimize for code simplicity and don't + * worry about the cost of double lookup. + */ + + /* Look up the parameter names and ignore the values. */ + + for (how = DICT_SEQ_FUN_FIRST; + dict->sequence(dict, how, ¶m_name, ¶m_value) == 0; + how = DICT_SEQ_FUN_NEXT) { + argv_add(argv, param_name, ARGV_END); + param_count++; + } + + /* Print the parameters in sorted order. */ + + qsort(argv->argv, param_count, sizeof(argv->argv[0]), pcf_sort_argv_cb); + for (cpp = argv->argv; (param_name = *cpp) != 0; cpp++) { + if ((param_value = dict_get(dict, param_name)) == 0) + msg_panic("%s: parameter name not found: %s", myname, param_name); + pcf_print_master_param(fp, mode, masterp, param_name, param_value); + } + + /* + * Clean up. + */ + argv_free(argv); +} + +/* pcf_show_master_params - show master.cf params */ + +void pcf_show_master_params(VSTREAM *fp, int mode, int argc, char **argv) +{ + PCF_MASTER_ENT *masterp; + PCF_MASTER_FLD_REQ *field_reqs; + PCF_MASTER_FLD_REQ *req; + DICT *dict; + const char *param_value; + + /* + * Parse the filter expressions. + */ + if (argc > 0) { + field_reqs = (PCF_MASTER_FLD_REQ *) + mymalloc(sizeof(*field_reqs) * argc); + for (req = field_reqs; req < field_reqs + argc; req++) { + req->match_count = 0; + req->raw_text = *argv++; + req->service_pattern = + pcf_parse_service_pattern(req->raw_text, 1, 3); + if (req->service_pattern == 0) + msg_fatal("-P option requires service_name[/type[/parameter]]"); + req->param_pattern = req->service_pattern->argv[2]; + } + } + + /* + * Iterate over the master table. + */ + for (masterp = pcf_master_table; masterp->argv != 0; masterp++) { + if ((dict = masterp->all_params) != 0) { + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (PCF_MATCH_SERVICE_PATTERN(req->service_pattern, + masterp->argv->argv[0], + masterp->argv->argv[1])) { + if (PCF_IS_MAGIC_PARAM_PATTERN(req->param_pattern)) { + pcf_show_master_any_param(fp, mode, masterp); + req->match_count += 1; + } else if ((param_value = dict_get(dict, + req->param_pattern)) != 0) { + pcf_print_master_param(fp, mode, masterp, + req->param_pattern, + param_value); + req->match_count += 1; + } + } + } + } else { + pcf_show_master_any_param(fp, mode, masterp); + } + } + } + + /* + * Cleanup. + */ + if (argc > 0) { + for (req = field_reqs; req < field_reqs + argc; req++) { + if (req->match_count == 0) + msg_warn("unmatched request: \"%s\"", req->raw_text); + argv_free(req->service_pattern); + } + myfree((void *) field_reqs); + } +} + +/* pcf_edit_master_param - update, add or remove -o parameter=value */ + +void pcf_edit_master_param(PCF_MASTER_ENT *masterp, int mode, + const char *param_name, + const char *param_value) +{ + const char *myname = "pcf_edit_master_param"; + ARGV *argv = masterp->argv; + const char *arg; + const char *aval; + int param_match = 0; + int name_len = strlen(param_name); + int field; + + for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) { + arg = argv->argv[field]; + + /* + * Stop at the first non-option argument or end-of-list. + */ + if (arg[0] != '-' || strcmp(arg, "--") == 0) { + break; + } + + /* + * Zoom in on command-line options with a value. + */ + else if (strchr(pcf_daemon_options_expecting_value, arg[1]) != 0 + && (aval = argv->argv[field + 1]) != 0) { + + /* + * Zoom in on "-o parameter=value". + */ + if (strcmp(arg, "-o") == 0) { + if (strncmp(aval, param_name, name_len) == 0 + && aval[name_len] == '=') { + param_match = 1; + switch (mode & (PCF_EDIT_CONF | PCF_EDIT_EXCL)) { + + /* + * Update parameter=value. + */ + case PCF_EDIT_CONF: + aval = concatenate(param_name, "=", + param_value, (char *) 0); + argv_replace_one(argv, field + 1, aval); + myfree((void *) aval); + if (masterp->all_params) + dict_put(masterp->all_params, param_name, param_value); + /* XXX Update parameter "used/defined" status. */ + break; + + /* + * Delete parameter=value. + */ + case PCF_EDIT_EXCL: + argv_delete(argv, field, 2); + if (masterp->all_params) + dict_del(masterp->all_params, param_name); + /* XXX Update parameter "used/defined" status. */ + field -= 2; + break; + default: + msg_panic("%s: unexpected mode: %d", myname, mode); + } + } + } + + /* + * Skip over the command-line option value. + */ + field += 1; + } + } + + /* + * Add unmatched parameter. + */ + if ((mode & PCF_EDIT_CONF) && param_match == 0) { + /* XXX Generalize: argv_insert(argv, where, list...) */ + argv_insert_one(argv, field, "-o"); + aval = concatenate(param_name, "=", + param_value, (char *) 0); + argv_insert_one(argv, field + 1, aval); + if (masterp->all_params) + dict_put(masterp->all_params, param_name, param_value); + /* XXX May affect parameter "used/defined" status. */ + myfree((void *) aval); + param_match = 1; + } +} diff --git a/src/postconf/postconf_match.c b/src/postconf/postconf_match.c new file mode 100644 index 0000000..53709a4 --- /dev/null +++ b/src/postconf/postconf_match.c @@ -0,0 +1,188 @@ +/*++ +/* NAME +/* postconf_match 3 +/* SUMMARY +/* pattern-matching support +/* SYNOPSIS +/* #include +/* +/* int pcf_parse_field_pattern(field_expr) +/* char *field_expr; +/* +/* const char *pcf_str_field_pattern(field_pattern) +/* int field_pattern; +/* +/* int pcf_is_magic_field_pattern(field_pattern) +/* int field_pattern; +/* +/* ARGV *pcf_parse_service_pattern(service_expr, min_expr, max_expr) +/* const char *service_expr; +/* int min_expr; +/* int max_expr; +/* +/* int PCF_IS_MAGIC_SERVICE_PATTERN(service_pattern) +/* const ARGV *service_pattern; +/* +/* int PCF_MATCH_SERVICE_PATTERN(service_pattern, service_name, +/* service_type) +/* const ARGV *service_pattern; +/* const char *service_name; +/* const char *service_type; +/* +/* const char *pcf_str_field_pattern(field_pattern) +/* int field_pattern; +/* +/* int PCF_IS_MAGIC_PARAM_PATTERN(param_pattern) +/* const char *param_pattern; +/* +/* int PCF_MATCH_PARAM_PATTERN(param_pattern, param_name) +/* const char *param_pattern; +/* const char *param_name; +/* DESCRIPTION +/* pcf_parse_service_pattern() takes an expression and splits +/* it up on '/' into an array of sub-expressions, This function +/* returns null if the input does fewer than "min" or more +/* than "max" sub-expressions. +/* +/* PCF_IS_MAGIC_SERVICE_PATTERN() returns non-zero if any of +/* the service name or service type sub-expressions contains +/* a matching operator (as opposed to string literals that +/* only match themselves). This is an unsafe macro that evaluates +/* its arguments more than once. +/* +/* PCF_MATCH_SERVICE_PATTERN() matches a service name and type +/* from master.cf against the parsed pattern. This is an unsafe +/* macro that evaluates its arguments more than once. +/* +/* pcf_parse_field_pattern() converts a field sub-expression, +/* and returns the conversion result. +/* +/* pcf_str_field_pattern() converts a result from +/* pcf_parse_field_pattern() into string form. +/* +/* pcf_is_magic_field_pattern() returns non-zero if the field +/* pattern sub-expression contained a matching operator (as +/* opposed to a string literal that only matches itself). +/* +/* PCF_IS_MAGIC_PARAM_PATTERN() returns non-zero if the parameter +/* sub-expression contains a matching operator (as opposed to +/* a string literal that only matches itself). This is an +/* unsafe macro that evaluates its arguments more than once. +/* +/* PCF_MATCH_PARAM_PATTERN() matches a parameter name from +/* master.cf against the parsed pattern. This is an unsafe +/* macro that evaluates its arguments more than once. +/* +/* Arguments +/* .IP field_expr +/* A field expression. +/* .IP service_expr +/* This argument is split on '/' into its constituent +/* sub-expressions. +/* .IP min_expr +/* The minimum number of sub-expressions in service_expr. +/* .IP max_expr +/* The maximum number of sub-expressions in service_expr. +/* .IP service_name +/* Service name from master.cf. +/* .IP service_type +/* Service type from master.cf. +/* .IP param_pattern +/* A parameter name expression. +/* DIAGNOSTICS +/* Fatal errors: invalid syntax. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include + + /* + * Conversion table. Each PCF_MASTER_NAME_XXX name entry must be stored at + * table offset PCF_MASTER_FLD_XXX. So don't mess it up. + */ +NAME_CODE pcf_field_name_offset[] = { + PCF_MASTER_NAME_SERVICE, PCF_MASTER_FLD_SERVICE, + PCF_MASTER_NAME_TYPE, PCF_MASTER_FLD_TYPE, + PCF_MASTER_NAME_PRIVATE, PCF_MASTER_FLD_PRIVATE, + PCF_MASTER_NAME_UNPRIV, PCF_MASTER_FLD_UNPRIV, + PCF_MASTER_NAME_CHROOT, PCF_MASTER_FLD_CHROOT, + PCF_MASTER_NAME_WAKEUP, PCF_MASTER_FLD_WAKEUP, + PCF_MASTER_NAME_MAXPROC, PCF_MASTER_FLD_MAXPROC, + PCF_MASTER_NAME_CMD, PCF_MASTER_FLD_CMD, + "*", PCF_MASTER_FLD_WILDC, + 0, PCF_MASTER_FLD_NONE, +}; + +/* pcf_parse_field_pattern - parse service attribute pattern */ + +int pcf_parse_field_pattern(const char *field_name) +{ + int field_pattern; + + if ((field_pattern = name_code(pcf_field_name_offset, + NAME_CODE_FLAG_STRICT_CASE, + field_name)) == PCF_MASTER_FLD_NONE) + msg_fatal("invalid service attribute name: \"%s\"", field_name); + return (field_pattern); +} + +/* pcf_parse_service_pattern - parse service pattern */ + +ARGV *pcf_parse_service_pattern(const char *pattern, int min_expr, int max_expr) +{ + ARGV *argv; + char **cpp; + + /* + * Work around argv_split() lameness. + */ + if (*pattern == '/') + return (0); + argv = argv_split(pattern, PCF_NAMESP_SEP_STR); + if (argv->argc < min_expr || argv->argc > max_expr) { + argv_free(argv); + return (0); + } + + /* + * Allow '*' only all by itself. + */ + for (cpp = argv->argv; *cpp; cpp++) { + if (!PCF_MATCH_ANY(*cpp) && strchr(*cpp, PCF_MATCH_WILDC_STR[0]) != 0) { + argv_free(argv); + return (0); + } + } + + /* + * Provide defaults for missing fields. + */ + while (argv->argc < max_expr) + argv_add(argv, PCF_MATCH_WILDC_STR, ARGV_END); + return (argv); +} diff --git a/src/postconf/postconf_misc.c b/src/postconf/postconf_misc.c new file mode 100644 index 0000000..0107651 --- /dev/null +++ b/src/postconf/postconf_misc.c @@ -0,0 +1,60 @@ +/*++ +/* NAME +/* postconf_misc 3 +/* SUMMARY +/* miscellaneous low-level code +/* SYNOPSIS +/* #include +/* +/* void pcf_set_config_dir() +/* DESCRIPTION +/* pcf_set_config_dir() forcibly overrides the var_config_dir +/* parameter setting with the value from the environment or +/* with the default pathname, and updates the mail parameter +/* dictionary. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include + +/* pcf_set_config_dir - forcibly override var_config_dir */ + +void pcf_set_config_dir(void) +{ + char *config_dir; + + if (var_config_dir) + myfree(var_config_dir); + if ((config_dir = safe_getenv(CONF_ENV_PATH)) != 0) { + var_config_dir = mystrdup(config_dir); + set_mail_conf_str(VAR_CONFIG_DIR, var_config_dir); + } else { + var_config_dir = mystrdup(DEF_CONFIG_DIR); + } +} diff --git a/src/postconf/postconf_node.c b/src/postconf/postconf_node.c new file mode 100644 index 0000000..dca3594 --- /dev/null +++ b/src/postconf/postconf_node.c @@ -0,0 +1,185 @@ +/*++ +/* NAME +/* postconf_node 3 +/* SUMMARY +/* low-level parameter node support +/* SYNOPSIS +/* #include +/* +/* PCF_PARAM_TABLE *PCF_PARAM_TABLE_CREATE(size) +/* ssize_t size; +/* +/* PCF_PARAM_INFO **PCF_PARAM_TABLE_LIST(table) +/* PCF_PARAM_TABLE *table; +/* +/* const char *PCF_PARAM_INFO_NAME(info) +/* PCF_PARAM_INFO *info; +/* +/* PCF_PARAM_NODE *PCF_PARAM_INFO_NODE(info) +/* PCF_PARAM_INFO *info; +/* +/* PCF_PARAM_NODE *PCF_PARAM_TABLE_FIND(table, name) +/* PCF_PARAM_TABLE *table; +/* const char *name; +/* +/* PCF_PARAM_INFO *PCF_PARAM_TABLE_LOCATE(table, name) +/* PCF_PARAM_TABLE *table; +/* const char *name; +/* +/* PCF_PARAM_INFO *PCF_PARAM_TABLE_ENTER(table, name, flags, +/* param_data, convert_fn) +/* PCF_PARAM_TABLE *table; +/* const char *name; +/* int flags; +/* void *param_data; +/* const char *(*convert_fn)(void *); +/* +/* PCF_PARAM_NODE *pcf_make_param_node(flags, param_data, convert_fn) +/* int flags; +/* void *param_data; +/* const char *(*convert_fn) (void *); +/* +/* const char *pcf_convert_param_node(mode, name, node) +/* int mode; +/* const char *name; +/* PCF_PARAM_NODE *node; +/* +/* VSTRING *pcf_param_string_buf; +/* +/* PCF_RAW_PARAMETER(node) +/* const PCF_PARAM_NODE *node; +/* DESCRIPTION +/* This module maintains data structures (PCF_PARAM_NODE) with +/* information about known-legitimate parameters. These data +/* structures are stored in a hash table. +/* +/* The PCF_PARAM_MUMBLE() macros are wrappers around the +/* htable(3) module. Their sole purpose is to encapsulate all +/* the pointer casting from and to (PCF_PARAM_NODE *). Apart +/* from that, the macros have no features worth discussing. +/* +/* pcf_make_param_node() creates a node for the global parameter +/* table. This node provides a parameter default value, and a +/* function that converts the default value to string. +/* +/* pcf_convert_param_node() produces a string representation +/* for a global parameter default value. +/* +/* PCF_RAW_PARAMETER() returns non-zero if the specified +/* parameter node represents a "raw parameter". The value of +/* such parameters must not be scanned for macro names. Some +/* "raw parameter" values contain "$" without macros, such as +/* the smtpd_expansion_filter "safe character" set; and some +/* contain $name from a private name space, such as forward_path. +/* Some "raw parameter" values in postscreen(8) are safe to +/* expand by one level. Support for that may be added later. +/* +/* pcf_param_string_buf is a buffer that is initialized on the +/* fly and that parameter-to-string conversion functions may +/* use for temporary result storage. +/* +/* Arguments: +/* .IP size +/* The initial size of the hash table. +/* .IP table +/* A hash table for storage of "valid parameter" information. +/* .IP info +/* A data structure with a name component and a PCF_PARAM_NODE +/* component. Use PCF_PARAM_INFO_NAME() and PCF_PARAM_INFO_NODE() +/* to access these components. +/* .IP name +/* The name of a "valid parameter". +/* .IP flags +/* PCF_PARAM_FLAG_RAW for a "raw parameter", PCF_PARAM_FLAG_NONE +/* otherwise. See the PCF_RAW_PARAMETER() discussion above for +/* discussion of "raw parameter" values. +/* .IP param_data +/* Information about the parameter value. Specify PCF_PARAM_NO_DATA +/* if this is not applicable. +/* .IP convert_fn +/* The function that will be invoked to produce a string +/* representation of the information in param_data. The function +/* receives the param_data value as argument. +/* .IP mode +/* For now, the PCF_SHOW_DEFS flag is required. +/* .IP name +/* The name of the parameter whose value is requested. This +/* is used for diagnostics. +/* .IP node +/* The (flags, param_data, convert_fn) information that needs +/* to be converted to a string representation of the default +/* value. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include + +VSTRING *pcf_param_string_buf; + +/* pcf_make_param_node - make node for global parameter table */ + +PCF_PARAM_NODE *pcf_make_param_node(int flags, void *param_data, + const char *(*convert_fn) (void *)) +{ + PCF_PARAM_NODE *node; + + node = (PCF_PARAM_NODE *) mymalloc(sizeof(*node)); + node->flags = flags; + node->param_data = param_data; + node->convert_fn = convert_fn; + return (node); +} + +/* pcf_convert_param_node - get default parameter value */ + +const char *pcf_convert_param_node(int mode, const char *name, PCF_PARAM_NODE *node) +{ + const char *myname = "pcf_convert_param_node"; + const char *value; + + /* + * One-off initialization. + */ + if (pcf_param_string_buf == 0) + pcf_param_string_buf = vstring_alloc(100); + + /* + * Sanity check. A null value indicates that a parameter does not have + * the requested value. At this time, the only requested value can be the + * default value, and a null pointer value makes no sense here. + */ + if ((mode & PCF_SHOW_DEFS) == 0) + msg_panic("%s: request for non-default value of parameter %s", + myname, name); + if ((value = node->convert_fn(node->param_data)) == 0) + msg_panic("%s: parameter %s has null pointer default value", + myname, name); + + /* + * Return the parameter default value. + */ + return (value); +} diff --git a/src/postconf/postconf_other.c b/src/postconf/postconf_other.c new file mode 100644 index 0000000..0c4c0c4 --- /dev/null +++ b/src/postconf/postconf_other.c @@ -0,0 +1,141 @@ +/*++ +/* NAME +/* postconf_other 3 +/* SUMMARY +/* support for miscellaneous information categories +/* SYNOPSIS +/* #include +/* +/* void pcf_show_maps() +/* +/* void pcf_show_locks() +/* +/* void pcf_show_sasl(mode) +/* int mode; +/* +/* void pcf_show_tls(what) +/* const char *what; +/* DESCRIPTION +/* pcf_show_maps() lists the available map (lookup table) +/* types. +/* +/* pcf_show_locks() lists the available mailbox lock types. +/* +/* pcf_show_sasl() shows the available SASL authentication +/* plugin types. +/* +/* pcf_show_tls() reports the "compile-version" or "run-version" +/* of the TLS library, or the supported public-key algorithms. +/* +/* Arguments: +/* .IP mode +/* Show server information if the PCF_SHOW_SASL_SERV flag is +/* set, otherwise show client information. +/* .IP what +/* One of the literals "compile-version", "run-version" or +/* "public-key-algorithms". +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* XSASL library. */ + +#include + +/* TLS library. */ + +#include + +/* Application-specific. */ + +#include + +/* pcf_show_maps - show available maps */ + +void pcf_show_maps(void) +{ + ARGV *maps_argv; + int i; + + maps_argv = dict_mapnames(); + for (i = 0; i < maps_argv->argc; i++) + vstream_printf("%s\n", maps_argv->argv[i]); + argv_free(maps_argv); +} + +/* pcf_show_locks - show available mailbox locking methods */ + +void pcf_show_locks(void) +{ + ARGV *locks_argv; + int i; + + locks_argv = mbox_lock_names(); + for (i = 0; i < locks_argv->argc; i++) + vstream_printf("%s\n", locks_argv->argv[i]); + argv_free(locks_argv); +} + +/* pcf_show_sasl - show SASL plug-in types */ + +void pcf_show_sasl(int what) +{ + ARGV *sasl_argv; + int i; + + sasl_argv = (what & PCF_SHOW_SASL_SERV) ? xsasl_server_types() : + xsasl_client_types(); + for (i = 0; i < sasl_argv->argc; i++) + vstream_printf("%s\n", sasl_argv->argv[i]); + argv_free(sasl_argv); +} + +/* pcf_show_tls - show TLS support */ + +void pcf_show_tls(const char *what) +{ +#ifdef USE_TLS + if (strcmp(what, "compile-version") == 0) + vstream_printf("%s\n", tls_compile_version()); + else if (strcmp(what, "run-version") == 0) + vstream_printf("%s\n", tls_run_version()); + else if (strcmp(what, "public-key-algorithms") == 0) { + const char **cpp; + + for (cpp = tls_pkey_algorithms(); *cpp; cpp++) + vstream_printf("%s\n", *cpp); + } else { + msg_warn("unknown 'postconf -T' mode: %s", what); + exit(1); + } +#endif /* USE_TLS */ +} diff --git a/src/postconf/postconf_print.c b/src/postconf/postconf_print.c new file mode 100644 index 0000000..32c4015 --- /dev/null +++ b/src/postconf/postconf_print.c @@ -0,0 +1,114 @@ +/*++ +/* NAME +/* postconf_print 3 +/* SUMMARY +/* basic line printing support +/* SYNOPSIS +/* #include +/* +/* void pcf_print_line(fp, mode, const char *fmt, ...) +/* VSTREAM *fp; +/* int mode; +/* const char *fmt; +/* DESCRIPTION +/* pcf_print_line() formats text, normalized whitespace, and +/* optionally folds long lines. +/* +/* Arguments: +/* .IP fp +/* Output stream. +/* .IP mode +/* Bit-wise OR of zero or more of the following (other flags +/* are ignored): +/* .RS +/* .IP PCF_FOLD_LINE +/* Fold long lines. +/* .RE +/* .IP fmt +/* Format string. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include + +/* SLMs. */ + +#define STR(x) vstring_str(x) + +/* pcf_print_line - show line possibly folded, and with normalized whitespace */ + +void pcf_print_line(VSTREAM *fp, int mode, const char *fmt,...) +{ + va_list ap; + static VSTRING *buf = 0; + char *start; + char *next; + int line_len = 0; + int word_len; + + /* + * One-off initialization. + */ + if (buf == 0) + buf = vstring_alloc(100); + + /* + * Format the text. + */ + va_start(ap, fmt); + vstring_vsprintf(buf, fmt, ap); + va_end(ap); + + /* + * Normalize the whitespace. We don't use the line_wrap() routine because + * 1) that function does not normalize whitespace between words and 2) we + * want to normalize whitespace even when not wrapping lines. + * + * XXX Some parameters preserve whitespace: for example, smtpd_banner and + * smtpd_reject_footer. If we have to preserve whitespace between words, + * then perhaps readlline() can be changed to canonicalize whitespace + * that follows a newline. + */ + for (start = STR(buf); *(start += strspn(start, PCF_SEPARATORS)) != 0; start = next) { + word_len = strcspn(start, PCF_SEPARATORS); + if (*(next = start + word_len) != 0) + *next++ = 0; + if (word_len > 0 && line_len > 0) { + if ((mode & PCF_FOLD_LINE) == 0 + || line_len + word_len < PCF_LINE_LIMIT) { + vstream_fputs(" ", fp); + line_len += 1; + } else { + vstream_fputs("\n" PCF_INDENT_TEXT, fp); + line_len = PCF_INDENT_LEN; + } + } + vstream_fputs(start, fp); + line_len += word_len; + } + vstream_fputs("\n", fp); +} diff --git a/src/postconf/postconf_service.c b/src/postconf/postconf_service.c new file mode 100644 index 0000000..38a4cce --- /dev/null +++ b/src/postconf/postconf_service.c @@ -0,0 +1,199 @@ +/*++ +/* NAME +/* postconf_service 3 +/* SUMMARY +/* service-defined main.cf parameter name support +/* SYNOPSIS +/* #include +/* +/* void pcf_register_service_parameters() +/* DESCRIPTION +/* Service-defined parameter names are created by appending +/* postfix-defined suffixes to master.cf service names. All +/* service-defined parameters have default values that are +/* defined by a built-in parameter. +/* +/* pcf_register_service_parameters() adds the service-defined +/* parameters to the global name space. This function must be +/* called after the built-in parameters are added to the global +/* name space, and after the master.cf file is read. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include + + /* + * Basename of programs in $daemon_directory. XXX These belong in a header + * file, or they should be made configurable. + */ +#ifndef MAIL_PROGRAM_LOCAL +#define MAIL_PROGRAM_LOCAL "local" +#define MAIL_PROGRAM_ERROR "error" +#define MAIL_PROGRAM_VIRTUAL "virtual" +#define MAIL_PROGRAM_SMTP "smtp" +#define MAIL_PROGRAM_LMTP "lmtp" +#define MAIL_PROGRAM_PIPE "pipe" +#define MAIL_PROGRAM_SPAWN "spawn" +#endif + + /* + * Ad-hoc name-value string pair. + */ +typedef struct { + const char *name; + const char *value; +} PCF_STRING_NV; + +#define STR(x) vstring_str(x) + +/* pcf_convert_service_parameter - get service parameter string value */ + +static const char *pcf_convert_service_parameter(void *ptr) +{ + return (STR(vstring_sprintf(pcf_param_string_buf, "$%s", (char *) ptr))); +} + +/* pcf_register_service_parameter - add service parameter name and default */ + +static void pcf_register_service_parameter(const char *service, + const char *suffix, + const char *defparam) +{ + char *name = concatenate(service, suffix, (char *) 0); + PCF_PARAM_NODE *node; + + /* + * Skip service parameter names that have built-in definitions. This + * happens with message delivery transports that have a non-default + * per-destination concurrency or recipient limit, such as local(8). + * + * Some parameters were tentatively flagged as built-in, but they are + * service parameters with their own default value. We don't change the + * default but we correct the parameter class. + */ + if ((node = PCF_PARAM_TABLE_FIND(pcf_param_table, name)) != 0) { + PCF_PARAM_CLASS_OVERRIDE(node, PCF_PARAM_FLAG_SERVICE); + } else { + PCF_PARAM_TABLE_ENTER(pcf_param_table, name, PCF_PARAM_FLAG_SERVICE, + (void *) defparam, pcf_convert_service_parameter); + } + myfree(name); +} + +/* pcf_register_service_parameters - add all service parameters with defaults */ + +void pcf_register_service_parameters(void) +{ + const char *myname = "pcf_register_service_parameters"; + static const PCF_STRING_NV pipe_params[] = { + /* suffix, default parameter name */ + _MAXTIME, VAR_COMMAND_MAXTIME, +#define service_params (pipe_params + 1) + _XPORT_RCPT_LIMIT, VAR_XPORT_RCPT_LIMIT, + _STACK_RCPT_LIMIT, VAR_STACK_RCPT_LIMIT, + _XPORT_REFILL_LIMIT, VAR_XPORT_REFILL_LIMIT, + _XPORT_REFILL_DELAY, VAR_XPORT_REFILL_DELAY, + _DELIVERY_SLOT_COST, VAR_DELIVERY_SLOT_COST, + _DELIVERY_SLOT_LOAN, VAR_DELIVERY_SLOT_LOAN, + _DELIVERY_SLOT_DISCOUNT, VAR_DELIVERY_SLOT_DISCOUNT, + _MIN_DELIVERY_SLOTS, VAR_MIN_DELIVERY_SLOTS, + _INIT_DEST_CON, VAR_INIT_DEST_CON, + _DEST_CON_LIMIT, VAR_DEST_CON_LIMIT, + _DEST_RCPT_LIMIT, VAR_DEST_RCPT_LIMIT, + _CONC_POS_FDBACK, VAR_CONC_POS_FDBACK, + _CONC_NEG_FDBACK, VAR_CONC_NEG_FDBACK, + _CONC_COHORT_LIM, VAR_CONC_COHORT_LIM, + _DEST_RATE_DELAY, VAR_DEST_RATE_DELAY, + _XPORT_RATE_DELAY, VAR_XPORT_RATE_DELAY, + 0, + }; + static const PCF_STRING_NV spawn_params[] = { + /* suffix, default parameter name */ + _MAXTIME, VAR_COMMAND_MAXTIME, + 0, + }; + typedef struct { + const char *progname; + const PCF_STRING_NV *params; + } PCF_SERVICE_DEF; + static const PCF_SERVICE_DEF service_defs[] = { + MAIL_PROGRAM_LOCAL, service_params, + MAIL_PROGRAM_ERROR, service_params, + MAIL_PROGRAM_VIRTUAL, service_params, + MAIL_PROGRAM_SMTP, service_params, + MAIL_PROGRAM_LMTP, service_params, + MAIL_PROGRAM_PIPE, pipe_params, + MAIL_PROGRAM_SPAWN, spawn_params, + 0, + }; + const PCF_STRING_NV *sp; + const char *progname; + const char *service; + PCF_MASTER_ENT *masterp; + ARGV *argv; + const PCF_SERVICE_DEF *sd; + + /* + * Sanity checks. + */ + if (pcf_param_table == 0) + msg_panic("%s: global parameter table is not initialized", myname); + if (pcf_master_table == 0) + msg_panic("%s: master table is not initialized", myname); + + /* + * Extract service names from master.cf and generate service parameter + * information. + */ + for (masterp = pcf_master_table; (argv = masterp->argv) != 0; masterp++) { + + /* + * Add service parameters for message delivery transports or spawn + * programs. + */ + progname = argv->argv[7]; + for (sd = service_defs; sd->progname; sd++) { + if (strcmp(sd->progname, progname) == 0) { + service = argv->argv[0]; + for (sp = sd->params; sp->name; sp++) + pcf_register_service_parameter(service, sp->name, sp->value); + break; + } + } + } +} diff --git a/src/postconf/postconf_unused.c b/src/postconf/postconf_unused.c new file mode 100644 index 0000000..d4416f8 --- /dev/null +++ b/src/postconf/postconf_unused.c @@ -0,0 +1,129 @@ +/*++ +/* NAME +/* postconf_unused 3 +/* SUMMARY +/* report unused parameters +/* SYNOPSIS +/* #include +/* +/* void pcf_flag_unused_main_parameters() +/* +/* void pcf_flag_unused_master_parameters() +/* DESCRIPTION +/* These functions must be called after all parameter information +/* is initialized: built-ins, service-defined and user-defined. +/* In other words, don't call these functions with "postconf +/* -d" which ignores user-defined main.cf settings. +/* +/* pcf_flag_unused_main_parameters() reports unused "name=value" +/* entries in main.cf. +/* +/* pcf_flag_unused_master_parameters() reports unused "-o +/* name=value" entries in master.cf. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include + +/* pcf_flag_unused_parameters - warn about unused parameters */ + +static void pcf_flag_unused_parameters(DICT *dict, const char *conf_name, + PCF_MASTER_ENT *local_scope) +{ + const char *myname = "pcf_flag_unused_parameters"; + const char *param_name; + const char *param_value; + int how; + + /* + * Sanity checks. + */ + if (pcf_param_table == 0) + msg_panic("%s: global parameter table is not initialized", myname); + + /* + * Iterate over all entries, and flag parameter names that aren't used + * anywhere. Show the warning message at the end of the output. + */ + if (dict->sequence == 0) + msg_panic("%s: parameter dictionary %s has no iterator", + myname, conf_name); + for (how = DICT_SEQ_FUN_FIRST; + dict->sequence(dict, how, ¶m_name, ¶m_value) == 0; + how = DICT_SEQ_FUN_NEXT) { + if (PCF_PARAM_TABLE_LOCATE(pcf_param_table, param_name) == 0 + && (local_scope == 0 + || PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, param_name) == 0)) { + vstream_fflush(VSTREAM_OUT); + msg_warn("%s/%s: unused parameter: %s=%s", + var_config_dir, conf_name, param_name, param_value); + } + } +} + +/* pcf_flag_unused_main_parameters - warn about unused parameters */ + +void pcf_flag_unused_main_parameters(void) +{ + const char *myname = "pcf_flag_unused_main_parameters"; + DICT *dict; + + /* + * Iterate over all main.cf entries, and flag parameter names that aren't + * used anywhere. + */ + if ((dict = dict_handle(CONFIG_DICT)) == 0) + msg_panic("%s: parameter dictionary %s not found", + myname, CONFIG_DICT); + pcf_flag_unused_parameters(dict, MAIN_CONF_FILE, (PCF_MASTER_ENT *) 0); +} + +/* pcf_flag_unused_master_parameters - warn about unused parameters */ + +void pcf_flag_unused_master_parameters(void) +{ + const char *myname = "pcf_flag_unused_master_parameters"; + PCF_MASTER_ENT *masterp; + DICT *dict; + + /* + * Sanity checks. + */ + if (pcf_master_table == 0) + msg_panic("%s: master table is not initialized", myname); + + /* + * Iterate over all master.cf entries, and flag parameter names that + * aren't used anywhere. + */ + for (masterp = pcf_master_table; masterp->argv != 0; masterp++) + if ((dict = masterp->all_params) != 0) + pcf_flag_unused_parameters(dict, MASTER_CONF_FILE, masterp); +} diff --git a/src/postconf/postconf_user.c b/src/postconf/postconf_user.c new file mode 100644 index 0000000..5942ec0 --- /dev/null +++ b/src/postconf/postconf_user.c @@ -0,0 +1,422 @@ +/*++ +/* NAME +/* postconf_user 3 +/* SUMMARY +/* support for user-defined main.cf parameter names +/* SYNOPSIS +/* #include +/* +/* void pcf_register_user_parameters() +/* DESCRIPTION +/* Postfix has multiple parameter name spaces: the global +/* main.cf parameter name space, and the local parameter name +/* space of each master.cf entry. Parameters in local name +/* spaces take precedence over global parameters. +/* +/* There are three categories of known parameter names: built-in, +/* service-defined (see postconf_service.c), and valid +/* user-defined. +/* +/* There are two categories of valid user-defined parameter +/* names: +/* +/* - Parameters whose user-defined-name appears in the value +/* of smtpd_restriction_classes in main.cf or master.cf. +/* +/* - Parameters whose $user-defined-name appear in the value +/* of "name=value" entries in main.cf or master.cf. +/* +/* - In both cases the parameters must have a +/* "user-defined-name=value" entry in main.cf or master.cf. +/* +/* Other user-defined parameter names are flagged as "unused". +/* +/* pcf_register_user_parameters() scans the global and per-service +/* name spaces for user-defined parameters and flags parameters +/* as "valid" in the global name space (pcf_param_table) or +/* in the per-service name space (valid_params). +/* +/* This function also invokes pcf_register_dbms_parameters() to +/* to instantiate legacy per-dbms parameters, and to examine +/* per-dbms configuration files. This is limited to the content +/* of global and local, built-in and per-service, parameters. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include + + /* + * Hash with all user-defined names in the global smtpd_restriction_classes + * value. This is used when validating "-o user-defined-name=value" entries + * in master.cf. + */ +static HTABLE *pcf_rest_class_table; + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + + /* + * Macros to make code with obscure constants more readable. + */ +#define NO_SCAN_RESULT ((VSTRING *) 0) +#define NO_SCAN_FILTER ((char *) 0) + +/* SCAN_USER_PARAMETER_VALUE - examine macro names in parameter value */ + +#define SCAN_USER_PARAMETER_VALUE(value, class, scope) do { \ + PCF_PARAM_CTX _ctx; \ + _ctx.local_scope = (scope); \ + _ctx.param_class = (class); \ + (void) mac_expand(NO_SCAN_RESULT, (value), MAC_EXP_FLAG_SCAN, \ + NO_SCAN_FILTER, pcf_flag_user_parameter_wrapper, (void *) &_ctx); \ +} while (0) + +/* pcf_convert_user_parameter - get user-defined parameter string value */ + +static const char *pcf_convert_user_parameter(void *unused_ptr) +{ + return (""); /* can't happen */ +} + +/* pcf_flag_user_parameter - flag user-defined name "valid" if it has name=value */ + +static const char *pcf_flag_user_parameter(const char *mac_name, + int param_class, + PCF_MASTER_ENT *local_scope) +{ + const char *source = local_scope ? MASTER_CONF_FILE : MAIN_CONF_FILE; + int user_supplied = 0; + + /* + * If the name=value exists in the local (or global) name space, update + * the local (or global) "valid" parameter name table. + * + * Do not "validate" user-defined parameters whose name appears only as + * macro expansion; this is how Postfix historically implements backwards + * compatibility after a feature name change. + */ + if (local_scope && dict_get(local_scope->all_params, mac_name)) { + user_supplied = 1; + /* $name in master.cf references name=value in master.cf. */ + if (PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0) { + PCF_PARAM_TABLE_ENTER(local_scope->valid_names, mac_name, + param_class, PCF_PARAM_NO_DATA, + pcf_convert_user_parameter); + if (msg_verbose) + msg_info("$%s in %s:%s validates %s=value in %s:%s", + mac_name, MASTER_CONF_FILE, + local_scope->name_space, + mac_name, MASTER_CONF_FILE, + local_scope->name_space); + } + } else if (mail_conf_lookup(mac_name) != 0) { + user_supplied = 1; + /* $name in main/master.cf references name=value in main.cf. */ + if (PCF_PARAM_TABLE_LOCATE(pcf_param_table, mac_name) == 0) { + PCF_PARAM_TABLE_ENTER(pcf_param_table, mac_name, param_class, + PCF_PARAM_NO_DATA, pcf_convert_user_parameter); + if (msg_verbose) { + if (local_scope) + msg_info("$%s in %s:%s validates %s=value in %s", + mac_name, MASTER_CONF_FILE, + local_scope->name_space, + mac_name, MAIN_CONF_FILE); + else + msg_info("$%s in %s validates %s=value in %s", + mac_name, MAIN_CONF_FILE, + mac_name, MAIN_CONF_FILE); + } + } + } + if (local_scope == 0) { + for (local_scope = pcf_master_table; local_scope->argv; local_scope++) { + if (local_scope->all_params != 0 + && dict_get(local_scope->all_params, mac_name) != 0) { + user_supplied = 1; + /* $name in main.cf references name=value in master.cf. */ + if (PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0) { + PCF_PARAM_TABLE_ENTER(local_scope->valid_names, mac_name, + param_class, PCF_PARAM_NO_DATA, + pcf_convert_user_parameter); + if (msg_verbose) + msg_info("$%s in %s validates %s=value in %s:%s", + mac_name, MAIN_CONF_FILE, + mac_name, MASTER_CONF_FILE, + local_scope->name_space); + } + } + } + } + + /* + * Warn about a $name that has no user-supplied explicit value or + * Postfix-supplied default value. We don't enforce this for legacy DBMS + * parameters because they exist only for backwards compatibility, so we + * don't bother to figure out which parameters come without defaults. + */ + if (user_supplied == 0 && (param_class & PCF_PARAM_FLAG_DBMS) == 0 + && PCF_PARAM_TABLE_LOCATE(pcf_param_table, mac_name) == 0) + msg_warn("%s/%s: undefined parameter: %s", + var_config_dir, source, mac_name); + return (0); +} + +/* pcf_flag_user_parameter_wrapper - mac_expand call-back helper */ + +static const char *pcf_flag_user_parameter_wrapper(const char *mac_name, + int unused_mode, + void *context) +{ + PCF_PARAM_CTX *ctx = (PCF_PARAM_CTX *) context; + + return (pcf_flag_user_parameter(mac_name, ctx->param_class, ctx->local_scope)); +} + +/* pcf_lookup_eval - generalized mail_conf_lookup_eval */ + +static const char *pcf_lookup_eval(const char *dict_name, const char *name) +{ + const char *value; + +#define RECURSIVE 1 + + if ((value = dict_lookup(dict_name, name)) != 0) + value = dict_eval(dict_name, value, RECURSIVE); + return (value); +} + +/* pcf_scan_user_parameter_namespace - scan parameters in name space */ + +static void pcf_scan_user_parameter_namespace(const char *dict_name, + PCF_MASTER_ENT *local_scope) +{ + const char *myname = "pcf_scan_user_parameter_namespace"; + const char *class_list; + char *saved_class_list; + char *cp; + DICT *dict; + char *param_name; + int how; + const char *cparam_name; + const char *cparam_value; + PCF_PARAM_NODE *node; + const char *source = local_scope ? MASTER_CONF_FILE : MAIN_CONF_FILE; + + /* + * Flag parameter names in smtpd_restriction_classes as "valid", but only + * if they have a "name=value" entry. If we are in not in a local name + * space, update the global restriction class name table, so that we can + * query the global table from within a local master.cf name space. + */ + if ((class_list = pcf_lookup_eval(dict_name, VAR_REST_CLASSES)) != 0) { + cp = saved_class_list = mystrdup(class_list); + while ((param_name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) { + if (local_scope == 0 + && htable_locate(pcf_rest_class_table, param_name) == 0) + htable_enter(pcf_rest_class_table, param_name, ""); + pcf_flag_user_parameter(param_name, PCF_PARAM_FLAG_USER, local_scope); + } + myfree(saved_class_list); + } + + /* + * For all "name=value" instances: a) if the name space is local and the + * name appears in the global restriction class table, flag the name as + * "valid" in the local name space; b) scan the value for macro + * expansions of unknown parameter names, and flag those parameter names + * as "valid" if they have a "name=value" entry. + * + * We delete name=value entries for read-only parameters, to maintain + * compatibility with Postfix programs that ignore such settings. + */ + if ((dict = dict_handle(dict_name)) == 0) + msg_panic("%s: parameter dictionary %s not found", + myname, dict_name); + if (dict->sequence == 0) + msg_panic("%s: parameter dictionary %s has no iterator", + myname, dict_name); + for (how = DICT_SEQ_FUN_FIRST; + dict->sequence(dict, how, &cparam_name, &cparam_value) == 0; + how = DICT_SEQ_FUN_NEXT) { + if (local_scope != 0 + && PCF_PARAM_TABLE_LOCATE(local_scope->valid_names, cparam_name) == 0 + && htable_locate(pcf_rest_class_table, cparam_name) != 0) + PCF_PARAM_TABLE_ENTER(local_scope->valid_names, cparam_name, + PCF_PARAM_FLAG_USER, PCF_PARAM_NO_DATA, + pcf_convert_user_parameter); + if ((node = PCF_PARAM_TABLE_FIND(pcf_param_table, cparam_name)) != 0) { + if (PCF_READONLY_PARAMETER(node)) { + msg_warn("%s/%s: read-only parameter assignment: %s=%s", + var_config_dir, source, cparam_name, cparam_value); + /* Can't use dict_del() with Postfix<2.10 htable_sequence(). */ + if (dict_del(dict, cparam_name) != 0) + msg_panic("%s: can't delete %s/%s parameter entry for %s", + myname, var_config_dir, source, cparam_name); + continue; + } + /* Re-label legacy parameter as user-defined, so it's printed. */ + if (PCF_LEGACY_PARAMETER(node)) + PCF_PARAM_CLASS_OVERRIDE(node, PCF_PARAM_FLAG_USER); + /* Skip "do not expand" parameters. */ + if (PCF_RAW_PARAMETER(node)) + continue; + } + SCAN_USER_PARAMETER_VALUE(cparam_value, PCF_PARAM_FLAG_USER, local_scope); +#ifdef LEGACY_DBMS_SUPPORT + + /* + * Scan global or local parameters that are built-in or per-service + * (when node == 0, the parameter doesn't exist in the global + * namespace and therefore it can't be built-in or per-service). + */ + if (node != 0 + && (PCF_BUILTIN_PARAMETER(node) || PCF_SERVICE_PARAMETER(node))) + pcf_register_dbms_parameters(cparam_value, pcf_flag_user_parameter, + local_scope); +#endif + } +} + +/* pcf_scan_default_parameter_values - scan parameters at implicit defaults */ + +static void pcf_scan_default_parameter_values(HTABLE *valid_params, + const char *dict_name, + PCF_MASTER_ENT *local_scope) +{ + const char *myname = "pcf_scan_default_parameter_values"; + PCF_PARAM_INFO **list; + PCF_PARAM_INFO **ht; + const char *param_value; + + list = PCF_PARAM_TABLE_LIST(valid_params); + for (ht = list; *ht; ht++) { + /* Skip "do not expand" parameters. */ + if (PCF_RAW_PARAMETER(PCF_PARAM_INFO_NODE(*ht))) + continue; + /* Skip parameters with a non-default value. */ + if (dict_lookup(dict_name, PCF_PARAM_INFO_NAME(*ht))) + continue; + if ((param_value = pcf_convert_param_node(PCF_SHOW_DEFS, PCF_PARAM_INFO_NAME(*ht), + PCF_PARAM_INFO_NODE(*ht))) == 0) + msg_panic("%s: parameter %s has no default value", + myname, PCF_PARAM_INFO_NAME(*ht)); + SCAN_USER_PARAMETER_VALUE(param_value, PCF_PARAM_FLAG_USER, local_scope); + /* No need to scan default values for legacy DBMS configuration. */ + } + myfree((void *) list); +} + +/* pcf_register_user_parameters - add parameters with user-defined names */ + +void pcf_register_user_parameters(void) +{ + const char *myname = "pcf_register_user_parameters"; + PCF_MASTER_ENT *masterp; + ARGV *argv; + char *arg; + char *aval; + int field; + char *saved_arg; + char *param_name; + char *param_value; + DICT *dict; + + /* + * Sanity checks. + */ + if (pcf_param_table == 0) + msg_panic("%s: global parameter table is not initialized", myname); + if (pcf_master_table == 0) + msg_panic("%s: master table is not initialized", myname); + if (pcf_rest_class_table != 0) + msg_panic("%s: restriction class table is already initialized", myname); + + /* + * Initialize the table with global restriction class names. + */ + pcf_rest_class_table = htable_create(1); + + /* + * Initialize the per-service parameter name spaces. + */ + for (masterp = pcf_master_table; (argv = masterp->argv) != 0; masterp++) { + for (field = PCF_MASTER_MIN_FIELDS; argv->argv[field] != 0; field++) { + arg = argv->argv[field]; + if (arg[0] != '-' || strcmp(arg, "--") == 0) + break; + if (strchr(pcf_daemon_options_expecting_value, arg[1]) == 0 + || (aval = argv->argv[field + 1]) == 0) + continue; + if (strcmp(arg, "-o") == 0) { + saved_arg = mystrdup(aval); + if (split_nameval(saved_arg, ¶m_name, ¶m_value) == 0) + dict_update(masterp->name_space, param_name, param_value); + myfree(saved_arg); + } + field += 1; + } + if ((dict = dict_handle(masterp->name_space)) != 0) { + masterp->all_params = dict; + masterp->valid_names = htable_create(1); + } + } + + /* + * Scan the "-o parameter=value" instances in each master.cf name space. + */ + for (masterp = pcf_master_table; masterp->argv != 0; masterp++) + if (masterp->all_params != 0) + pcf_scan_user_parameter_namespace(masterp->name_space, masterp); + + /* + * Scan parameter values that are left at their defaults in the global + * name space. Some defaults contain the $name of an obsolete parameter + * for backwards compatibility purposes. We might warn that an explicit + * name=value is obsolete, but we must not warn that the parameter is + * unused. + */ + pcf_scan_default_parameter_values(pcf_param_table, CONFIG_DICT, + (PCF_MASTER_ENT *) 0); + + /* + * Scan the explicit name=value entries in the global name space. + */ + pcf_scan_user_parameter_namespace(CONFIG_DICT, (PCF_MASTER_ENT *) 0); +} diff --git a/src/postconf/test1.ref b/src/postconf/test1.ref new file mode 100644 index 0000000..2aa9903 --- /dev/null +++ b/src/postconf/test1.ref @@ -0,0 +1,4 @@ +./postconf: warning: ./main.cf: undefined parameter: bar +config_directory = . +foo = yes +smtpd_restriction_classes = foo bar diff --git a/src/postconf/test10.ref b/src/postconf/test10.ref new file mode 100644 index 0000000..5bb6891 --- /dev/null +++ b/src/postconf/test10.ref @@ -0,0 +1,2 @@ +./postconf: warning: unmatched request: "bar/inet" +./postconf: warning: unmatched request: "foo/unix" diff --git a/src/postconf/test11.ref b/src/postconf/test11.ref new file mode 100644 index 0000000..c4cc727 --- /dev/null +++ b/src/postconf/test11.ref @@ -0,0 +1,2 @@ +foo inet - n n - 0 spawn +bar unix - n n - 0 spawn diff --git a/src/postconf/test12.ref b/src/postconf/test12.ref new file mode 100644 index 0000000..a9f5eb4 --- /dev/null +++ b/src/postconf/test12.ref @@ -0,0 +1,2 @@ +foo inet - n n - 0 spawn -o always_bcc=$bar -o +foo inet - n n - 0 spawn -o always_bcc=$bar -o diff --git a/src/postconf/test13.ref b/src/postconf/test13.ref new file mode 100644 index 0000000..09787fa --- /dev/null +++ b/src/postconf/test13.ref @@ -0,0 +1,3 @@ +bar = yes +config_directory = . +./postconf: warning: ./main.cf: unused parameter: baz=xx diff --git a/src/postconf/test14.ref b/src/postconf/test14.ref new file mode 100644 index 0000000..98884f3 --- /dev/null +++ b/src/postconf/test14.ref @@ -0,0 +1,3 @@ +config_directory = . +smtpd_restriction_classes = bar +./postconf: warning: ./master.cf: unused parameter: baz=xx diff --git a/src/postconf/test15.ref b/src/postconf/test15.ref new file mode 100644 index 0000000..2a15ca8 --- /dev/null +++ b/src/postconf/test15.ref @@ -0,0 +1,3 @@ +baz = yy +config_directory = . +./postconf: warning: ./main.cf: unused parameter: bar=xx diff --git a/src/postconf/test16.ref b/src/postconf/test16.ref new file mode 100644 index 0000000..a1c2e06 --- /dev/null +++ b/src/postconf/test16.ref @@ -0,0 +1,2 @@ +./postconf: warning: open ./master.cf: No such file or directory +config_directory = . diff --git a/src/postconf/test17.ref b/src/postconf/test17.ref new file mode 100644 index 0000000..bcfb716 --- /dev/null +++ b/src/postconf/test17.ref @@ -0,0 +1 @@ +./postconf: fatal: open ./master.cf: No such file or directory diff --git a/src/postconf/test18.ref b/src/postconf/test18.ref new file mode 100644 index 0000000..09224a6 --- /dev/null +++ b/src/postconf/test18.ref @@ -0,0 +1,3 @@ +config_directory = . +smtpd_client_connection_limit_exceptions = yyy +virtual_maps = xxx diff --git a/src/postconf/test19.ref b/src/postconf/test19.ref new file mode 100644 index 0000000..5a286c6 --- /dev/null +++ b/src/postconf/test19.ref @@ -0,0 +1,3 @@ +config_directory = . +default_rbl_reply = $bbbb +forward_path = $aaaa diff --git a/src/postconf/test2.ref b/src/postconf/test2.ref new file mode 100644 index 0000000..49af249 --- /dev/null +++ b/src/postconf/test2.ref @@ -0,0 +1,3 @@ +config_directory = . +./postconf: warning: ./main.cf: unused parameter: restriction_classes=foo bar +./postconf: warning: ./main.cf: unused parameter: foo=yes diff --git a/src/postconf/test20.ref b/src/postconf/test20.ref new file mode 100644 index 0000000..0a430d2 --- /dev/null +++ b/src/postconf/test20.ref @@ -0,0 +1,4 @@ +./postconf: warning: ./master.cf: undefined parameter: bar +./postconf: warning: ./master.cf: undefined parameter: baz +foo inet - n n - 0 spawn + -o always_bcc=$bar$baz diff --git a/src/postconf/test21.ref b/src/postconf/test21.ref new file mode 100644 index 0000000..423197e --- /dev/null +++ b/src/postconf/test21.ref @@ -0,0 +1,3 @@ +config_directory = . +forward_path = xxxxxxxxxxxxx xxxxxxxxxxxxxx xxxxxxxxxxxx xxxxxxxxxxxxx + xxxxxxxxxxxxxx diff --git a/src/postconf/test22.ref b/src/postconf/test22.ref new file mode 100644 index 0000000..1e4629f --- /dev/null +++ b/src/postconf/test22.ref @@ -0,0 +1,16 @@ +whatevershebrings_delivery_slot_cost = $default_delivery_slot_cost +whatevershebrings_delivery_slot_discount = $default_delivery_slot_discount +whatevershebrings_delivery_slot_loan = $default_delivery_slot_loan +whatevershebrings_destination_concurrency_failed_cohort_limit = $default_destination_concurrency_failed_cohort_limit +whatevershebrings_destination_concurrency_limit = $default_destination_concurrency_limit +whatevershebrings_destination_concurrency_negative_feedback = $default_destination_concurrency_negative_feedback +whatevershebrings_destination_concurrency_positive_feedback = $default_destination_concurrency_positive_feedback +whatevershebrings_destination_rate_delay = $default_destination_rate_delay +whatevershebrings_destination_recipient_limit = $default_destination_recipient_limit +whatevershebrings_extra_recipient_limit = $default_extra_recipient_limit +whatevershebrings_initial_destination_concurrency = $initial_destination_concurrency +whatevershebrings_minimum_delivery_slots = $default_minimum_delivery_slots +whatevershebrings_recipient_limit = $default_recipient_limit +whatevershebrings_recipient_refill_delay = $default_recipient_refill_delay +whatevershebrings_recipient_refill_limit = $default_recipient_refill_limit +whatevershebrings_transport_rate_delay = $default_transport_rate_delay diff --git a/src/postconf/test23.ref b/src/postconf/test23.ref new file mode 100644 index 0000000..d1f9a90 --- /dev/null +++ b/src/postconf/test23.ref @@ -0,0 +1,2 @@ +always_bcc = yes +config_directory = . diff --git a/src/postconf/test24.ref b/src/postconf/test24.ref new file mode 100644 index 0000000..2292793 --- /dev/null +++ b/src/postconf/test24.ref @@ -0,0 +1 @@ +name = value diff --git a/src/postconf/test25.ref b/src/postconf/test25.ref new file mode 100644 index 0000000..1e4629f --- /dev/null +++ b/src/postconf/test25.ref @@ -0,0 +1,16 @@ +whatevershebrings_delivery_slot_cost = $default_delivery_slot_cost +whatevershebrings_delivery_slot_discount = $default_delivery_slot_discount +whatevershebrings_delivery_slot_loan = $default_delivery_slot_loan +whatevershebrings_destination_concurrency_failed_cohort_limit = $default_destination_concurrency_failed_cohort_limit +whatevershebrings_destination_concurrency_limit = $default_destination_concurrency_limit +whatevershebrings_destination_concurrency_negative_feedback = $default_destination_concurrency_negative_feedback +whatevershebrings_destination_concurrency_positive_feedback = $default_destination_concurrency_positive_feedback +whatevershebrings_destination_rate_delay = $default_destination_rate_delay +whatevershebrings_destination_recipient_limit = $default_destination_recipient_limit +whatevershebrings_extra_recipient_limit = $default_extra_recipient_limit +whatevershebrings_initial_destination_concurrency = $initial_destination_concurrency +whatevershebrings_minimum_delivery_slots = $default_minimum_delivery_slots +whatevershebrings_recipient_limit = $default_recipient_limit +whatevershebrings_recipient_refill_delay = $default_recipient_refill_delay +whatevershebrings_recipient_refill_limit = $default_recipient_refill_limit +whatevershebrings_transport_rate_delay = $default_transport_rate_delay diff --git a/src/postconf/test26.ref b/src/postconf/test26.ref new file mode 100644 index 0000000..0b33025 --- /dev/null +++ b/src/postconf/test26.ref @@ -0,0 +1,3 @@ +always_bcc = yes +config_directory = . +name = value diff --git a/src/postconf/test27.ref b/src/postconf/test27.ref new file mode 100644 index 0000000..1e4629f --- /dev/null +++ b/src/postconf/test27.ref @@ -0,0 +1,16 @@ +whatevershebrings_delivery_slot_cost = $default_delivery_slot_cost +whatevershebrings_delivery_slot_discount = $default_delivery_slot_discount +whatevershebrings_delivery_slot_loan = $default_delivery_slot_loan +whatevershebrings_destination_concurrency_failed_cohort_limit = $default_destination_concurrency_failed_cohort_limit +whatevershebrings_destination_concurrency_limit = $default_destination_concurrency_limit +whatevershebrings_destination_concurrency_negative_feedback = $default_destination_concurrency_negative_feedback +whatevershebrings_destination_concurrency_positive_feedback = $default_destination_concurrency_positive_feedback +whatevershebrings_destination_rate_delay = $default_destination_rate_delay +whatevershebrings_destination_recipient_limit = $default_destination_recipient_limit +whatevershebrings_extra_recipient_limit = $default_extra_recipient_limit +whatevershebrings_initial_destination_concurrency = $initial_destination_concurrency +whatevershebrings_minimum_delivery_slots = $default_minimum_delivery_slots +whatevershebrings_recipient_limit = $default_recipient_limit +whatevershebrings_recipient_refill_delay = $default_recipient_refill_delay +whatevershebrings_recipient_refill_limit = $default_recipient_refill_limit +whatevershebrings_transport_rate_delay = $default_transport_rate_delay diff --git a/src/postconf/test28.ref b/src/postconf/test28.ref new file mode 100644 index 0000000..4e93734 --- /dev/null +++ b/src/postconf/test28.ref @@ -0,0 +1,10 @@ +aap_domain = whatever +config_directory = . +db = memcache +header_checks = ldap:hh +hh_domain = whatever +yy = aap +zz = $yy +./postconf: warning: ./main.cf: unused parameter: foo_domain=bar +./postconf: warning: ./main.cf: unused parameter: xx=proxy:ldap:foo +./postconf: warning: ./main.cf: unused parameter: aa_domain=whatever diff --git a/src/postconf/test29.ref b/src/postconf/test29.ref new file mode 100644 index 0000000..646890a --- /dev/null +++ b/src/postconf/test29.ref @@ -0,0 +1,16 @@ +config_directory = . +./postconf: warning: ./main.cf: unused parameter: pgsqlfoo_domain=bar +./postconf: warning: ./main.cf: unused parameter: sqlitefoo_domain=bar +./postconf: warning: ./main.cf: unused parameter: ldapxx=proxy:ldap:ldapfoo +./postconf: warning: ./main.cf: unused parameter: sqlitexx=proxy:sqlite:sqlitefoo +./postconf: warning: ./main.cf: unused parameter: mysqlfoo_domain=bar +./postconf: warning: ./main.cf: unused parameter: sqlitefoo_domainx=bar +./postconf: warning: ./main.cf: unused parameter: memcachefoo_domain=bar +./postconf: warning: ./main.cf: unused parameter: pgsqlfoo_domainx=bar +./postconf: warning: ./main.cf: unused parameter: ldapfoo_domainx=bar +./postconf: warning: ./main.cf: unused parameter: ldapfoo_domain=bar +./postconf: warning: ./main.cf: unused parameter: memcachexx=proxy:memcache:memcachefoo +./postconf: warning: ./main.cf: unused parameter: memcachefoo_domainx=bar +./postconf: warning: ./main.cf: unused parameter: mysqlfoo_domainx=bar +./postconf: warning: ./main.cf: unused parameter: mysqlxx=proxy:mysql:mysqlfoo +./postconf: warning: ./main.cf: unused parameter: pgsqlxx=proxy:pgsql:pgsqlfoo diff --git a/src/postconf/test3.ref b/src/postconf/test3.ref new file mode 100644 index 0000000..bdeff73 --- /dev/null +++ b/src/postconf/test3.ref @@ -0,0 +1,4 @@ +always_bcc = $bar +bar = $foo +config_directory = . +foo = yes diff --git a/src/postconf/test30.ref b/src/postconf/test30.ref new file mode 100644 index 0000000..706a429 --- /dev/null +++ b/src/postconf/test30.ref @@ -0,0 +1,7 @@ +config_directory = . +p1 = xx +p2 = xx +p3 = xx +p4 = xx +./postconf: warning: ./master.cf: unused parameter: bodyx_checks=$p2 +./postconf: warning: ./master.cf: unused parameter: headerx_checks=$p4 diff --git a/src/postconf/test31.ref b/src/postconf/test31.ref new file mode 100644 index 0000000..6fda09c --- /dev/null +++ b/src/postconf/test31.ref @@ -0,0 +1,3 @@ +config_directory = . +smtpd_helo_restrictions = whatever +smtpd_sender_restrictions = whatever diff --git a/src/postconf/test32.ref b/src/postconf/test32.ref new file mode 100644 index 0000000..43e2b7b --- /dev/null +++ b/src/postconf/test32.ref @@ -0,0 +1 @@ +fast_flush_domains = whatever diff --git a/src/postconf/test33.ref b/src/postconf/test33.ref new file mode 100644 index 0000000..7b3f52d --- /dev/null +++ b/src/postconf/test33.ref @@ -0,0 +1 @@ +always_bcc = whatever diff --git a/src/postconf/test34.ref b/src/postconf/test34.ref new file mode 100644 index 0000000..2c9d6bd --- /dev/null +++ b/src/postconf/test34.ref @@ -0,0 +1,4 @@ +./postconf: warning: ./main.cf: read-only parameter assignment: process_name=xxx +./postconf: warning: ./main.cf: read-only parameter assignment: process_id=yyy +mydestination = whatever +process_name = postconf diff --git a/src/postconf/test35.ref b/src/postconf/test35.ref new file mode 100644 index 0000000..601648f --- /dev/null +++ b/src/postconf/test35.ref @@ -0,0 +1,3 @@ +./postconf: warning: ./master.cf: read-only parameter assignment: process_name=aaa +./postconf: warning: ./master.cf: read-only parameter assignment: process_id=bbb +process_name = postconf diff --git a/src/postconf/test36.ref b/src/postconf/test36.ref new file mode 100644 index 0000000..bf09921 --- /dev/null +++ b/src/postconf/test36.ref @@ -0,0 +1,4 @@ +./postconf: warning: ./main.cf: undefined parameter: virtual_mapx +config_directory = . +mydestination = +virtual_alias_maps = diff --git a/src/postconf/test37.ref b/src/postconf/test37.ref new file mode 100644 index 0000000..8ef6d6d --- /dev/null +++ b/src/postconf/test37.ref @@ -0,0 +1,4 @@ +whatever unix - n n - 0 other + -o mydestination=yyy + -o always_bcc=ccc + -o aaa=ccc diff --git a/src/postconf/test38.ref b/src/postconf/test38.ref new file mode 100644 index 0000000..186ffc3 --- /dev/null +++ b/src/postconf/test38.ref @@ -0,0 +1 @@ +bar unix - n n - 0 other -o aaa=ccc diff --git a/src/postconf/test39.ref b/src/postconf/test39.ref new file mode 100644 index 0000000..35b078d --- /dev/null +++ b/src/postconf/test39.ref @@ -0,0 +1,2 @@ +foo unix - n n - 0 other +baz unix - n n - 0 other diff --git a/src/postconf/test4.ref b/src/postconf/test4.ref new file mode 100644 index 0000000..6ec1913 --- /dev/null +++ b/src/postconf/test4.ref @@ -0,0 +1,3 @@ +bar = $foo +config_directory = . +foo = yes diff --git a/src/postconf/test40.ref b/src/postconf/test40.ref new file mode 100644 index 0000000..fa3a5d7 --- /dev/null +++ b/src/postconf/test40.ref @@ -0,0 +1,7 @@ +foo unix - n n - 0 other -v + -o aaa=bbb + -v + -o ccc=bbb + -v + -o ddd=bbb +./postconf: warning: ./master.cf: unused parameter: ddd=$ccc diff --git a/src/postconf/test41.ref b/src/postconf/test41.ref new file mode 100644 index 0000000..f8200d4 --- /dev/null +++ b/src/postconf/test41.ref @@ -0,0 +1,18 @@ +foo unix - n n - 0 other +bar unix - n n - 0 other + -o xxx=yyy + -o aaa=bbb +baz unix - n n - 0 other +./postconf: warning: ./master.cf: unused parameter: aaa=bbb +./postconf: warning: ./master.cf: unused parameter: xxx=yyy +foo unix - n n - 0 other +bar unix - n n - 0 other + -o xxx=YYY + -o aaa=BBB +baz unix - n n - 0 other +./postconf: warning: ./master.cf: unused parameter: aaa=BBB +./postconf: warning: ./master.cf: unused parameter: xxx=YYY +bar/unix/aaa = BBB +bar/unix/xxx = YYY +./postconf: warning: ./master.cf: unused parameter: aaa=BBB +./postconf: warning: ./master.cf: unused parameter: xxx=YYY diff --git a/src/postconf/test42.ref b/src/postconf/test42.ref new file mode 100644 index 0000000..80676f3 --- /dev/null +++ b/src/postconf/test42.ref @@ -0,0 +1,14 @@ +foo unix - n n - 0 other +bar unix - n n - 0 other + -o xxx=yyy + -o aaa=bbb +baz unix - n n - 0 other +./postconf: warning: ./master.cf: unused parameter: aaa=bbb +./postconf: warning: ./master.cf: unused parameter: xxx=yyy +bar/unix/aaa = bbb +bar/unix/xxx = yyy +./postconf: warning: ./master.cf: unused parameter: aaa=bbb +./postconf: warning: ./master.cf: unused parameter: xxx=yyy +foo unix - n n - 0 other +bar unix - n n - 0 other +baz unix - n n - 0 other diff --git a/src/postconf/test43.ref b/src/postconf/test43.ref new file mode 100644 index 0000000..2bfea0e --- /dev/null +++ b/src/postconf/test43.ref @@ -0,0 +1,6 @@ +foo unix - n n - 0 other +bar unix - n y - 0 aa -stuff + -o bb=cc + dd +baz unix - n n - 0 other +./postconf: warning: ./master.cf: unused parameter: bb=cc diff --git a/src/postconf/test44.ref b/src/postconf/test44.ref new file mode 100644 index 0000000..b8cf23d --- /dev/null +++ b/src/postconf/test44.ref @@ -0,0 +1,6 @@ +foo unix - n n - 0 other +xx inet - n n - 0 aa -stuff + -o bb=cc + dd +baz unix - n n - 0 other +./postconf: warning: ./master.cf: unused parameter: bb=cc diff --git a/src/postconf/test45.ref b/src/postconf/test45.ref new file mode 100644 index 0000000..8ae54a0 --- /dev/null +++ b/src/postconf/test45.ref @@ -0,0 +1 @@ +./postconf: fatal: invalid type field "xxxx" in "bar xxxx - n n - 0 other" diff --git a/src/postconf/test46.ref b/src/postconf/test46.ref new file mode 100644 index 0000000..aac7c1f --- /dev/null +++ b/src/postconf/test46.ref @@ -0,0 +1 @@ +./postconf: fatal: invalid private field "X" in "bar inet X n n - 0 other" diff --git a/src/postconf/test47.ref b/src/postconf/test47.ref new file mode 100644 index 0000000..e8d46ed --- /dev/null +++ b/src/postconf/test47.ref @@ -0,0 +1 @@ +./postconf: fatal: invalid unprivileged field "X" in "bar inet - X n - 0 other" diff --git a/src/postconf/test48.ref b/src/postconf/test48.ref new file mode 100644 index 0000000..9403117 --- /dev/null +++ b/src/postconf/test48.ref @@ -0,0 +1 @@ +./postconf: fatal: invalid chroot field "X" in "bar inet - n X - 0 other" diff --git a/src/postconf/test49.ref b/src/postconf/test49.ref new file mode 100644 index 0000000..f52b171 --- /dev/null +++ b/src/postconf/test49.ref @@ -0,0 +1 @@ +./postconf: fatal: invalid wakeup field "X" in "bar inet - n n X 0 other" diff --git a/src/postconf/test4b.ref b/src/postconf/test4b.ref new file mode 100644 index 0000000..5dda033 --- /dev/null +++ b/src/postconf/test4b.ref @@ -0,0 +1,5 @@ +always_bcc = $foo +bar = aaa +biff = $bar +config_directory = . +./postconf: warning: ./master.cf: unused parameter: baz=zzz diff --git a/src/postconf/test5.ref b/src/postconf/test5.ref new file mode 100644 index 0000000..41bee93 --- /dev/null +++ b/src/postconf/test5.ref @@ -0,0 +1 @@ +config_directory = . diff --git a/src/postconf/test50.ref b/src/postconf/test50.ref new file mode 100644 index 0000000..41cdc44 --- /dev/null +++ b/src/postconf/test50.ref @@ -0,0 +1 @@ +./postconf: fatal: invalid process_limit field "X" in "bar inet - n n - X other" diff --git a/src/postconf/test51.ref b/src/postconf/test51.ref new file mode 100644 index 0000000..a37d749 --- /dev/null +++ b/src/postconf/test51.ref @@ -0,0 +1 @@ +./postconf: fatal: invalid wakeup field "X?" in "bar inet - n n X? 0 other" diff --git a/src/postconf/test52.ref b/src/postconf/test52.ref new file mode 100644 index 0000000..8902db9 --- /dev/null +++ b/src/postconf/test52.ref @@ -0,0 +1 @@ +baz unix - n n 0 0 other diff --git a/src/postconf/test53.ref b/src/postconf/test53.ref new file mode 100644 index 0000000..c81cf65 --- /dev/null +++ b/src/postconf/test53.ref @@ -0,0 +1,3 @@ +foo unix - n n - 0 other +#bar inet - n n 0 0 other +baz unix - n n 0 0 other diff --git a/src/postconf/test54.ref b/src/postconf/test54.ref new file mode 100644 index 0000000..045a9f6 --- /dev/null +++ b/src/postconf/test54.ref @@ -0,0 +1,3 @@ +#foo unix - n n - 0 other +#bar inet - n n 0 0 other +baz unix - n n 0 0 other diff --git a/src/postconf/test55.ref b/src/postconf/test55.ref new file mode 100644 index 0000000..96c11b0 --- /dev/null +++ b/src/postconf/test55.ref @@ -0,0 +1,3 @@ +foo unix - n n - 0 other +#bar inet - n n 0 0 other +#baz unix - n n 0 0 other diff --git a/src/postconf/test56.ref b/src/postconf/test56.ref new file mode 100644 index 0000000..ac845f6 --- /dev/null +++ b/src/postconf/test56.ref @@ -0,0 +1,5 @@ +foo unix - n n - 0 other +#bar inet - n n 0 0 other +# -o first +# -o second +baz unix - n n 0 0 other diff --git a/src/postconf/test57.ref b/src/postconf/test57.ref new file mode 100644 index 0000000..362fd16 --- /dev/null +++ b/src/postconf/test57.ref @@ -0,0 +1,10 @@ +./postconf: warning: ./main.cf: undefined parameter: z +./postconf: warning: ./main.cf: undefined parameter: z +bar = y-value +baz = +config_directory = . +t1 = Postfix 2.11 compatible +x = x-value +y = y-value +./postconf: warning: ./main.cf: unused parameter: t2=$t1 +./postconf: warning: ./main.cf: unused parameter: foo=$bar$baz diff --git a/src/postconf/test58.ref b/src/postconf/test58.ref new file mode 100644 index 0000000..ac24a4d --- /dev/null +++ b/src/postconf/test58.ref @@ -0,0 +1,8 @@ +./postconf: warning: main.cf: syntax error after '}' in "{ldap:xxx, memcache:yy}x" +./postconf: warning: main.cf: missing '}' in "{xx" +config_directory = . +mydestination = foo bar pipemap:{ldap:xxx, memcache:yy}x randmap:{xx +xxx_domain = foo +yy_backup = bbb +./postconf: warning: ./main.cf: unused parameter: xxx_bogus=foo +./postconf: warning: ./main.cf: unused parameter: yy_bogus=bbb diff --git a/src/postconf/test59.ref b/src/postconf/test59.ref new file mode 100644 index 0000000..c5cb3f6 --- /dev/null +++ b/src/postconf/test59.ref @@ -0,0 +1,10 @@ +./postconf: warning: master.cf: syntax error after '}' in "{ arg2a arg2b }x" +./postconf: warning: master.cf: missing '}' in "{ arg3a arg3b " +foo unix - n n - 0 other +bar inet - n n 0 0 other + -o name1=value1 + -o {name2=value2a value2b} + arg1a arg1b {arg2a arg2b} {arg3a arg3b} +baz unix - n n 0 0 other +./postconf: warning: ./master.cf: unused parameter: name2=value2a value2b +./postconf: warning: ./master.cf: unused parameter: name1=value1 diff --git a/src/postconf/test6.ref b/src/postconf/test6.ref new file mode 100644 index 0000000..55e47f2 --- /dev/null +++ b/src/postconf/test6.ref @@ -0,0 +1,17 @@ +whatevershebrings_delivery_slot_cost = $default_delivery_slot_cost +whatevershebrings_delivery_slot_discount = $default_delivery_slot_discount +whatevershebrings_delivery_slot_loan = $default_delivery_slot_loan +whatevershebrings_destination_concurrency_failed_cohort_limit = $default_destination_concurrency_failed_cohort_limit +whatevershebrings_destination_concurrency_limit = $default_destination_concurrency_limit +whatevershebrings_destination_concurrency_negative_feedback = $default_destination_concurrency_negative_feedback +whatevershebrings_destination_concurrency_positive_feedback = $default_destination_concurrency_positive_feedback +whatevershebrings_destination_rate_delay = $default_destination_rate_delay +whatevershebrings_destination_recipient_limit = $default_destination_recipient_limit +whatevershebrings_extra_recipient_limit = $default_extra_recipient_limit +whatevershebrings_initial_destination_concurrency = $initial_destination_concurrency +whatevershebrings_minimum_delivery_slots = $default_minimum_delivery_slots +whatevershebrings_recipient_limit = $default_recipient_limit +whatevershebrings_recipient_refill_delay = $default_recipient_refill_delay +whatevershebrings_recipient_refill_limit = $default_recipient_refill_limit +whatevershebrings_time_limit = $command_time_limit +whatevershebrings_transport_rate_delay = $default_transport_rate_delay diff --git a/src/postconf/test60.ref b/src/postconf/test60.ref new file mode 100644 index 0000000..5a6f3ca --- /dev/null +++ b/src/postconf/test60.ref @@ -0,0 +1,8 @@ +foo +unix +- +n +n +- +0 +other -o always_bcc=bar diff --git a/src/postconf/test61.ref b/src/postconf/test61.ref new file mode 100644 index 0000000..5716ca5 --- /dev/null +++ b/src/postconf/test61.ref @@ -0,0 +1 @@ +bar diff --git a/src/postconf/test62.ref b/src/postconf/test62.ref new file mode 100644 index 0000000..ffc4913 --- /dev/null +++ b/src/postconf/test62.ref @@ -0,0 +1,8 @@ +foo/unix/service +foo/unix/type +foo/unix/private +foo/unix/unprivileged +foo/unix/chroot +foo/unix/wakeup +foo/unix/process_limit +foo/unix/command diff --git a/src/postconf/test63.ref b/src/postconf/test63.ref new file mode 100644 index 0000000..b609d62 --- /dev/null +++ b/src/postconf/test63.ref @@ -0,0 +1 @@ +foo/unix/always_bcc diff --git a/src/postconf/test64.ref b/src/postconf/test64.ref new file mode 100644 index 0000000..595620c --- /dev/null +++ b/src/postconf/test64.ref @@ -0,0 +1 @@ +relayhost = relay-from-main.cf diff --git a/src/postconf/test65.ref b/src/postconf/test65.ref new file mode 100644 index 0000000..6bc7fd8 --- /dev/null +++ b/src/postconf/test65.ref @@ -0,0 +1 @@ +relayhost = relay-from-cmd-line diff --git a/src/postconf/test66.ref b/src/postconf/test66.ref new file mode 100644 index 0000000..bd35822 --- /dev/null +++ b/src/postconf/test66.ref @@ -0,0 +1,5 @@ +./postconf: warning: ldap:PWD/test66.cf: unused parameter: junk=junk +./postconf: warning: mysql:PWD/test66.cf: unused parameter: junk=junk +./postconf: warning: pgsql:PWD/test66.cf: unused parameter: junk=junk +./postconf: warning: sqlite:PWD/test66.cf: unused parameter: junk=junk +./postconf: warning: memcache:PWD/test66.cf: unused parameter: junk=junk diff --git a/src/postconf/test67.ref b/src/postconf/test67.ref new file mode 100644 index 0000000..2014e99 --- /dev/null +++ b/src/postconf/test67.ref @@ -0,0 +1,10 @@ +smtp inet n - n - - smtpd + -o test1_process_name=smtpd + -o test1_service_name=smtp +smtp unix n - n - - smtp + -o test2_process_name=smtp + -o test2_service_name=smtp +./postconf: warning: ./master.cf: unused parameter: test1_service_name=$service_name +./postconf: warning: ./master.cf: unused parameter: test1_process_name=$process_name +./postconf: warning: ./master.cf: unused parameter: test2_service_name=$service_name +./postconf: warning: ./master.cf: unused parameter: test2_process_name=$process_name diff --git a/src/postconf/test68.ref b/src/postconf/test68.ref new file mode 100644 index 0000000..e2d7c7d --- /dev/null +++ b/src/postconf/test68.ref @@ -0,0 +1,5 @@ +./postconf: warning: ldap:PWD/test68.cf: unused parameter: junk=junk +./postconf: warning: mysql:PWD/test68.cf: unused parameter: junk=junk +./postconf: warning: pgsql:PWD/test68.cf: unused parameter: junk=junk +./postconf: warning: sqlite:PWD/test68.cf: unused parameter: junk=junk +./postconf: warning: memcache:PWD/test68.cf: unused parameter: junk=junk diff --git a/src/postconf/test69.ref b/src/postconf/test69.ref new file mode 100644 index 0000000..465a91f --- /dev/null +++ b/src/postconf/test69.ref @@ -0,0 +1,2 @@ +./postconf: warning: ldap:PWD/test69.cf: unused parameter: junk=junk +config_directory = . diff --git a/src/postconf/test7.ref b/src/postconf/test7.ref new file mode 100644 index 0000000..1bd731b --- /dev/null +++ b/src/postconf/test7.ref @@ -0,0 +1 @@ +whatevershebrings_time_limit = $command_time_limit diff --git a/src/postconf/test70.ref b/src/postconf/test70.ref new file mode 100644 index 0000000..d81a9d1 --- /dev/null +++ b/src/postconf/test70.ref @@ -0,0 +1,4 @@ +config_directory = . +smtpd_client_restrictions = check_sender_access { pipemap:{ldap:used} { search_order = foo, bar } } +used_server_host = 127.0.0.1 +./postconf: warning: ./main.cf: unused parameter: unused_server_host=127.0.0.1 diff --git a/src/postconf/test8.ref b/src/postconf/test8.ref new file mode 100644 index 0000000..af47b44 --- /dev/null +++ b/src/postconf/test8.ref @@ -0,0 +1 @@ +whatevershebrings_time_limit = 1 diff --git a/src/postconf/test9.ref b/src/postconf/test9.ref new file mode 100644 index 0000000..b62303f --- /dev/null +++ b/src/postconf/test9.ref @@ -0,0 +1 @@ +foo inet - n n - 0 spawn diff --git a/src/postdrop/.indent.pro b/src/postdrop/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postdrop/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postdrop/.printfck b/src/postdrop/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/postdrop/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/postdrop/Makefile.in b/src/postdrop/Makefile.in new file mode 100644 index 0000000..cd60a94 --- /dev/null +++ b/src/postdrop/Makefile.in @@ -0,0 +1,96 @@ +SHELL = /bin/sh +SRCS = postdrop.c +OBJS = postdrop.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = postdrop +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postdrop.o: ../../include/argv.h +postdrop.o: ../../include/attr.h +postdrop.o: ../../include/check_arg.h +postdrop.o: ../../include/clean_env.h +postdrop.o: ../../include/cleanup_user.h +postdrop.o: ../../include/dict.h +postdrop.o: ../../include/htable.h +postdrop.o: ../../include/iostuff.h +postdrop.o: ../../include/login_sender_match.h +postdrop.o: ../../include/mail_conf.h +postdrop.o: ../../include/mail_dict.h +postdrop.o: ../../include/mail_params.h +postdrop.o: ../../include/mail_parm_split.h +postdrop.o: ../../include/mail_proto.h +postdrop.o: ../../include/mail_queue.h +postdrop.o: ../../include/mail_stream.h +postdrop.o: ../../include/mail_task.h +postdrop.o: ../../include/mail_version.h +postdrop.o: ../../include/maillog_client.h +postdrop.o: ../../include/msg.h +postdrop.o: ../../include/msg_vstream.h +postdrop.o: ../../include/myflock.h +postdrop.o: ../../include/mymalloc.h +postdrop.o: ../../include/mypwd.h +postdrop.o: ../../include/nvtable.h +postdrop.o: ../../include/rec_attr_map.h +postdrop.o: ../../include/rec_type.h +postdrop.o: ../../include/record.h +postdrop.o: ../../include/stringops.h +postdrop.o: ../../include/sys_defs.h +postdrop.o: ../../include/user_acl.h +postdrop.o: ../../include/vbuf.h +postdrop.o: ../../include/vstream.h +postdrop.o: ../../include/vstring.h +postdrop.o: ../../include/warn_stat.h +postdrop.o: postdrop.c diff --git a/src/postdrop/postdrop.c b/src/postdrop/postdrop.c new file mode 100644 index 0000000..e9335e9 --- /dev/null +++ b/src/postdrop/postdrop.c @@ -0,0 +1,628 @@ +/*++ +/* NAME +/* postdrop 1 +/* SUMMARY +/* Postfix mail posting utility +/* SYNOPSIS +/* \fBpostdrop\fR [\fB-rv\fR] [\fB-c \fIconfig_dir\fR] +/* DESCRIPTION +/* The \fBpostdrop\fR(1) command creates a file in the \fBmaildrop\fR +/* directory and copies its standard input to the file. +/* +/* Options: +/* .IP "\fB-c \fIconfig_dir\fR" +/* The \fBmain.cf\fR configuration file is in the named directory +/* instead of the default configuration directory. See also the +/* MAIL_CONFIG environment setting below. +/* .IP \fB-r\fR +/* Use a Postfix-internal protocol for reading the message from +/* standard input, and for reporting status information on standard +/* output. This is currently the only supported method. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. As of Postfix 2.3, +/* this option is available for the super-user only. +/* SECURITY +/* .ad +/* .fi +/* The command is designed to run with set-group ID privileges, so +/* that it can write to the \fBmaildrop\fR queue directory and so that +/* it can connect to Postfix daemon processes. +/* DIAGNOSTICS +/* Fatal errors: malformed input, I/O error, out of memory. Problems +/* are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8) and to +/* the standard error stream. +/* When the input is incomplete, or when the process receives a HUP, +/* INT, QUIT or TERM signal, the queue file is deleted. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP MAIL_CONFIG +/* Directory with the \fBmain.cf\fR file. In order to avoid exploitation +/* of set-group ID privileges, a non-standard directory is allowed only +/* if: +/* .RS +/* .IP \(bu +/* The name is listed in the standard \fBmain.cf\fR file with the +/* \fBalternate_config_directories\fR configuration parameter. +/* .IP \(bu +/* The command is invoked by the super-user. +/* .RE +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBalternate_config_directories (empty)\fR" +/* A list of non-default Postfix configuration directories that may +/* be specified with "-c config_directory" on the command line (in the +/* case of \fBsendmail\fR(1), with the "-C" option), or via the MAIL_CONFIG +/* environment parameter. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment parameters that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .IP "\fBtrigger_timeout (10s)\fR" +/* The time limit for sending a trigger to a Postfix daemon (for +/* example, the \fBpickup\fR(8) or \fBqmgr\fR(8) daemon). +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBauthorized_submit_users (static:anyone)\fR" +/* List of users who are authorized to submit mail with the \fBsendmail\fR(1) +/* command (and with the privileged \fBpostdrop\fR(1) helper command). +/* .PP +/* Available in Postfix version 3.6 and later: +/* .IP "\fBlocal_login_sender_maps (static:*)\fR" +/* A list of lookup tables that are searched by the UNIX login name, +/* and that return a list of allowed envelope sender patterns separated +/* by space or comma. +/* .IP "\fBempty_address_local_login_sender_maps_lookup_key (<>)\fR" +/* The lookup key to be used in local_login_sender_maps tables, instead +/* of the null sender address. +/* .IP "\fBrecipient_delimiter (empty)\fR" +/* The set of characters that can separate an email address +/* localpart, user name, or a .forward file name from its extension. +/* FILES +/* /var/spool/postfix/maildrop, maildrop queue +/* SEE ALSO +/* sendmail(1), compatibility interface +/* postconf(5), configuration parameters +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include /* remove() */ +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * WARNING WARNING WARNING + * + * This software is designed to run set-gid. In order to avoid exploitation of + * privilege, this software should not run any external commands, nor should + * it take any information from the user unless that information can be + * properly sanitized. To get an idea of how much information a process can + * inherit from a potentially hostile user, examine all the members of the + * process structure (typically, in /usr/include/sys/proc.h): the current + * directory, open files, timers, signals, environment, command line, umask, + * and so on. + */ + + /* + * Local mail submission access list. + */ +char *var_submit_acl; +char *var_local_login_snd_maps; +char *var_null_local_login_snd_maps_key; + +static const CONFIG_STR_TABLE str_table[] = { + VAR_SUBMIT_ACL, DEF_SUBMIT_ACL, &var_submit_acl, 0, 0, + VAR_LOCAL_LOGIN_SND_MAPS, DEF_LOCAL_LOGIN_SND_MAPS, &var_local_login_snd_maps, 0, 0, + VAR_NULL_LOCAL_LOGIN_SND_MAPS_KEY, DEF_NULL_LOCAL_LOGIN_SND_MAPS_KEY, &var_null_local_login_snd_maps_key, 0, 0, + 0, +}; + + /* + * Queue file name. Global, so that the cleanup routine can find it when + * called by the run-time error handler. + */ +static char *postdrop_path; + +/* postdrop_sig - catch signal and clean up */ + +static void postdrop_sig(int sig) +{ + + /* + * This is the fatal error handler. Don't try to do anything fancy. + * + * To avoid privilege escalation in a set-gid program, Postfix logging + * functions must not be called from a user-triggered signal handler, + * because Postfix logging functions may allocate memory on the fly (as + * does the syslog() library function), and the memory allocator is not + * reentrant. + * + * Assume atomic signal() updates, even when emulated with sigaction(). We + * use the in-kernel SIGINT handler address as an atomic variable to + * prevent nested postdrop_sig() calls. For this reason, main() must + * configure postdrop_sig() as SIGINT handler before other signal + * handlers are allowed to invoke postdrop_sig(). + */ + if (signal(SIGINT, SIG_IGN) != SIG_IGN) { + (void) signal(SIGQUIT, SIG_IGN); + (void) signal(SIGTERM, SIG_IGN); + (void) signal(SIGHUP, SIG_IGN); + if (postdrop_path) { + (void) remove(postdrop_path); + postdrop_path = 0; + } + /* Future proofing. If you need exit() here then you broke Postfix. */ + if (sig) + _exit(sig); + } +} + +/* postdrop_cleanup - callback for the runtime error handler */ + +static void postdrop_cleanup(void) +{ + postdrop_sig(0); +} + +/* check_login_sender_acl - check if a user is authorized to use this sender */ + +static int check_login_sender_acl(uid_t uid, VSTRING *sender_buf, + VSTRING *reason) +{ + const char myname[] = "check_login_sender_acl"; + struct mypasswd *user_info; + char *user_name; + VSTRING *user_name_buf = 0; + LOGIN_SENDER_MATCH *lsm; + int res; + + /* + * Sanity checks. + */ + if (vstring_memchr(sender_buf, '\0') != 0) { + vstring_sprintf(reason, "NUL in FROM record"); + return (CLEANUP_STAT_BAD); + } + + /* + * Optimization. + */ +#ifndef SNAPSHOT + if (strcmp(var_local_login_snd_maps, DEF_LOCAL_LOGIN_SND_MAPS) == 0) + return (CLEANUP_STAT_OK); +#endif + + /* + * Get the username. + */ + if ((user_info = mypwuid(uid)) != 0) { + user_name = user_info->pw_name; + } else { + user_name_buf = vstring_alloc(10); + vstring_sprintf(user_name_buf, "uid:%ld", (long) uid); + user_name = vstring_str(user_name_buf); + } + + + /* + * Apply the a login-sender matcher. TODO: add DICT flags. + */ + lsm = login_sender_create(VAR_LOCAL_LOGIN_SND_MAPS, + var_local_login_snd_maps, + var_rcpt_delim, + var_null_local_login_snd_maps_key, "*"); + res = login_sender_match(lsm, user_name, vstring_str(sender_buf)); + login_sender_free(lsm); + if (user_name_buf) + vstring_free(user_name_buf); + switch (res) { + case LSM_STAT_FOUND: + return (CLEANUP_STAT_OK); + case LSM_STAT_NOTFOUND: + vstring_sprintf(reason, "not authorized to use sender='%s'", + vstring_str(sender_buf)); + return (CLEANUP_STAT_NOPERM); + case LSM_STAT_RETRY: + case LSM_STAT_CONFIG: + vstring_sprintf(reason, "%s table lookup error for '%s'", + VAR_LOCAL_LOGIN_SND_MAPS, var_local_login_snd_maps); + return (CLEANUP_STAT_WRITE); + default: + msg_panic("%s: bad login_sender_match() result: %d", myname, res); + } +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + struct stat st; + int fd; + int c; + VSTRING *buf; + int status = CLEANUP_STAT_OK; + VSTRING *reason = vstring_alloc(100); + MAIL_STREAM *dst; + int rec_type; + static char *segment_info[] = { + REC_TYPE_POST_ENVELOPE, REC_TYPE_POST_CONTENT, REC_TYPE_POST_EXTRACT, "" + }; + char **expected; + uid_t uid = getuid(); + ARGV *import_env; + const char *error_text; + char *attr_name; + char *attr_value; + const char *errstr; + char *junk; + struct timeval start; + int saved_errno; + int from_count = 0; + int rcpt_count = 0; + int validate_input = 1; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Be consistent with file permissions. + */ + umask(022); + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Set up logging. Censor the process name: it is provided by the user. + */ + argv[0] = "postdrop"; + msg_vstream_init(argv[0], VSTREAM_ERR); + maillog_client_init(mail_task("postdrop"), MAILLOG_CLIENT_FLAG_NONE); + set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Parse JCL. This program is set-gid and must sanitize all command-line + * arguments. The configuration directory argument is validated by the + * mail configuration read routine. Don't do complex things until we have + * completed initializations. + */ + while ((c = GETOPT(argc, argv, "c:rv")) > 0) { + switch (c) { + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'r': /* forward compatibility */ + break; + case 'v': + if (geteuid() == 0) + msg_verbose++; + break; + default: + msg_fatal("usage: %s [-c config_dir] [-v]", argv[0]); + } + } + + /* + * Read the global configuration file and extract configuration + * information. + */ + mail_conf_read(); + /* Re-evaluate mail_task() after reading main.cf. */ + maillog_client_init(mail_task("postdrop"), MAILLOG_CLIENT_FLAG_NONE); + get_mail_conf_str_table(str_table); + + /* + * Stop run-away process accidents by limiting the queue file size. This + * is not a defense against DOS attack. + */ + if (ENFORCING_SIZE_LIMIT(var_message_limit) + && get_file_limit() > var_message_limit) + set_file_limit((off_t) var_message_limit); + + /* + * This program is installed with setgid privileges. Strip the process + * environment so that we don't have to trust the C library. + */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + clean_env(import_env->argv); + argv_free(import_env); + + if (chdir(var_queue_dir)) + msg_fatal("chdir %s: %m", var_queue_dir); + if (msg_verbose) + msg_info("chdir %s", var_queue_dir); + + /* + * Set up signal handlers and a runtime error handler so that we can + * clean up incomplete output. + * + * postdrop_sig() uses the in-kernel SIGINT handler address as an atomic + * variable to prevent nested postdrop_sig() calls. For this reason, the + * SIGINT handler must be configured before other signal handlers are + * allowed to invoke postdrop_sig(). + */ + signal(SIGPIPE, SIG_IGN); + signal(SIGXFSZ, SIG_IGN); + + signal(SIGINT, postdrop_sig); + signal(SIGQUIT, postdrop_sig); + if (signal(SIGTERM, SIG_IGN) == SIG_DFL) + signal(SIGTERM, postdrop_sig); + if (signal(SIGHUP, SIG_IGN) == SIG_DFL) + signal(SIGHUP, postdrop_sig); + msg_cleanup(postdrop_cleanup); + + /* End of initializations. */ + + /* + * Mail submission access control. Should this be in the user-land gate, + * or in the daemon process? + */ + mail_dict_init(); + if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, + uid)) != 0) + msg_fatal("User %s(%ld) is not allowed to submit mail", + errstr, (long) uid); + + /* + * Don't trust the caller's time information. + */ + GETTIMEOFDAY(&start); + + /* + * Create queue file. mail_stream_file() never fails. Send the queue ID + * to the caller. Stash away a copy of the queue file name so we can + * clean up in case of a fatal error or an interrupt. + */ + dst = mail_stream_file(MAIL_QUEUE_MAILDROP, MAIL_CLASS_PUBLIC, + var_pickup_service, 0444); + attr_print(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_POSTDROP), + SEND_ATTR_STR(MAIL_ATTR_QUEUEID, dst->id), + ATTR_TYPE_END); + vstream_fflush(VSTREAM_OUT); + postdrop_path = mystrdup(VSTREAM_PATH(dst->stream)); + + /* + * Copy stdin to file. The format is checked so that we can recognize + * incomplete input and cancel the operation. With the sanity checks + * applied here, the pickup daemon could skip format checks and pass a + * file descriptor to the cleanup daemon. These are by no means all + * sanity checks - the cleanup service and queue manager services will + * reject messages that lack required information. + * + * If something goes wrong, slurp up the input before responding to the + * client, otherwise the client will give up after detecting SIGPIPE. + * + * Allow attribute records if the attribute specifies the MIME body type + * (sendmail -B). + */ + vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END); + buf = vstring_alloc(100); + expected = segment_info; + /* Override time information from the untrusted caller. */ + rec_fprintf(dst->stream, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, + REC_TYPE_TIME_ARG(start)); + for (;;) { + /* Don't allow PTR records. */ + rec_type = rec_get_raw(VSTREAM_IN, buf, var_line_limit, REC_FLAG_NONE); + if (rec_type == REC_TYPE_EOF) { /* request canceled */ + mail_stream_cleanup(dst); + if (remove(postdrop_path)) + msg_warn("uid=%ld: remove %s: %m", (long) uid, postdrop_path); + else if (msg_verbose) + msg_info("remove %s", postdrop_path); + myfree(postdrop_path); + postdrop_path = 0; + exit(0); + } + if (rec_type == REC_TYPE_ERROR) + msg_fatal("uid=%ld: malformed input", (long) uid); + if (strchr(*expected, rec_type) == 0) + msg_fatal("uid=%ld: unexpected record type: %d", (long) uid, rec_type); + if (rec_type == **expected) + expected++; + /* Override time information from the untrusted caller. */ + if (rec_type == REC_TYPE_TIME) + continue; + /* Check these at submission time instead of pickup time. */ + if (rec_type == REC_TYPE_FROM) { + status |= check_login_sender_acl(uid, buf, reason); + from_count++; + } + if (rec_type == REC_TYPE_RCPT) + rcpt_count++; + /* Limit the attribute types that users may specify. */ + if (rec_type == REC_TYPE_ATTR) { + if ((error_text = split_nameval(vstring_str(buf), &attr_name, + &attr_value)) != 0) { + msg_warn("uid=%ld: ignoring malformed record: %s: %.200s", + (long) uid, error_text, vstring_str(buf)); + continue; + } +#define STREQ(x,y) (strcmp(x,y) == 0) + + if ((STREQ(attr_name, MAIL_ATTR_ENCODING) + && (STREQ(attr_value, MAIL_ATTR_ENC_7BIT) + || STREQ(attr_value, MAIL_ATTR_ENC_8BIT) + || STREQ(attr_value, MAIL_ATTR_ENC_NONE))) + || STREQ(attr_name, MAIL_ATTR_DSN_ENVID) + || STREQ(attr_name, MAIL_ATTR_DSN_NOTIFY) + || rec_attr_map(attr_name) + || (STREQ(attr_name, MAIL_ATTR_RWR_CONTEXT) + && (STREQ(attr_value, MAIL_ATTR_RWR_LOCAL) + || STREQ(attr_value, MAIL_ATTR_RWR_REMOTE))) + || STREQ(attr_name, MAIL_ATTR_TRACE_FLAGS)) { /* XXX */ + rec_fprintf(dst->stream, REC_TYPE_ATTR, "%s=%s", + attr_name, attr_value); + } else { + msg_warn("uid=%ld: ignoring attribute record: %.200s=%.200s", + (long) uid, attr_name, attr_value); + } + continue; + } + if (status != CLEANUP_STAT_OK + || REC_PUT_BUF(dst->stream, rec_type, buf) < 0) { + /* rec_get() errors must not clobber errno. */ + saved_errno = errno; + while ((rec_type = rec_get_raw(VSTREAM_IN, buf, var_line_limit, + REC_FLAG_NONE)) != REC_TYPE_END + && rec_type != REC_TYPE_EOF) + if (rec_type == REC_TYPE_ERROR) + msg_fatal("uid=%ld: malformed input", (long) uid); + validate_input = 0; + errno = saved_errno; + break; + } + if (rec_type == REC_TYPE_END) + break; + } + vstring_free(buf); + + /* + * As of Postfix 2.7 the pickup daemon discards mail without recipients. + * Such mail may enter the maildrop queue when "postsuper -r" is invoked + * before the queue manager deletes an already delivered message. Looking + * at file ownership is not a good way to make decisions on what mail to + * discard. Instead, the pickup server now requires that new submissions + * always have at least one recipient record. + * + * The Postfix sendmail command already rejects mail without recipients. + * However, in the future postdrop may receive mail via other programs, + * so we add a redundant recipient check here for future proofing. + * + * The test for the sender address is just for consistency of error + * reporting (report at submission time instead of pickup time). Besides + * the segment terminator records, there aren't any other mandatory + * records in a Postfix submission queue file. + * + * TODO: return an informative reason for missing sender, too many senders, + * or missing recipient. + */ + if (validate_input && (from_count == 0 || rcpt_count == 0)) + status |= CLEANUP_STAT_BAD; + if (status != CLEANUP_STAT_OK) { + mail_stream_cleanup(dst); + } + + /* + * Finish the file. + */ + else if ((status = mail_stream_finish(dst, reason)) != 0) { + msg_warn("uid=%ld: %m", (long) uid); + postdrop_cleanup(); + } + + /* + * Disable deletion on fatal error before reporting success, so the file + * will not be deleted after we have taken responsibility for delivery. + */ + if (postdrop_path) { + junk = postdrop_path; + postdrop_path = 0; + myfree(junk); + } + + /* + * Send the completion status to the caller and terminate. + */ + attr_print(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + SEND_ATTR_STR(MAIL_ATTR_WHY, status != CLEANUP_STAT_OK + && VSTRING_LEN(reason) > 0 ? + vstring_str(reason) : ""), + ATTR_TYPE_END); + vstream_fflush(VSTREAM_OUT); + exit(status); +} diff --git a/src/postfix/.indent.pro b/src/postfix/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postfix/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postfix/.printfck b/src/postfix/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/postfix/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/postfix/Makefile.in b/src/postfix/Makefile.in new file mode 100644 index 0000000..dc3bf43 --- /dev/null +++ b/src/postfix/Makefile.in @@ -0,0 +1,83 @@ +SHELL = /bin/sh +SRCS = postfix.c +OBJS = postfix.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +FILES = Makefile $(SRCS) $(HDRS) +INC_DIR = ../../include +TESTPROG= +PROG = postfix +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +shar: + @shar $(FILES) + +lint: + lint $(SRCS) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postfix.o: ../../include/argv.h +postfix.o: ../../include/check_arg.h +postfix.o: ../../include/clean_env.h +postfix.o: ../../include/compat_level.h +postfix.o: ../../include/mail_conf.h +postfix.o: ../../include/mail_params.h +postfix.o: ../../include/mail_parm_split.h +postfix.o: ../../include/mail_version.h +postfix.o: ../../include/maillog_client.h +postfix.o: ../../include/msg.h +postfix.o: ../../include/msg_vstream.h +postfix.o: ../../include/safe.h +postfix.o: ../../include/stringops.h +postfix.o: ../../include/sys_defs.h +postfix.o: ../../include/vbuf.h +postfix.o: ../../include/vstream.h +postfix.o: ../../include/vstring.h +postfix.o: ../../include/warn_stat.h +postfix.o: postfix.c diff --git a/src/postfix/postfix.c b/src/postfix/postfix.c new file mode 100644 index 0000000..fed152d --- /dev/null +++ b/src/postfix/postfix.c @@ -0,0 +1,660 @@ +/*++ +/* NAME +/* postfix 1 +/* SUMMARY +/* Postfix control program +/* SYNOPSIS +/* .fi +/* \fBpostfix\fR [\fB-Dv\fR] [\fB-c \fIconfig_dir\fR] \fIcommand\fR +/* DESCRIPTION +/* This command is reserved for the superuser. To submit mail, +/* use the Postfix \fBsendmail\fR(1) command. +/* +/* The \fBpostfix\fR(1) command controls the operation of the Postfix +/* mail system: start or stop the \fBmaster\fR(8) daemon, do a health +/* check, and other maintenance. +/* +/* By default, the \fBpostfix\fR(1) command sets up a standardized +/* environment and runs the \fBpostfix-script\fR shell script +/* to do the actual work. +/* +/* However, when support for multiple Postfix instances is +/* configured, \fBpostfix\fR(1) executes the command specified +/* with the \fBmulti_instance_wrapper\fR configuration parameter. +/* This command will execute the \fIcommand\fR for each +/* applicable Postfix instance. +/* +/* The following commands are implemented: +/* .IP \fBcheck\fR +/* Warn about bad directory/file ownership or permissions, +/* and create missing directories. +/* .IP \fBstart\fR +/* Start the Postfix mail system. This also runs the configuration +/* check described above. +/* .IP \fBstart-fg\fR +/* Like \fBstart\fR, but keep the \fBmaster\fR(8) daemon running +/* in the foreground, and enable \fBmaster\fR(8) "init" mode +/* when running as PID 1. +/* This command requires that multi-instance support is +/* disabled (i.e. the multi_instance_directories parameter +/* value must be empty). +/* +/* When running Postfix inside a container, see MAILLOG_README +/* for logging to stdout. Postfix logs to syslog by default, +/* which requires a) running a syslogd process inside the +/* container, or b) mounting the container host's /dev/log +/* socket inside the container (example: "docker run -v +/* /dev/log:/dev/log ..."), and c) a distinct Postfix "syslog_name" +/* prefix that identifies logging from the Postfix instance. +/* .IP \fBstop\fR +/* Stop the Postfix mail system in an orderly fashion. If +/* possible, running processes are allowed to terminate at +/* their earliest convenience. +/* .sp +/* Note: in order to refresh the Postfix mail system after a +/* configuration change, do not use the \fBstart\fR and \fBstop\fR +/* commands in succession. Use the \fBreload\fR command instead. +/* .IP \fBabort\fR +/* Stop the Postfix mail system abruptly. Running processes are +/* signaled to stop immediately. +/* .IP \fBflush\fR +/* Force delivery: attempt to deliver every message in the deferred +/* mail queue. Normally, attempts to deliver delayed mail happen at +/* regular intervals, the interval doubling after each failed attempt. +/* .sp +/* Warning: flushing undeliverable mail frequently will result in +/* poor delivery performance of all other mail. +/* .IP \fBreload\fR +/* Re-read configuration files. Running processes terminate at their +/* earliest convenience. +/* .IP \fBstatus\fR +/* Indicate if the Postfix mail system is currently running. +/* .IP "\fBset-permissions\fR [\fIname\fR=\fIvalue ...\fR]" +/* Set the ownership and permissions of Postfix related files and +/* directories, as specified in the \fBpostfix-files\fR file. +/* .sp +/* Specify \fIname\fR=\fIvalue\fR to override and update specific +/* main.cf configuration parameters. Use this, for example, to +/* change the \fBmail_owner\fR or \fBsetgid_group\fR setting for an +/* already installed Postfix system. +/* .sp +/* This feature is available in Postfix 2.1 and later. With +/* Postfix 2.0 and earlier, use "\fB$config_directory/post-install +/* set-permissions\fR". +/* .IP "\fBlogrotate\fR" +/* Rotate the logfile specified with $maillog_file, by appending +/* a time-stamp suffix that is formatted according to +/* $maillog_file_rotate_suffix, and by compressing the file +/* with the command specified with $maillog_file_compressor. +/* This will not rotate /dev/* files. +/* .sp +/* This feature is available in Postfix 3.4 and later. +/* .IP "\fBtls\fR \fIsubcommand\fR" +/* Enable opportunistic TLS in the Postfix SMTP client or +/* server, and manage Postfix SMTP server TLS private keys and +/* certificates. See postfix-tls(1) for documentation. +/* .sp +/* This feature is available in Postfix 3.1 and later. +/* .IP "\fBupgrade-configuration\fR [\fIname\fR=\fIvalue ...\fR]" +/* Update the \fBmain.cf\fR and \fBmaster.cf\fR files with information +/* that Postfix needs in order to run: add or update services, and add +/* or update configuration parameter settings. +/* .sp +/* Specify \fIname\fR=\fIvalue\fR to override and update specific +/* main.cf configuration parameters. +/* .sp +/* This feature is available in Postfix 2.1 and later. With +/* Postfix 2.0 and earlier, use "\fB$config_directory/post-install +/* upgrade-configuration\fR". +/* .PP +/* The following options are implemented: +/* .IP "\fB-c \fIconfig_dir\fR" +/* Read the \fBmain.cf\fR and \fBmaster.cf\fR configuration files in +/* the named directory instead of the default configuration directory. +/* Use this to distinguish between multiple Postfix instances on the +/* same host. +/* +/* With Postfix 2.6 and later, this option forces the postfix(1) +/* command to operate on the specified Postfix instance only. +/* This behavior is inherited by postfix(1) commands that run +/* as a descendant of the current process. +/* .IP "\fB-D\fR (with \fBpostfix start\fR only)" +/* Run each Postfix daemon under control of a debugger as specified +/* via the \fBdebugger_command\fR configuration parameter. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. +/* ENVIRONMENT +/* .ad +/* .fi +/* The \fBpostfix\fR(1) command exports the following environment +/* variables before executing the \fBpostfix-script\fR file: +/* .IP \fBMAIL_CONFIG\fR +/* This is set when the -c command-line option is present. +/* +/* With Postfix 2.6 and later, this environment variable forces +/* the postfix(1) command to operate on the specified Postfix +/* instance only. This behavior is inherited by postfix(1) +/* commands that run as a descendant of the current process. +/* .IP \fBMAIL_VERBOSE\fR +/* This is set when the -v command-line option is present. +/* .IP \fBMAIL_DEBUG\fR +/* This is set when the -D command-line option is present. +/* .PP +/* When the internal logging service is enabled (by setting a +/* non-empty maillog_file parameter value) the postfix(1) +/* command exports settings that are used by child processes +/* before they have processed main.cf or command-line settings. +/* .IP \fBPOSTLOG_SERVICE +/* The name of the public postlog service endpoint. +/* .IP \fBPOSTLOG_HOSTNAME +/* The hostname to prepend to internal logging. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR configuration parameters are +/* exported as environment variables with the same names: +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR" +/* The location of all postfix administrative commands. +/* .IP "\fBdaemon_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix support programs and daemon programs. +/* .IP "\fBhtml_directory (see 'postconf -d' output)\fR" +/* The location of Postfix HTML files that describe how to build, +/* configure or operate a specific Postfix subsystem or feature. +/* .IP "\fBmail_owner (postfix)\fR" +/* The UNIX system account that owns the Postfix queue and most Postfix +/* daemon processes. +/* .IP "\fBmailq_path (see 'postconf -d' output)\fR" +/* Sendmail compatibility feature that specifies where the Postfix +/* \fBmailq\fR(1) command is installed. +/* .IP "\fBmanpage_directory (see 'postconf -d' output)\fR" +/* Where the Postfix manual pages are installed. +/* .IP "\fBnewaliases_path (see 'postconf -d' output)\fR" +/* Sendmail compatibility feature that specifies the location of the +/* \fBnewaliases\fR(1) command. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBreadme_directory (see 'postconf -d' output)\fR" +/* The location of Postfix README files that describe how to build, +/* configure or operate a specific Postfix subsystem or feature. +/* .IP "\fBsendmail_path (see 'postconf -d' output)\fR" +/* A Sendmail compatibility feature that specifies the location of +/* the Postfix \fBsendmail\fR(1) command. +/* .IP "\fBsetgid_group (postdrop)\fR" +/* The group ownership of set-gid Postfix commands and of group-writable +/* Postfix directories. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBdata_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix-writable data files (for example: +/* caches, pseudo-random numbers). +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBcompatibility_level (0)\fR" +/* A safety net that causes Postfix to run with backwards-compatible +/* default settings after an upgrade to a newer Postfix version. +/* .IP "\fBmeta_directory (see 'postconf -d' output)\fR" +/* The location of non-executable files that are shared among +/* multiple Postfix instances, such as postfix-files, dynamicmaps.cf, +/* and the multi-instance template files main.cf.proto and master.cf.proto. +/* .IP "\fBshlib_directory (see 'postconf -d' output)\fR" +/* The location of Postfix dynamically-linked libraries +/* (libpostfix-*.so), and the default location of Postfix database +/* plugins (postfix-*.so) that have a relative pathname in the +/* dynamicmaps.cf file. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBopenssl_path (openssl)\fR" +/* The location of the OpenSSL command line program \fBopenssl\fR(1). +/* .PP +/* Other configuration parameters: +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment variables that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBmulti_instance_directories (empty)\fR" +/* An optional list of non-default Postfix configuration directories; +/* these directories belong to additional Postfix instances that share +/* the Postfix executable files and documentation with the default +/* Postfix instance, and that are started, stopped, etc., together +/* with the default Postfix instance. +/* .IP "\fBmulti_instance_wrapper (empty)\fR" +/* The pathname of a multi-instance manager command that the +/* \fBpostfix\fR(1) command invokes when the multi_instance_directories +/* parameter value is non-empty. +/* .IP "\fBmulti_instance_group (empty)\fR" +/* The optional instance group name of this Postfix instance. +/* .IP "\fBmulti_instance_name (empty)\fR" +/* The optional instance name of this Postfix instance. +/* .IP "\fBmulti_instance_enable (no)\fR" +/* Allow this Postfix instance to be started, stopped, etc., by a +/* multi-instance manager. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBmaillog_file (empty)\fR" +/* The name of an optional logfile that is written by the Postfix +/* \fBpostlogd\fR(8) service. +/* .IP "\fBmaillog_file_compressor (gzip)\fR" +/* The program to run after rotating $maillog_file with "postfix +/* logrotate". +/* .IP "\fBmaillog_file_prefixes (/var, /dev/stdout)\fR" +/* A list of allowed prefixes for a maillog_file value. +/* .IP "\fBmaillog_file_rotate_suffix (%Y%m%d-%H%M%S)\fR" +/* The format of the suffix to append to $maillog_file while rotating +/* the file with "postfix logrotate". +/* .IP "\fBpostlog_service_name (postlog)\fR" +/* The name of the \fBpostlogd\fR(8) service entry in master.cf. +/* FILES +/* .ad +/* .fi +/* Prior to Postfix version 2.6, all of the following files +/* were in \fB$config_directory\fR. Some files are now in +/* \fB$daemon_directory\fR or \fB$meta_directory\fR so that they +/* can be shared among multiple instances that run the same Postfix +/* version. +/* +/* Use the command "\fBpostconf config_directory\fR" or +/* "\fBpostconf daemon_directory\fR" to expand the names +/* into their actual values. +/* .na +/* .nf +/* +/* $config_directory/main.cf, Postfix configuration parameters +/* $config_directory/master.cf, Postfix daemon processes +/* $daemon_directory/postfix-script, administrative commands +/* $daemon_directory/post-install, post-installation configuration +/* $meta_directory/dynamicmaps.cf, plug-in database clients +/* $meta_directory/postfix-files, file/directory permissions +/* SEE ALSO +/* Commands: +/* postalias(1), create/update/query alias database +/* postcat(1), examine Postfix queue file +/* postconf(1), Postfix configuration utility +/* postdrop(1), Postfix mail posting utility +/* postfix(1), Postfix control program +/* postfix-tls(1), Postfix TLS management +/* postkick(1), trigger Postfix daemon +/* postlock(1), Postfix-compatible locking +/* postlog(1), Postfix-compatible logging +/* postmap(1), Postfix lookup table manager +/* postmulti(1), Postfix multi-instance manager +/* postqueue(1), Postfix mail queue control +/* postsuper(1), Postfix housekeeping +/* mailq(1), Sendmail compatibility interface +/* newaliases(1), Sendmail compatibility interface +/* sendmail(1), Sendmail compatibility interface +/* +/* Postfix configuration: +/* bounce(5), Postfix bounce message templates +/* master(5), Postfix master.cf file syntax +/* postconf(5), Postfix main.cf file syntax +/* postfix-wrapper(5), Postfix multi-instance API +/* +/* Table-driven mechanisms: +/* access(5), Postfix SMTP access control table +/* aliases(5), Postfix alias database +/* canonical(5), Postfix input address rewriting +/* generic(5), Postfix output address rewriting +/* header_checks(5), body_checks(5), Postfix content inspection +/* relocated(5), Users that have moved +/* transport(5), Postfix routing table +/* virtual(5), Postfix virtual aliasing +/* +/* Table lookup mechanisms: +/* cidr_table(5), Associate CIDR pattern with value +/* ldap_table(5), Postfix LDAP client +/* lmdb_table(5), Postfix LMDB database driver +/* memcache_table(5), Postfix memcache client +/* mysql_table(5), Postfix MYSQL client +/* nisplus_table(5), Postfix NIS+ client +/* pcre_table(5), Associate PCRE pattern with value +/* pgsql_table(5), Postfix PostgreSQL client +/* regexp_table(5), Associate POSIX regexp pattern with value +/* socketmap_table(5), Postfix socketmap client +/* sqlite_table(5), Postfix SQLite database driver +/* tcp_table(5), Postfix client-server table lookup +/* +/* Daemon processes: +/* anvil(8), Postfix connection/rate limiting +/* bounce(8), defer(8), trace(8), Delivery status reports +/* cleanup(8), canonicalize and enqueue message +/* discard(8), Postfix discard delivery agent +/* dnsblog(8), DNS allow/denylist logger +/* error(8), Postfix error delivery agent +/* flush(8), Postfix fast ETRN service +/* local(8), Postfix local delivery agent +/* master(8), Postfix master daemon +/* oqmgr(8), old Postfix queue manager +/* pickup(8), Postfix local mail pickup +/* pipe(8), deliver mail to non-Postfix command +/* postlogd(8), Postfix internal logging service +/* postscreen(8), Postfix zombie blocker +/* proxymap(8), Postfix lookup table proxy server +/* qmgr(8), Postfix queue manager +/* qmqpd(8), Postfix QMQP server +/* scache(8), Postfix connection cache manager +/* showq(8), list Postfix mail queue +/* smtp(8), lmtp(8), Postfix SMTP+LMTP client +/* smtpd(8), Postfix SMTP server +/* spawn(8), run non-Postfix server +/* tlsmgr(8), Postfix TLS cache and randomness manager +/* tlsproxy(8), Postfix TLS proxy server +/* trivial-rewrite(8), Postfix address rewriting +/* verify(8), Postfix address verification +/* virtual(8), Postfix virtual delivery agent +/* +/* Other: +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* OVERVIEW, overview of Postfix commands and processes +/* BASIC_CONFIGURATION_README, Postfix basic configuration +/* ADDRESS_REWRITING_README, Postfix address rewriting +/* SMTPD_ACCESS_README, SMTP relay/access control +/* CONTENT_INSPECTION_README, Postfix content inspection +/* QSHAPE_README, Postfix queue analysis +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support by: +/* Lutz Jaenicke +/* Brandenburg University of Technology +/* Cottbus, Germany +/* +/* Victor Duchovni +/* Morgan Stanley +/* +/* SASL support originally by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* LMTP support originally by: +/* Philip A. Prindeville +/* Mirapoint, Inc. +/* USA. +/* +/* Amos Gouaux +/* University of Texas at Dallas +/* P.O. Box 830688, MC34 +/* Richardson, TX 75083, USA +/* +/* IPv6 support originally by: +/* Mark Huizer, Eindhoven University, The Netherlands +/* Jun-ichiro 'itojun' Hagino, KAME project, Japan +/* The Linux PLD project +/* Dean Strik, Eindhoven University, The Netherlands +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_PATHS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* Additional installation parameters. */ + +static char *var_sendmail_path; +static char *var_mailq_path; +static char *var_newalias_path; +static char *var_manpage_dir; +static char *var_sample_dir; +static char *var_readme_dir; +static char *var_html_dir; + +/* check_setenv - setenv() with extreme prejudice */ + +static void check_setenv(char *name, char *value) +{ +#define CLOBBER 1 + if (setenv(name, value, CLOBBER) < 0) + msg_fatal("setenv: %m"); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - run administrative script from controlled environment */ + +int main(int argc, char **argv) +{ + char *script; + struct stat st; + char *slash; + int fd; + int ch; + ARGV *import_env; + static const CONFIG_STR_TABLE str_table[] = { + VAR_SENDMAIL_PATH, DEF_SENDMAIL_PATH, &var_sendmail_path, 1, 0, + VAR_MAILQ_PATH, DEF_MAILQ_PATH, &var_mailq_path, 1, 0, + VAR_NEWALIAS_PATH, DEF_NEWALIAS_PATH, &var_newalias_path, 1, 0, + VAR_MANPAGE_DIR, DEF_MANPAGE_DIR, &var_manpage_dir, 1, 0, + VAR_SAMPLE_DIR, DEF_SAMPLE_DIR, &var_sample_dir, 1, 0, + VAR_README_DIR, DEF_README_DIR, &var_readme_dir, 1, 0, + VAR_HTML_DIR, DEF_HTML_DIR, &var_html_dir, 1, 0, + 0, + }; + int force_single_instance; + ARGV *my_argv; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Be consistent with file permissions. + */ + umask(022); + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Set up diagnostics. XXX What if stdin is the system console during + * boot time? It seems a bad idea to log startup errors to the console. + * This is UNIX, a system that can run without hand holding. + */ + if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) + argv[0] = slash + 1; + if (isatty(STDERR_FILENO)) + msg_vstream_init(argv[0], VSTREAM_ERR); + maillog_client_init(argv[0], MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * The mail system must be run by the superuser so it can revoke + * privileges for selected operations. That's right - it takes privileges + * to toss privileges. + */ + if (getuid() != 0) { + msg_error("to submit mail, use the Postfix sendmail command"); + msg_fatal("the postfix command is reserved for the superuser"); + } + if (unsafe() != 0) + msg_fatal("the postfix command must not run as a set-uid process"); + + /* + * Parse switches. + */ + while ((ch = GETOPT(argc, argv, "c:Dv")) > 0) { + switch (ch) { + default: + msg_fatal("usage: %s [-c config_dir] [-Dv] command", argv[0]); + case 'c': + if (*optarg != '/') + msg_fatal("-c requires absolute pathname"); + check_setenv(CONF_ENV_PATH, optarg); + break; + case 'D': + check_setenv(CONF_ENV_DEBUG, ""); + break; + case 'v': + msg_verbose++; + check_setenv(CONF_ENV_VERB, ""); + break; + } + } + force_single_instance = (getenv(CONF_ENV_PATH) != 0); + + /* + * Copy a bunch of configuration parameters into the environment for easy + * access by the maintenance shell script. + */ + mail_conf_read(); + get_mail_conf_str_table(str_table); + + /* + * Environment import filter, to enforce consistent behavior whether this + * command is started by hand, or at system boot time. This is necessary + * because some shell scripts use environment settings to override + * main.cf settings. + */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + clean_env(import_env->argv); + argv_free(import_env); + + /* + * This is after calling clean_env(), to ensure that POSTLOG_XXX exports + * will work, even if import_environment would remove them. + */ + maillog_client_init(argv[0], MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + + /* + * Alert the sysadmin that the backwards-compatible settings are still in + * effect. + */ + if (compat_level < compat_level_from_string(LAST_COMPAT_LEVEL, msg_panic)) { + msg_info("Postfix is using backwards-compatible default settings"); + msg_info("See http://www.postfix.org/COMPATIBILITY_README.html " + "for details"); + msg_info("To disable backwards compatibility use \"postconf " + VAR_COMPAT_LEVEL "=%s\" and \"postfix reload\"", + LAST_COMPAT_LEVEL); + } + check_setenv("PATH", ROOT_PATH); /* sys_defs.h */ + check_setenv(CONF_ENV_PATH, var_config_dir);/* mail_conf.h */ + + check_setenv(VAR_COMMAND_DIR, var_command_dir); /* main.cf */ + check_setenv(VAR_DAEMON_DIR, var_daemon_dir); /* main.cf */ + check_setenv(VAR_DATA_DIR, var_data_dir); /* main.cf */ + check_setenv(VAR_META_DIR, var_meta_dir); /* main.cf */ + check_setenv(VAR_QUEUE_DIR, var_queue_dir); /* main.cf */ + check_setenv(VAR_CONFIG_DIR, var_config_dir); /* main.cf */ + check_setenv(VAR_SHLIB_DIR, var_shlib_dir); /* main.cf */ + + /* + * Do we want to keep adding things here as shell scripts evolve? + */ + check_setenv(VAR_MAIL_OWNER, var_mail_owner); /* main.cf */ + check_setenv(VAR_SGID_GROUP, var_sgid_group); /* main.cf */ + check_setenv(VAR_SENDMAIL_PATH, var_sendmail_path); /* main.cf */ + check_setenv(VAR_MAILQ_PATH, var_mailq_path); /* main.cf */ + check_setenv(VAR_NEWALIAS_PATH, var_newalias_path); /* main.cf */ + check_setenv(VAR_MANPAGE_DIR, var_manpage_dir); /* main.cf */ + check_setenv(VAR_SAMPLE_DIR, var_sample_dir); /* main.cf */ + check_setenv(VAR_README_DIR, var_readme_dir); /* main.cf */ + check_setenv(VAR_HTML_DIR, var_html_dir); /* main.cf */ + + /* + * Make sure these directories exist. Run the maintenance scripts with as + * current directory the mail database. + */ + if (chdir(var_command_dir)) + msg_fatal("chdir(%s): %m", var_command_dir); + if (chdir(var_daemon_dir)) + msg_fatal("chdir(%s): %m", var_daemon_dir); + if (chdir(var_queue_dir)) + msg_fatal("chdir(%s): %m", var_queue_dir); + + /* + * Run the management script. + */ + if (force_single_instance + || argv_split(var_multi_conf_dirs, CHARS_COMMA_SP)->argc == 0) { + script = concatenate(var_daemon_dir, "/postfix-script", (char *) 0); + if (optind < 1) + msg_panic("bad optind value"); + argv[optind - 1] = script; + execvp(script, argv + optind - 1); + msg_fatal("%s: %m", script); + } + + /* + * Hand off control to a multi-instance manager. + */ + else { + if (*var_multi_wrapper == 0) + msg_fatal("multi-instance support is requested, but %s is empty", + VAR_MULTI_WRAPPER); + my_argv = argv_split(var_multi_wrapper, CHARS_SPACE); + do { + argv_add(my_argv, argv[optind], (char *) 0); + } while (argv[optind++] != 0); + execvp(my_argv->argv[0], my_argv->argv); + msg_fatal("%s: %m", my_argv->argv[0]); + } +} diff --git a/src/postkick/.indent.pro b/src/postkick/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postkick/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postkick/.printfck b/src/postkick/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/postkick/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/postkick/Makefile.in b/src/postkick/Makefile.in new file mode 100644 index 0000000..a0c7271 --- /dev/null +++ b/src/postkick/Makefile.in @@ -0,0 +1,83 @@ +SHELL = /bin/sh +SRCS = postkick.c +OBJS = postkick.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = postkick +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postkick.o: ../../include/argv.h +postkick.o: ../../include/attr.h +postkick.o: ../../include/check_arg.h +postkick.o: ../../include/clean_env.h +postkick.o: ../../include/events.h +postkick.o: ../../include/htable.h +postkick.o: ../../include/iostuff.h +postkick.o: ../../include/mail_conf.h +postkick.o: ../../include/mail_params.h +postkick.o: ../../include/mail_parm_split.h +postkick.o: ../../include/mail_proto.h +postkick.o: ../../include/mail_version.h +postkick.o: ../../include/msg.h +postkick.o: ../../include/msg_vstream.h +postkick.o: ../../include/mymalloc.h +postkick.o: ../../include/nvtable.h +postkick.o: ../../include/safe.h +postkick.o: ../../include/sys_defs.h +postkick.o: ../../include/vbuf.h +postkick.o: ../../include/vstream.h +postkick.o: ../../include/vstring.h +postkick.o: ../../include/warn_stat.h +postkick.o: postkick.c diff --git a/src/postkick/postkick.c b/src/postkick/postkick.c new file mode 100644 index 0000000..6bf9245 --- /dev/null +++ b/src/postkick/postkick.c @@ -0,0 +1,221 @@ +/*++ +/* NAME +/* postkick 1 +/* SUMMARY +/* kick a Postfix service +/* SYNOPSIS +/* .fi +/* \fBpostkick\fR [\fB-c \fIconfig_dir\fR] [\fB-v\fR] +/* \fIclass service request\fR +/* DESCRIPTION +/* The \fBpostkick\fR(1) command sends \fIrequest\fR to the +/* specified \fIservice\fR over a local transport channel. +/* This command makes Postfix private IPC accessible +/* for use in, for example, shell scripts. +/* +/* Options: +/* .IP "\fB-c\fR \fIconfig_dir\fR" +/* Read the \fBmain.cf\fR configuration file in the named directory +/* instead of the default configuration directory. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. +/* .PP +/* Arguments: +/* .IP \fIclass\fR +/* Name of a class of local transport channel endpoints, +/* either \fBpublic\fR (accessible by any local user) or +/* \fBprivate\fR (administrative access only). +/* .IP \fIservice\fR +/* The name of a local transport endpoint within the named class. +/* .IP \fIrequest\fR +/* A string. The list of valid requests is service-specific. +/* DIAGNOSTICS +/* Problems and transactions are logged to the standard error +/* stream. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP \fBMAIL_CONFIG\fR +/* Directory with Postfix configuration files. +/* .IP \fBMAIL_VERBOSE\fR +/* Enable verbose logging for debugging purposes. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBapplication_event_drain_time (100s)\fR" +/* How long the \fBpostkick\fR(1) command waits for a request to enter the +/* Postfix daemon process input buffer before giving up. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment parameters that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* FILES +/* /var/spool/postfix/private, private class endpoints +/* /var/spool/postfix/public, public class endpoints +/* SEE ALSO +/* qmgr(8), queue manager trigger protocol +/* pickup(8), local pickup daemon +/* postconf(5), configuration parameters +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +static NORETURN usage(char *myname) +{ + msg_fatal("usage: %s [-c config_dir] [-v] class service request", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + char *class; + char *service; + char *request; + int fd; + struct stat st; + char *slash; + int c; + ARGV *import_env; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Process environment options as early as we can. + */ + if (safe_getenv(CONF_ENV_VERB)) + msg_verbose = 1; + + /* + * Initialize. Set up logging. Read the global configuration file after + * parsing command-line arguments. + */ + if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) + argv[0] = slash + 1; + msg_vstream_init(argv[0], VSTREAM_ERR); + set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Parse JCL. + */ + while ((c = GETOPT(argc, argv, "c:v")) > 0) { + switch (c) { + default: + usage(argv[0]); + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'v': + msg_verbose++; + break; + } + } + if (argc != optind + 3) + usage(argv[0]); + class = argv[optind]; + service = argv[optind + 1]; + request = argv[optind + 2]; + + /* + * Finish initializations. + */ + mail_conf_read(); + /* Enforce consistent operation of different Postfix parts. */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + update_env(import_env->argv); + argv_free(import_env); + if (chdir(var_queue_dir)) + msg_fatal("chdir %s: %m", var_queue_dir); + + /* + * Kick the service. + */ + if (mail_trigger(class, service, request, strlen(request)) < 0) { + msg_warn("Cannot contact class %s service %s - perhaps the mail system is down", + class, service); + exit(1); + } + + /* + * Problem: With triggers over full duplex (i.e. non-FIFO) channels, we + * must avoid closing the channel before the server has received the + * request. Otherwise some hostile kernel may throw away the request. + * + * Solution: The trigger routine registers a read event handler that runs + * when the server closes the channel. The event_drain() routine waits + * for the event handler to run, but gives up when it takes too long. + */ + else { + event_drain(var_event_drain); + exit(0); + } +} diff --git a/src/postlock/.indent.pro b/src/postlock/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postlock/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postlock/.printfck b/src/postlock/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/postlock/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/postlock/Makefile.in b/src/postlock/Makefile.in new file mode 100644 index 0000000..fcb01d2 --- /dev/null +++ b/src/postlock/Makefile.in @@ -0,0 +1,86 @@ +SHELL = /bin/sh +SRCS = postlock.c +OBJS = postlock.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = postlock +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postlock.o: ../../include/argv.h +postlock.o: ../../include/check_arg.h +postlock.o: ../../include/clean_env.h +postlock.o: ../../include/deliver_flock.h +postlock.o: ../../include/dot_lockfile.h +postlock.o: ../../include/dsn.h +postlock.o: ../../include/dsn_buf.h +postlock.o: ../../include/dsn_util.h +postlock.o: ../../include/iostuff.h +postlock.o: ../../include/mail_conf.h +postlock.o: ../../include/mail_params.h +postlock.o: ../../include/mail_parm_split.h +postlock.o: ../../include/mail_version.h +postlock.o: ../../include/mbox_conf.h +postlock.o: ../../include/mbox_open.h +postlock.o: ../../include/msg.h +postlock.o: ../../include/msg_vstream.h +postlock.o: ../../include/myflock.h +postlock.o: ../../include/safe_open.h +postlock.o: ../../include/sys_defs.h +postlock.o: ../../include/sys_exits.h +postlock.o: ../../include/vbuf.h +postlock.o: ../../include/vstream.h +postlock.o: ../../include/vstring.h +postlock.o: ../../include/warn_stat.h +postlock.o: postlock.c diff --git a/src/postlock/postlock.c b/src/postlock/postlock.c new file mode 100644 index 0000000..a05d11e --- /dev/null +++ b/src/postlock/postlock.c @@ -0,0 +1,282 @@ +/*++ +/* NAME +/* postlock 1 +/* SUMMARY +/* lock mail folder and execute command +/* SYNOPSIS +/* .fi +/* \fBpostlock\fR [\fB-c \fIconfig_dir\fR] [\fB-l \fIlock_style\fR] +/* [\fB-v\fR] \fIfile command...\fR +/* DESCRIPTION +/* The \fBpostlock\fR(1) command locks \fIfile\fR for exclusive +/* access, and executes \fIcommand\fR. The locking method is +/* compatible with the Postfix UNIX-style local delivery agent. +/* +/* Options: +/* .IP "\fB-c \fIconfig_dir\fR" +/* Read the \fBmain.cf\fR configuration file in the named directory +/* instead of the default configuration directory. +/* .IP "\fB-l \fIlock_style\fR" +/* Override the locking method specified via the +/* \fBmailbox_delivery_lock\fR configuration parameter (see below). +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. +/* .PP +/* Arguments: +/* .IP \fIfile\fR +/* A mailbox file. The user should have read/write permission. +/* .IP \fIcommand...\fR +/* The command to execute while \fIfile\fR is locked for exclusive +/* access. The command is executed directly, i.e. without +/* interpretation by a shell command interpreter. +/* DIAGNOSTICS +/* The result status is 75 (EX_TEMPFAIL) when \fBpostlock\fR(1) +/* could not perform the requested operation. Otherwise, the +/* exit status is the exit status from the command. +/* BUGS +/* With remote file systems, the ability to acquire a lock does not +/* necessarily eliminate access conflicts. Avoid file access by +/* processes running on different machines. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP \fBMAIL_CONFIG\fR +/* Directory with Postfix configuration files. +/* .IP \fBMAIL_VERBOSE\fR +/* Enable verbose logging for debugging purposes. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* LOCKING CONTROLS +/* .ad +/* .fi +/* .IP "\fBdeliver_lock_attempts (20)\fR" +/* The maximal number of attempts to acquire an exclusive lock on a +/* mailbox file or \fBbounce\fR(8) logfile. +/* .IP "\fBdeliver_lock_delay (1s)\fR" +/* The time between attempts to acquire an exclusive lock on a mailbox +/* file or \fBbounce\fR(8) logfile. +/* .IP "\fBstale_lock_time (500s)\fR" +/* The time after which a stale exclusive mailbox lockfile is removed. +/* .IP "\fBmailbox_delivery_lock (see 'postconf -d' output)\fR" +/* How to lock a UNIX-style \fBlocal\fR(8) mailbox before attempting delivery. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBfork_attempts (5)\fR" +/* The maximal number of attempts to fork() a child process. +/* .IP "\fBfork_delay (1s)\fR" +/* The delay between attempts to fork() a child process. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment parameters that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* SEE ALSO +/* postconf(5), configuration parameters +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +/* usage - explain */ + +static NORETURN usage(char *myname) +{ + msg_fatal("usage: %s [-c config_dir] [-l lock_style] [-v] folder command...", myname); +} + +/* fatal_exit - all failures are deemed recoverable */ + +static void fatal_exit(void) +{ + exit(EX_TEMPFAIL); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - go for it */ + +int main(int argc, char **argv) +{ + DSN_BUF *why; + char *folder; + char **command; + int ch; + int fd; + struct stat st; + int count; + WAIT_STATUS_T status; + pid_t pid; + int lock_mask; + char *lock_style = 0; + MBOX *mp; + ARGV *import_env; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Be consistent with file permissions. + */ + umask(022); + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Process environment options as early as we can. We are not set-uid, + * and we are supposed to be running in a controlled environment. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + + /* + * Set up logging and error handling. Intercept fatal exits so we can + * return a distinguished exit status. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_cleanup(fatal_exit); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "c:l:v")) > 0) { + switch (ch) { + default: + usage(argv[0]); + break; + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'l': + lock_style = optarg; + break; + case 'v': + msg_verbose++; + break; + } + } + if (optind + 2 > argc) + usage(argv[0]); + folder = argv[optind]; + command = argv + optind + 1; + + /* + * Read the config file. The command line lock style can override the + * configured lock style. + */ + mail_conf_read(); + /* Enforce consistent operation of different Postfix parts. */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + update_env(import_env->argv); + argv_free(import_env); + lock_mask = mbox_lock_mask(lock_style ? lock_style : + get_mail_conf_str(VAR_MAILBOX_LOCK, DEF_MAILBOX_LOCK, 1, 0)); + + /* + * Lock the folder for exclusive access. Lose the lock upon exit. The + * command is not supposed to disappear into the background. + */ + why = dsb_create(); + if ((mp = mbox_open(folder, O_APPEND | O_WRONLY | O_CREAT, + S_IRUSR | S_IWUSR, (struct stat *) 0, + -1, -1, lock_mask, "5.2.0", why)) == 0) + msg_fatal("open file %s: %s", folder, vstring_str(why->reason)); + dsb_free(why); + + /* + * Run the command. Remove the lock after completion. + */ + for (count = 1; (pid = fork()) == -1; count++) { + msg_warn("fork %s: %m", command[0]); + if (count >= var_fork_tries) { + mbox_release(mp); + exit(EX_TEMPFAIL); + } + sleep(var_fork_delay); + } + switch (pid) { + case 0: + (void) msg_cleanup((MSG_CLEANUP_FN) 0); + execvp(command[0], command); + msg_fatal("execvp %s: %m", command[0]); + default: + if (waitpid(pid, &status, 0) < 0) + msg_fatal("waitpid: %m"); + vstream_fclose(mp->fp); + mbox_release(mp); + exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1); + } +} diff --git a/src/postlog/.indent.pro b/src/postlog/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postlog/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postlog/.printfck b/src/postlog/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/postlog/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/postlog/Makefile.in b/src/postlog/Makefile.in new file mode 100644 index 0000000..297eae2 --- /dev/null +++ b/src/postlog/Makefile.in @@ -0,0 +1,84 @@ +SHELL = /bin/sh +SRCS = postlog.c +OBJS = postlog.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +FILES = Makefile $(SRCS) $(HDRS) +INC_DIR = ../../include +TESTPROG= +PROG = postlog +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +shar: + @shar $(FILES) + +lint: + lint $(SRCS) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postlog.o: ../../include/argv.h +postlog.o: ../../include/check_arg.h +postlog.o: ../../include/clean_env.h +postlog.o: ../../include/mail_conf.h +postlog.o: ../../include/mail_params.h +postlog.o: ../../include/mail_parm_split.h +postlog.o: ../../include/mail_task.h +postlog.o: ../../include/mail_version.h +postlog.o: ../../include/maillog_client.h +postlog.o: ../../include/msg.h +postlog.o: ../../include/msg_output.h +postlog.o: ../../include/msg_vstream.h +postlog.o: ../../include/stringops.h +postlog.o: ../../include/sys_defs.h +postlog.o: ../../include/vbuf.h +postlog.o: ../../include/vstream.h +postlog.o: ../../include/vstring.h +postlog.o: ../../include/vstring_vstream.h +postlog.o: ../../include/warn_stat.h +postlog.o: postlog.c diff --git a/src/postlog/postlog.c b/src/postlog/postlog.c new file mode 100644 index 0000000..eddc432 --- /dev/null +++ b/src/postlog/postlog.c @@ -0,0 +1,372 @@ +/*++ +/* NAME +/* postlog 1 +/* SUMMARY +/* Postfix-compatible logging utility +/* SYNOPSIS +/* .fi +/* .ad +/* \fBpostlog\fR [\fB-iv\fR] [\fB-c \fIconfig_dir\fR] +/* [\fB-p \fIpriority\fR] [\fB-t \fItag\fR] [\fItext...\fR] +/* DESCRIPTION +/* The \fBpostlog\fR(1) command implements a Postfix-compatible logging +/* interface for use in, for example, shell scripts. +/* +/* By default, \fBpostlog\fR(1) logs the \fItext\fR given on the command +/* line as one record. If no \fItext\fR is specified on the command +/* line, \fBpostlog\fR(1) reads from standard input and logs each input +/* line as one record. +/* +/* By default, logging is sent to \fBsyslogd\fR(8) or +/* \fBpostlogd\fR(8); when the +/* standard error stream is connected to a terminal, logging +/* is sent there as well. +/* +/* The following options are implemented: +/* .IP "\fB-c \fIconfig_dir\fR" +/* Read the \fBmain.cf\fR configuration file in the named directory +/* instead of the default configuration directory. +/* .IP "\fB-i\fR (obsolete)" +/* Include the process ID in the logging tag. This flag is ignored as +/* of Postfix 3.4, where the PID is always included. +/* .IP "\fB-p \fIpriority\fR (default: \fBinfo\fR)" +/* Specifies the logging severity: \fBinfo\fR, \fBwarn\fR, +/* \fBerror\fR, \fBfatal\fR, or \fBpanic\fR. With Postfix 3.1 +/* and later, the program will pause for 1 second after reporting +/* a \fBfatal\fR or \fBpanic\fR condition, just like other +/* Postfix programs. +/* .IP "\fB-t \fItag\fR" +/* Specifies the logging tag, that is, the identifying name that +/* appears at the beginning of each logging record. A default tag +/* is used when none is specified. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. +/* SECURITY +/* .ad +/* .fi +/* The \fBpostlog\fR(1) command is designed to run with +/* set-groupid privileges, so that it can connect to the +/* \fBpostlogd\fR(8) daemon process (Postfix 3.7 and later; +/* earlier implementations of this command must not have +/* set-groupid or set-userid permissions). +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP MAIL_CONFIG +/* Directory with the \fBmain.cf\fR file. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment parameters that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.4 and later: +/* .IP "\fBmaillog_file (empty)\fR" +/* The name of an optional logfile that is written by the Postfix +/* \fBpostlogd\fR(8) service. +/* .IP "\fBpostlog_service_name (postlog)\fR" +/* The name of the \fBpostlogd\fR(8) service entry in master.cf. +/* SEE ALSO +/* postconf(5), configuration parameters +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* The \fBpostlog\fR(1) command was introduced with Postfix +/* version 3.4. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include /* XXX right place for LOG_FACILITY? */ +#include +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * WARNING WARNING WARNING + * + * This software is designed to run set-gid. In order to avoid exploitation of + * privilege, this software should not run any external commands, nor should + * it take any information from the user, unless that information can be + * properly sanitized. To get an idea of how much information a process can + * inherit from a potentially hostile user, examine all the members of the + * process structure (typically, in /usr/include/sys/proc.h): the current + * directory, open files, timers, signals, environment, command line, umask, + * and so on. + */ + + /* + * Access lists. + */ +#if 0 +char *var_postlog_acl; + +X static const CONFIG_STR_TABLE str_table[] = { + X VAR_POSTLOG_ACL, DEF_POSTLOG_ACL, &var_postlog_acl, 0, 0, + X 0, +X}; + +#endif + + /* + * Support for the severity level mapping. + */ +struct level_table { + char *name; + int level; +}; + +static struct level_table level_table[] = { + "info", MSG_INFO, + "warn", MSG_WARN, + "warning", MSG_WARN, + "error", MSG_ERROR, + "err", MSG_ERROR, + "fatal", MSG_FATAL, + "crit", MSG_FATAL, + "panic", MSG_PANIC, + 0, +}; + +#define POSTLOG_CMD "postlog" + +/* level_map - lookup facility or severity value */ + +static int level_map(char *name) +{ + struct level_table *t; + + for (t = level_table; t->name; t++) + if (strcasecmp(t->name, name) == 0) + return (t->level); + msg_fatal("bad severity: \"%s\"", name); +} + +/* log_argv - log the command line */ + +static void log_argv(int level, char **argv) +{ + VSTRING *buf = vstring_alloc(100); + + while (*argv) { + vstring_strcat(buf, *argv++); + if (*argv) + vstring_strcat(buf, " "); + } + msg_printf(level, "%s", vstring_str(buf)); + vstring_free(buf); +} + +/* log_stream - log lines from a stream */ + +static void log_stream(int level, VSTREAM *fp) +{ + VSTRING *buf = vstring_alloc(100); + + while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) + msg_printf(level, "%s", vstring_str(buf)); + vstring_free(buf); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - logger */ + +int main(int argc, char **argv) +{ + struct stat st; + int fd; + int ch; + const char *tag; + char *unsanitized_tag; + int level = MSG_INFO; + ARGV *import_env; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Be consistent with file permissions. + */ + umask(022); + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Set up diagnostics. Censor the process name: it is provided by the + * user. + */ + argv[0] = POSTLOG_CMD; + tag = mail_task(argv[0]); + if (isatty(STDERR_FILENO)) + msg_vstream_init(tag, VSTREAM_ERR); + maillog_client_init(tag, MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Parse switches. This program is set-gid and must sanitize all + * command-line parameters. The configuration directory argument is + * validated by the mail configuration read routine. Don't do complex + * things until we have completed initializations. + */ + unsanitized_tag = 0; + while ((ch = GETOPT(argc, argv, "c:ip:t:v")) > 0) { + switch (ch) { + default: + msg_fatal("usage: %s [-c config_dir] [-i] [-p priority] [-t tag] [-v] [text]", argv[0]); + break; + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'i': + break; + case 'p': + level = level_map(optarg); + break; + case 't': + unsanitized_tag = optarg; + break; + case 'v': + msg_verbose++; + break; + } + } + + /* + * Process the main.cf file. This may change the syslog_name setting and + * may require that mail_task() be re-evaluated. + */ + mail_conf_read(); + /* Re-evaluate mail_task() after reading main.cf. */ + maillog_client_init(mail_task(POSTLOG_CMD), MAILLOG_CLIENT_FLAG_NONE); +#if 0 + mail_dict_init(); /* proxy, sql, ldap */ + get_mail_conf_str_table(str_table); +#endif + + /* + * This program is designed to be set-gid, which makes it a potential + * target for attack. Strip and optionally override the process + * environment so that we don't have to trust the C library. + */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + clean_env(import_env->argv); + argv_free(import_env); + + /* + * Sanitize the user-specified tag. The result depends on the value of + * var_smtputf8_enable, therefore this code is after the mail_conf_read() + * call. + */ + if (unsanitized_tag != 0) + tag = printable(unsanitized_tag, '?'); + + /* + * Re-initialize the logging, this time with the tag specified in main.cf + * or on the command line. + */ + if (isatty(STDERR_FILENO)) + msg_vstream_init(tag, VSTREAM_ERR); + maillog_client_init(tag, MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + +#if 0 + uid_t uid = getuid(); + + if (uid != 0 && uid != var_owner_uid + && (errstr = check_user_acl_byuid(VAR_SHOWQ_ACL, var_showq_acl, + uid)) != 0) + msg_fatal_status(EX_NOPERM, + "User %s(%ld) is not allowed to invoke 'postlog'", + errstr, (long) uid); +#endif + + /* + * Log the command line or log lines from standard input. + */ + if (argc > optind) { + log_argv(level, argv + optind); + } else { + log_stream(level, VSTREAM_IN); + } + + /* + * Consistency with msg(3) functions. + */ + if (level >= MSG_FATAL) + sleep(1); + exit(0); +} diff --git a/src/postlogd/Makefile.in b/src/postlogd/Makefile.in new file mode 100644 index 0000000..21fad76 --- /dev/null +++ b/src/postlogd/Makefile.in @@ -0,0 +1,79 @@ +SHELL = /bin/sh +SRCS = postlogd.c +OBJS = postlogd.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = postlogd +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: test + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postlogd.o: ../../include/check_arg.h +postlogd.o: ../../include/htable.h +postlogd.o: ../../include/logwriter.h +postlogd.o: ../../include/mail_conf.h +postlogd.o: ../../include/mail_params.h +postlogd.o: ../../include/mail_server.h +postlogd.o: ../../include/mail_task.h +postlogd.o: ../../include/mail_version.h +postlogd.o: ../../include/maillog_client.h +postlogd.o: ../../include/msg.h +postlogd.o: ../../include/msg_logger.h +postlogd.o: ../../include/stringops.h +postlogd.o: ../../include/sys_defs.h +postlogd.o: ../../include/vbuf.h +postlogd.o: ../../include/vstream.h +postlogd.o: ../../include/vstring.h +postlogd.o: postlogd.c diff --git a/src/postlogd/postlogd.c b/src/postlogd/postlogd.c new file mode 100644 index 0000000..902cbe5 --- /dev/null +++ b/src/postlogd/postlogd.c @@ -0,0 +1,267 @@ +/*++ +/* NAME +/* postlogd 8 +/* SUMMARY +/* Postfix internal log server +/* SYNOPSIS +/* \fBpostlogd\fR [generic Postfix daemon options] +/* DESCRIPTION +/* This program logs events on behalf of Postfix programs +/* when the maillog configuration parameter specifies a non-empty +/* value. +/* BUGS +/* Non-daemon Postfix programs don't know that they should log +/* to the internal logging service before they have processed +/* command-line options and main.cf parameters. These programs +/* still log earlier events to the syslog service. +/* +/* If Postfix is down, the non-daemon programs \fBpostfix\fR(1), +/* \fBpostsuper\fR(1), \fBpostmulti\fR(1), and \fBpostlog\fR(1), +/* will log directly to \fB$maillog_file\fR. These programs +/* expect to run with root privileges, for example during +/* Postfix start-up, reload, or shutdown. +/* +/* Other non-daemon Postfix programs will never write directly to +/* \fB$maillog_file\fR (also, logging to stdout would interfere +/* with the operation of some of these programs). These programs +/* can log to \fBpostlogd\fR(8) if they are run by the super-user, +/* or if their executable file has set-gid permission. Do not +/* set this permission on programs other than \fBpostdrop\fR(1), +/* \fBpostqueue\fR(1) and (Postfix >= 3.7) \fBpostlog\fR(1). +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically, as +/* \fBpostlogd\fR(8) processes run for only a limited amount +/* of time. Use the command "\fBpostfix reload\fR" to speed +/* up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBmaillog_file (empty)\fR" +/* The name of an optional logfile that is written by the Postfix +/* \fBpostlogd\fR(8) service. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .IP "\fBpostlogd_watchdog_timeout (10s)\fR" +/* How much time a \fBpostlogd\fR(8) process may take to process a request +/* before it is terminated by a built-in watchdog timer. +/* SEE ALSO +/* postconf(5), configuration parameters +/* syslogd(8), system logging +/* README_FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* MAILLOG_README, Postfix logging to file or stdout +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* This service was introduced with Postfix version 3.4. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include + + /* + * Server skeleton. + */ +#include + + /* + * Tunable parameters. + */ +int var_postlogd_watchdog; + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + + /* + * Logfile stream. + */ +static VSTREAM *postlogd_stream = 0; + +/* postlogd_fallback - log messages from postlogd(8) itself */ + +static void postlogd_fallback(const char *buf) +{ + (void) logwriter_write(postlogd_stream, buf, strlen(buf)); +} + +/* postlogd_service - perform service for client */ + +static void postlogd_service(char *buf, ssize_t len, char *unused_service, + char **unused_argv) +{ + + if (postlogd_stream) { + (void) logwriter_write(postlogd_stream, buf, len); + } + + /* + * After a configuration change that removes the maillog_file pathname, + * this service may still receive messages (after "postfix reload" or + * after process refresh) from programs that use the old maillog_file + * setting. Redirect those messages to the current logging mechanism. + */ + else { + char *bp = buf; + char *progname_pid; + + /* + * Avoid surprises: strip off the date, time, host, and program[pid]: + * prefix that were prepended by msg_logger(3). Then, hope that the + * current logging driver suppresses its own PID, when it sees that + * there is a PID embedded in the 'program name'. + */ + (void) mystrtok(&bp, CHARS_SPACE); /* month */ + (void) mystrtok(&bp, CHARS_SPACE); /* day */ + (void) mystrtok(&bp, CHARS_SPACE); /* time */ + (void) mystrtok(&bp, CHARS_SPACE); /* host */ + progname_pid = mystrtok(&bp, ":" CHARS_SPACE); /* name[pid] sans ':' */ + bp += strspn(bp, CHARS_SPACE); + if (progname_pid) + maillog_client_init(progname_pid, MAILLOG_CLIENT_FLAG_NONE); + msg_info("%.*s", (int) (len - (bp - buf)), bp); + + /* + * Restore the program name, in case postlogd(8) needs to log + * something about itself. We have to call maillog_client_init() in + * any case, because neither msg_syslog_init() nor openlog() make a + * copy of the name argument. We can't leave that pointing into the + * middle of the above message buffer. + */ + maillog_client_init(mail_task((char *) 0), MAILLOG_CLIENT_FLAG_NONE); + } +} + +/* pre_jail_init - pre-jail handling */ + +static void pre_jail_init(char *unused_service_name, char **argv) +{ + + /* + * During process initialization, the postlogd daemon will log events to + * the postlog socket, so that they can be logged to file later. Once the + * postlogd daemon is handling requests, it will stop logging to the + * postlog socket and will instead write to the logfile, to avoid + * infinite recursion. + */ + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * After a configuration change that removes the maillog_file pathname, + * this service may still receive messages from processes that still use + * the old configuration. Those messages will have to be redirected to + * the current logging subsystem. + */ + if (*var_maillog_file != 0) { + + /* + * Instantiate the logwriter or bust. + */ + postlogd_stream = logwriter_open_or_die(var_maillog_file); + + /* + * Inform the msg_logger client to stop using the postlog socket, and + * to call our logwriter. + */ + msg_logger_control(CA_MSG_LOGGER_CTL_FALLBACK_ONLY, + CA_MSG_LOGGER_CTL_FALLBACK_FN(postlogd_fallback), + CA_MSG_LOGGER_CTL_END); + } +} + +/* post_jail_init - post-jail initialization */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Prevent automatic process suicide after a limited number of client + * requests. It is OK to terminate after a limited amount of idle time. + */ + var_use_limit = 0; +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the multi-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_POSTLOGD_WATCHDOG, DEF_POSTLOGD_WATCHDOG, &var_postlogd_watchdog, 10, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * This is a datagram service, not a stream service, so that postlogd can + * restart immediately after "postfix reload" without requiring clients + * to resend messages. Those messages remain queued in the kernel until a + * new postlogd process retrieves them. It would be unreasonable to + * require that clients retransmit logs, especially in the case of a + * fatal or panic error. + */ + dgram_server_main(argc, argv, postlogd_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_SOLITARY, + CA_MAIL_SERVER_WATCHDOG(&var_postlogd_watchdog), + 0); +} diff --git a/src/postmap/.indent.pro b/src/postmap/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postmap/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postmap/.printfck b/src/postmap/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/postmap/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/postmap/Makefile.in b/src/postmap/Makefile.in new file mode 100644 index 0000000..dc5369d --- /dev/null +++ b/src/postmap/Makefile.in @@ -0,0 +1,162 @@ +SHELL = /bin/sh +SRCS = postmap.c +OBJS = postmap.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = postmap +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +tests: test1 test2 fail_test quote_test file_test lmdb_abb_test \ + lmdb_bulk_test lmdb_incr_test + +root_tests: + +test1: $(PROG) map.in map-abc1.ref map-ghi1.ref map-uABC1.ref + $(SHLIB_ENV) $(VALGRIND) ./$(PROG) map.in + for key in abc ghi; \ + do \ + $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -q $${key} map.in | diff map-$${key}1.ref -; \ + done + $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -f map.in + for key in ABC; \ + do \ + $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -fq $${key} map.in | diff map-u$${key}1.ref -; \ + done + rm -f map.in.db + +test2: $(PROG) map.in map-abc2.ref map-ghi2.ref map-uABC2.ref + $(SHLIB_ENV) $(VALGRIND) ./$(PROG) map.in + for key in abc ghi; \ + do \ + echo $${key} | $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -q - map.in | diff map-$${key}2.ref -; \ + done + $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -f map.in + for key in ABC; \ + do \ + echo $${key} | $(SHLIB_ENV) $(VALGRIND) ./$(PROG) -fq - map.in | diff map-u$${key}2.ref -; \ + done + rm -f map.in.db + +fail_test: $(PROG) aliases fail_test.in fail_test.ref + -($(SHLIB_ENV) sh fail_test.in || exit 0) >fail_test.tmp 2>&1 + diff fail_test.ref fail_test.tmp + rm -f fail_test.tmp + +quote_test: $(PROG) aliases quote_test.in quote_test.ref + rm -f quote_test_map.* + $(SHLIB_ENV) sh quote_test.in >quote_test.tmp 2>&1 + diff quote_test.ref quote_test.tmp + rm -f quote_test.tmp quote_test_map.* + +file_test: $(PROG) file_test.in file_test.ref + rm -f file_test_map.* postmap-file-1 postmap-file-2 + $(SHLIB_ENV) sh file_test.in >file_test.tmp 2>&1 + diff file_test.ref file_test.tmp + rm -f file_test.tmp file_test_map.* postmap-file-1 postmap-file-2 + +lmdb_abb_test: $(PROG) lmdb_abb lmdb_abb.ref + rm -f lmdb_abb.lmdb + ($(SHLIB_ENV) $(VALGRIND) ./postmap lmdb:lmdb_abb; \ + $(SHLIB_ENV) $(VALGRIND) ./postmap -s lmdb:lmdb_abb | sort) >lmdb_abb.tmp 2>&1 + diff lmdb_abb.ref lmdb_abb.tmp + rm -f lmdb_abb.tmp lmdb_abb.lmdb + +lmdb_bulk_test: $(PROG) + rm -f lmdb_retry.lmdb main.cf + tr A-Z a-z < /usr/share/dict/words| \ + sed -e 's/.*/& &/' -e 10000q| LANG=C sort -u >lmdb_retry + echo lmdb_map_size=10240 >main.cf + ($(SHLIB_ENV) $(VALGRIND) ./postmap -c . lmdb:lmdb_retry; \ + $(SHLIB_ENV) $(VALGRIND) ./postmap -s lmdb:lmdb_retry | \ + LANG=C sort > lmdb_retry.tmp) + cmp lmdb_retry lmdb_retry.tmp + rm -f lmdb_retry lmdb_retry.tmp lmdb_retry.lmdb main.cf + +lmdb_incr_test: $(PROG) + rm -f lmdb_retry.lmdb main.cf + tr A-Z a-z < /usr/share/dict/words| \ + sed -e 's/.*/& &/' -e 1000q| LANG=C sort -u >lmdb_retry + echo lmdb_map_size=10240 >main.cf + ($(SHLIB_ENV) $(VALGRIND) ./postmap -ic . lmdb:lmdb_retry lmdb_retry.tmp) + cmp lmdb_retry lmdb_retry.tmp + rm -f lmdb_retry lmdb_retry.tmp lmdb_retry.lmdb main.cf + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) *.tmp junk *.db + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postmap.o: ../../include/argv.h +postmap.o: ../../include/check_arg.h +postmap.o: ../../include/clean_env.h +postmap.o: ../../include/dict.h +postmap.o: ../../include/dict_proxy.h +postmap.o: ../../include/header_opts.h +postmap.o: ../../include/mail_conf.h +postmap.o: ../../include/mail_dict.h +postmap.o: ../../include/mail_params.h +postmap.o: ../../include/mail_parm_split.h +postmap.o: ../../include/mail_task.h +postmap.o: ../../include/mail_version.h +postmap.o: ../../include/maillog_client.h +postmap.o: ../../include/mime_state.h +postmap.o: ../../include/mkmap.h +postmap.o: ../../include/msg.h +postmap.o: ../../include/msg_vstream.h +postmap.o: ../../include/myflock.h +postmap.o: ../../include/mymalloc.h +postmap.o: ../../include/readlline.h +postmap.o: ../../include/rec_type.h +postmap.o: ../../include/set_eugid.h +postmap.o: ../../include/split_at.h +postmap.o: ../../include/stringops.h +postmap.o: ../../include/sys_defs.h +postmap.o: ../../include/vbuf.h +postmap.o: ../../include/vstream.h +postmap.o: ../../include/vstring.h +postmap.o: ../../include/vstring_vstream.h +postmap.o: ../../include/warn_stat.h +postmap.o: postmap.c diff --git a/src/postmap/aliases b/src/postmap/aliases new file mode 100644 index 0000000..cce2b6b --- /dev/null +++ b/src/postmap/aliases @@ -0,0 +1 @@ +xx yy diff --git a/src/postmap/fail_test.in b/src/postmap/fail_test.in new file mode 100644 index 0000000..ce01127 --- /dev/null +++ b/src/postmap/fail_test.in @@ -0,0 +1,8 @@ +${VALGRIND} ./postmap -q xx fail:aliases +echo xx | ${VALGRIND} ./postmap -q - fail:aliases +echo xx | ${VALGRIND} ./postmap -bq - fail:aliases +${VALGRIND} ./postmap -d xx fail:aliases +echo xx | ${VALGRIND} ./postmap -d - fail:aliases +${VALGRIND} ./postmap -s fail:aliases +${VALGRIND} ./postmap -i fail:aliases < aliases +${VALGRIND} ./postmap fail:aliases diff --git a/src/postmap/fail_test.ref b/src/postmap/fail_test.ref new file mode 100644 index 0000000..54c6f33 --- /dev/null +++ b/src/postmap/fail_test.ref @@ -0,0 +1,8 @@ +postmap: fatal: table fail:aliases: query error: Application error +postmap: fatal: table fail:aliases: query error: Application error +postmap: fatal: table fail:aliases: query error: Application error +postmap: fatal: table fail:aliases: delete error: Application error +postmap: fatal: table fail:aliases: delete error: Application error +postmap: fatal: table fail:aliases: sequence error: Application error +postmap: fatal: table fail:aliases: write error: Application error +postmap: fatal: table fail:aliases: write error: Application error diff --git a/src/postmap/file_test.in b/src/postmap/file_test.in new file mode 100644 index 0000000..8c8a1a9 --- /dev/null +++ b/src/postmap/file_test.in @@ -0,0 +1,17 @@ +echo -n this-is-file1 > postmap-file-1 +echo -n this-is-file2 > postmap-file-2 +echo 'file-1 postmap-file-1' | ${VALGRIND} ./postmap -iF file_test_map || exit 1 +echo 'file-2 postmap-file-2' | ${VALGRIND} ./postmap -iF file_test_map || exit 1 +echo 'file-3 postmap-file-3' | ${VALGRIND} ./postmap -iF file_test_map || exit 1 +echo 'entry-4 postmap-entry-4' | ${VALGRIND} ./postmap -i file_test_map || exit 1 +${VALGRIND} ./postmap -s file_test_map | LC_ALL=C sort +${VALGRIND} ./postmap -q file-1 file_test_map +${VALGRIND} ./postmap -q file-2 file_test_map +${VALGRIND} ./postmap -q file-3 file_test_map +${VALGRIND} ./postmap -q entry-4 file_test_map +${VALGRIND} ./postmap -Fs file_test_map | LC_ALL=C sort +${VALGRIND} ./postmap -Fq file-1 file_test_map +${VALGRIND} ./postmap -Fq file-2 file_test_map +${VALGRIND} ./postmap -Fq file-3 file_test_map +${VALGRIND} ./postmap -Fq entry-4 file_test_map +exit 0 diff --git a/src/postmap/file_test.ref b/src/postmap/file_test.ref new file mode 100644 index 0000000..bc4815d --- /dev/null +++ b/src/postmap/file_test.ref @@ -0,0 +1,14 @@ +postmap: warning: stdin, line 1: open postmap-file-3: No such file or directory: skipping this entry +entry-4 postmap-entry-4 +file-1 dGhpcy1pcy1maWxlMQ== +file-2 dGhpcy1pcy1maWxlMg== +dGhpcy1pcy1maWxlMQ== +dGhpcy1pcy1maWxlMg== +postmap-entry-4 +postmap: warning: table hash:file_test_map.db: key entry-4: malformed BASE64 value: postmap-entry-4 +file-1 this-is-file1 +file-2 this-is-file2 +this-is-file1 +this-is-file2 +postmap: warning: table hash:file_test_map.db: key entry-4: malformed BASE64 value: postmap-entry-4 +postmap: fatal: table hash:file_test_map.db: query error diff --git a/src/postmap/lmdb_abb b/src/postmap/lmdb_abb new file mode 100644 index 0000000..7ea8d03 --- /dev/null +++ b/src/postmap/lmdb_abb @@ -0,0 +1,3 @@ +a 1 +b 2 +b 3 diff --git a/src/postmap/lmdb_abb.ref b/src/postmap/lmdb_abb.ref new file mode 100644 index 0000000..ea66c71 --- /dev/null +++ b/src/postmap/lmdb_abb.ref @@ -0,0 +1,3 @@ +postmap: warning: lmdb:lmdb_abb: duplicate entry: "b" +a 1 +b 2 diff --git a/src/postmap/map-abc1.ref b/src/postmap/map-abc1.ref new file mode 100644 index 0000000..142195f --- /dev/null +++ b/src/postmap/map-abc1.ref @@ -0,0 +1 @@ +DEF diff --git a/src/postmap/map-abc2.ref b/src/postmap/map-abc2.ref new file mode 100644 index 0000000..2ee5fab --- /dev/null +++ b/src/postmap/map-abc2.ref @@ -0,0 +1 @@ +abc DEF diff --git a/src/postmap/map-ghi1.ref b/src/postmap/map-ghi1.ref new file mode 100644 index 0000000..7beb1db --- /dev/null +++ b/src/postmap/map-ghi1.ref @@ -0,0 +1 @@ +jkl diff --git a/src/postmap/map-ghi2.ref b/src/postmap/map-ghi2.ref new file mode 100644 index 0000000..5926a18 --- /dev/null +++ b/src/postmap/map-ghi2.ref @@ -0,0 +1 @@ +ghi jkl diff --git a/src/postmap/map-uABC1.ref b/src/postmap/map-uABC1.ref new file mode 100644 index 0000000..142195f --- /dev/null +++ b/src/postmap/map-uABC1.ref @@ -0,0 +1 @@ +DEF diff --git a/src/postmap/map-uABC2.ref b/src/postmap/map-uABC2.ref new file mode 100644 index 0000000..cc3ea4e --- /dev/null +++ b/src/postmap/map-uABC2.ref @@ -0,0 +1 @@ +ABC DEF diff --git a/src/postmap/map.in b/src/postmap/map.in new file mode 100644 index 0000000..f180554 --- /dev/null +++ b/src/postmap/map.in @@ -0,0 +1,2 @@ +ABC DEF +ghi jkl diff --git a/src/postmap/postmap.c b/src/postmap/postmap.c new file mode 100644 index 0000000..66421d0 --- /dev/null +++ b/src/postmap/postmap.c @@ -0,0 +1,1155 @@ +/*++ +/* NAME +/* postmap 1 +/* SUMMARY +/* Postfix lookup table management +/* SYNOPSIS +/* .fi +/* \fBpostmap\fR [\fB-bfFhimnNoprsuUvw\fR] [\fB-c \fIconfig_dir\fR] +/* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR] +/* [\fIfile_type\fR:]\fIfile_name\fR ... +/* DESCRIPTION +/* The \fBpostmap\fR(1) command creates or queries one or more Postfix +/* lookup tables, or updates an existing one. +/* +/* If the result files do not exist they will be created with the +/* same group and other read permissions as their source file. +/* +/* While the table update is in progress, signal delivery is +/* postponed, and an exclusive, advisory, lock is placed on the +/* entire table, in order to avoid surprises in spectator +/* processes. +/* INPUT FILE FORMAT +/* .ad +/* .fi +/* The format of a lookup table input file is as follows: +/* .IP \(bu +/* A table entry has the form +/* .sp +/* .nf +/* \fIkey\fR whitespace \fIvalue\fR +/* .fi +/* .IP \(bu +/* Empty lines and whitespace-only lines are ignored, as +/* are lines whose first non-whitespace character is a `#'. +/* .IP \(bu +/* A logical line starts with non-whitespace text. A line that +/* starts with whitespace continues a logical line. +/* .PP +/* The \fIkey\fR and \fIvalue\fR are processed as is, except that +/* surrounding white space is stripped off. Whitespace in lookup +/* keys is supported in Postfix 3.2 and later, by surrounding the +/* key with double quote characters `"'. Within the double quotes, +/* double quote `"' and backslash `\\' characters can be included +/* by quoting them with a preceding backslash. +/* +/* When the \fB-F\fR option is given, the \fIvalue\fR must +/* specify one or more filenames separated by comma and/or +/* whitespace; \fBpostmap\fR(1) will concatenate the file +/* content (with a newline character inserted between files) +/* and will store the base64-encoded result instead of the +/* \fIvalue\fR. +/* +/* When the \fIkey\fR specifies email address information, the +/* localpart should be enclosed with double quotes if required +/* by RFC 5322. For example, an address localpart that contains +/* ";", or a localpart that starts or ends with ".". +/* +/* By default the lookup key is mapped to lowercase to make +/* the lookups case insensitive; as of Postfix 2.3 this case +/* folding happens only with tables whose lookup keys are +/* fixed-case strings such as btree:, dbm: or hash:. With +/* earlier versions, the lookup key is folded even with tables +/* where a lookup field can match both upper and lower case +/* text, such as regexp: and pcre:. This resulted in loss of +/* information with $\fInumber\fR substitutions. +/* COMMAND-LINE ARGUMENTS +/* .ad +/* .fi +/* .IP \fB-b\fR +/* Enable message body query mode. When reading lookup keys +/* from standard input with "\fB-q -\fR", process the input +/* as if it is an email message in RFC 5322 format. Each line +/* of body content becomes one lookup key. +/* .sp +/* By default, the \fB-b\fR option starts generating lookup +/* keys at the first non-header line, and stops when the end +/* of the message is reached. +/* To simulate \fBbody_checks\fR(5) processing, enable MIME +/* parsing with \fB-m\fR. With this, the \fB-b\fR option +/* generates no body-style lookup keys for attachment MIME +/* headers and for attached message/* headers. +/* .sp +/* NOTE: with "smtputf8_enable = yes", the \fB-b\fR option +/* disables UTF-8 syntax checks on query keys and lookup +/* results. Specify the \fB-U\fR option to force UTF-8 +/* syntax checks anyway. +/* .sp +/* This feature is available in Postfix version 2.6 and later. +/* .IP "\fB-c \fIconfig_dir\fR" +/* Read the \fBmain.cf\fR configuration file in the named directory +/* instead of the default configuration directory. +/* .IP "\fB-d \fIkey\fR" +/* Search the specified maps for \fIkey\fR and remove one entry per map. +/* The exit status is zero when the requested information was found. +/* +/* If a key value of \fB-\fR is specified, the program reads key +/* values from the standard input stream. The exit status is zero +/* when at least one of the requested keys was found. +/* .IP \fB-f\fR +/* Do not fold the lookup key to lower case while creating or querying +/* a table. +/* +/* With Postfix version 2.3 and later, this option has no +/* effect for regular expression tables. There, case folding +/* is controlled by appending a flag to a pattern. +/* .IP \fB-F\fR +/* When querying a map, or listing a map, base64-decode each +/* value. When creating a map from source file, process each +/* value as a list of filenames, concatenate the content of +/* those files, and store the base64-encoded result instead +/* of the value (see INPUT FILE FORMAT for details). +/* .sp +/* This feature is available in Postfix version 3.4 and later. +/* .IP \fB-h\fR +/* Enable message header query mode. When reading lookup keys +/* from standard input with "\fB-q -\fR", process the input +/* as if it is an email message in RFC 5322 format. Each +/* logical header line becomes one lookup key. A multi-line +/* header becomes one lookup key with one or more embedded +/* newline characters. +/* .sp +/* By default, the \fB-h\fR option generates lookup keys until +/* the first non-header line is reached. +/* To simulate \fBheader_checks\fR(5) processing, enable MIME +/* parsing with \fB-m\fR. With this, the \fB-h\fR option also +/* generates header-style lookup keys for attachment MIME +/* headers and for attached message/* headers. +/* .sp +/* NOTE: with "smtputf8_enable = yes", the \fB-b\fR option +/* option disables UTF-8 syntax checks on query keys and +/* lookup results. Specify the \fB-U\fR option to force UTF-8 +/* syntax checks anyway. +/* .sp +/* This feature is available in Postfix version 2.6 and later. +/* .IP \fB-i\fR +/* Incremental mode. Read entries from standard input and do not +/* truncate an existing database. By default, \fBpostmap\fR(1) creates +/* a new database from the entries in \fBfile_name\fR. +/* .IP \fB-m\fR +/* Enable MIME parsing with "\fB-b\fR" and "\fB-h\fR". +/* .sp +/* This feature is available in Postfix version 2.6 and later. +/* .IP \fB-N\fR +/* Include the terminating null character that terminates lookup keys +/* and values. By default, \fBpostmap\fR(1) does whatever is +/* the default for +/* the host operating system. +/* .IP \fB-n\fR +/* Don't include the terminating null character that terminates lookup +/* keys and values. By default, \fBpostmap\fR(1) does whatever +/* is the default for +/* the host operating system. +/* .IP \fB-o\fR +/* Do not release root privileges when processing a non-root +/* input file. By default, \fBpostmap\fR(1) drops root privileges +/* and runs as the source file owner instead. +/* .IP \fB-p\fR +/* Do not inherit the file access permissions from the input file +/* when creating a new file. Instead, create a new file with default +/* access permissions (mode 0644). +/* .IP "\fB-q \fIkey\fR" +/* Search the specified maps for \fIkey\fR and write the first value +/* found to the standard output stream. The exit status is zero +/* when the requested information was found. +/* +/* Note: this performs a single query with the key as specified, +/* and does not make iterative queries with substrings of the +/* key as described for access(5), canonical(5), transport(5), +/* virtual(5) and other Postfix table-driven features. +/* +/* If a key value of \fB-\fR is specified, the program reads key +/* values from the standard input stream and writes one line of +/* \fIkey value\fR output for each key that was found. The exit +/* status is zero when at least one of the requested keys was found. +/* .IP \fB-r\fR +/* When updating a table, do not complain about attempts to update +/* existing entries, and make those updates anyway. +/* .IP \fB-s\fR +/* Retrieve all database elements, and write one line of +/* \fIkey value\fR output for each element. The elements are +/* printed in database order, which is not necessarily the same +/* as the original input order. +/* .sp +/* This feature is available in Postfix version 2.2 and later, +/* and is not available for all database types. +/* .IP \fB-u\fR +/* Disable UTF-8 support. UTF-8 support is enabled by default +/* when "smtputf8_enable = yes". It requires that keys and +/* values are valid UTF-8 strings. +/* .IP \fB-U\fR +/* With "smtputf8_enable = yes", force UTF-8 syntax checks +/* with the \fB-b\fR and \fB-h\fR options. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. +/* .IP \fB-w\fR +/* When updating a table, do not complain about attempts to update +/* existing entries, and ignore those attempts. +/* .PP +/* Arguments: +/* .IP \fIfile_type\fR +/* The database type. To find out what types are supported, use +/* the "\fBpostconf -m\fR" command. +/* +/* The \fBpostmap\fR(1) command can query any supported file type, +/* but it can create only the following file types: +/* .RS +/* .IP \fBbtree\fR +/* The output file is a btree file, named \fIfile_name\fB.db\fR. +/* This is available on systems with support for \fBdb\fR databases. +/* .IP \fBcdb\fR +/* The output consists of one file, named \fIfile_name\fB.cdb\fR. +/* This is available on systems with support for \fBcdb\fR databases. +/* .IP \fBdbm\fR +/* The output consists of two files, named \fIfile_name\fB.pag\fR and +/* \fIfile_name\fB.dir\fR. +/* This is available on systems with support for \fBdbm\fR databases. +/* .IP \fBfail\fR +/* A table that reliably fails all requests. The lookup table +/* name is used for logging only. This table exists to simplify +/* Postfix error tests. +/* .IP \fBhash\fR +/* The output file is a hashed file, named \fIfile_name\fB.db\fR. +/* This is available on systems with support for \fBdb\fR databases. +/* .IP \fBlmdb\fR +/* The output is a btree-based file, named \fIfile_name\fB.lmdb\fR. +/* \fBlmdb\fR supports concurrent writes and reads from different +/* processes, unlike other supported file-based tables. +/* This is available on systems with support for \fBlmdb\fR databases. +/* .IP \fBsdbm\fR +/* The output consists of two files, named \fIfile_name\fB.pag\fR and +/* \fIfile_name\fB.dir\fR. +/* This is available on systems with support for \fBsdbm\fR databases. +/* .PP +/* When no \fIfile_type\fR is specified, the software uses the database +/* type specified via the \fBdefault_database_type\fR configuration +/* parameter. +/* .RE +/* .IP \fIfile_name\fR +/* The name of the lookup table source file when rebuilding a database. +/* DIAGNOSTICS +/* Problems are logged to the standard error stream and to +/* \fBsyslogd\fR(8) or \fBpostlogd\fR(8). +/* No output means that no problems were detected. Duplicate entries are +/* skipped and are flagged with a warning. +/* +/* \fBpostmap\fR(1) terminates with zero exit status in case of success +/* (including successful "\fBpostmap -q\fR" lookup) and terminates +/* with non-zero exit status in case of failure. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP \fBMAIL_CONFIG\fR +/* Directory with Postfix configuration files. +/* .IP \fBMAIL_VERBOSE\fR +/* Enable verbose logging for debugging purposes. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBberkeley_db_create_buffer_size (16777216)\fR" +/* The per-table I/O buffer size for programs that create Berkeley DB +/* hash or btree tables. +/* .IP "\fBberkeley_db_read_buffer_size (131072)\fR" +/* The per-table I/O buffer size for programs that read Berkeley DB +/* hash or btree tables. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdefault_database_type (see 'postconf -d' output)\fR" +/* The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1) +/* and \fBpostmap\fR(1) commands. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment variables that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable preliminary SMTPUTF8 support for the protocols described +/* in RFC 6531, RFC 6532, and RFC 6533. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 2.11 and later: +/* .IP "\fBlmdb_map_size (16777216)\fR" +/* The initial OpenLDAP LMDB database size limit in bytes. +/* SEE ALSO +/* postalias(1), create/update/query alias database +/* postconf(1), supported database types +/* postconf(5), configuration parameters +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* DATABASE_README, Postfix lookup table overview +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#define STR vstring_str +#define LEN VSTRING_LEN + +#define POSTMAP_FLAG_AS_OWNER (1<<0) /* open dest as owner of source */ +#define POSTMAP_FLAG_SAVE_PERM (1<<1) /* copy access permission from source */ +#define POSTMAP_FLAG_HEADER_KEY (1<<2) /* apply to header text */ +#define POSTMAP_FLAG_BODY_KEY (1<<3) /* apply to body text */ +#define POSTMAP_FLAG_MIME_KEY (1<<4) /* enable MIME parsing */ + +#define POSTMAP_FLAG_HB_KEY (POSTMAP_FLAG_HEADER_KEY | POSTMAP_FLAG_BODY_KEY) +#define POSTMAP_FLAG_FULL_KEY (POSTMAP_FLAG_BODY_KEY | POSTMAP_FLAG_MIME_KEY) +#define POSTMAP_FLAG_ANY_KEY (POSTMAP_FLAG_HB_KEY | POSTMAP_FLAG_MIME_KEY) + + /* + * MIME Engine call-back state for generating lookup keys from an email + * message read from standard input. + */ +typedef struct { + DICT **dicts; /* map handles */ + char **maps; /* map names */ + int map_count; /* yes, indeed */ + int dict_flags; /* query flags */ + int header_done; /* past primary header */ + int found; /* result */ +} POSTMAP_KEY_STATE; + +/* postmap - create or update mapping database */ + +static void postmap(char *map_type, char *path_name, int postmap_flags, + int open_flags, int dict_flags) +{ + VSTREAM *NOCLOBBER source_fp; + VSTRING *line_buffer; + MKMAP *mkmap; + int lineno; + int last_line; + char *key; + char *value; + struct stat st; + mode_t saved_mask; + + /* + * Initialize. + */ + line_buffer = vstring_alloc(100); + if ((open_flags & O_TRUNC) == 0) { + /* Incremental mode. */ + source_fp = VSTREAM_IN; + vstream_control(source_fp, CA_VSTREAM_CTL_PATH("stdin"), CA_VSTREAM_CTL_END); + } else { + /* Create database. */ + if (strcmp(map_type, DICT_TYPE_PROXY) == 0) + msg_fatal("can't create maps via the proxy service"); + dict_flags |= DICT_FLAG_BULK_UPDATE; + if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", path_name); + } + if (fstat(vstream_fileno(source_fp), &st) < 0) + msg_fatal("fstat %s: %m", path_name); + + /* + * Turn off group/other read permissions as indicated in the source file. + */ + if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) + saved_mask = umask(022 | (~st.st_mode & 077)); + + /* + * If running as root, run as the owner of the source file, so that the + * result shows proper ownership, and so that a bug in postmap does not + * allow privilege escalation. + */ + if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0 + && (st.st_uid != geteuid() || st.st_gid != getegid())) + set_eugid(st.st_uid, st.st_gid); + + /* + * Open the database, optionally create it when it does not exist, + * optionally truncate it when it does exist, and lock out any + * spectators. + */ + mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags); + + /* + * And restore the umask, in case it matters. + */ + if ((postmap_flags & POSTMAP_FLAG_SAVE_PERM) && S_ISREG(st.st_mode)) + umask(saved_mask); + + /* + * Trap "exceptions" so that we can restart a bulk-mode update after a + * recoverable error. + */ + for (;;) { + if (dict_isjmp(mkmap->dict) != 0 + && dict_setjmp(mkmap->dict) != 0 + && vstream_fseek(source_fp, SEEK_SET, 0) < 0) + msg_fatal("seek %s: %m", VSTREAM_PATH(source_fp)); + + /* + * Add records to the database. XXX This duplicates the parser in + * dict_thash.c. + */ + last_line = 0; + while (readllines(line_buffer, source_fp, &last_line, &lineno)) { + int in_quotes = 0; + + /* + * First some UTF-8 checks sans casefolding. + */ + if ((mkmap->dict->flags & DICT_FLAG_UTF8_ACTIVE) + && !allascii(STR(line_buffer)) + && !valid_utf8_string(STR(line_buffer), LEN(line_buffer))) { + msg_warn("%s, line %d: non-UTF-8 input \"%s\"" + " -- ignoring this line", + VSTREAM_PATH(source_fp), lineno, STR(line_buffer)); + continue; + } + + /* + * Terminate the key on the first unquoted whitespace character, + * then trim leading and trailing whitespace from the value. + */ + for (value = STR(line_buffer); *value; value++) { + if (*value == '\\') { + if (*++value == 0) + break; + } else if (ISSPACE(*value)) { + if (!in_quotes) + break; + } else if (*value == '"') { + in_quotes = !in_quotes; + } + } + if (in_quotes) { + msg_warn("%s, line %d: unbalanced '\"' in '%s'" + " -- ignoring this line", + VSTREAM_PATH(source_fp), lineno, STR(line_buffer)); + continue; + } + if (*value) + *value++ = 0; + while (ISSPACE(*value)) + value++; + trimblanks(value, 0)[0] = 0; + + /* + * Leave the key in quoted form, because 1) postmap cannot assume + * that a string without @ contains an email address localpart, + * and 2) an address localpart may require quoting even when the + * quoted form contains no backslash or ". + */ + key = STR(line_buffer); + + /* + * Enforce the "key whitespace value" format. Disallow missing + * keys or missing values. + */ + if (*key == 0 || *value == 0) { + msg_warn("%s, line %d: expected format: key whitespace value", + VSTREAM_PATH(source_fp), lineno); + continue; + } + if (key[strlen(key) - 1] == ':') + msg_warn("%s, line %d: record is in \"key: value\" format; is this an alias file?", + VSTREAM_PATH(source_fp), lineno); + + /* + * Optionally treat the vale as a filename, and replace the value + * with the BASE64-encoded content of the named file. + */ + if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) { + VSTRING *base64_buf; + char *err; + + if ((base64_buf = dict_file_to_b64(mkmap->dict, value)) == 0) { + err = dict_file_get_error(mkmap->dict); + msg_warn("%s, line %d: %s: skipping this entry", + VSTREAM_PATH(source_fp), lineno, err); + myfree(err); + continue; + } + value = vstring_str(base64_buf); + } + + /* + * Store the value under a (possibly case-insensitive) key, as + * specified with open_flags. + */ + mkmap_append(mkmap, key, value); + if (mkmap->dict->error) + msg_fatal("table %s:%s: write error: %m", + mkmap->dict->type, mkmap->dict->name); + } + break; + } + + /* + * Close the mapping database, and release the lock. + */ + mkmap_close(mkmap); + + /* + * Cleanup. We're about to terminate, but it is a good sanity check. + */ + vstring_free(line_buffer); + if (source_fp != VSTREAM_IN) + vstream_fclose(source_fp); +} + +/* postmap_body - MIME engine body call-back routine */ + +static void postmap_body(void *ptr, int unused_rec_type, + const char *keybuf, + ssize_t unused_len, + off_t unused_offset) +{ + POSTMAP_KEY_STATE *state = (POSTMAP_KEY_STATE *) ptr; + DICT **dicts = state->dicts; + char **maps = state->maps; + int map_count = state->map_count; + int dict_flags = state->dict_flags; + const char *map_name; + const char *value; + int n; + + for (n = 0; n < map_count; n++) { + if (dicts[n] == 0) + dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? + dict_open3(maps[n], map_name, O_RDONLY, dict_flags) : + dict_open3(var_db_type, maps[n], O_RDONLY, dict_flags)); + if ((value = dict_get(dicts[n], keybuf)) != 0) { + if (*value == 0) { + msg_warn("table %s:%s: key %s: empty string result is not allowed", + dicts[n]->type, dicts[n]->name, keybuf); + msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", + dicts[n]->type, dicts[n]->name); + } + vstream_printf("%s %s\n", keybuf, value); + state->found = 1; + break; + } + if (dicts[n]->error) + msg_fatal("table %s:%s: query error: %m", + dicts[n]->type, dicts[n]->name); + } +} + +/* postmap_header - MIME engine header call-back routine */ + +static void postmap_header(void *ptr, int unused_header_class, + const HEADER_OPTS *unused_header_info, + VSTRING *header_buf, + off_t offset) +{ + + /* + * Don't re-invent an already working wheel. + */ + postmap_body(ptr, 0, STR(header_buf), LEN(header_buf), offset); +} + +/* postmap_head_end - MIME engine end-of-header call-back routine */ + +static void postmap_head_end(void *ptr) +{ + POSTMAP_KEY_STATE *state = (POSTMAP_KEY_STATE *) ptr; + + /* + * Don't process the message body when we only examine primary headers. + */ + state->header_done = 1; +} + +/* postmap_queries - apply multiple requests from stdin */ + +static int postmap_queries(VSTREAM *in, char **maps, const int map_count, + const int postmap_flags, + const int dict_flags) +{ + int found = 0; + VSTRING *keybuf = vstring_alloc(100); + DICT **dicts; + const char *map_name; + const char *value; + int n; + + /* + * Sanity check. + */ + if (map_count <= 0) + msg_panic("postmap_queries: bad map count"); + + /* + * Prepare to open maps lazily. + */ + dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); + for (n = 0; n < map_count; n++) + dicts[n] = 0; + + /* + * Perform all queries. Open maps on the fly, to avoid opening unnecessary + * maps. + */ + if ((postmap_flags & POSTMAP_FLAG_HB_KEY) == 0) { + while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) { + for (n = 0; n < map_count; n++) { + if (dicts[n] == 0) + dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? + dict_open3(maps[n], map_name, O_RDONLY, dict_flags) : + dict_open3(var_db_type, maps[n], O_RDONLY, dict_flags)); + value = ((dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) ? + dict_file_lookup : dicts[n]->lookup) + (dicts[n], STR(keybuf)); + if (value != 0) { + if (*value == 0) { + msg_warn("table %s:%s: key %s: empty string result is not allowed", + dicts[n]->type, dicts[n]->name, STR(keybuf)); + msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", + dicts[n]->type, dicts[n]->name); + } + vstream_printf("%s %s\n", STR(keybuf), value); + found = 1; + break; + } + switch (dicts[n]->error) { + case 0: + break; + case DICT_ERR_CONFIG: + msg_fatal("table %s:%s: query error", + dicts[n]->type, dicts[n]->name); + default: + msg_fatal("table %s:%s: query error: %m", + dicts[n]->type, dicts[n]->name); + } + } + } + } else { + POSTMAP_KEY_STATE key_state; + MIME_STATE *mime_state; + int mime_errs = 0; + + /* + * Bundle up the request and instantiate a MIME parsing engine. + */ + key_state.dicts = dicts; + key_state.maps = maps; + key_state.map_count = map_count; + key_state.dict_flags = dict_flags; + key_state.header_done = 0; + key_state.found = 0; + mime_state = + mime_state_alloc((postmap_flags & POSTMAP_FLAG_MIME_KEY) ? + 0 : MIME_OPT_DISABLE_MIME, + (postmap_flags & POSTMAP_FLAG_HEADER_KEY) ? + postmap_header : (MIME_STATE_HEAD_OUT) 0, + (postmap_flags & POSTMAP_FLAG_FULL_KEY) ? + (MIME_STATE_ANY_END) 0 : postmap_head_end, + (postmap_flags & POSTMAP_FLAG_BODY_KEY) ? + postmap_body : (MIME_STATE_BODY_OUT) 0, + (MIME_STATE_ANY_END) 0, + (MIME_STATE_ERR_PRINT) 0, + (void *) &key_state); + + /* + * Process the input message. + */ + while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF + && key_state.header_done == 0 && mime_errs == 0) + mime_errs = mime_state_update(mime_state, REC_TYPE_NORM, + STR(keybuf), LEN(keybuf)); + + /* + * Flush the MIME engine output buffer and tidy up loose ends. + */ + if (mime_errs == 0) + mime_errs = mime_state_update(mime_state, REC_TYPE_END, "", 0); + if (mime_errs) + msg_fatal("message format error: %s", + mime_state_detail(mime_errs)->text); + mime_state_free(mime_state); + found = key_state.found; + } + + if (found) + vstream_fflush(VSTREAM_OUT); + + /* + * Cleanup. + */ + for (n = 0; n < map_count; n++) + if (dicts[n]) + dict_close(dicts[n]); + myfree((void *) dicts); + vstring_free(keybuf); + + return (found); +} + +/* postmap_query - query a map and print the result to stdout */ + +static int postmap_query(const char *map_type, const char *map_name, + const char *key, int dict_flags) +{ + DICT *dict; + const char *value; + + dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags); + value = ((dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) ? + dict_file_lookup : dict->lookup) (dict, key); + if (value != 0) { + if (*value == 0) { + msg_warn("table %s:%s: key %s: empty string result is not allowed", + map_type, map_name, key); + msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", + map_type, map_name); + } + vstream_printf("%s\n", value); + } + switch (dict->error) { + case 0: + break; + case DICT_ERR_CONFIG: + msg_fatal("table %s:%s: query error", + dict->type, dict->name); + default: + msg_fatal("table %s:%s: query error: %m", + dict->type, dict->name); + } + vstream_fflush(VSTREAM_OUT); + dict_close(dict); + return (value != 0); +} + +/* postmap_deletes - apply multiple requests from stdin */ + +static int postmap_deletes(VSTREAM *in, char **maps, const int map_count, + int dict_flags) +{ + int found = 0; + VSTRING *keybuf = vstring_alloc(100); + DICT **dicts; + const char *map_name; + int n; + int open_flags; + + /* + * Sanity check. + */ + if (map_count <= 0) + msg_panic("postmap_deletes: bad map count"); + + /* + * Open maps ahead of time. + */ + dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); + for (n = 0; n < map_count; n++) { + map_name = split_at(maps[n], ':'); + if (map_name && strcmp(maps[n], DICT_TYPE_PROXY) == 0) + open_flags = O_RDWR | O_CREAT; /* XXX */ + else + open_flags = O_RDWR; + dicts[n] = (map_name != 0 ? + dict_open3(maps[n], map_name, open_flags, dict_flags) : + dict_open3(var_db_type, maps[n], open_flags, dict_flags)); + } + + /* + * Perform all requests. + */ + while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) { + for (n = 0; n < map_count; n++) { + found |= (dict_del(dicts[n], STR(keybuf)) == 0); + if (dicts[n]->error) + msg_fatal("table %s:%s: delete error: %m", + dicts[n]->type, dicts[n]->name); + } + } + + /* + * Cleanup. + */ + for (n = 0; n < map_count; n++) + if (dicts[n]) + dict_close(dicts[n]); + myfree((void *) dicts); + vstring_free(keybuf); + + return (found); +} + +/* postmap_delete - delete a (key, value) pair from a map */ + +static int postmap_delete(const char *map_type, const char *map_name, + const char *key, int dict_flags) +{ + DICT *dict; + int status; + int open_flags; + + if (strcmp(map_type, DICT_TYPE_PROXY) == 0) + open_flags = O_RDWR | O_CREAT; /* XXX */ + else + open_flags = O_RDWR; + dict = dict_open3(map_type, map_name, open_flags, dict_flags); + status = dict_del(dict, key); + if (dict->error) + msg_fatal("table %s:%s: delete error: %m", dict->type, dict->name); + dict_close(dict); + return (status == 0); +} + +/* postmap_seq - print all map entries to stdout */ + +static void postmap_seq(const char *map_type, const char *map_name, + int dict_flags) +{ + DICT *dict; + const char *key; + const char *value; + int func; + + if (strcmp(map_type, DICT_TYPE_PROXY) == 0) + msg_fatal("can't sequence maps via the proxy service"); + dict = dict_open3(map_type, map_name, O_RDONLY, dict_flags); + for (func = DICT_SEQ_FUN_FIRST; /* void */ ; func = DICT_SEQ_FUN_NEXT) { + if (dict_seq(dict, func, &key, &value) != 0) + break; + if (*key == 0) { + msg_warn("table %s:%s: empty lookup key value is not allowed", + map_type, map_name); + } else if (*value == 0) { + msg_warn("table %s:%s: key %s: empty string result is not allowed", + map_type, map_name, key); + msg_warn("table %s:%s should return NO RESULT in case of NOT FOUND", + map_type, map_name); + } + if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) { + VSTRING *unb64; + char *err; + + if ((unb64 = dict_file_from_b64(dict, value)) == 0) { + err = dict_file_get_error(dict); + msg_warn("table %s:%s: key %s: %s", + dict->type, dict->name, key, err); + myfree(err); + /* dict->error = DICT_ERR_CONFIG; */ + continue; + } + value = STR(unb64); + } + vstream_printf("%s %s\n", key, value); + } + if (dict->error) + msg_fatal("table %s:%s: sequence error: %m", dict->type, dict->name); + vstream_fflush(VSTREAM_OUT); + dict_close(dict); +} + +/* usage - explain */ + +static NORETURN usage(char *myname) +{ + msg_fatal("usage: %s [-bfFhimnNoprsuUvw] [-c config_dir] [-d key] [-q key] [map_type:]file...", + myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + char *path_name; + int ch; + int fd; + char *slash; + struct stat st; + int postmap_flags = POSTMAP_FLAG_AS_OWNER | POSTMAP_FLAG_SAVE_PERM; + int open_flags = O_RDWR | O_CREAT | O_TRUNC; + int dict_flags = (DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + char *query = 0; + char *delkey = 0; + int sequence = 0; + int found; + int force_utf8 = 0; + ARGV *import_env; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Be consistent with file permissions. + */ + umask(022); + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Process environment options as early as we can. We are not set-uid, + * and we are supposed to be running in a controlled environment. + */ + if (getenv(CONF_ENV_VERB)) + msg_verbose = 1; + + /* + * Initialize. Set up logging. Read the global configuration file after + * parsing command-line arguments. + */ + if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) + argv[0] = slash + 1; + msg_vstream_init(argv[0], VSTREAM_ERR); + maillog_client_init(mail_task(argv[0]), MAILLOG_CLIENT_FLAG_NONE); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "bc:d:fFhimnNopq:rsuUvw")) > 0) { + switch (ch) { + default: + usage(argv[0]); + break; + case 'N': + dict_flags |= DICT_FLAG_TRY1NULL; + dict_flags &= ~DICT_FLAG_TRY0NULL; + break; + case 'b': + postmap_flags |= POSTMAP_FLAG_BODY_KEY; + break; + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'd': + if (sequence || query || delkey) + msg_fatal("specify only one of -s -q or -d"); + delkey = optarg; + break; + case 'f': + dict_flags &= ~DICT_FLAG_FOLD_FIX; + break; + case 'F': + dict_flags |= DICT_FLAG_SRC_RHS_IS_FILE; + break; + case 'h': + postmap_flags |= POSTMAP_FLAG_HEADER_KEY; + break; + case 'i': + open_flags &= ~O_TRUNC; + break; + case 'm': + postmap_flags |= POSTMAP_FLAG_MIME_KEY; + break; + case 'n': + dict_flags |= DICT_FLAG_TRY0NULL; + dict_flags &= ~DICT_FLAG_TRY1NULL; + break; + case 'o': + postmap_flags &= ~POSTMAP_FLAG_AS_OWNER; + break; + case 'p': + postmap_flags &= ~POSTMAP_FLAG_SAVE_PERM; + break; + case 'q': + if (sequence || query || delkey) + msg_fatal("specify only one of -s -q or -d"); + query = optarg; + break; + case 'r': + dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE); + dict_flags |= DICT_FLAG_DUP_REPLACE; + break; + case 's': + if (query || delkey) + msg_fatal("specify only one of -s or -q or -d"); + sequence = 1; + break; + case 'u': + dict_flags &= ~DICT_FLAG_UTF8_REQUEST; + break; + case 'U': + force_utf8 = 1; + break; + case 'v': + msg_verbose++; + break; + case 'w': + dict_flags &= ~(DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_REPLACE); + dict_flags |= DICT_FLAG_DUP_IGNORE; + break; + } + } + mail_conf_read(); + /* Enforce consistent operation of different Postfix parts. */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + update_env(import_env->argv); + argv_free(import_env); + /* Re-evaluate mail_task() after reading main.cf. */ + maillog_client_init(mail_task(argv[0]), MAILLOG_CLIENT_FLAG_NONE); + mail_dict_init(); + if ((query == 0 || strcmp(query, "-") != 0) + && (postmap_flags & POSTMAP_FLAG_ANY_KEY)) + msg_fatal("specify -b -h or -m only with \"-q -\""); + if ((postmap_flags & POSTMAP_FLAG_ANY_KEY) != 0 + && (postmap_flags & POSTMAP_FLAG_ANY_KEY) + == (postmap_flags & POSTMAP_FLAG_MIME_KEY)) + msg_warn("ignoring -m option without -b or -h"); + if ((postmap_flags & (POSTMAP_FLAG_ANY_KEY & ~POSTMAP_FLAG_MIME_KEY)) + && force_utf8 == 0) + dict_flags &= ~DICT_FLAG_UTF8_MASK; + + /* + * Use the map type specified by the user, or fall back to a default + * database type. + */ + if (delkey) { /* remove entry */ + if (optind + 1 > argc) + usage(argv[0]); + if (strcmp(delkey, "-") == 0) + exit(postmap_deletes(VSTREAM_IN, argv + optind, argc - optind, + dict_flags | DICT_FLAG_LOCK) == 0); + found = 0; + while (optind < argc) { + if ((path_name = split_at(argv[optind], ':')) != 0) { + found |= postmap_delete(argv[optind], path_name, delkey, + dict_flags | DICT_FLAG_LOCK); + } else { + found |= postmap_delete(var_db_type, argv[optind], delkey, + dict_flags | DICT_FLAG_LOCK); + } + optind++; + } + exit(found ? 0 : 1); + } else if (query) { /* query map(s) */ + if (optind + 1 > argc) + usage(argv[0]); + if (strcmp(query, "-") == 0) + exit(postmap_queries(VSTREAM_IN, argv + optind, argc - optind, + postmap_flags, dict_flags | DICT_FLAG_LOCK) == 0); + while (optind < argc) { + if ((path_name = split_at(argv[optind], ':')) != 0) { + found = postmap_query(argv[optind], path_name, query, + dict_flags | DICT_FLAG_LOCK); + } else { + found = postmap_query(var_db_type, argv[optind], query, + dict_flags | DICT_FLAG_LOCK); + } + if (found) + exit(0); + optind++; + } + exit(1); + } else if (sequence) { + while (optind < argc) { + if ((path_name = split_at(argv[optind], ':')) != 0) { + postmap_seq(argv[optind], path_name, + dict_flags | DICT_FLAG_LOCK); + } else { + postmap_seq(var_db_type, argv[optind], + dict_flags | DICT_FLAG_LOCK); + } + exit(0); + } + exit(1); + } else { /* create/update map(s) */ + if (optind + 1 > argc) + usage(argv[0]); + while (optind < argc) { + if ((path_name = split_at(argv[optind], ':')) != 0) { + postmap(argv[optind], path_name, postmap_flags, + open_flags, dict_flags); + } else { + postmap(var_db_type, argv[optind], postmap_flags, + open_flags, dict_flags); + } + optind++; + } + exit(0); + } +} diff --git a/src/postmap/quote_test.in b/src/postmap/quote_test.in new file mode 100644 index 0000000..fb507fc --- /dev/null +++ b/src/postmap/quote_test.in @@ -0,0 +1,8 @@ +echo '"aa bb" cc' | ${VALGRIND} ./postmap -i quote_test_map || exit 1 +echo '"dd ee ff' | ${VALGRIND} ./postmap -i quote_test_map || exit 1 +echo 'gg\ hh ii' | ${VALGRIND} ./postmap -i quote_test_map || exit 1 +echo '"gg\"hh" ii' | ${VALGRIND} ./postmap -i quote_test_map || exit 1 +echo '"jj@kk" ll' | ${VALGRIND} ./postmap -i quote_test_map || exit 1 +echo 'mm@nn@oo pp' | ${VALGRIND} ./postmap -i quote_test_map || exit 1 +echo '@oo pp' | ${VALGRIND} ./postmap -i quote_test_map || exit 1 +${VALGRIND} ./postmap -s quote_test_map | LC_ALL=C sort diff --git a/src/postmap/quote_test.ref b/src/postmap/quote_test.ref new file mode 100644 index 0000000..1643244 --- /dev/null +++ b/src/postmap/quote_test.ref @@ -0,0 +1,7 @@ +postmap: warning: stdin, line 1: unbalanced '"' in '"dd ee ff' -- ignoring this line +"aa bb" cc +"gg\"hh" ii +"jj@kk" ll +@oo pp +gg\ hh ii +mm@nn@oo pp diff --git a/src/postmulti/.indent.pro b/src/postmulti/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postmulti/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postmulti/Makefile.in b/src/postmulti/Makefile.in new file mode 100644 index 0000000..f2e3680 --- /dev/null +++ b/src/postmulti/Makefile.in @@ -0,0 +1,87 @@ +SHELL = /bin/sh +SRCS = postmulti.c +OBJS = postmulti.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +FILES = Makefile $(SRCS) $(HDRS) +INC_DIR = ../../include +TESTPROG= +PROG = postmulti +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +shar: + @shar $(FILES) + +lint: + lint $(SRCS) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postmulti.o: ../../include/argv.h +postmulti.o: ../../include/check_arg.h +postmulti.o: ../../include/clean_env.h +postmulti.o: ../../include/htable.h +postmulti.o: ../../include/mail_conf.h +postmulti.o: ../../include/mail_params.h +postmulti.o: ../../include/mail_parm_split.h +postmulti.o: ../../include/mail_version.h +postmulti.o: ../../include/maillog_client.h +postmulti.o: ../../include/msg.h +postmulti.o: ../../include/msg_vstream.h +postmulti.o: ../../include/mymalloc.h +postmulti.o: ../../include/name_code.h +postmulti.o: ../../include/ring.h +postmulti.o: ../../include/safe.h +postmulti.o: ../../include/stringops.h +postmulti.o: ../../include/sys_defs.h +postmulti.o: ../../include/vbuf.h +postmulti.o: ../../include/vstream.h +postmulti.o: ../../include/vstring.h +postmulti.o: ../../include/vstring_vstream.h +postmulti.o: ../../include/warn_stat.h +postmulti.o: postmulti.c diff --git a/src/postmulti/postmulti.c b/src/postmulti/postmulti.c new file mode 100644 index 0000000..5adcd27 --- /dev/null +++ b/src/postmulti/postmulti.c @@ -0,0 +1,1843 @@ +/*++ +/* NAME +/* postmulti 1 +/* SUMMARY +/* Postfix multi-instance manager +/* SYNOPSIS +/* .fi +/* .ti -4 +/* \fBEnabling multi-instance management:\fR +/* +/* \fBpostmulti\fR \fB-e init\fR [\fB-v\fR] +/* +/* .ti -4 +/* \fBIterator mode:\fR +/* +/* \fBpostmulti\fR \fB-l\fR [\fB-aRv\fR] [\fB-g \fIgroup\fR] +/* [\fB-i \fIname\fR] +/* +/* \fBpostmulti\fR \fB-p\fR [\fB-av\fR] [\fB-g \fIgroup\fR] +/* [\fB-i \fIname\fR] \fIpostfix-command...\fR +/* +/* \fBpostmulti\fR \fB-x\fR [\fB-aRv\fR] [\fB-g \fIgroup\fR] +/* [\fB-i \fIname\fR] \fIunix-command...\fR +/* +/* .ti -4 +/* \fBLife-cycle management:\fR +/* +/* \fBpostmulti\fR \fB-e create\fR [\fB-av\fR] +/* [\fB-g \fIgroup\fR] [\fB-i \fIname\fR] [\fB-G \fIgroup\fR] +/* [\fB-I \fIname\fR] [\fIparam=value\fR ...] +/* +/* \fBpostmulti\fR \fB-e import\fR [\fB-av\fR] +/* [\fB-g \fIgroup\fR] [\fB-i \fIname\fR] [\fB-G \fIgroup\fR] +/* [\fB-I \fIname\fR] [\fBconfig_directory=\fI/path\fR] +/* +/* \fBpostmulti\fR \fB-e destroy\fR [\fB-v\fR] \fB-i \fIname\fR +/* +/* \fBpostmulti\fR \fB-e deport\fR [\fB-v\fR] \fB-i \fIname\fR +/* +/* \fBpostmulti\fR \fB-e enable\fR [\fB-v\fR] \fB-i \fIname\fR +/* +/* \fBpostmulti\fR \fB-e disable\fR [\fB-v\fR] \fB-i \fIname\fR +/* +/* \fBpostmulti\fR \fB-e assign\fR [\fB-v\fR] \fB-i \fIname\fR +/* [\fB-I \fIname\fR] [-G \fIgroup\fR] +/* DESCRIPTION +/* The \fBpostmulti\fR(1) command allows a Postfix administrator +/* to manage multiple Postfix instances on a single host. +/* +/* \fBpostmulti\fR(1) implements two fundamental modes of +/* operation. In \fBiterator\fR mode, it executes the same +/* command for multiple Postfix instances. In \fBlife-cycle +/* management\fR mode, it adds or deletes one instance, or +/* changes the multi-instance status of one instance. +/* +/* Each mode of operation has its own command syntax. For this +/* reason, each mode is documented in separate sections below. +/* BACKGROUND +/* .ad +/* .fi +/* A multi-instance configuration consists of one primary +/* Postfix instance, and one or more secondary instances whose +/* configuration directory pathnames are recorded in the primary +/* instance's main.cf file. Postfix instances share program +/* files and documentation, but have their own configuration, +/* queue and data directories. +/* +/* Currently, only the default Postfix instance can be used +/* as primary instance in a multi-instance configuration. The +/* \fBpostmulti\fR(1) command does not currently support a \fB-c\fR +/* option to select an alternative primary instance, and exits +/* with a fatal error if the \fBMAIL_CONFIG\fR environment +/* variable is set to a non-default configuration directory. +/* +/* See the MULTI_INSTANCE_README tutorial for a more detailed +/* discussion of multi-instance management with \fBpostmulti\fR(1). +/* ITERATOR MODE +/* .ad +/* .fi +/* In iterator mode, \fBpostmulti\fR performs the same operation +/* on all Postfix instances in turn. +/* +/* If multi-instance support is not enabled, the requested +/* command is performed just for the primary instance. +/* .PP +/* Iterator mode implements the following command options: +/* .SH "Instance selection" +/* .IP \fB-a\fR +/* Perform the operation on all instances. This is the default. +/* .IP "\fB-g \fIgroup\fR" +/* Perform the operation only for members of the named \fIgroup\fR. +/* .IP "\fB-i \fIname\fR" +/* Perform the operation only for the instance with the specified +/* \fIname\fR. You can specify either the instance name +/* or the absolute pathname of the instance's configuration +/* directory. Specify "-" to select the primary Postfix instance. +/* .IP \fB-R\fR +/* Reverse the iteration order. This may be appropriate when +/* updating a multi-instance system, where "sink" instances +/* are started before "source" instances. +/* .sp +/* This option cannot be used with \fB-p\fR. +/* .SH "List mode" +/* .IP \fB-l\fR +/* List Postfix instances with their instance name, instance +/* group name, enable/disable status and configuration directory. +/* .SH "Postfix-wrapper mode" +/* .IP "\fB-p \fIpostfix-command\fR" +/* Invoke \fBpostfix(1)\fR to execute \fIpostfix-command\fR. +/* This option implements the \fBpostfix-wrapper\fR(5) interface. +/* .RS +/* .IP \(bu +/* With "start"-like commands, "postfix check" is executed for +/* instances that are not enabled. The full list of commands +/* is specified with the postmulti_start_commands parameter. +/* .IP \(bu +/* With "stop"-like commands, the iteration order is reversed, +/* and disabled instances are skipped. The full list of commands +/* is specified with the postmulti_stop_commands parameter. +/* .IP \(bu +/* With "reload" and other commands that require a started +/* instance, disabled instances are skipped. The full list of +/* commands is specified with the postmulti_control_commands +/* parameter. +/* .IP \(bu +/* With "status" and other commands that don't require a started +/* instance, the command is executed for all instances. +/* .RE +/* .IP +/* The \fB-p\fR option can also be used interactively to +/* start/stop/etc. a named instance or instance group. For +/* example, to start just the instances in the group "msa", +/* invoke \fBpostmulti\fR(1) as follows: +/* .RS +/* .IP +/* # postmulti -g msa -p start +/* .RE +/* .SH "Command mode" +/* .IP "\fB-x \fIunix-command\fR" +/* Execute the specified \fIunix-command\fR for all Postfix instances. +/* The command runs with appropriate environment settings for +/* MAIL_CONFIG, command_directory, daemon_directory, +/* config_directory, queue_directory, data_directory, +/* multi_instance_name, multi_instance_group and +/* multi_instance_enable. +/* .SH "Other options" +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple +/* \fB-v\fR options make the software increasingly verbose. +/* LIFE-CYCLE MANAGEMENT MODE +/* .ad +/* .fi +/* With the \fB-e\fR option \fBpostmulti\fR(1) can be used to +/* add or delete a Postfix instance, and to manage the +/* multi-instance status of an existing instance. +/* .PP +/* The following options are implemented: +/* .SH "Existing instance selection" +/* .IP \fB-a\fR +/* When creating or importing an instance, place the new +/* instance at the front of the secondary instance list. +/* .IP "\fB-g \fIgroup\fR" +/* When creating or importing an instance, place the new +/* instance before the first secondary instance that is a +/* member of the specified group. +/* .IP "\fB-i \fIname\fR" +/* When creating or importing an instance, place the new +/* instance before the matching secondary instance. +/* .sp +/* With other life-cycle operations, apply the operation to +/* the named existing instance. Specify "-" to select the +/* primary Postfix instance. +/* .SH "New or existing instance name assignment" +/* .IP "\fB-I \fIname\fR" +/* Assign the specified instance \fIname\fR to an existing +/* instance, newly-created instance, or imported instance. +/* Instance +/* names other than "-" (which makes the instance "nameless") +/* must start with "postfix-". This restriction reduces the +/* likelihood of name collisions with system files. +/* .IP "\fB-G \fIgroup\fR" +/* Assign the specified \fIgroup\fR name to an existing instance +/* or to a newly created or imported instance. +/* .SH "Instance creation/deletion/status change" +/* .IP "\fB-e \fIaction\fR" +/* "Edit" managed instances. The following actions are supported: +/* .RS +/* .IP \fBinit\fR +/* This command is required before \fBpostmulti\fR(1) can be +/* used to manage Postfix instances. The "postmulti -e init" +/* command updates the primary instance's main.cf file by +/* setting: +/* .RS +/* .IP +/* .nf +/* multi_instance_wrapper = +/* ${command_directory}/postmulti -p -- +/* multi_instance_enable = yes +/* .fi +/* .RE +/* .IP +/* You can set these by other means if you prefer. +/* .IP \fBcreate\fR +/* Create a new Postfix instance and add it to the +/* multi_instance_directories parameter of the primary instance. +/* The "\fB-I \fIname\fR" option is recommended to give the +/* instance a short name that is used to construct default +/* values for the private directories of the new instance. The +/* "\fB-G \fIgroup\fR" option may be specified to assign the +/* instance to a group, otherwise, the new instance is not a +/* member of any group. +/* .sp +/* The new instance main.cf is the stock main.cf with the +/* parameters that specify the locations of shared files cloned +/* from the primary instance. For "nameless" instances, you +/* should manually adjust "syslog_name" to yield a unique +/* "logtag" starting with "postfix-" that will uniquely identify +/* the instance in the mail logs. It is simpler to assign the +/* instance a short name with the "\fB-I \fIname\fR" option. +/* .sp +/* Optional "name=value" arguments specify the instance +/* config_directory, queue_directory and data_directory. +/* For example: +/* .RS +/* .IP +/* .nf +/* # postmulti -I postfix-mumble \e +/* -G mygroup -e create \e +/* config_directory=/my/config/dir \e +/* queue_directory=/my/queue/dir \e +/* data_directory=/my/data/dir +/* .fi +/* .RE +/* .IP +/* If any of these pathnames is not supplied, the program +/* attempts to generate the missing pathname(s) by taking the +/* corresponding primary instance pathname, and replacing the +/* last pathname component by the value of the \fB-I\fR option. +/* .sp +/* If the instance configuration directory already exists, and +/* contains both a main.cf and master.cf file, \fBcreate\fR +/* will "import" the instance as-is. For existing instances, +/* \fBcreate\fR and \fBimport\fR are identical. +/* .IP \fBimport\fR +/* Import an existing instance into the list of instances +/* managed by the \fBpostmulti\fR(1) multi-instance manager. +/* This adds the instance to the multi_instance_directories +/* list of the primary instance. If the "\fB-I \fIname\fR" +/* option is provided it specifies the new name for the instance +/* and is used to define a default location for the instance +/* configuration directory (as with \fBcreate\fR above). The +/* "\fB-G \fIgroup\fR" option may be used to assign the instance +/* to a group. Add a "\fBconfig_directory=\fI/path\fR" argument +/* to override a default pathname based on "\fB-I \fIname\fR". +/* .IP \fBdestroy\fR +/* Destroy a secondary Postfix instance. To be a candidate for +/* destruction an instance must be disabled, stopped and its +/* queue must not contain any messages. Attempts to destroy +/* the primary Postfix instance trigger a fatal error, without +/* destroying the instance. +/* .sp +/* The instance is removed from the primary instance main.cf +/* file's alternate_config_directories parameter and its data, +/* queue and configuration directories are cleaned of files +/* and directories created by the Postfix system. The main.cf +/* and master.cf files are removed from the configuration +/* directory even if they have been modified since initial +/* creation. Finally, the instance is "deported" from the list +/* of managed instances. +/* .sp +/* If other files are present in instance private directories, +/* the directories may not be fully removed, a warning is +/* logged to alert the administrator. It is expected that an +/* instance built using "fresh" directories via the \fBcreate\fR +/* action will be fully removed by the \fBdestroy\fR action +/* (if first disabled). If the instance configuration and queue +/* directories are populated with additional files (access and +/* rewriting tables, chroot jail content, etc.) the instance +/* directories will not be fully removed. +/* .sp +/* The \fBdestroy\fR action triggers potentially dangerous +/* file removal operations. Make sure the instance's data, +/* queue and configuration directories are set correctly and +/* do not contain any valuable files. +/* .IP \fBdeport\fR +/* Deport a secondary instance from the list of managed +/* instances. This deletes the instance configuration directory +/* from the primary instance's multi_instance_directories list, +/* but does not remove any files or directories. +/* .IP \fBassign\fR +/* Assign a new instance name or a new group name to the +/* selected instance. Use "\fB-G -\fR" to specify "no group" +/* and "\fB-I -\fR" to specify "no name". If you choose to +/* make an instance "nameless", set a suitable syslog_name in +/* the corresponding main.cf file. +/* .IP \fBenable\fR +/* Mark the selected instance as enabled. This just sets the +/* multi_instance_enable parameter to "yes" in the instance's +/* main.cf file. +/* .IP \fBdisable\fR +/* Mark the selected instance as disabled. This means that +/* the instance will not be started etc. with "postfix start", +/* "postmulti -p start" and so on. The instance can still be +/* started etc. with "postfix -c config-directory start". +/* .SH "Other options" +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple +/* \fB-v\fR options make the software increasingly verbose. +/* .RE +/* ENVIRONMENT +/* .ad +/* .fi +/* The \fBpostmulti\fR(1) command exports the following environment +/* variables before executing the requested \fIcommand\fR for a given +/* instance: +/* .IP \fBMAIL_VERBOSE\fR +/* This is set when the -v command-line option is present. +/* .IP \fBMAIL_CONFIG\fR +/* The location of the configuration directory of the instance. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix support programs and daemon programs. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment variables that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBmulti_instance_directories (empty)\fR" +/* An optional list of non-default Postfix configuration directories; +/* these directories belong to additional Postfix instances that share +/* the Postfix executable files and documentation with the default +/* Postfix instance, and that are started, stopped, etc., together +/* with the default Postfix instance. +/* .IP "\fBmulti_instance_group (empty)\fR" +/* The optional instance group name of this Postfix instance. +/* .IP "\fBmulti_instance_name (empty)\fR" +/* The optional instance name of this Postfix instance. +/* .IP "\fBmulti_instance_enable (no)\fR" +/* Allow this Postfix instance to be started, stopped, etc., by a +/* multi-instance manager. +/* .IP "\fBpostmulti_start_commands (start)\fR" +/* The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager treats +/* as "start" commands. +/* .IP "\fBpostmulti_stop_commands (see 'postconf -d' output)\fR" +/* The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager treats +/* as "stop" commands. +/* .IP "\fBpostmulti_control_commands (reload flush)\fR" +/* The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager +/* treats as "control" commands, that operate on running instances. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.0 and later: +/* .IP "\fBmeta_directory (see 'postconf -d' output)\fR" +/* The location of non-executable files that are shared among +/* multiple Postfix instances, such as postfix-files, dynamicmaps.cf, +/* and the multi-instance template files main.cf.proto and master.cf.proto. +/* .IP "\fBshlib_directory (see 'postconf -d' output)\fR" +/* The location of Postfix dynamically-linked libraries +/* (libpostfix-*.so), and the default location of Postfix database +/* plugins (postfix-*.so) that have a relative pathname in the +/* dynamicmaps.cf file. +/* FILES +/* $meta_directory/main.cf.proto, stock configuration file +/* $meta_directory/master.cf.proto, stock configuration file +/* $daemon_directory/postmulti-script, life-cycle helper program +/* SEE ALSO +/* postfix(1), Postfix control program +/* postfix-wrapper(5), Postfix multi-instance API +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or "\fBpostconf +/* html_directory\fR" to locate this information. +/* .nf +/* .na +/* MULTI_INSTANCE_README, Postfix multi-instance management +/* HISTORY +/* .ad +/* .fi +/* The \fBpostmulti\fR(1) command was introduced with Postfix +/* version 2.6. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Victor Duchovni +/* Morgan Stanley +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#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 +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * Configuration parameters, specific to postmulti(1). + */ +char *var_multi_start_cmds; +char *var_multi_stop_cmds; +char *var_multi_cntrl_cmds; + + /* + * Shared directory pathnames. + */ +typedef struct { + const char *param_name; + char **param_value; +} SHARED_PATH; + +static SHARED_PATH shared_dir_table[] = { + VAR_COMMAND_DIR, &var_command_dir, + VAR_DAEMON_DIR, &var_daemon_dir, + VAR_META_DIR, &var_meta_dir, + VAR_SHLIB_DIR, &var_shlib_dir, + 0, +}; + + /* + * Actions. + */ +#define ITER_CMD_POSTFIX (1<<0) /* postfix(1) iterator mode */ +#define ITER_CMD_LIST (1<<1) /* listing iterator mode */ +#define ITER_CMD_GENERIC (1<<2) /* generic command iterator mode */ + +#define ITER_CMD_MASK_ALL \ + (ITER_CMD_POSTFIX | ITER_CMD_LIST | ITER_CMD_GENERIC) + +#define EDIT_CMD_CREATE (1<<4) /* create new instance */ +#define EDIT_CMD_IMPORT (1<<5) /* import existing instance */ +#define EDIT_CMD_DESTROY (1<<6) /* destroy instance */ +#define EDIT_CMD_DEPORT (1<<7) /* export instance */ +#define EDIT_CMD_ENABLE (1<<8) /* enable start/stop */ +#define EDIT_CMD_DISABLE (1<<9) /* disable start/stop */ +#define EDIT_CMD_ASSIGN (1<<10) /* assign name/group */ +#define EDIT_CMD_INIT (1<<11) /* hook into main.cf */ + +#define EDIT_CMD_MASK_ADD (EDIT_CMD_CREATE | EDIT_CMD_IMPORT) +#define EDIT_CMD_MASK_DEL (EDIT_CMD_DESTROY | EDIT_CMD_DEPORT) +#define EDIT_CMD_MASK_ASSIGN (EDIT_CMD_MASK_ADD | EDIT_CMD_ASSIGN) +#define EDIT_CMD_MASK_ENB (EDIT_CMD_ENABLE | EDIT_CMD_DISABLE) +#define EDIT_CMD_MASK_ALL \ + (EDIT_CMD_MASK_ASSIGN | EDIT_CMD_MASK_DEL | EDIT_CMD_MASK_ENB | \ + EDIT_CMD_INIT) + + /* + * Edit command to number mapping, and vice versa. + */ +static NAME_CODE edit_command_table[] = { + "create", EDIT_CMD_CREATE, + "import", EDIT_CMD_IMPORT, + "destroy", EDIT_CMD_DESTROY, + "deport", EDIT_CMD_DEPORT, + "enable", EDIT_CMD_ENABLE, + "disable", EDIT_CMD_DISABLE, + "assign", EDIT_CMD_ASSIGN, + "init", EDIT_CMD_INIT, + 0, -1, +}; + +#define EDIT_CMD_CODE(str) \ + name_code(edit_command_table, NAME_CODE_FLAG_STRICT_CASE, (str)) +#define EDIT_CMD_STR(code) str_name_code(edit_command_table, (code)) + + /* + * Mandatory prefix for non-empty instance names. + */ +#ifndef NAME_PREFIX +#define NAME_PREFIX "postfix-" +#endif +#define HAS_NAME_PREFIX(name) \ + (strncmp((name), NAME_PREFIX, sizeof(NAME_PREFIX)-1) == 0) +#define NEED_NAME_PREFIX(name) \ + ((name) != 0 && strcmp((name), "-") != 0 && !HAS_NAME_PREFIX(name)) +#define NAME_SUFFIX(name) ((name) + sizeof(NAME_PREFIX) - 1) + + /* + * In-core instance structure. Only private information is kept here. + */ +typedef struct instance { + RING ring; /* linkage. */ + char *config_dir; /* private */ + char *queue_dir; /* private */ + char *data_dir; /* private */ + char *name; /* null or name */ + char *gname; /* null or group */ + int enabled; /* start/stop enable */ + int primary; /* special */ +} INSTANCE; + + /* + * Managed instance list (edit mode and iterator mode). + */ +static RING instance_hd[1]; /* instance list head */ + +#define RING_TO_INSTANCE(ring_ptr) RING_TO_APPL(ring_ptr, INSTANCE, ring) +#define RING_PTR_OF(x) (&((x)->ring)) + +#define FOREACH_INSTANCE(entry) \ + for ((entry) = instance_hd; \ + ((entry) = ring_succ(entry)) != instance_hd;) + +#define FOREACH_SECONDARY_INSTANCE(entry) \ + for ((entry) = ring_succ(instance_hd); \ + ((entry) = ring_succ(entry)) != instance_hd;) + +#define NEXT_ITERATOR_INSTANCE(flags, entry) \ + (((flags) & ITER_FLAG_REVERSE) ? ring_pred(entry) : ring_succ(entry)) + +#define FOREACH_ITERATOR_INSTANCE(flags, entry) \ + for ((entry) = instance_hd; \ + ((entry) = NEXT_ITERATOR_INSTANCE(flags, (entry))) != instance_hd;) + + /* + * Instance selection. One can either select all instances, select by + * instance name, or select by instance group. + */ +typedef struct { + int type; /* see below */ + char *name; /* undefined or name */ +} INST_SELECTION; + +#define INST_SEL_NONE 0 /* default: no selection */ +#define INST_SEL_ALL 1 /* select all instances */ +#define INST_SEL_NAME 2 /* select instance name */ +#define INST_SEL_GROUP 3 /* select instance group */ + + /* + * Instance name assignment. Each instance may be assigned an instance name + * (this must be globally unique within a multi-instance cluster) or an + * instance group name (this is intended to be shared). Externally, empty + * names may be represented as "-". Internally, we use "" only, to simplify + * the code. + */ +typedef struct { + char *name; /* null or assigned instance name */ + char *gname; /* null or assigned group name */ +} NAME_ASSIGNMENT; + + /* + * Iterator controls for non-edit commands. One can reverse the iteration + * order, or give special treatment to disabled instances. + */ +#define ITER_FLAG_DEFAULT 0 /* default setting */ +#define ITER_FLAG_REVERSE (1<<0) /* reverse iteration order */ +#define ITER_FLAG_CHECK_DISABLED (1<<1) /* check disabled instances */ +#define ITER_FLAG_SKIP_DISABLED (1<<2) /* skip disabled instances */ + + /* + * Environment export controls for edit commands. postmulti(1) exports only + * things that need to be updated. + */ +#define EXP_FLAG_MULTI_DIRS (1<<0) /* export multi_instance_directories */ +#define EXP_FLAG_MULTI_NAME (1<<1) /* export multi_instance_name */ +#define EXP_FLAG_MULTI_GROUP (1<<2) /* export multi_instance_group */ + + /* + * To detect conflicts, each instance name and each shared or private + * pathname is registered in one place, with its owner. Everyone must + * register their claims when they join, and will be rejected in case of + * conflict. + * + * Each claim value involves a parameter value (either a directory name or an + * instance name). Each claim owner is the config_directory pathname plus + * the parameter name. + * + * XXX: No multi.cf lock file, so this is not race-free. + */ +static HTABLE *claim_table; + +#define IS_CLAIMED_BY(name) \ + (claim_table ? htable_find(claim_table, (name)) : 0) + + /* + * Forward references. + */ +static int iterate_command(int, int, char **, INST_SELECTION *); +static int match_instance_selection(INSTANCE *, INST_SELECTION *); + + /* + * Convenience. + */ +#define INSTANCE_NAME(i) ((i)->name ? (i)->name : (i)->config_dir) +#define STR(buf) vstring_str(buf) + +/* register_claim - register claim or bust */ + +static void register_claim(const char *instance_path, const char *param_name, + const char *param_value) +{ + const char *myname = "register_claim"; + char *requestor; + const char *owner; + + /* + * Sanity checks. + */ + if (instance_path == 0 || *instance_path == 0) + msg_panic("%s: no or empty instance pathname", myname); + if (param_name == 0 || *param_name == 0) + msg_panic("%s: no or empty parameter name", myname); + if (param_value == 0) + msg_panic("%s: no parameter value", myname); + + /* + * Make a claim or report a conflict. + */ + if (claim_table == 0) + claim_table = htable_create(100); + requestor = concatenate(instance_path, ", ", param_name, (char *) 0); + if ((owner = htable_find(claim_table, param_value)) == 0) { + (void) htable_enter(claim_table, param_value, requestor); + } else if (strcmp(owner, requestor) == 0) { + myfree(requestor); + } else { + msg_fatal("instance %s, %s=%s conflicts with instance %s=%s", + instance_path, param_name, param_value, owner, param_value); + } +} + +/* claim_instance_attributes - claim multiple private instance attributes */ + +static void claim_instance_attributes(INSTANCE *ip) +{ + + /* + * Detect instance name or pathname conflicts between this instance and + * other instances. XXX: No multi.cf lock file, so this is not race-free. + */ + if (ip->name) + register_claim(ip->config_dir, VAR_MULTI_NAME, ip->name); + register_claim(ip->config_dir, VAR_CONFIG_DIR, ip->config_dir); + register_claim(ip->config_dir, VAR_QUEUE_DIR, ip->queue_dir); + register_claim(ip->config_dir, VAR_DATA_DIR, ip->data_dir); +} + +/* alloc_instance - allocate a single instance object */ + +static INSTANCE *alloc_instance(const char *config_dir) +{ + INSTANCE *ip = (INSTANCE *) mymalloc(sizeof(INSTANCE)); + + ring_init(RING_PTR_OF(ip)); + ip->config_dir = config_dir ? mystrdup(config_dir) : 0; + ip->queue_dir = 0; + ip->data_dir = 0; + ip->name = 0; + ip->gname = 0; + ip->enabled = 0; + ip->primary = 0; + + return (ip); +} + +#if 0 + +/* free_instance - free a single instance object */ + +static void free_instance(INSTANCE *ip) +{ + + /* + * If we continue after secondary main.cf file read error, we must be + * prepared for the case that some parameters may be missing. + */ + if (ip->name) + myfree(ip->name); + if (ip->gname) + myfree(ip->gname); + if (ip->config_dir) + myfree(ip->config_dir); + if (ip->queue_dir) + myfree(ip->queue_dir); + if (ip->data_dir) + myfree(ip->data_dir); + myfree((void *) ip); +} + +#endif + +/* insert_instance - insert instance before selected location, claim names */ + +static void insert_instance(INSTANCE *ip, INST_SELECTION *selection) +{ + RING *old; + +#define append_instance(ip) insert_instance((ip), (INST_SELECTION *) 0) + + /* + * Insert instance before the selected site. + */ + claim_instance_attributes(ip); + if (ring_succ(instance_hd) == 0) + ring_init(instance_hd); + if (selection && selection->type != INST_SEL_NONE) { + FOREACH_SECONDARY_INSTANCE(old) { + if (match_instance_selection(RING_TO_INSTANCE(old), selection)) { + ring_prepend(old, RING_PTR_OF(ip)); + return; + } + } + if (selection->type != INST_SEL_ALL) + msg_fatal("No matching secondary instances"); + } + ring_prepend(instance_hd, RING_PTR_OF(ip)); +} + +/* create_primary_instance - synthetic entry for primary instance */ + +static INSTANCE *create_primary_instance(void) +{ + INSTANCE *ip = alloc_instance(var_config_dir); + + /* + * There is no need to load primary instance parameter settings from + * file. We already have the main.cf parameters of interest in memory. + */ +#define SAVE_INSTANCE_NAME(val) (*(val) ? mystrdup(val) : 0) + + ip->name = SAVE_INSTANCE_NAME(var_multi_name); + ip->gname = SAVE_INSTANCE_NAME(var_multi_group); + ip->enabled = var_multi_enable; + ip->queue_dir = mystrdup(var_queue_dir); + ip->data_dir = mystrdup(var_data_dir); + ip->primary = 1; + return (ip); +} + +/* load_instance - read instance parameters from config_dir/main.cf */ + +static INSTANCE *load_instance(INSTANCE *ip) +{ + VSTREAM *pipe; + VSTRING *buf; + char *name; + char *value; + ARGV *cmd; + int count = 0; + static NAME_CODE bool_code[] = { + CONFIG_BOOL_YES, 1, + CONFIG_BOOL_NO, 0, + 0, -1, + }; + + /* + * Expand parameter values in the context of the target main.cf file. + */ +#define REQUEST_PARAM_COUNT 5 /* # of requested parameters */ + + cmd = argv_alloc(REQUEST_PARAM_COUNT + 3); + name = concatenate(var_command_dir, "/", "postconf", (char *) 0); + argv_add(cmd, name, "-xc", ip->config_dir, + VAR_QUEUE_DIR, VAR_DATA_DIR, + VAR_MULTI_NAME, VAR_MULTI_GROUP, VAR_MULTI_ENABLE, + (char *) 0); + myfree(name); + pipe = vstream_popen(O_RDONLY, CA_VSTREAM_POPEN_ARGV(cmd->argv), + CA_VSTREAM_POPEN_END); + argv_free(cmd); + if (pipe == 0) + msg_fatal("Cannot parse %s/main.cf file: %m", ip->config_dir); + + /* + * Read parameter settings from postconf. See also comments below on + * whether we should continue or skip groups after error instead of + * bailing out immediately. + */ + buf = vstring_alloc(100); + while (vstring_get_nonl(buf, pipe) != VSTREAM_EOF) { + if (split_nameval(STR(buf), &name, &value)) + msg_fatal("Invalid %s/main.cf parameter: %s", + ip->config_dir, STR(buf)); + if (strcmp(name, VAR_QUEUE_DIR) == 0 && ++count) + ip->queue_dir = mystrdup(value); + else if (strcmp(name, VAR_DATA_DIR) == 0 && ++count) + ip->data_dir = mystrdup(value); + else if (strcmp(name, VAR_MULTI_NAME) == 0 && ++count) + ip->name = SAVE_INSTANCE_NAME(value); + else if (strcmp(name, VAR_MULTI_GROUP) == 0 && ++count) + ip->gname = SAVE_INSTANCE_NAME(value); + else if (strcmp(name, VAR_MULTI_ENABLE) == 0 && ++count) { + /* mail_conf_bool(3) is case insensitive! */ + ip->enabled = name_code(bool_code, NAME_CODE_FLAG_NONE, value); + if (ip->enabled < 0) + msg_fatal("Unexpected %s/main.cf entry: %s = %s", + ip->config_dir, VAR_MULTI_ENABLE, value); + } + } + vstring_free(buf); + + /* + * XXX We should not bail out while reading a bad secondary main.cf file. + * When we manage dozens or more instances, the likelihood increases that + * some file will be damaged or missing after a system crash. That is not + * a good reason to prevent undamaged Postfix instances from starting. + */ + if (count != REQUEST_PARAM_COUNT) + msg_fatal("Failed to obtain all required %s/main.cf parameters", + ip->config_dir); + + if (vstream_pclose(pipe)) + msg_fatal("Cannot parse %s/main.cf file", ip->config_dir); + return (ip); +} + +/* load_all_instances - compute list of Postfix instances */ + +static void load_all_instances(void) +{ + INSTANCE *primary_instance; + char **cpp; + ARGV *secondary_names; + + /* + * Avoid unexpected behavior when $multi_instance_directories contains + * only comma characters. Count the actual number of elements, before we + * decide that the list is empty. + */ + secondary_names = argv_split(var_multi_conf_dirs, CHARS_COMMA_SP); + + /* + * First, the primary instance. This is synthesized out of thin air. + */ + primary_instance = create_primary_instance(); + if (secondary_names->argc == 0) + primary_instance->enabled = 1; /* Single-instance mode */ + append_instance(primary_instance); + + /* + * Next, instances defined in $multi_instance_directories. Note: + * load_instance() has side effects on the global config dictionary, but + * this does not affect the values that have already been extracted into + * C variables. + */ + for (cpp = secondary_names->argv; *cpp != 0; cpp++) + append_instance(load_instance(alloc_instance(*cpp))); + + argv_free(secondary_names); +} + +/* match_instance_selection - match all/name/group constraints */ + +static int match_instance_selection(INSTANCE *ip, INST_SELECTION *selection) +{ + char *iname; + char *name; + + /* + * When selecting (rather than assigning names) an instance, we match by + * the instance name, config_directory path, or the instance name suffix + * (name without mandatory prefix). Selecting "-" selects the primary + * instance. + */ + switch (selection->type) { + case INST_SEL_NONE: + return (0); + case INST_SEL_ALL: + return (1); + case INST_SEL_GROUP: + return (ip->gname != 0 && strcmp(selection->name, ip->gname) == 0); + case INST_SEL_NAME: + name = selection->name; + if (*name == '/' || ip->name == 0) + iname = ip->config_dir; + else if (!HAS_NAME_PREFIX(name) && HAS_NAME_PREFIX(ip->name)) + iname = NAME_SUFFIX(ip->name); + else + iname = ip->name; + return (strcmp(name, iname) == 0 + || (ip->primary && strcmp(name, "-") == 0)); + default: + msg_panic("match_instance_selection: unknown selection type: %d", + selection->type); + } +} + +/* check_setenv - setenv() with extreme prejudice */ + +static void check_setenv(const char *name, const char *value) +{ +#define CLOBBER 1 + if (setenv(name, value, CLOBBER) < 0) + msg_fatal("setenv: %m"); +} + +/* prepend_command_path - prepend command_directory to PATH */ + +static void prepend_command_path(void) +{ + char *cmd_path; + + /* + * Carefully prepend "$command_directory:" to PATH. We can free the + * buffer after check_setenv(), since the value is copied there. + */ + cmd_path = safe_getenv("PATH"); + cmd_path = concatenate(var_command_dir, ":", (cmd_path && *cmd_path) ? + cmd_path : ROOT_PATH, (char *) 0); + check_setenv("PATH", cmd_path); + myfree(cmd_path); +} + +/* check_shared_dir_status - check and claim shared directories */ + +static void check_shared_dir_status(void) +{ + struct stat st; + const SHARED_PATH *sp; + + /* + * XXX Avoid false conflicts with meta_directory. This usually overlaps + * with other directories, typically config_directory, shlib_directory or + * daemon_directory. + */ + for (sp = shared_dir_table; sp->param_name; ++sp) { + if (sp->param_value[0][0] != '/') /* "no" or other special */ + continue; + if (stat(sp->param_value[0], &st) < 0) + msg_fatal("%s = '%s': directory not found: %m", + sp->param_name, sp->param_value[0]); + if (!S_ISDIR(st.st_mode)) + msg_fatal("%s = '%s' is not a directory", + sp->param_name, sp->param_value[0]); + if (strcmp(sp->param_name, VAR_META_DIR) == 0) + continue; + register_claim(var_config_dir, sp->param_name, sp->param_value[0]); + } +} + +/* check_safe_name - allow instance or group name with only "safe" characters */ + +static int check_safe_name(const char *s) +{ +#define SAFE_PUNCT "!@%-_=+:./" + if (*s == 0) + return (0); + for (; *s; ++s) { + if (!ISALNUM(*s) && !strchr(SAFE_PUNCT, *s)) + return (0); + } + return (1); +} + +/* check_name_assignments - Check validity of assigned instance or group name */ + +static void check_name_assignments(NAME_ASSIGNMENT *assignment) +{ + + /* + * Syntax check the assigned instance name. This name is also used to + * generate directory pathnames, so we must not allow "/" characters. + * + * The value "" will clear the name and is always valid. The command-line + * parser has already converted "-" into "", to simplify implementation. + */ + if (assignment->name && *assignment->name) { + if (!check_safe_name(assignment->name)) + msg_fatal("Unsafe characters in new instance name: '%s'", + assignment->name); + if (strchr(assignment->name, '/')) + msg_fatal("Illegal '/' character in new instance name: '%s'", + assignment->name); + if (NEED_NAME_PREFIX(assignment->name)) + msg_fatal("New instance name must start with '%s'", + NAME_PREFIX); + } + + /* + * Syntax check the assigned group name. + */ + if (assignment->gname && *assignment->gname) { + if (!check_safe_name(assignment->gname)) + msg_fatal("Unsafe characters in '-G %s'", assignment->gname); + } +} + +/* do_name_assignments - assign instance/group names */ + +static int do_name_assignments(INSTANCE *target, NAME_ASSIGNMENT *assignment) +{ + int export_flags = 0; + + /* + * The command-line parser has already converted "-" into "", to simplify + * implementation. + */ + if (assignment->name + && strcmp(assignment->name, target->name ? target->name : "")) { + register_claim(target->config_dir, VAR_MULTI_NAME, assignment->name); + if (target->name) + myfree(target->name); + target->name = SAVE_INSTANCE_NAME(assignment->name); + export_flags |= EXP_FLAG_MULTI_NAME; + } + if (assignment->gname + && strcmp(assignment->gname, target->gname ? target->gname : "")) { + if (target->gname) + myfree(target->gname); + target->gname = SAVE_INSTANCE_NAME(assignment->gname); + export_flags |= EXP_FLAG_MULTI_GROUP; + } + return (export_flags); +} + +/* make_private_path - generate secondary pathname using primary as template */ + +static char *make_private_path(const char *param_name, + const char *primary_value, + NAME_ASSIGNMENT *assignment) +{ + char *path; + char *base; + char *end; + + /* + * The command-line parser has already converted "-" into "", to simplify + * implementation. + */ + if (assignment->name == 0 || *assignment->name == 0) + msg_fatal("Missing %s parameter value", param_name); + + if (*primary_value != '/') + msg_fatal("Invalid default %s parameter value: '%s': " + "specify an absolute pathname", + param_name, primary_value); + + base = mystrdup(primary_value); + if ((end = strrchr(base, '/')) != 0) { + /* Drop trailing slashes */ + if (end[1] == '\0') { + while (--end > base && *end == '/') + *end = '\0'; + end = strrchr(base, '/'); + } + /* Drop last path component */ + while (end > base && *end == '/') + *end-- = '\0'; + } + path = concatenate(base[1] ? base : "", "/", + assignment->name, (char *) 0); + myfree(base); + return (path); +} + +/* assign_new_parameter - assign new instance private name=value */ + +static void assign_new_parameter(INSTANCE *new, int edit_cmd, + const char *arg) +{ + char *saved_arg; + char *name; + char *value; + char *end; + char **target = 0; + + /* + * With "import", only config_directory is specified on the command line + * (either explicitly as config_directory=/path/name, or implicitly as + * instance name). The other private directory pathnames are taken from + * the existing instance's main.cf file. + * + * With "create", all private pathname parameters are specified on the + * command line, or generated from an instance name. + */ + saved_arg = mystrdup(arg); + if (split_nameval(saved_arg, &name, &value)) + msg_fatal("Malformed parameter setting '%s'", arg); + + if (strcmp(VAR_CONFIG_DIR, name) == 0) { + target = &new->config_dir; + } else if (edit_cmd != EDIT_CMD_IMPORT) { + if (strcmp(VAR_QUEUE_DIR, name) == 0) { + target = &new->queue_dir; + } else if (strcmp(VAR_DATA_DIR, name) == 0) { + target = &new->data_dir; + } + } + if (target == 0) + msg_fatal("Parameter '%s' not valid with action %s", + name, EDIT_CMD_STR(edit_cmd)); + + /* + * Extract and assign the parameter value. We do a limited number of + * checks here. Conflicts between instances are checked by the caller. + * More checks may be implemented in the helper script if inspired. + */ + if (*value != '/') + msg_fatal("Parameter setting '%s' is not an absolute path", name); + + /* Tolerate+trim trailing "/" from readline completion */ + for (end = value + strlen(value) - 1; end > value && *end == '/'; --end) + *end = 0; + + /* No checks here for "/." or other shoot-foot silliness. */ + if (end == value) + msg_fatal("Parameter setting '%s' is the root directory", name); + + if (*target) + myfree(*target); + *target = mystrdup(value); + + /* + * Cleanup. + */ + myfree(saved_arg); +} + +/* assign_new_parameters - initialize new instance private parameters */ + +static void assign_new_parameters(INSTANCE *new, int edit_cmd, + char **argv, NAME_ASSIGNMENT *assignment) +{ + const char *owner; + + /* + * Sanity check the explicit parameter settings. More stringent checks + * may take place in the helper script. + */ + while (*argv) + assign_new_parameter(new, edit_cmd, *argv++); + + /* + * Initialize any missing private directory pathnames, using the primary + * configuration directory parameter values as a template, and using the + * assigned instance name to fill in the blanks. + * + * When importing an existing instance, load private directory pathnames + * from its main.cf file. + */ + if (new->config_dir == 0) + new->config_dir = + make_private_path(VAR_CONFIG_DIR, var_config_dir, assignment); + /* Needed for better-quality error message. */ + if ((owner = IS_CLAIMED_BY(new->config_dir)) != 0) + msg_fatal("new %s=%s is already in use by instance %s=%s", + VAR_CONFIG_DIR, new->config_dir, owner, new->config_dir); + if (edit_cmd != EDIT_CMD_IMPORT) { + if (new->queue_dir == 0) + new->queue_dir = + make_private_path(VAR_QUEUE_DIR, var_queue_dir, assignment); + if (new->data_dir == 0) + new->data_dir = + make_private_path(VAR_DATA_DIR, var_data_dir, assignment); + } else { + load_instance(new); + } +} + +/* export_helper_environment - update environment settings for helper command */ + +static void export_helper_environment(INSTANCE *target, int export_flags) +{ + ARGV *import_env; + VSTRING *multi_dirs; + const SHARED_PATH *sp; + RING *entry; + + /* + * Environment import filter, to enforce consistent behavior whether this + * command is started by hand, or at system boot time. This is necessary + * because some shell scripts use environment settings to override + * main.cf settings. + */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + clean_env(import_env->argv); + argv_free(import_env); + + /* + * Prepend $command_directory: to PATH. This supposedly ensures that + * naive programs will execute commands from the right Postfix version. + */ + prepend_command_path(); + + /* + * The following ensures that Postfix's own programs will target the + * primary instance. + */ + check_setenv(CONF_ENV_PATH, var_config_dir); + + /* + * Export the parameter settings that are shared between instances. + */ + for (sp = shared_dir_table; sp->param_name; ++sp) + check_setenv(sp->param_name, sp->param_value[0]); + + /* + * Export the target instance's private directory locations. + */ + check_setenv(VAR_CONFIG_DIR, target->config_dir); + check_setenv(VAR_QUEUE_DIR, target->queue_dir); + check_setenv(VAR_DATA_DIR, target->data_dir); + + /* + * With operations that add or delete a secondary instance, we export the + * modified multi_instance_directories parameter value for the primary + * Postfix instance. + */ + if (export_flags & EXP_FLAG_MULTI_DIRS) { + multi_dirs = vstring_alloc(100); + FOREACH_SECONDARY_INSTANCE(entry) { + if (VSTRING_LEN(multi_dirs) > 0) + VSTRING_ADDCH(multi_dirs, ' '); + vstring_strcat(multi_dirs, RING_TO_INSTANCE(entry)->config_dir); + } + check_setenv(VAR_MULTI_CONF_DIRS, STR(multi_dirs)); + vstring_free(multi_dirs); + } + + /* + * Export updates for the instance name and group. Empty value (or no + * export) means don't update, "-" means clear. + */ + if (export_flags & EXP_FLAG_MULTI_NAME) + check_setenv(VAR_MULTI_NAME, target->name && *target->name ? + target->name : "-"); + + if (export_flags & EXP_FLAG_MULTI_GROUP) + check_setenv(VAR_MULTI_GROUP, target->gname && *target->gname ? + target->gname : "-"); + + /* + * If we would implement enable/disable commands by exporting the updated + * parameter value, then we could skip commands that have no effect, just + * like we can skip "assign" commands that make no change. + */ +} + +/* install_new_instance - install and return newly created instance */ + +static INSTANCE *install_new_instance(int edit_cmd, char **argv, + INST_SELECTION *selection, + NAME_ASSIGNMENT *assignment, + int *export_flags) +{ + INSTANCE *new; + + new = alloc_instance((char *) 0); + check_name_assignments(assignment); + assign_new_parameters(new, edit_cmd, argv, assignment); + *export_flags |= + (do_name_assignments(new, assignment) | EXP_FLAG_MULTI_DIRS); + insert_instance(new, selection); + return (new); +} + +/* update_instance - update existing instance, return export flags */ + +static int update_instance(INSTANCE *target, NAME_ASSIGNMENT *assignment) +{ + int export_flags; + + check_name_assignments(assignment); + export_flags = do_name_assignments(target, assignment); + return (export_flags); +} + +/* select_existing_instance - return instance selected for management */ + +static INSTANCE *select_existing_instance(INST_SELECTION *selection, + int unlink_flag, + int *export_flags) +{ + INSTANCE *selected = 0; + RING *entry; + INSTANCE *ip; + +#define DONT_UNLINK 0 +#define DO_UNLINK 1 + + if (selection->type != INST_SEL_NAME) + msg_fatal("Select an instance via '-i name'"); + + /* Find the selected instance and its predecessor */ + FOREACH_INSTANCE(entry) { + if (match_instance_selection(ip = RING_TO_INSTANCE(entry), selection)) { + selected = ip; + break; + } + } + + if (selected == 0) + msg_fatal("No instance named %s", selection->name); + + if (unlink_flag) { + /* Splice the target instance out of the list */ + if (ring_pred(entry) == instance_hd) + msg_fatal("Cannot remove the primary instance"); + if (selected->enabled) + msg_fatal("Cannot remove enabled instances"); + ring_detach(entry); + if (export_flags == 0) + msg_panic("select_existing_instance: no export flags"); + *export_flags |= EXP_FLAG_MULTI_DIRS; + } + return (selected); +} + +/* manage - create/destroy/... manage instances */ + +static NORETURN manage(int edit_cmd, int argc, char **argv, + INST_SELECTION *selection, + NAME_ASSIGNMENT *assignment) +{ + char *cmd; + INSTANCE *target; + int export_flags; + + /* + * Edit mode is not subject to iterator controls. + */ +#define NO_EXPORT_FLAGS ((int *) 0) + export_flags = 0; + + switch (edit_cmd) { + case EDIT_CMD_INIT: + target = create_primary_instance(); + break; + + case EDIT_CMD_CREATE: + case EDIT_CMD_IMPORT: + load_all_instances(); + target = install_new_instance(edit_cmd, argv, selection, + assignment, &export_flags); + break; + + case EDIT_CMD_ASSIGN: + load_all_instances(); + target = + select_existing_instance(selection, DONT_UNLINK, NO_EXPORT_FLAGS); + export_flags |= update_instance(target, assignment); + if (export_flags == 0) + exit(0); + break; + + case EDIT_CMD_DESTROY: + case EDIT_CMD_DEPORT: + load_all_instances(); + target = select_existing_instance(selection, DO_UNLINK, &export_flags); + break; + + default: + load_all_instances(); + target = + select_existing_instance(selection, DONT_UNLINK, NO_EXPORT_FLAGS); + break; + } + + /* + * Set up the helper script's process environment, and execute the helper + * script. + */ +#define HELPER "postmulti-script" + + export_helper_environment(target, export_flags); + cmd = concatenate(var_daemon_dir, "/" HELPER, (char *) 0); + execl(cmd, cmd, "-e", EDIT_CMD_STR(edit_cmd), (char *) 0); + msg_fatal("%s: %m", cmd); +} + +/* run_user_command - execute external command with requested MAIL_CONFIG env */ + +static int run_user_command(INSTANCE *ip, int iter_cmd, int iter_flags, + char **argv) +{ + WAIT_STATUS_T status; + int pid; + int wpid; + + /* + * Set up a process environment. The postfix(1) command needs MAIL_CONFIG + * (or the equivalent command-line option); it overrides everything else. + * + * postmulti(1) typically runs various Postfix utilities (postsuper, ...) in + * the context of one or more instances. It can also run various scripts + * on the users PATH. So we can't clobber the user's PATH, but do want to + * make sure that the utilities in $command_directory are always found in + * the right place (or at all). + */ + switch (pid = fork()) { + case -1: + msg_warn("fork %s: %m", argv[0]); + return -1; + case 0: + check_setenv(CONF_ENV_PATH, ip->config_dir); + if (iter_cmd != ITER_CMD_POSTFIX) { + check_setenv(VAR_DAEMON_DIR, var_daemon_dir); + check_setenv(VAR_COMMAND_DIR, var_command_dir); + check_setenv(VAR_CONFIG_DIR, ip->config_dir); + check_setenv(VAR_QUEUE_DIR, ip->queue_dir); + check_setenv(VAR_DATA_DIR, ip->data_dir); + check_setenv(VAR_MULTI_NAME, ip->name ? ip->name : ""); + check_setenv(VAR_MULTI_GROUP, ip->gname ? ip->gname : ""); + check_setenv(VAR_MULTI_ENABLE, ip->enabled ? + CONFIG_BOOL_YES : CONFIG_BOOL_NO); + prepend_command_path(); + } + + /* + * Replace: postfix -- start ... With: postfix -- check ... + */ + if (iter_cmd == ITER_CMD_POSTFIX + && (iter_flags & ITER_FLAG_CHECK_DISABLED) && !ip->enabled) + argv[2] = "check"; + + execvp(argv[0], argv); + msg_fatal("execvp %s: %m", argv[0]); + default: + do { + wpid = waitpid(pid, &status, 0); + } while (wpid == -1 && errno == EINTR); + return (wpid == -1 ? -1 : + WIFEXITED(status) ? WEXITSTATUS(status) : 1); + } +} + +/* word_in_list - look up command in start, stop, or control list */ + +static int word_in_list(char *cmdlist, const char *cmd) +{ + char *saved; + char *cp; + char *elem; + + cp = saved = mystrdup(cmdlist); + while ((elem = mystrtok(&cp, CHARS_COMMA_SP)) != 0 && strcmp(elem, cmd) != 0) + /* void */ ; + myfree(saved); + return (elem != 0); +} + +/* iterate_postfix_command - execute postfix(1) command */ + +static int iterate_postfix_command(int iter_cmd, int argc, char **argv, + INST_SELECTION *selection) +{ + int exit_status; + char *cmd; + ARGV *my_argv; + int iter_flags; + + /* + * Override the iterator controls. + */ + if (word_in_list(var_multi_start_cmds, argv[0])) { + iter_flags = ITER_FLAG_CHECK_DISABLED; + } else if (word_in_list(var_multi_stop_cmds, argv[0])) { + iter_flags = ITER_FLAG_SKIP_DISABLED | ITER_FLAG_REVERSE; + } else if (word_in_list(var_multi_cntrl_cmds, argv[0])) { + iter_flags = ITER_FLAG_SKIP_DISABLED; + } else { + iter_flags = 0; + } + + /* + * Override the command line in a straightforward manner: prepend + * "postfix --" to the command arguments. Other overrides (environment, + * start -> check) are implemented below the iterator. + */ +#define POSTFIX_CMD "postfix" + + my_argv = argv_alloc(argc + 2); + cmd = concatenate(var_command_dir, "/" POSTFIX_CMD, (char *) 0); + argv_add(my_argv, cmd, "--", (char *) 0); + myfree(cmd); + while (*argv) + argv_add(my_argv, *argv++, (char *) 0); + + /* + * Execute the command for all applicable Postfix instances. + */ + exit_status = + iterate_command(iter_cmd, iter_flags, my_argv->argv, selection); + + argv_free(my_argv); + return (exit_status); +} + +/* list_instances - list all selected instances */ + +static void list_instances(int iter_flags, INST_SELECTION *selection) +{ + RING *entry; + INSTANCE *ip; + + /* + * Iterate over the selected instances. + */ + FOREACH_ITERATOR_INSTANCE(iter_flags, entry) { + ip = RING_TO_INSTANCE(entry); + if (match_instance_selection(ip, selection)) + vstream_printf("%-15s %-15s %-9s %s\n", + ip->name ? ip->name : "-", + ip->gname ? ip->gname : "-", + ip->enabled ? "y" : "n", + ip->config_dir); + } + if (vstream_fflush(VSTREAM_OUT)) + msg_fatal("error writing output: %m"); +} + +/* iterate_command - execute command for selected instances */ + +static int iterate_command(int iter_cmd, int iter_flags, char **argv, + INST_SELECTION *selection) +{ + int exit_status = 0; + int matched = 0; + RING *entry; + INSTANCE *ip; + + /* + * Iterate over the selected instances. + */ + FOREACH_ITERATOR_INSTANCE(iter_flags, entry) { + ip = RING_TO_INSTANCE(entry); + if ((iter_flags & ITER_FLAG_SKIP_DISABLED) && !ip->enabled) + continue; + if (!match_instance_selection(ip, selection)) + continue; + matched = 1; + + /* Run the requested command */ + if (run_user_command(ip, iter_cmd, iter_flags, argv) != 0) + exit_status = 1; + } + if (matched == 0) + msg_fatal("No matching instances"); + + return (exit_status); +} + +/* iterate - Iterate over all or selected instances */ + +static NORETURN iterate(int iter_cmd, int iter_flags, int argc, char **argv, + INST_SELECTION *selection) +{ + int exit_status; + + /* + * In iterator mode, no selection means wild-card selection. + */ + if (selection->type == INST_SEL_NONE) + selection->type = INST_SEL_ALL; + + /* + * Load the in-memory instance table from main.cf files. + */ + load_all_instances(); + + /* + * Iterate over the selected instances. + */ + switch (iter_cmd) { + case ITER_CMD_POSTFIX: + exit_status = iterate_postfix_command(iter_cmd, argc, argv, selection); + break; + case ITER_CMD_LIST: + list_instances(iter_flags, selection); + exit_status = 0; + break; + case ITER_CMD_GENERIC: + exit_status = iterate_command(iter_cmd, iter_flags, argv, selection); + break; + default: + msg_panic("iterate: unknown mode: %d", iter_cmd); + } + exit(exit_status); +} + +static NORETURN usage(const char *progname) +{ + msg_fatal("Usage:" + "%s -l [-v] [-a] [-g group] [-i instance] | " + "%s -p [-v] [-a] [-g group] [-i instance] command... | " + "%s -x [-v] [-a] [-i name] [-g group] command... | " + "%s -e action [-v] [-a] [-i name] [-g group] [-I name] " + "[-G group] [param=value ...]", + progname, progname, progname, progname); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - iterate commands over multiple instance or manage instances */ + +int main(int argc, char **argv) +{ + int fd; + struct stat st; + char *slash; + char *config_dir; + int ch; + static const CONFIG_STR_TABLE str_table[] = { + VAR_MULTI_START_CMDS, DEF_MULTI_START_CMDS, &var_multi_start_cmds, 0, 0, + VAR_MULTI_STOP_CMDS, DEF_MULTI_STOP_CMDS, &var_multi_stop_cmds, 0, 0, + VAR_MULTI_CNTRL_CMDS, DEF_MULTI_CNTRL_CMDS, &var_multi_cntrl_cmds, 0, 0, + 0, + }; + int instance_select_count = 0; + int command_mode_count = 0; + INST_SELECTION selection; + NAME_ASSIGNMENT assignment; + int iter_flags = ITER_FLAG_DEFAULT; + int cmd_mode = 0; + int code; + + selection.type = INST_SEL_NONE; + assignment.name = assignment.gname = 0; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Be consistent with file permissions. + */ + umask(022); + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Set up diagnostics. XXX What if stdin is the system console during + * boot time? It seems a bad idea to log startup errors to the console. + * This is UNIX, a system that can run without hand holding. + */ + if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) + argv[0] = slash + 1; + if (isatty(STDERR_FILENO)) + msg_vstream_init(argv[0], VSTREAM_ERR); + maillog_client_init(argv[0], MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Process main.cf parameters. This is done before the GETOPT() loop to + * improve logging. This assumes that no command-line option can affect + * parameter processing. + */ + mail_conf_read(); + get_mail_conf_str_table(str_table); + maillog_client_init(argv[0], MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + + if ((config_dir = getenv(CONF_ENV_PATH)) != 0 + && strcmp(config_dir, DEF_CONFIG_DIR) != 0) + msg_fatal("Non-default configuration directory: %s=%s", + CONF_ENV_PATH, config_dir); + + /* + * Parse switches. Move the above mail_conf_read() block after this loop, + * if any command-line option can affect parameter processing. + */ + while ((ch = GETOPT(argc, argv, "ae:g:i:G:I:lpRvx")) > 0) { + switch (ch) { + default: + usage(argv[0]); + /* NOTREACHED */ + case 'a': + if (selection.type != INST_SEL_ALL) + instance_select_count++; + selection.type = INST_SEL_ALL; + break; + case 'e': + if ((code = EDIT_CMD_CODE(optarg)) < 0) + msg_fatal("Invalid '-e' edit action '%s'. Specify '%s', " + "'%s', '%s', '%s', '%s', '%s', '%s' or '%s'", + optarg, + EDIT_CMD_STR(EDIT_CMD_CREATE), + EDIT_CMD_STR(EDIT_CMD_DESTROY), + EDIT_CMD_STR(EDIT_CMD_IMPORT), + EDIT_CMD_STR(EDIT_CMD_DEPORT), + EDIT_CMD_STR(EDIT_CMD_ENABLE), + EDIT_CMD_STR(EDIT_CMD_DISABLE), + EDIT_CMD_STR(EDIT_CMD_ASSIGN), + EDIT_CMD_STR(EDIT_CMD_INIT)); + if (cmd_mode != code) + command_mode_count++; + cmd_mode = code; + break; + case 'g': + instance_select_count++; + selection.type = INST_SEL_GROUP; + selection.name = optarg; + break; + case 'i': + instance_select_count++; + selection.type = INST_SEL_NAME; + selection.name = optarg; + break; + case 'G': + if (assignment.gname != 0) + msg_fatal("Specify at most one '-G' option"); + assignment.gname = strcmp(optarg, "-") == 0 ? "" : optarg; + break; + case 'I': + if (assignment.name != 0) + msg_fatal("Specify at most one '-I' option"); + assignment.name = strcmp(optarg, "-") == 0 ? "" : optarg; + break; + case 'l': + if (cmd_mode != ITER_CMD_LIST) + command_mode_count++; + cmd_mode = ITER_CMD_LIST; + break; + case 'p': + if (cmd_mode != ITER_CMD_POSTFIX) + command_mode_count++; + cmd_mode = ITER_CMD_POSTFIX; + break; + case 'R': + iter_flags ^= ITER_FLAG_REVERSE; + break; + case 'v': + msg_verbose++; + check_setenv(CONF_ENV_VERB, ""); + break; + case 'x': + if (cmd_mode != ITER_CMD_GENERIC) + command_mode_count++; + cmd_mode = ITER_CMD_GENERIC; + break; + } + } + + /* + * Report missing arguments, or wrong arguments in the wrong context. + */ + if (instance_select_count > 1) + msg_fatal("Specity no more than one of '-a', '-g', '-i'"); + + if (command_mode_count != 1) + msg_fatal("Specify exactly one of '-e', '-l', '-p', '-x'"); + + if (cmd_mode == ITER_CMD_LIST && argc > optind) + msg_fatal("Command not allowed with '-l'"); + + if (cmd_mode == ITER_CMD_POSTFIX || cmd_mode == ITER_CMD_GENERIC) + if (argc == optind) + msg_fatal("Command required with '-p' or '-x' option"); + + if (cmd_mode == ITER_CMD_POSTFIX || (cmd_mode & EDIT_CMD_MASK_ALL)) + if (iter_flags != ITER_FLAG_DEFAULT) + msg_fatal("The '-p' and '-e' options preclude the use of '-R'"); + + if ((cmd_mode & EDIT_CMD_MASK_ASSIGN) == 0 + && (assignment.name || assignment.gname)) { + if ((cmd_mode & EDIT_CMD_MASK_ALL) == 0) + msg_fatal("Cannot assign instance name or group without '-e %s'", + EDIT_CMD_STR(EDIT_CMD_ASSIGN)); + else + msg_fatal("Cannot assign instance name or group with '-e %s'", + EDIT_CMD_STR(cmd_mode)); + } + if (cmd_mode & EDIT_CMD_MASK_ALL) { + if (cmd_mode == EDIT_CMD_ASSIGN + && (assignment.name == 0 && assignment.gname == 0)) + msg_fatal("Specify new instance name or group with '-e %s'", + EDIT_CMD_STR(cmd_mode)); + + if ((cmd_mode & ~EDIT_CMD_MASK_ADD) != 0 && argc > optind) + msg_fatal("Parameter overrides not valid with '-e %s'", + EDIT_CMD_STR(cmd_mode)); + } + + /* + * Sanity checks. + */ + check_shared_dir_status(); + + /* + * Iterate over selected instances, or manipulate one instance. + */ + if (cmd_mode & ITER_CMD_MASK_ALL) + iterate(cmd_mode, iter_flags, argc - optind, argv + optind, &selection); + else + manage(cmd_mode, argc - optind, argv + optind, &selection, &assignment); +} diff --git a/src/postqueue/.indent.pro b/src/postqueue/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postqueue/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postqueue/Makefile.in b/src/postqueue/Makefile.in new file mode 100644 index 0000000..f01c3ae --- /dev/null +++ b/src/postqueue/Makefile.in @@ -0,0 +1,133 @@ +SHELL = /bin/sh +SRCS = postqueue.c showq_compat.c showq_json.c +OBJS = postqueue.o showq_compat.o showq_json.o +HDRS = postqueue.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = postqueue +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out postqueue.h + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postqueue.o: ../../include/argv.h +postqueue.o: ../../include/attr.h +postqueue.o: ../../include/check_arg.h +postqueue.o: ../../include/clean_env.h +postqueue.o: ../../include/connect.h +postqueue.o: ../../include/events.h +postqueue.o: ../../include/flush_clnt.h +postqueue.o: ../../include/htable.h +postqueue.o: ../../include/iostuff.h +postqueue.o: ../../include/mail_conf.h +postqueue.o: ../../include/mail_dict.h +postqueue.o: ../../include/mail_flush.h +postqueue.o: ../../include/mail_params.h +postqueue.o: ../../include/mail_parm_split.h +postqueue.o: ../../include/mail_proto.h +postqueue.o: ../../include/mail_queue.h +postqueue.o: ../../include/mail_run.h +postqueue.o: ../../include/mail_task.h +postqueue.o: ../../include/mail_version.h +postqueue.o: ../../include/maillog_client.h +postqueue.o: ../../include/msg.h +postqueue.o: ../../include/msg_vstream.h +postqueue.o: ../../include/mymalloc.h +postqueue.o: ../../include/nvtable.h +postqueue.o: ../../include/safe.h +postqueue.o: ../../include/smtp_stream.h +postqueue.o: ../../include/stringops.h +postqueue.o: ../../include/sys_defs.h +postqueue.o: ../../include/user_acl.h +postqueue.o: ../../include/valid_hostname.h +postqueue.o: ../../include/valid_mailhost_addr.h +postqueue.o: ../../include/vbuf.h +postqueue.o: ../../include/vstream.h +postqueue.o: ../../include/vstring.h +postqueue.o: ../../include/warn_stat.h +postqueue.o: postqueue.c +postqueue.o: postqueue.h +showq_compat.o: ../../include/attr.h +showq_compat.o: ../../include/check_arg.h +showq_compat.o: ../../include/htable.h +showq_compat.o: ../../include/iostuff.h +showq_compat.o: ../../include/mail_date.h +showq_compat.o: ../../include/mail_params.h +showq_compat.o: ../../include/mail_proto.h +showq_compat.o: ../../include/mail_queue.h +showq_compat.o: ../../include/msg.h +showq_compat.o: ../../include/mymalloc.h +showq_compat.o: ../../include/nvtable.h +showq_compat.o: ../../include/stringops.h +showq_compat.o: ../../include/sys_defs.h +showq_compat.o: ../../include/vbuf.h +showq_compat.o: ../../include/vstream.h +showq_compat.o: ../../include/vstring.h +showq_compat.o: postqueue.h +showq_compat.o: showq_compat.c +showq_json.o: ../../include/attr.h +showq_json.o: ../../include/check_arg.h +showq_json.o: ../../include/htable.h +showq_json.o: ../../include/iostuff.h +showq_json.o: ../../include/mail_date.h +showq_json.o: ../../include/mail_params.h +showq_json.o: ../../include/mail_proto.h +showq_json.o: ../../include/mail_queue.h +showq_json.o: ../../include/msg.h +showq_json.o: ../../include/mymalloc.h +showq_json.o: ../../include/nvtable.h +showq_json.o: ../../include/stringops.h +showq_json.o: ../../include/sys_defs.h +showq_json.o: ../../include/vbuf.h +showq_json.o: ../../include/vstream.h +showq_json.o: ../../include/vstring.h +showq_json.o: postqueue.h +showq_json.o: showq_json.c diff --git a/src/postqueue/postqueue.c b/src/postqueue/postqueue.c new file mode 100644 index 0000000..09e5bca --- /dev/null +++ b/src/postqueue/postqueue.c @@ -0,0 +1,727 @@ +/*++ +/* NAME +/* postqueue 1 +/* SUMMARY +/* Postfix queue control +/* SYNOPSIS +/* .ti -4 +/* \fBTo flush the mail queue\fR: +/* +/* \fBpostqueue\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR] \fB-f\fR +/* +/* \fBpostqueue\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR] \fB-i \fIqueue_id\fR +/* +/* \fBpostqueue\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR] \fB-s \fIsite\fR +/* +/* .ti -4 +/* \fBTo list the mail queue\fR: +/* +/* \fBpostqueue\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR] \fB-j\fR +/* +/* \fBpostqueue\fR [\fB-v\fR] [\fB-c \fIconfig_dir\fR] \fB-p\fR +/* DESCRIPTION +/* The \fBpostqueue\fR(1) command implements the Postfix user interface +/* for queue management. It implements operations that are +/* traditionally available via the \fBsendmail\fR(1) command. +/* See the \fBpostsuper\fR(1) command for queue operations +/* that require super-user privileges such as deleting a message +/* from the queue or changing the status of a message. +/* +/* The following options are recognized: +/* .IP "\fB-c \fIconfig_dir\fR" +/* The \fBmain.cf\fR configuration file is in the named directory +/* instead of the default configuration directory. See also the +/* MAIL_CONFIG environment setting below. +/* .IP \fB-f\fR +/* Flush the queue: attempt to deliver all queued mail. +/* +/* This option implements the traditional "\fBsendmail -q\fR" command, +/* by contacting the Postfix \fBqmgr\fR(8) daemon. +/* +/* Warning: flushing undeliverable mail frequently will result in +/* poor delivery performance of all other mail. +/* .IP "\fB-i \fIqueue_id\fR" +/* Schedule immediate delivery of deferred mail with the +/* specified queue ID. +/* +/* This option implements the traditional \fBsendmail -qI\fR +/* command, by contacting the \fBflush\fR(8) server. +/* +/* This feature is available with Postfix version 2.4 and later. +/* .IP "\fB-j\fR" +/* Produce a queue listing in JSON format, based on output +/* from the showq(8) daemon. The result is a stream of zero +/* or more JSON objects, one per queue file. Each object is +/* followed by a newline character to support simple streaming +/* parsers. See "\fBJSON OBJECT FORMAT\fR" below for details. +/* +/* This feature is available in Postfix 3.1 and later. +/* .IP \fB-p\fR +/* Produce a traditional sendmail-style queue listing. +/* This option implements the traditional \fBmailq\fR command, +/* by contacting the Postfix \fBshowq\fR(8) daemon. +/* +/* Each queue entry shows the queue file ID, message +/* size, arrival time, sender, and the recipients that still need to +/* be delivered. If mail could not be delivered upon the last attempt, +/* the reason for failure is shown. The queue ID string +/* is followed by an optional status character: +/* .RS +/* .IP \fB*\fR +/* The message is in the \fBactive\fR queue, i.e. the message is +/* selected for delivery. +/* .IP \fB!\fR +/* The message is in the \fBhold\fR queue, i.e. no further delivery +/* attempt will be made until the mail is taken off hold. +/* .IP \fB#\fR +/* The message is forced to expire. See the \fBpostsuper\fR(1) +/* options \fB-e\fR or \fB-f\fR. +/* .sp +/* This feature is available in Postfix 3.5 and later. +/* .RE +/* .IP "\fB-s \fIsite\fR" +/* Schedule immediate delivery of all mail that is queued for the named +/* \fIsite\fR. A numerical site must be specified as a valid RFC 5321 +/* address literal enclosed in [], just like in email addresses. +/* The site must be eligible for the "fast flush" service. +/* See \fBflush\fR(8) for more information about the "fast flush" +/* service. +/* +/* This option implements the traditional "\fBsendmail -qR\fIsite\fR" +/* command, by contacting the Postfix \fBflush\fR(8) daemon. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. As of Postfix 2.3, +/* this option is available for the super-user only. +/* JSON OBJECT FORMAT +/* .ad +/* .fi +/* Each JSON object represents one queue file; it is emitted +/* as a single text line followed by a newline character. +/* +/* Object members have string values unless indicated otherwise. +/* Programs should ignore object members that are not listed +/* here; the list of members is expected to grow over time. +/* .IP \fBqueue_name\fR +/* The name of the queue where the message was found. Note +/* that the contents of the mail queue may change while it is +/* being listed; some messages may appear more than once, and +/* some messages may be missed. +/* .IP \fBqueue_id\fR +/* The queue file name. The queue_id may be reused within a +/* Postfix instance unless "enable_long_queue_ids = true" and +/* time is monotonic. Even then, the queue_id is not expected +/* to be unique between different Postfix instances. Management +/* tools that require a unique name should combine the queue_id +/* with the myhostname setting of the Postfix instance. +/* .IP \fBarrival_time\fR +/* The number of seconds since the start of the UNIX epoch. +/* .IP \fBmessage_size\fR +/* The number of bytes in the message header and body. This +/* number does not include message envelope information. It +/* is approximately equal to the number of bytes that would +/* be transmitted via SMTP including the line endings. +/* .IP \fBforced_expire\fR +/* The message is forced to expire (\fBtrue\fR or \fBfalse\fR). +/* See the \fBpostsuper\fR(1) options \fB-e\fR or \fB-f\fR. +/* .sp +/* This feature is available in Postfix 3.5 and later. +/* .IP \fBsender\fR +/* The envelope sender address. +/* .IP \fBrecipients\fR +/* An array containing zero or more objects with members: +/* .RS +/* .IP \fBaddress\fR +/* One recipient address. +/* .IP \fBdelay_reason\fR +/* If present, the reason for delayed delivery. Delayed +/* recipients may have no delay reason, for example, while +/* delivery is in progress, or after the system was stopped +/* before it could record the reason. +/* .RE +/* SECURITY +/* .ad +/* .fi +/* This program is designed to run with set-group ID privileges, so +/* that it can connect to Postfix daemon processes. +/* STANDARDS +/* RFC 7159 (JSON notation) +/* DIAGNOSTICS +/* Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8), +/* and to the standard error stream. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP MAIL_CONFIG +/* Directory with the \fBmain.cf\fR file. In order to avoid exploitation +/* of set-group ID privileges, a non-standard directory is allowed only +/* if: +/* .RS +/* .IP \(bu +/* The name is listed in the standard \fBmain.cf\fR file with the +/* \fBalternate_config_directories\fR configuration parameter. +/* .IP \(bu +/* The command is invoked by the super-user. +/* .RE +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBalternate_config_directories (empty)\fR" +/* A list of non-default Postfix configuration directories that may +/* be specified with "-c config_directory" on the command line (in the +/* case of \fBsendmail\fR(1), with the "-C" option), or via the MAIL_CONFIG +/* environment parameter. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR" +/* The location of all postfix administrative commands. +/* .IP "\fBfast_flush_domains ($relay_domains)\fR" +/* Optional list of destinations that are eligible for per-destination +/* logfiles with mail that is queued to those destinations. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment variables that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .IP "\fBtrigger_timeout (10s)\fR" +/* The time limit for sending a trigger to a Postfix daemon (for +/* example, the \fBpickup\fR(8) or \fBqmgr\fR(8) daemon). +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBauthorized_flush_users (static:anyone)\fR" +/* List of users who are authorized to flush the queue. +/* .IP "\fBauthorized_mailq_users (static:anyone)\fR" +/* List of users who are authorized to view the queue. +/* FILES +/* /var/spool/postfix, mail queue +/* SEE ALSO +/* qmgr(8), queue manager +/* showq(8), list mail queue +/* flush(8), fast flush service +/* sendmail(1), Sendmail-compatible user interface +/* postsuper(1), privileged queue operations +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* ETRN_README, Postfix ETRN howto +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* The postqueue command was introduced with Postfix version 1.1. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include + + /* + * WARNING WARNING WARNING + * + * This software is designed to run set-gid. In order to avoid exploitation of + * privilege, this software should not run any external commands, nor should + * it take any information from the user, unless that information can be + * properly sanitized. To get an idea of how much information a process can + * inherit from a potentially hostile user, examine all the members of the + * process structure (typically, in /usr/include/sys/proc.h): the current + * directory, open files, timers, signals, environment, command line, umask, + * and so on. + */ + + /* + * Modes of operation. + * + * XXX To support flush by recipient domain, or for destinations that have no + * mapping to logfile, the server has to defend against resource exhaustion + * attacks. A malicious user could fork off a postqueue client that starts + * an expensive requests and then kills the client immediately; this way she + * could create a high Postfix load on the system without ever exceeding her + * own per-user process limit. To prevent this, either the server needs to + * establish frequent proof of client liveliness with challenge/response, or + * the client needs to restrict expensive requests to privileged users only. + * + * We don't have this problem with queue listings. The showq server detects an + * EPIPE error after reporting a few queue entries. + */ +#define PQ_MODE_DEFAULT 0 /* noop */ +#define PQ_MODE_MAILQ_LIST 1 /* list mail queue */ +#define PQ_MODE_FLUSH_QUEUE 2 /* flush queue */ +#define PQ_MODE_FLUSH_SITE 3 /* flush site */ +#define PQ_MODE_FLUSH_FILE 4 /* flush message */ +#define PQ_MODE_JSON_LIST 5 /* JSON-format queue listing */ + + /* + * Silly little macros (SLMs). + */ +#define STR vstring_str + + /* + * Queue manipulation access lists. + */ +char *var_flush_acl; +char *var_showq_acl; + +static const CONFIG_STR_TABLE str_table[] = { + VAR_FLUSH_ACL, DEF_FLUSH_ACL, &var_flush_acl, 0, 0, + VAR_SHOWQ_ACL, DEF_SHOWQ_ACL, &var_showq_acl, 0, 0, + 0, +}; + +/* showq_client - run the appropriate showq protocol client */ + +static void showq_client(int mode, VSTREAM *showq) +{ + if (attr_scan(showq, ATTR_FLAG_STRICT, + RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SHOWQ), + ATTR_TYPE_END) != 0) + msg_fatal_status(EX_SOFTWARE, "malformed showq server response"); + switch (mode) { + case PQ_MODE_MAILQ_LIST: + showq_compat(showq); + break; + case PQ_MODE_JSON_LIST: + showq_json(showq); + break; + default: + msg_panic("show_queue: unknown mode %d", mode); + } +} + +/* show_queue - show queue status */ + +static void show_queue(int mode) +{ + const char *errstr; + VSTREAM *showq; + int n; + uid_t uid = getuid(); + + if (uid != 0 && uid != var_owner_uid + && (errstr = check_user_acl_byuid(VAR_SHOWQ_ACL, var_showq_acl, + uid)) != 0) + msg_fatal_status(EX_NOPERM, + "User %s(%ld) is not allowed to view the mail queue", + errstr, (long) uid); + + /* + * Connect to the show queue service. + */ + if ((showq = mail_connect(MAIL_CLASS_PUBLIC, var_showq_service, BLOCKING)) != 0) { + showq_client(mode, showq); + if (vstream_fclose(showq)) + msg_warn("close: %m"); + } + + /* + * Don't assume that the mail system is down when the user has + * insufficient permission to access the showq socket. + */ + else if (errno == EACCES || errno == EPERM) { + msg_fatal_status(EX_SOFTWARE, + "Connect to the %s %s service: %m", + var_mail_name, var_showq_service); + } + + /* + * When the mail system is down, the superuser can still access the queue + * directly. Just run the showq program in stand-alone mode. + */ + else if (geteuid() == 0) { + char *showq_path; + ARGV *argv; + int stat; + + msg_warn("Mail system is down -- accessing queue directly" + " (Connect to the %s %s service: %m)", + var_mail_name, var_showq_service); + showq_path = concatenate(var_daemon_dir, "/", var_showq_service, + (char *) 0); + argv = argv_alloc(6); + argv_add(argv, showq_path, "-u", "-S", (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(argv, "-v", (char *) 0); + argv_terminate(argv); + if ((showq = vstream_popen(O_RDONLY, + CA_VSTREAM_POPEN_ARGV(argv->argv), + CA_VSTREAM_POPEN_END)) == 0) { + stat = -1; + } else { + showq_client(mode, showq); + stat = vstream_pclose(showq); + } + argv_free(argv); + myfree(showq_path); + if (stat != 0) + msg_fatal_status(stat < 0 ? EX_OSERR : EX_SOFTWARE, + "Error running %s", showq_path); + } + + /* + * When the mail system is down, unprivileged users are stuck, because by + * design the mail system contains no set_uid programs. The only way for + * an unprivileged user to cross protection boundaries is to talk to the + * showq daemon. + */ + else { + msg_fatal_status(EX_UNAVAILABLE, + "Queue report unavailable - mail system is down" + " (Connect to the %s %s service: %m)", + var_mail_name, var_showq_service); + } +} + +/* flush_queue - force delivery */ + +static void flush_queue(void) +{ + const char *errstr; + uid_t uid = getuid(); + + if (uid != 0 && uid != var_owner_uid + && (errstr = check_user_acl_byuid(VAR_FLUSH_ACL, var_flush_acl, + uid)) != 0) + msg_fatal_status(EX_NOPERM, + "User %s(%ld) is not allowed to flush the mail queue", + errstr, (long) uid); + + /* + * Trigger the flush queue service. + */ + if (mail_flush_deferred() < 0) + msg_fatal_status(EX_UNAVAILABLE, + "Cannot flush mail queue - mail system is down"); + if (mail_flush_maildrop() < 0) + msg_fatal_status(EX_UNAVAILABLE, + "Cannot flush mail queue - mail system is down"); + event_drain(2); +} + +/* flush_site - flush mail for site */ + +static void flush_site(const char *site) +{ + int status; + const char *errstr; + uid_t uid = getuid(); + + if (uid != 0 && uid != var_owner_uid + && (errstr = check_user_acl_byuid(VAR_FLUSH_ACL, var_flush_acl, + uid)) != 0) + msg_fatal_status(EX_NOPERM, + "User %s(%ld) is not allowed to flush the mail queue", + errstr, (long) uid); + + flush_init(); + + switch (status = flush_send_site(site)) { + case FLUSH_STAT_OK: + exit(0); + case FLUSH_STAT_BAD: + msg_fatal_status(EX_USAGE, "Invalid request: \"%s\"", site); + case FLUSH_STAT_FAIL: + msg_fatal_status(EX_UNAVAILABLE, + "Cannot flush mail queue - mail system is down"); + case FLUSH_STAT_DENY: + msg_fatal_status(EX_UNAVAILABLE, + "Flush service is not configured for destination \"%s\"", + site); + default: + msg_fatal_status(EX_SOFTWARE, + "Unknown flush server reply status %d", status); + } +} + +/* flush_file - flush mail with specific queue ID */ + +static void flush_file(const char *queue_id) +{ + int status; + const char *errstr; + uid_t uid = getuid(); + + if (uid != 0 && uid != var_owner_uid + && (errstr = check_user_acl_byuid(VAR_FLUSH_ACL, var_flush_acl, + uid)) != 0) + msg_fatal_status(EX_NOPERM, + "User %s(%ld) is not allowed to flush the mail queue", + errstr, (long) uid); + + switch (status = flush_send_file(queue_id)) { + case FLUSH_STAT_OK: + exit(0); + case FLUSH_STAT_BAD: + msg_fatal_status(EX_USAGE, "Invalid request: \"%s\"", queue_id); + case FLUSH_STAT_FAIL: + msg_fatal_status(EX_UNAVAILABLE, + "Cannot flush mail queue - mail system is down"); + default: + msg_fatal_status(EX_SOFTWARE, + "Unexpected flush server reply status %d", status); + } +} + +/* unavailable - sanitize exit status from library run-time errors */ + +static void unavailable(void) +{ + exit(EX_UNAVAILABLE); +} + +/* usage - scream and die */ + +static NORETURN usage(void) +{ + msg_fatal_status(EX_USAGE, "usage: postqueue -f | postqueue -i queueid | postqueue -j | postqueue -p | postqueue -s site"); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + struct stat st; + int c; + int fd; + int mode = PQ_MODE_DEFAULT; + char *site_to_flush = 0; + char *id_to_flush = 0; + ARGV *import_env; + int bad_site; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Be consistent with file permissions. + */ + umask(022); + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal_status(EX_UNAVAILABLE, "open /dev/null: %m"); + + /* + * Initialize. Set up logging. Read the global configuration file after + * parsing command-line arguments. Censor the process name: it is + * provided by the user. + */ + argv[0] = "postqueue"; + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_cleanup(unavailable); + maillog_client_init(mail_task("postqueue"), MAILLOG_CLIENT_FLAG_NONE); + set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Parse JCL. This program is set-gid and must sanitize all command-line + * parameters. The configuration directory argument is validated by the + * mail configuration read routine. Don't do complex things until we have + * completed initializations. + */ + while ((c = GETOPT(argc, argv, "c:fi:jps:v")) > 0) { + switch (c) { + case 'c': /* non-default configuration */ + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal_status(EX_UNAVAILABLE, "out of memory"); + break; + case 'f': /* flush queue */ + if (mode != PQ_MODE_DEFAULT) + usage(); + mode = PQ_MODE_FLUSH_QUEUE; + break; + case 'i': /* flush queue file */ + if (mode != PQ_MODE_DEFAULT) + usage(); + mode = PQ_MODE_FLUSH_FILE; + id_to_flush = optarg; + break; + case 'j': + if (mode != PQ_MODE_DEFAULT) + usage(); + mode = PQ_MODE_JSON_LIST; + break; + case 'p': /* traditional mailq */ + if (mode != PQ_MODE_DEFAULT) + usage(); + mode = PQ_MODE_MAILQ_LIST; + break; + case 's': /* flush site */ + if (mode != PQ_MODE_DEFAULT) + usage(); + mode = PQ_MODE_FLUSH_SITE; + site_to_flush = optarg; + break; + case 'v': + if (geteuid() == 0) + msg_verbose++; + break; + default: + usage(); + } + } + if (argc > optind) + usage(); + + /* + * Further initialization... + */ + mail_conf_read(); + /* Re-evaluate mail_task() after reading main.cf. */ + maillog_client_init(mail_task("postqueue"), MAILLOG_CLIENT_FLAG_NONE); + mail_dict_init(); /* proxy, sql, ldap */ + get_mail_conf_str_table(str_table); + + /* + * This program is designed to be set-gid, which makes it a potential + * target for attack. Strip and optionally override the process + * environment so that we don't have to trust the C library. + */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + clean_env(import_env->argv); + argv_free(import_env); + + if (chdir(var_queue_dir)) + msg_fatal_status(EX_UNAVAILABLE, "chdir %s: %m", var_queue_dir); + + signal(SIGPIPE, SIG_IGN); + + /* End of initializations. */ + + /* + * Further input validation. + */ + if (site_to_flush != 0) { + bad_site = 0; + if (*site_to_flush == '[') { + bad_site = !valid_mailhost_literal(site_to_flush, DONT_GRIPE); + } else { + bad_site = !valid_hostname(site_to_flush, DONT_GRIPE); + } + if (bad_site) + msg_fatal_status(EX_USAGE, + "Cannot flush mail queue - invalid destination: \"%.100s%s\"", + site_to_flush, strlen(site_to_flush) > 100 ? "..." : ""); + } + if (id_to_flush != 0) { + if (!mail_queue_id_ok(id_to_flush)) + msg_fatal_status(EX_USAGE, + "Cannot flush queue ID - invalid name: \"%.100s%s\"", + id_to_flush, strlen(id_to_flush) > 100 ? "..." : ""); + } + + /* + * Start processing. + */ + switch (mode) { + default: + msg_panic("unknown operation mode: %d", mode); + /* NOTREACHED */ + case PQ_MODE_MAILQ_LIST: + case PQ_MODE_JSON_LIST: + show_queue(mode); + exit(0); + break; + case PQ_MODE_FLUSH_SITE: + flush_site(site_to_flush); + exit(0); + break; + case PQ_MODE_FLUSH_FILE: + flush_file(id_to_flush); + exit(0); + break; + case PQ_MODE_FLUSH_QUEUE: + flush_queue(); + exit(0); + break; + case PQ_MODE_DEFAULT: + usage(); + /* NOTREACHED */ + } +} diff --git a/src/postqueue/postqueue.h b/src/postqueue/postqueue.h new file mode 100644 index 0000000..b1b7d27 --- /dev/null +++ b/src/postqueue/postqueue.h @@ -0,0 +1,35 @@ +/*++ +/* NAME +/* postqueue 5h +/* SUMMARY +/* postqueue internal interfaces +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * showq_compat.c + */ +extern void showq_compat(VSTREAM *); + + /* + * showq_json.c + */ +extern void showq_json(VSTREAM *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/postqueue/showq_compat.c b/src/postqueue/showq_compat.c new file mode 100644 index 0000000..f5ca059 --- /dev/null +++ b/src/postqueue/showq_compat.c @@ -0,0 +1,222 @@ +/*++ +/* NAME +/* showq_compat 8 +/* SUMMARY +/* Sendmail mailq compatibility adapter +/* SYNOPSIS +/* void showq_compat( +/* VSTREAM *showq) +/* DESCRIPTION +/* This function converts a record stream from the showq(8) +/* daemon to of an approximation of Sendmail mailq command +/* output. +/* DIAGNOSTICS +/* Fatal errors: out of memory, malformed showq(8) daemon output. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include + + /* + * The enable_long_queue_ids parameter determines the output format. + * + * The historical output format for short queue IDs (inode number and time in + * microseconds modulo 1) is not suitable for large inode numbers, but we + * won't change it to avoid breaking compatibility with programs that parse + * this output. + */ +#define S_STRING_FORMAT "%-11s %7s %-20s %s\n" +#define S_SENDER_FORMAT "%-11s %7ld %20.20s %s\n" +#define S_HEADINGS "-Queue ID-", "--Size--", \ + "----Arrival Time----", "-Sender/Recipient-------" + +#define L_STRING_FORMAT "%-17s %8s %-19s %s\n" +#define L_SENDER_FORMAT "%-17s %8ld %19.19s %s\n" +#define L_HEADINGS "----Queue ID-----", "--Size--", \ + "---Arrival Time----", "--Sender/Recipient------" + +#define STR(x) vstring_str(x) + +/* showq_message - report status for one message */ + +static unsigned long showq_message(VSTREAM *showq_stream) +{ + static VSTRING *queue_name = 0; + static VSTRING *queue_id = 0; + static VSTRING *id_status = 0; + static VSTRING *addr = 0; + static VSTRING *why = 0; + long arrival_time; + long message_size; + char *saved_reason = mystrdup(""); + const char *show_reason; + int padding; + int showq_status; + time_t time_t_arrival_time; + int forced_expire; + + /* + * One-time initialization. + */ + if (queue_name == 0) { + queue_name = vstring_alloc(100); + queue_id = vstring_alloc(100); + id_status = vstring_alloc(100); + addr = vstring_alloc(100); + why = vstring_alloc(100); + } + + /* + * Read the message properties and sender address. + */ + if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT + | ATTR_FLAG_PRINTABLE, + RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name), + RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), + RECV_ATTR_LONG(MAIL_ATTR_TIME, &arrival_time), + RECV_ATTR_LONG(MAIL_ATTR_SIZE, &message_size), + RECV_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE, &forced_expire), + RECV_ATTR_STR(MAIL_ATTR_SENDER, addr), + ATTR_TYPE_END) != 6) + msg_fatal_status(EX_SOFTWARE, "malformed showq server response"); + + /* + * Decorate queue file names in specific states, then print the result + * left-aligned, followed by other status info and the sender address + * which is already in externalized RFC 5321 form. + */ + vstring_strcpy(id_status, STR(queue_id)); + if (strcmp(STR(queue_name), MAIL_QUEUE_ACTIVE) == 0) + vstring_strcat(id_status, "*"); + else if (strcmp(STR(queue_name), MAIL_QUEUE_HOLD) == 0) + vstring_strcat(id_status, "!"); + if (forced_expire) + vstring_strcat(id_status, "#"); + time_t_arrival_time = arrival_time; + vstream_printf(var_long_queue_ids ? + L_SENDER_FORMAT : S_SENDER_FORMAT, STR(id_status), + message_size, asctime(localtime(&time_t_arrival_time)), + STR(addr)); + + /* + * Read zero or more (recipient, reason) pair(s) until attr_scan_more() + * consumes a terminator. If the showq daemon messes up, don't try to + * resynchronize. + */ + while ((showq_status = attr_scan_more(showq_stream)) > 0) { + if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT + | ATTR_FLAG_PRINTABLE, + RECV_ATTR_STR(MAIL_ATTR_RECIP, addr), + RECV_ATTR_STR(MAIL_ATTR_WHY, why), + ATTR_TYPE_END) != 2) + msg_fatal_status(EX_SOFTWARE, "malformed showq server response"); + + /* + * Don't output a "(reason)" line when no recipient has a reason, or + * when the previous recipient has the same (non)reason as the + * current recipient. Do output a "(reason unavailable)" when the + * previous recipient has a reason, and the current recipient has + * none. + */ + if (strcmp(saved_reason, STR(why)) != 0) { + myfree(saved_reason); + saved_reason = mystrdup(STR(why)); + show_reason = *saved_reason ? saved_reason : "reason unavailable"; + if ((padding = 76 - (int) strlen(show_reason)) < 0) + padding = 0; + vstream_printf("%*s(%s)\n", padding, "", show_reason); + } + vstream_printf(var_long_queue_ids ? + L_STRING_FORMAT : S_STRING_FORMAT, + "", "", "", STR(addr)); + } + if (showq_status < 0) + msg_fatal_status(EX_SOFTWARE, "malformed showq server response"); + myfree(saved_reason); + return (message_size); +} + +/* showq_compat - legacy mailq-style output adapter */ + +void showq_compat(VSTREAM *showq_stream) +{ + unsigned long file_count = 0; + unsigned long queue_size = 0; + int showq_status; + + /* + * Process zero or more queue file objects until attr_scan_more() + * consumes a terminator. + */ + while ((showq_status = attr_scan_more(showq_stream)) > 0) { + if (file_count > 0) { + vstream_printf("\n"); + } else if (var_long_queue_ids) { + vstream_printf(L_STRING_FORMAT, L_HEADINGS); + } else { + vstream_printf(S_STRING_FORMAT, S_HEADINGS); + } + queue_size += showq_message(showq_stream); + file_count++; + if (vstream_fflush(VSTREAM_OUT)) { + if (errno != EPIPE) + msg_fatal_status(EX_IOERR, "output write error: %m"); + return; + } + } + if (showq_status < 0) + msg_fatal_status(EX_SOFTWARE, "malformed showq server response"); + + /* + * Print the queue summary. + */ + if (file_count == 0) + vstream_printf("Mail queue is empty\n"); + else { + vstream_printf("\n-- %lu Kbytes in %lu Request%s.\n", + queue_size / 1024, file_count, + file_count == 1 ? "" : "s"); + } + if (vstream_fflush(VSTREAM_OUT) && errno != EPIPE) + msg_fatal_status(EX_IOERR, "output write error: %m"); +} diff --git a/src/postqueue/showq_json.c b/src/postqueue/showq_json.c new file mode 100644 index 0000000..fc205c7 --- /dev/null +++ b/src/postqueue/showq_json.c @@ -0,0 +1,221 @@ +/*++ +/* NAME +/* showq_json 8 +/* SUMMARY +/* JSON queue status formatter +/* SYNOPSIS +/* void showq_json( +/* VSTREAM *showq) +/* DESCRIPTION +/* This function converts showq(8) daemon output to JSON format. +/* DIAGNOSTICS +/* Fatal errors: out of memory, malformed showq(8) daemon output. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* json_quote - quote JSON string */ + +static char *json_quote(VSTRING *result, const char *text) +{ + unsigned char *cp; + int ch; + + /* + * We use short escape sequences for common control characters. Note that + * RFC 4627 allows "/" (0x2F) to be sent without quoting. Differences + * with RFC 4627: we send DEL (0x7f) as \u007F; the result remains RFC + * 4627 complaint. + */ + VSTRING_RESET(result); + for (cp = (unsigned char *) text; (ch = *cp) != 0; cp++) { + if (UNEXPECTED(ISCNTRL(ch))) { + switch (ch) { + case '\b': + VSTRING_ADDCH(result, '\\'); + VSTRING_ADDCH(result, 'b'); + break; + case '\f': + VSTRING_ADDCH(result, '\\'); + VSTRING_ADDCH(result, 'f'); + break; + case '\n': + VSTRING_ADDCH(result, '\\'); + VSTRING_ADDCH(result, 'n'); + break; + case '\r': + VSTRING_ADDCH(result, '\\'); + VSTRING_ADDCH(result, 'r'); + break; + case '\t': + VSTRING_ADDCH(result, '\\'); + VSTRING_ADDCH(result, 't'); + break; + default: + vstring_sprintf(result, "\\u%04X", ch); + break; + } + } else { + switch (ch) { + case '\\': + case '"': + VSTRING_ADDCH(result, '\\'); + /* FALLTHROUGH */ + default: + VSTRING_ADDCH(result, ch); + break; + } + } + } + VSTRING_TERMINATE(result); + + /* + * Force the result to be UTF-8 (with SMTPUTF8 enabled) or ASCII (with + * SMTPUTF8 disabled). + */ + printable(STR(result), '?'); + return (STR(result)); +} + +/* json_message - report status for one message */ + +static void format_json(VSTREAM *showq_stream) +{ + static VSTRING *queue_name = 0; + static VSTRING *queue_id = 0; + static VSTRING *addr = 0; + static VSTRING *why = 0; + static VSTRING *quote_buf = 0; + long arrival_time; + long message_size; + int showq_status; + int rcpt_count = 0; + int forced_expire; + + /* + * One-time initialization. + */ + if (queue_name == 0) { + queue_name = vstring_alloc(100); + queue_id = vstring_alloc(100); + addr = vstring_alloc(100); + why = vstring_alloc(100); + quote_buf = vstring_alloc(100); + } + + /* + * Read the message properties and sender address. + */ + if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT + | ATTR_FLAG_PRINTABLE, + RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name), + RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), + RECV_ATTR_LONG(MAIL_ATTR_TIME, &arrival_time), + RECV_ATTR_LONG(MAIL_ATTR_SIZE, &message_size), + RECV_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE, &forced_expire), + RECV_ATTR_STR(MAIL_ATTR_SENDER, addr), + ATTR_TYPE_END) != 6) + msg_fatal_status(EX_SOFTWARE, "malformed showq server response"); + vstream_printf("{"); + vstream_printf("\"queue_name\": \"%s\", ", + json_quote(quote_buf, STR(queue_name))); + vstream_printf("\"queue_id\": \"%s\", ", + json_quote(quote_buf, STR(queue_id))); + vstream_printf("\"arrival_time\": %ld, ", arrival_time); + vstream_printf("\"message_size\": %ld, ", message_size); + vstream_printf("\"forced_expire\": %s, ", forced_expire ? "true" : "false"); + vstream_printf("\"sender\": \"%s\", ", + json_quote(quote_buf, STR(addr))); + + /* + * Read zero or more (recipient, reason) pair(s) until attr_scan_more() + * consumes a terminator. If the showq daemon messes up, don't try to + * resynchronize. + */ + vstream_printf("\"recipients\": ["); + for (rcpt_count = 0; (showq_status = attr_scan_more(showq_stream)) > 0; rcpt_count++) { + if (rcpt_count > 0) + vstream_printf(", "); + vstream_printf("{"); + if (attr_scan(showq_stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT + | ATTR_FLAG_PRINTABLE, + RECV_ATTR_STR(MAIL_ATTR_RECIP, addr), + RECV_ATTR_STR(MAIL_ATTR_WHY, why), + ATTR_TYPE_END) != 2) + msg_fatal_status(EX_SOFTWARE, "malformed showq server response"); + vstream_printf("\"address\": \"%s\"", + json_quote(quote_buf, STR(addr))); + if (LEN(why) > 0) + vstream_printf(", \"delay_reason\": \"%s\"", + json_quote(quote_buf, STR(why))); + vstream_printf("}"); + } + vstream_printf("]"); + if (showq_status < 0) + msg_fatal_status(EX_SOFTWARE, "malformed showq server response"); + vstream_printf("}\n"); + if (vstream_fflush(VSTREAM_OUT) && errno != EPIPE) + msg_fatal_status(EX_IOERR, "output write error: %m"); +} + +/* showq_json - streaming JSON-format output adapter */ + +void showq_json(VSTREAM *showq_stream) +{ + int showq_status; + + /* + * Emit zero or more queue file objects until attr_scan_more() consumes a + * terminator. + */ + while ((showq_status = attr_scan_more(showq_stream)) > 0 + && vstream_ferror(VSTREAM_OUT) == 0) { + format_json(showq_stream); + } + if (showq_status < 0) + msg_fatal_status(EX_SOFTWARE, "malformed showq server response"); +} diff --git a/src/postscreen/.indent.pro b/src/postscreen/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postscreen/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postscreen/Makefile.in b/src/postscreen/Makefile.in new file mode 100644 index 0000000..8ed8692 --- /dev/null +++ b/src/postscreen/Makefile.in @@ -0,0 +1,428 @@ +SHELL = /bin/sh +SRCS = postscreen.c postscreen_dict.c postscreen_dnsbl.c \ + postscreen_early.c postscreen_smtpd.c postscreen_misc.c \ + postscreen_state.c postscreen_tests.c postscreen_send.c \ + postscreen_starttls.c postscreen_expand.c postscreen_endpt.c \ + postscreen_haproxy.c +OBJS = postscreen.o postscreen_dict.o postscreen_dnsbl.o \ + postscreen_early.o postscreen_smtpd.o postscreen_misc.o \ + postscreen_state.o postscreen_tests.o postscreen_send.o \ + postscreen_starttls.o postscreen_expand.o postscreen_endpt.o \ + postscreen_haproxy.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = postscreen +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: test + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postscreen.o: ../../include/addr_match_list.h +postscreen.o: ../../include/argv.h +postscreen.o: ../../include/attr.h +postscreen.o: ../../include/check_arg.h +postscreen.o: ../../include/data_redirect.h +postscreen.o: ../../include/dict.h +postscreen.o: ../../include/dict_cache.h +postscreen.o: ../../include/events.h +postscreen.o: ../../include/htable.h +postscreen.o: ../../include/inet_proto.h +postscreen.o: ../../include/iostuff.h +postscreen.o: ../../include/mail_conf.h +postscreen.o: ../../include/mail_params.h +postscreen.o: ../../include/mail_proto.h +postscreen.o: ../../include/mail_server.h +postscreen.o: ../../include/mail_version.h +postscreen.o: ../../include/maps.h +postscreen.o: ../../include/match_list.h +postscreen.o: ../../include/msg.h +postscreen.o: ../../include/myaddrinfo.h +postscreen.o: ../../include/myflock.h +postscreen.o: ../../include/mymalloc.h +postscreen.o: ../../include/name_code.h +postscreen.o: ../../include/nvtable.h +postscreen.o: ../../include/server_acl.h +postscreen.o: ../../include/set_eugid.h +postscreen.o: ../../include/string_list.h +postscreen.o: ../../include/sys_defs.h +postscreen.o: ../../include/vbuf.h +postscreen.o: ../../include/vstream.h +postscreen.o: ../../include/vstring.h +postscreen.o: postscreen.c +postscreen.o: postscreen.h +postscreen_dict.o: ../../include/addr_match_list.h +postscreen_dict.o: ../../include/argv.h +postscreen_dict.o: ../../include/check_arg.h +postscreen_dict.o: ../../include/dict.h +postscreen_dict.o: ../../include/dict_cache.h +postscreen_dict.o: ../../include/events.h +postscreen_dict.o: ../../include/htable.h +postscreen_dict.o: ../../include/maps.h +postscreen_dict.o: ../../include/match_list.h +postscreen_dict.o: ../../include/msg.h +postscreen_dict.o: ../../include/myaddrinfo.h +postscreen_dict.o: ../../include/myflock.h +postscreen_dict.o: ../../include/server_acl.h +postscreen_dict.o: ../../include/string_list.h +postscreen_dict.o: ../../include/sys_defs.h +postscreen_dict.o: ../../include/vbuf.h +postscreen_dict.o: ../../include/vstream.h +postscreen_dict.o: ../../include/vstring.h +postscreen_dict.o: postscreen.h +postscreen_dict.o: postscreen_dict.c +postscreen_dnsbl.o: ../../include/addr_match_list.h +postscreen_dnsbl.o: ../../include/argv.h +postscreen_dnsbl.o: ../../include/attr.h +postscreen_dnsbl.o: ../../include/check_arg.h +postscreen_dnsbl.o: ../../include/connect.h +postscreen_dnsbl.o: ../../include/dict.h +postscreen_dnsbl.o: ../../include/dict_cache.h +postscreen_dnsbl.o: ../../include/events.h +postscreen_dnsbl.o: ../../include/htable.h +postscreen_dnsbl.o: ../../include/iostuff.h +postscreen_dnsbl.o: ../../include/ip_match.h +postscreen_dnsbl.o: ../../include/mail_params.h +postscreen_dnsbl.o: ../../include/mail_proto.h +postscreen_dnsbl.o: ../../include/maps.h +postscreen_dnsbl.o: ../../include/match_list.h +postscreen_dnsbl.o: ../../include/msg.h +postscreen_dnsbl.o: ../../include/myaddrinfo.h +postscreen_dnsbl.o: ../../include/myflock.h +postscreen_dnsbl.o: ../../include/mymalloc.h +postscreen_dnsbl.o: ../../include/nvtable.h +postscreen_dnsbl.o: ../../include/server_acl.h +postscreen_dnsbl.o: ../../include/split_at.h +postscreen_dnsbl.o: ../../include/string_list.h +postscreen_dnsbl.o: ../../include/stringops.h +postscreen_dnsbl.o: ../../include/sys_defs.h +postscreen_dnsbl.o: ../../include/valid_hostname.h +postscreen_dnsbl.o: ../../include/vbuf.h +postscreen_dnsbl.o: ../../include/vstream.h +postscreen_dnsbl.o: ../../include/vstring.h +postscreen_dnsbl.o: postscreen.h +postscreen_dnsbl.o: postscreen_dnsbl.c +postscreen_early.o: ../../include/addr_match_list.h +postscreen_early.o: ../../include/argv.h +postscreen_early.o: ../../include/check_arg.h +postscreen_early.o: ../../include/dict.h +postscreen_early.o: ../../include/dict_cache.h +postscreen_early.o: ../../include/events.h +postscreen_early.o: ../../include/htable.h +postscreen_early.o: ../../include/mail_params.h +postscreen_early.o: ../../include/maps.h +postscreen_early.o: ../../include/match_list.h +postscreen_early.o: ../../include/msg.h +postscreen_early.o: ../../include/myaddrinfo.h +postscreen_early.o: ../../include/myflock.h +postscreen_early.o: ../../include/mymalloc.h +postscreen_early.o: ../../include/server_acl.h +postscreen_early.o: ../../include/string_list.h +postscreen_early.o: ../../include/stringops.h +postscreen_early.o: ../../include/sys_defs.h +postscreen_early.o: ../../include/vbuf.h +postscreen_early.o: ../../include/vstream.h +postscreen_early.o: ../../include/vstring.h +postscreen_early.o: postscreen.h +postscreen_early.o: postscreen_early.c +postscreen_endpt.o: ../../include/addr_match_list.h +postscreen_endpt.o: ../../include/argv.h +postscreen_endpt.o: ../../include/check_arg.h +postscreen_endpt.o: ../../include/dict.h +postscreen_endpt.o: ../../include/dict_cache.h +postscreen_endpt.o: ../../include/events.h +postscreen_endpt.o: ../../include/haproxy_srvr.h +postscreen_endpt.o: ../../include/htable.h +postscreen_endpt.o: ../../include/inet_proto.h +postscreen_endpt.o: ../../include/mail_params.h +postscreen_endpt.o: ../../include/maps.h +postscreen_endpt.o: ../../include/match_list.h +postscreen_endpt.o: ../../include/msg.h +postscreen_endpt.o: ../../include/myaddrinfo.h +postscreen_endpt.o: ../../include/myflock.h +postscreen_endpt.o: ../../include/server_acl.h +postscreen_endpt.o: ../../include/string_list.h +postscreen_endpt.o: ../../include/sys_defs.h +postscreen_endpt.o: ../../include/vbuf.h +postscreen_endpt.o: ../../include/vstream.h +postscreen_endpt.o: ../../include/vstring.h +postscreen_endpt.o: postscreen.h +postscreen_endpt.o: postscreen_endpt.c +postscreen_endpt.o: postscreen_haproxy.h +postscreen_expand.o: ../../include/addr_match_list.h +postscreen_expand.o: ../../include/argv.h +postscreen_expand.o: ../../include/attr.h +postscreen_expand.o: ../../include/check_arg.h +postscreen_expand.o: ../../include/dict.h +postscreen_expand.o: ../../include/dict_cache.h +postscreen_expand.o: ../../include/events.h +postscreen_expand.o: ../../include/htable.h +postscreen_expand.o: ../../include/iostuff.h +postscreen_expand.o: ../../include/mail_params.h +postscreen_expand.o: ../../include/mail_proto.h +postscreen_expand.o: ../../include/maps.h +postscreen_expand.o: ../../include/match_list.h +postscreen_expand.o: ../../include/msg.h +postscreen_expand.o: ../../include/myaddrinfo.h +postscreen_expand.o: ../../include/myflock.h +postscreen_expand.o: ../../include/mymalloc.h +postscreen_expand.o: ../../include/nvtable.h +postscreen_expand.o: ../../include/server_acl.h +postscreen_expand.o: ../../include/string_list.h +postscreen_expand.o: ../../include/stringops.h +postscreen_expand.o: ../../include/sys_defs.h +postscreen_expand.o: ../../include/vbuf.h +postscreen_expand.o: ../../include/vstream.h +postscreen_expand.o: ../../include/vstring.h +postscreen_expand.o: postscreen.h +postscreen_expand.o: postscreen_expand.c +postscreen_haproxy.o: ../../include/addr_match_list.h +postscreen_haproxy.o: ../../include/argv.h +postscreen_haproxy.o: ../../include/check_arg.h +postscreen_haproxy.o: ../../include/dict.h +postscreen_haproxy.o: ../../include/dict_cache.h +postscreen_haproxy.o: ../../include/events.h +postscreen_haproxy.o: ../../include/haproxy_srvr.h +postscreen_haproxy.o: ../../include/htable.h +postscreen_haproxy.o: ../../include/mail_params.h +postscreen_haproxy.o: ../../include/maps.h +postscreen_haproxy.o: ../../include/match_list.h +postscreen_haproxy.o: ../../include/msg.h +postscreen_haproxy.o: ../../include/myaddrinfo.h +postscreen_haproxy.o: ../../include/myflock.h +postscreen_haproxy.o: ../../include/mymalloc.h +postscreen_haproxy.o: ../../include/server_acl.h +postscreen_haproxy.o: ../../include/string_list.h +postscreen_haproxy.o: ../../include/stringops.h +postscreen_haproxy.o: ../../include/sys_defs.h +postscreen_haproxy.o: ../../include/vbuf.h +postscreen_haproxy.o: ../../include/vstream.h +postscreen_haproxy.o: ../../include/vstring.h +postscreen_haproxy.o: postscreen.h +postscreen_haproxy.o: postscreen_haproxy.c +postscreen_haproxy.o: postscreen_haproxy.h +postscreen_misc.o: ../../include/addr_match_list.h +postscreen_misc.o: ../../include/argv.h +postscreen_misc.o: ../../include/check_arg.h +postscreen_misc.o: ../../include/dict.h +postscreen_misc.o: ../../include/dict_cache.h +postscreen_misc.o: ../../include/events.h +postscreen_misc.o: ../../include/format_tv.h +postscreen_misc.o: ../../include/htable.h +postscreen_misc.o: ../../include/iostuff.h +postscreen_misc.o: ../../include/mail_params.h +postscreen_misc.o: ../../include/maps.h +postscreen_misc.o: ../../include/match_list.h +postscreen_misc.o: ../../include/msg.h +postscreen_misc.o: ../../include/myaddrinfo.h +postscreen_misc.o: ../../include/myflock.h +postscreen_misc.o: ../../include/server_acl.h +postscreen_misc.o: ../../include/string_list.h +postscreen_misc.o: ../../include/sys_defs.h +postscreen_misc.o: ../../include/vbuf.h +postscreen_misc.o: ../../include/vstream.h +postscreen_misc.o: ../../include/vstring.h +postscreen_misc.o: postscreen.h +postscreen_misc.o: postscreen_misc.c +postscreen_send.o: ../../include/addr_match_list.h +postscreen_send.o: ../../include/argv.h +postscreen_send.o: ../../include/attr.h +postscreen_send.o: ../../include/check_arg.h +postscreen_send.o: ../../include/connect.h +postscreen_send.o: ../../include/dict.h +postscreen_send.o: ../../include/dict_cache.h +postscreen_send.o: ../../include/events.h +postscreen_send.o: ../../include/htable.h +postscreen_send.o: ../../include/iostuff.h +postscreen_send.o: ../../include/mac_expand.h +postscreen_send.o: ../../include/mac_parse.h +postscreen_send.o: ../../include/mail_params.h +postscreen_send.o: ../../include/mail_proto.h +postscreen_send.o: ../../include/maps.h +postscreen_send.o: ../../include/match_list.h +postscreen_send.o: ../../include/msg.h +postscreen_send.o: ../../include/myaddrinfo.h +postscreen_send.o: ../../include/myflock.h +postscreen_send.o: ../../include/mymalloc.h +postscreen_send.o: ../../include/nvtable.h +postscreen_send.o: ../../include/server_acl.h +postscreen_send.o: ../../include/smtp_reply_footer.h +postscreen_send.o: ../../include/string_list.h +postscreen_send.o: ../../include/sys_defs.h +postscreen_send.o: ../../include/vbuf.h +postscreen_send.o: ../../include/vstream.h +postscreen_send.o: ../../include/vstring.h +postscreen_send.o: postscreen.h +postscreen_send.o: postscreen_send.c +postscreen_smtpd.o: ../../include/addr_match_list.h +postscreen_smtpd.o: ../../include/argv.h +postscreen_smtpd.o: ../../include/attr.h +postscreen_smtpd.o: ../../include/check_arg.h +postscreen_smtpd.o: ../../include/dict.h +postscreen_smtpd.o: ../../include/dict_cache.h +postscreen_smtpd.o: ../../include/dns.h +postscreen_smtpd.o: ../../include/ehlo_mask.h +postscreen_smtpd.o: ../../include/events.h +postscreen_smtpd.o: ../../include/htable.h +postscreen_smtpd.o: ../../include/info_log_addr_form.h +postscreen_smtpd.o: ../../include/iostuff.h +postscreen_smtpd.o: ../../include/is_header.h +postscreen_smtpd.o: ../../include/lex_822.h +postscreen_smtpd.o: ../../include/mail_params.h +postscreen_smtpd.o: ../../include/mail_proto.h +postscreen_smtpd.o: ../../include/maps.h +postscreen_smtpd.o: ../../include/match_list.h +postscreen_smtpd.o: ../../include/msg.h +postscreen_smtpd.o: ../../include/myaddrinfo.h +postscreen_smtpd.o: ../../include/myflock.h +postscreen_smtpd.o: ../../include/mymalloc.h +postscreen_smtpd.o: ../../include/name_code.h +postscreen_smtpd.o: ../../include/name_mask.h +postscreen_smtpd.o: ../../include/nvtable.h +postscreen_smtpd.o: ../../include/server_acl.h +postscreen_smtpd.o: ../../include/sock_addr.h +postscreen_smtpd.o: ../../include/string_list.h +postscreen_smtpd.o: ../../include/stringops.h +postscreen_smtpd.o: ../../include/sys_defs.h +postscreen_smtpd.o: ../../include/tls.h +postscreen_smtpd.o: ../../include/vbuf.h +postscreen_smtpd.o: ../../include/vstream.h +postscreen_smtpd.o: ../../include/vstring.h +postscreen_smtpd.o: postscreen.h +postscreen_smtpd.o: postscreen_smtpd.c +postscreen_starttls.o: ../../include/addr_match_list.h +postscreen_starttls.o: ../../include/argv.h +postscreen_starttls.o: ../../include/attr.h +postscreen_starttls.o: ../../include/check_arg.h +postscreen_starttls.o: ../../include/connect.h +postscreen_starttls.o: ../../include/dict.h +postscreen_starttls.o: ../../include/dict_cache.h +postscreen_starttls.o: ../../include/dns.h +postscreen_starttls.o: ../../include/events.h +postscreen_starttls.o: ../../include/htable.h +postscreen_starttls.o: ../../include/iostuff.h +postscreen_starttls.o: ../../include/mail_params.h +postscreen_starttls.o: ../../include/mail_proto.h +postscreen_starttls.o: ../../include/maps.h +postscreen_starttls.o: ../../include/match_list.h +postscreen_starttls.o: ../../include/msg.h +postscreen_starttls.o: ../../include/myaddrinfo.h +postscreen_starttls.o: ../../include/myflock.h +postscreen_starttls.o: ../../include/mymalloc.h +postscreen_starttls.o: ../../include/name_code.h +postscreen_starttls.o: ../../include/name_mask.h +postscreen_starttls.o: ../../include/nvtable.h +postscreen_starttls.o: ../../include/server_acl.h +postscreen_starttls.o: ../../include/sock_addr.h +postscreen_starttls.o: ../../include/string_list.h +postscreen_starttls.o: ../../include/stringops.h +postscreen_starttls.o: ../../include/sys_defs.h +postscreen_starttls.o: ../../include/tls.h +postscreen_starttls.o: ../../include/tls_proxy.h +postscreen_starttls.o: ../../include/vbuf.h +postscreen_starttls.o: ../../include/vstream.h +postscreen_starttls.o: ../../include/vstring.h +postscreen_starttls.o: postscreen.h +postscreen_starttls.o: postscreen_starttls.c +postscreen_state.o: ../../include/addr_match_list.h +postscreen_state.o: ../../include/argv.h +postscreen_state.o: ../../include/attr.h +postscreen_state.o: ../../include/check_arg.h +postscreen_state.o: ../../include/dict.h +postscreen_state.o: ../../include/dict_cache.h +postscreen_state.o: ../../include/events.h +postscreen_state.o: ../../include/htable.h +postscreen_state.o: ../../include/iostuff.h +postscreen_state.o: ../../include/mail_conf.h +postscreen_state.o: ../../include/mail_proto.h +postscreen_state.o: ../../include/mail_server.h +postscreen_state.o: ../../include/maps.h +postscreen_state.o: ../../include/match_list.h +postscreen_state.o: ../../include/msg.h +postscreen_state.o: ../../include/myaddrinfo.h +postscreen_state.o: ../../include/myflock.h +postscreen_state.o: ../../include/mymalloc.h +postscreen_state.o: ../../include/name_mask.h +postscreen_state.o: ../../include/nvtable.h +postscreen_state.o: ../../include/server_acl.h +postscreen_state.o: ../../include/string_list.h +postscreen_state.o: ../../include/sys_defs.h +postscreen_state.o: ../../include/vbuf.h +postscreen_state.o: ../../include/vstream.h +postscreen_state.o: ../../include/vstring.h +postscreen_state.o: postscreen.h +postscreen_state.o: postscreen_state.c +postscreen_tests.o: ../../include/addr_match_list.h +postscreen_tests.o: ../../include/argv.h +postscreen_tests.o: ../../include/check_arg.h +postscreen_tests.o: ../../include/dict.h +postscreen_tests.o: ../../include/dict_cache.h +postscreen_tests.o: ../../include/events.h +postscreen_tests.o: ../../include/htable.h +postscreen_tests.o: ../../include/mail_params.h +postscreen_tests.o: ../../include/maps.h +postscreen_tests.o: ../../include/match_list.h +postscreen_tests.o: ../../include/msg.h +postscreen_tests.o: ../../include/myaddrinfo.h +postscreen_tests.o: ../../include/myflock.h +postscreen_tests.o: ../../include/name_code.h +postscreen_tests.o: ../../include/sane_strtol.h +postscreen_tests.o: ../../include/server_acl.h +postscreen_tests.o: ../../include/string_list.h +postscreen_tests.o: ../../include/sys_defs.h +postscreen_tests.o: ../../include/vbuf.h +postscreen_tests.o: ../../include/vstream.h +postscreen_tests.o: ../../include/vstring.h +postscreen_tests.o: postscreen.h +postscreen_tests.o: postscreen_tests.c diff --git a/src/postscreen/postscreen.c b/src/postscreen/postscreen.c new file mode 100644 index 0000000..67716cb --- /dev/null +++ b/src/postscreen/postscreen.c @@ -0,0 +1,1252 @@ +/*++ +/* NAME +/* postscreen 8 +/* SUMMARY +/* Postfix zombie blocker +/* SYNOPSIS +/* \fBpostscreen\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The Postfix \fBpostscreen\fR(8) server provides additional +/* protection against mail server overload. One \fBpostscreen\fR(8) +/* process handles multiple inbound SMTP connections, and decides +/* which clients may talk to a Postfix SMTP server process. +/* By keeping spambots away, \fBpostscreen\fR(8) leaves more +/* SMTP server processes available for legitimate clients, and +/* delays the onset of server overload conditions. +/* +/* This program should not be used on SMTP ports that receive +/* mail from end-user clients (MUAs). In a typical deployment, +/* \fBpostscreen\fR(8) handles the MX service on TCP port 25, and +/* \fBsmtpd\fR(8) receives mail from MUAs on the \fBsubmission\fR +/* service (TCP port 587) which requires client authentication. +/* Alternatively, a site could set up a dedicated, non-postscreen, +/* "port 25" server that provides \fBsubmission\fR service and +/* client authentication, but no MX service. +/* +/* \fBpostscreen\fR(8) maintains a temporary allowlist for +/* clients that have passed a number of tests. When an SMTP +/* client IP address is allowlisted, \fBpostscreen\fR(8) hands +/* off the connection immediately to a Postfix SMTP server +/* process. This minimizes the overhead for legitimate mail. +/* +/* By default, \fBpostscreen\fR(8) logs statistics and hands +/* off each connection to a Postfix SMTP server process, while +/* excluding clients in mynetworks from all tests (primarily, +/* to avoid problems with non-standard SMTP implementations +/* in network appliances). This default mode blocks no clients, +/* and is useful for non-destructive testing. +/* +/* In a typical production setting, \fBpostscreen\fR(8) is +/* configured to reject mail from clients that fail one or +/* more tests. \fBpostscreen\fR(8) logs rejected mail with the +/* client address, helo, sender and recipient information. +/* +/* \fBpostscreen\fR(8) is not an SMTP proxy; this is intentional. +/* The purpose is to keep spambots away from Postfix SMTP +/* server processes, while minimizing overhead for legitimate +/* traffic. +/* SECURITY +/* .ad +/* .fi +/* The \fBpostscreen\fR(8) server is moderately security-sensitive. +/* It talks to untrusted clients on the network. The process +/* can be run chrooted at fixed low privilege. +/* STANDARDS +/* RFC 821 (SMTP protocol) +/* RFC 1123 (Host requirements) +/* RFC 1652 (8bit-MIME transport) +/* RFC 1869 (SMTP service extensions) +/* RFC 1870 (Message Size Declaration) +/* RFC 1985 (ETRN command) +/* RFC 2034 (SMTP Enhanced Status Codes) +/* RFC 2821 (SMTP protocol) +/* Not: RFC 2920 (SMTP Pipelining) +/* RFC 3030 (CHUNKING without BINARYMIME) +/* RFC 3207 (STARTTLS command) +/* RFC 3461 (SMTP DSN Extension) +/* RFC 3463 (Enhanced Status Codes) +/* RFC 5321 (SMTP protocol, including multi-line 220 banners) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* The \fBpostscreen\fR(8) built-in SMTP protocol engine +/* currently does not announce support for AUTH, XCLIENT or +/* XFORWARD. +/* If you need to make these services available +/* on port 25, then do not enable the optional "after 220 +/* server greeting" tests. +/* +/* The optional "after 220 server greeting" tests may result in +/* unexpected delivery delays from senders that retry email delivery +/* from a different IP address. Reason: after passing these tests a +/* new client must disconnect, and reconnect from the same IP +/* address before it can deliver mail. See POSTSCREEN_README, section +/* "Tests after the 220 SMTP server greeting", for a discussion. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to main.cf are not picked up automatically, as +/* \fBpostscreen\fR(8) processes may run for several hours. +/* Use the command "postfix reload" after a configuration +/* change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* +/* NOTE: Some \fBpostscreen\fR(8) parameters implement +/* stress-dependent behavior. This is supported only when the +/* default parameter value is stress-dependent (that is, it +/* looks like ${stress?{X}:{Y}}, or it is the $\fIname\fR +/* of an smtpd parameter with a stress-dependent default). +/* Other parameters always evaluate as if the \fBstress\fR +/* parameter value is the empty string. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* .IP "\fBpostscreen_command_filter ($smtpd_command_filter)\fR" +/* A mechanism to transform commands from remote SMTP clients. +/* .IP "\fBpostscreen_discard_ehlo_keyword_address_maps ($smtpd_discard_ehlo_keyword_address_maps)\fR" +/* Lookup tables, indexed by the remote SMTP client address, with +/* case insensitive lists of EHLO keywords (pipelining, starttls, auth, +/* etc.) that the \fBpostscreen\fR(8) server will not send in the EHLO response +/* to a remote SMTP client. +/* .IP "\fBpostscreen_discard_ehlo_keywords ($smtpd_discard_ehlo_keywords)\fR" +/* A case insensitive list of EHLO keywords (pipelining, starttls, +/* auth, etc.) that the \fBpostscreen\fR(8) server will not send in the EHLO +/* response to a remote SMTP client. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBdns_ncache_ttl_fix_enable (no)\fR" +/* Enable a workaround for future libc incompatibility. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBpostscreen_reject_footer_maps ($smtpd_reject_footer_maps)\fR" +/* Optional lookup table for information that is appended after a 4XX +/* or 5XX \fBpostscreen\fR(8) server response. +/* .PP +/* Available in Postfix 3.6 and later: +/* .IP "\fBrespectful_logging (see 'postconf -d' output)\fR" +/* Avoid logging that implies white is better than black. +/* TROUBLE SHOOTING CONTROLS +/* .ad +/* .fi +/* .IP "\fBpostscreen_expansion_filter (see 'postconf -d' output)\fR" +/* List of characters that are permitted in postscreen_reject_footer +/* attribute expansions. +/* .IP "\fBpostscreen_reject_footer ($smtpd_reject_footer)\fR" +/* Optional information that is appended after a 4XX or 5XX +/* \fBpostscreen\fR(8) server +/* response. +/* .IP "\fBsoft_bounce (no)\fR" +/* Safety net to keep mail queued that would otherwise be returned to +/* the sender. +/* BEFORE-POSTSCREEN PROXY AGENT +/* .ad +/* .fi +/* Available in Postfix version 2.10 and later: +/* .IP "\fBpostscreen_upstream_proxy_protocol (empty)\fR" +/* The name of the proxy protocol used by an optional before-postscreen +/* proxy agent. +/* .IP "\fBpostscreen_upstream_proxy_timeout (5s)\fR" +/* The time limit for the proxy protocol specified with the +/* postscreen_upstream_proxy_protocol parameter. +/* PERMANENT ALLOW/DENYLIST TEST +/* .ad +/* .fi +/* This test is executed immediately after a remote SMTP client +/* connects. If a client is permanently allowlisted, the client +/* will be handed off immediately to a Postfix SMTP server +/* process. +/* .IP "\fBpostscreen_access_list (permit_mynetworks)\fR" +/* Permanent allow/denylist for remote SMTP client IP addresses. +/* .IP "\fBpostscreen_blacklist_action (ignore)\fR" +/* Renamed to postscreen_denylist_action in Postfix 3.6. +/* MAIL EXCHANGER POLICY TESTS +/* .ad +/* .fi +/* When \fBpostscreen\fR(8) is configured to monitor all primary +/* and backup MX addresses, it can refuse to allowlist clients +/* that connect to a backup MX address only. For small sites, +/* this requires configuring primary and backup MX addresses +/* on the same MTA. Larger sites would have to share the +/* \fBpostscreen\fR(8) cache between primary and backup MTAs, +/* which would introduce a common point of failure. +/* .IP "\fBpostscreen_whitelist_interfaces (static:all)\fR" +/* Renamed to postscreen_allowlist_interfaces in Postfix 3.6. +/* BEFORE 220 GREETING TESTS +/* .ad +/* .fi +/* These tests are executed before the remote SMTP client +/* receives the "220 servername" greeting. If no tests remain +/* after the successful completion of this phase, the client +/* will be handed off immediately to a Postfix SMTP server +/* process. +/* .IP "\fBdnsblog_service_name (dnsblog)\fR" +/* The name of the \fBdnsblog\fR(8) service entry in master.cf. +/* .IP "\fBpostscreen_dnsbl_action (ignore)\fR" +/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client's combined +/* DNSBL score is equal to or greater than a threshold (as defined +/* with the postscreen_dnsbl_sites and postscreen_dnsbl_threshold +/* parameters). +/* .IP "\fBpostscreen_dnsbl_reply_map (empty)\fR" +/* A mapping from actual DNSBL domain name which includes a secret +/* password, to the DNSBL domain name that postscreen will reply with +/* when it rejects mail. +/* .IP "\fBpostscreen_dnsbl_sites (empty)\fR" +/* Optional list of DNS allow/denylist domains, filters and weight +/* factors. +/* .IP "\fBpostscreen_dnsbl_threshold (1)\fR" +/* The inclusive lower bound for blocking a remote SMTP client, based on +/* its combined DNSBL score as defined with the postscreen_dnsbl_sites +/* parameter. +/* .IP "\fBpostscreen_greet_action (ignore)\fR" +/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client speaks +/* before its turn within the time specified with the postscreen_greet_wait +/* parameter. +/* .IP "\fBpostscreen_greet_banner ($smtpd_banner)\fR" +/* The \fItext\fR in the optional "220-\fItext\fR..." server +/* response that +/* \fBpostscreen\fR(8) sends ahead of the real Postfix SMTP server's "220 +/* text..." response, in an attempt to confuse bad SMTP clients so +/* that they speak before their turn (pre-greet). +/* .IP "\fBpostscreen_greet_wait (normal: 6s, overload: 2s)\fR" +/* The amount of time that \fBpostscreen\fR(8) will wait for an SMTP +/* client to send a command before its turn, and for DNS blocklist +/* lookup results to arrive (default: up to 2 seconds under stress, +/* up to 6 seconds otherwise). +/* .IP "\fBsmtpd_service_name (smtpd)\fR" +/* The internal service that \fBpostscreen\fR(8) hands off allowed +/* connections to. +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBpostscreen_dnsbl_whitelist_threshold (0)\fR" +/* Renamed to postscreen_dnsbl_allowlist_threshold in Postfix 3.6. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBpostscreen_dnsbl_timeout (10s)\fR" +/* The time limit for DNSBL or DNSWL lookups. +/* .PP +/* Available in Postfix version 3.6 and later: +/* .IP "\fBpostscreen_denylist_action (ignore)\fR" +/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client is +/* permanently denylisted with the postscreen_access_list parameter. +/* .IP "\fBpostscreen_allowlist_interfaces (static:all)\fR" +/* A list of local \fBpostscreen\fR(8) server IP addresses where a +/* non-allowlisted remote SMTP client can obtain \fBpostscreen\fR(8)'s temporary +/* allowlist status. +/* .IP "\fBpostscreen_dnsbl_allowlist_threshold (0)\fR" +/* Allow a remote SMTP client to skip "before" and "after 220 +/* greeting" protocol tests, based on its combined DNSBL score as +/* defined with the postscreen_dnsbl_sites parameter. +/* AFTER 220 GREETING TESTS +/* .ad +/* .fi +/* These tests are executed after the remote SMTP client +/* receives the "220 servername" greeting. If a client passes +/* all tests during this phase, it will receive a 4XX response +/* to all RCPT TO commands. After the client reconnects, it +/* will be allowed to talk directly to a Postfix SMTP server +/* process. +/* .IP "\fBpostscreen_bare_newline_action (ignore)\fR" +/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client sends +/* a bare newline character, that is, a newline not preceded by carriage +/* return. +/* .IP "\fBpostscreen_bare_newline_enable (no)\fR" +/* Enable "bare newline" SMTP protocol tests in the \fBpostscreen\fR(8) +/* server. +/* .IP "\fBpostscreen_disable_vrfy_command ($disable_vrfy_command)\fR" +/* Disable the SMTP VRFY command in the \fBpostscreen\fR(8) daemon. +/* .IP "\fBpostscreen_forbidden_commands ($smtpd_forbidden_commands)\fR" +/* List of commands that the \fBpostscreen\fR(8) server considers in +/* violation of the SMTP protocol. +/* .IP "\fBpostscreen_helo_required ($smtpd_helo_required)\fR" +/* Require that a remote SMTP client sends HELO or EHLO before +/* commencing a MAIL transaction. +/* .IP "\fBpostscreen_non_smtp_command_action (drop)\fR" +/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client sends +/* non-SMTP commands as specified with the postscreen_forbidden_commands +/* parameter. +/* .IP "\fBpostscreen_non_smtp_command_enable (no)\fR" +/* Enable "non-SMTP command" tests in the \fBpostscreen\fR(8) server. +/* .IP "\fBpostscreen_pipelining_action (enforce)\fR" +/* The action that \fBpostscreen\fR(8) takes when a remote SMTP client +/* sends +/* multiple commands instead of sending one command and waiting for +/* the server to respond. +/* .IP "\fBpostscreen_pipelining_enable (no)\fR" +/* Enable "pipelining" SMTP protocol tests in the \fBpostscreen\fR(8) +/* server. +/* CACHE CONTROLS +/* .ad +/* .fi +/* .IP "\fBpostscreen_cache_cleanup_interval (12h)\fR" +/* The amount of time between \fBpostscreen\fR(8) cache cleanup runs. +/* .IP "\fBpostscreen_cache_map (btree:$data_directory/postscreen_cache)\fR" +/* Persistent storage for the \fBpostscreen\fR(8) server decisions. +/* .IP "\fBpostscreen_cache_retention_time (7d)\fR" +/* The amount of time that \fBpostscreen\fR(8) will cache an expired +/* temporary allowlist entry before it is removed. +/* .IP "\fBpostscreen_bare_newline_ttl (30d)\fR" +/* The amount of time that \fBpostscreen\fR(8) will use the result from +/* a successful "bare newline" SMTP protocol test. +/* .IP "\fBpostscreen_dnsbl_max_ttl (${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h)\fR" +/* The maximum amount of time that \fBpostscreen\fR(8) will use the +/* result from a successful DNS-based reputation test before a +/* client IP address is required to pass that test again. +/* .IP "\fBpostscreen_dnsbl_min_ttl (60s)\fR" +/* The minimum amount of time that \fBpostscreen\fR(8) will use the +/* result from a successful DNS-based reputation test before a +/* client IP address is required to pass that test again. +/* .IP "\fBpostscreen_greet_ttl (1d)\fR" +/* The amount of time that \fBpostscreen\fR(8) will use the result from +/* a successful PREGREET test. +/* .IP "\fBpostscreen_non_smtp_command_ttl (30d)\fR" +/* The amount of time that \fBpostscreen\fR(8) will use the result from +/* a successful "non_smtp_command" SMTP protocol test. +/* .IP "\fBpostscreen_pipelining_ttl (30d)\fR" +/* The amount of time that \fBpostscreen\fR(8) will use the result from +/* a successful "pipelining" SMTP protocol test. +/* RESOURCE CONTROLS +/* .ad +/* .fi +/* .IP "\fBline_length_limit (2048)\fR" +/* Upon input, long lines are chopped up into pieces of at most +/* this length; upon delivery, long lines are reconstructed. +/* .IP "\fBpostscreen_client_connection_count_limit ($smtpd_client_connection_count_limit)\fR" +/* How many simultaneous connections any remote SMTP client is +/* allowed to have +/* with the \fBpostscreen\fR(8) daemon. +/* .IP "\fBpostscreen_command_count_limit (20)\fR" +/* The limit on the total number of commands per SMTP session for +/* \fBpostscreen\fR(8)'s built-in SMTP protocol engine. +/* .IP "\fBpostscreen_command_time_limit (normal: 300s, overload: 10s)\fR" +/* The time limit to read an entire command line with \fBpostscreen\fR(8)'s +/* built-in SMTP protocol engine. +/* .IP "\fBpostscreen_post_queue_limit ($default_process_limit)\fR" +/* The number of clients that can be waiting for service from a +/* real Postfix SMTP server process. +/* .IP "\fBpostscreen_pre_queue_limit ($default_process_limit)\fR" +/* The number of non-allowlisted clients that can be waiting for +/* a decision whether they will receive service from a real Postfix +/* SMTP server +/* process. +/* .IP "\fBpostscreen_watchdog_timeout (10s)\fR" +/* How much time a \fBpostscreen\fR(8) process may take to respond to +/* a remote SMTP client command or to perform a cache operation before it +/* is terminated by a built-in watchdog timer. +/* STARTTLS CONTROLS +/* .ad +/* .fi +/* .IP "\fBpostscreen_tls_security_level ($smtpd_tls_security_level)\fR" +/* The SMTP TLS security level for the \fBpostscreen\fR(8) server; when +/* a non-empty value is specified, this overrides the obsolete parameters +/* postscreen_use_tls and postscreen_enforce_tls. +/* .IP "\fBtlsproxy_service_name (tlsproxy)\fR" +/* The name of the \fBtlsproxy\fR(8) service entry in master.cf. +/* OBSOLETE STARTTLS SUPPORT CONTROLS +/* .ad +/* .fi +/* These parameters are supported for compatibility with +/* \fBsmtpd\fR(8) legacy parameters. +/* .IP "\fBpostscreen_use_tls ($smtpd_use_tls)\fR" +/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients, +/* but do not require that clients use TLS encryption. +/* .IP "\fBpostscreen_enforce_tls ($smtpd_enforce_tls)\fR" +/* Mandatory TLS: announce STARTTLS support to remote SMTP clients, and +/* require that clients use TLS encryption. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR" +/* The location of all postfix administrative commands. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.5 and later: +/* .IP "\fBinfo_log_address_format (external)\fR" +/* The email address form that will be used in non-debug logging +/* (info, warning, etc.). +/* SEE ALSO +/* smtpd(8), Postfix SMTP server +/* tlsproxy(8), Postfix TLS proxy server +/* dnsblog(8), DNS allow/denylist logger +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or "\fBpostconf +/* html_directory\fR" to locate this information. +/* .nf +/* .na +/* POSTSCREEN_README, Postfix Postscreen Howto +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* This service was introduced with Postfix version 2.8. +/* +/* Many ideas in \fBpostscreen\fR(8) were explored in earlier +/* work by Michael Tokarev, in OpenBSD spamd, and in MailChannels +/* Traffic Control. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include + +/* Master server protocols. */ + +#include + +/* Application-specific. */ + +#include + + /* + * Configuration parameters. + */ +char *var_smtpd_service; +char *var_smtpd_banner; +bool var_disable_vrfy_cmd; +bool var_helo_required; + +char *var_smtpd_cmd_filter; +char *var_psc_cmd_filter; + +char *var_smtpd_forbid_cmds; +char *var_psc_forbid_cmds; + +char *var_smtpd_ehlo_dis_words; +char *var_smtpd_ehlo_dis_maps; +char *var_psc_ehlo_dis_words; +char *var_psc_ehlo_dis_maps; + +char *var_smtpd_tls_level; +bool var_smtpd_use_tls; +bool var_smtpd_enforce_tls; +char *var_psc_tls_level; +bool var_psc_use_tls; +bool var_psc_enforce_tls; + +bool var_psc_disable_vrfy; +bool var_psc_helo_required; + +char *var_psc_cache_map; +int var_psc_cache_scan; +int var_psc_cache_ret; +int var_psc_post_queue_limit; +int var_psc_pre_queue_limit; +int var_psc_watchdog; + +char *var_psc_acl; +char *var_psc_dnlist_action; + +char *var_psc_greet_ttl; +int var_psc_greet_wait; + +char *var_psc_pregr_banner; +char *var_psc_pregr_action; +int var_psc_pregr_ttl; + +char *var_psc_dnsbl_sites; +char *var_psc_dnsbl_reply; +int var_psc_dnsbl_thresh; +int var_psc_dnsbl_althresh; +char *var_psc_dnsbl_action; +int var_psc_dnsbl_min_ttl; +int var_psc_dnsbl_max_ttl; +int var_psc_dnsbl_tmout; + +bool var_psc_pipel_enable; +char *var_psc_pipel_action; +int var_psc_pipel_ttl; + +bool var_psc_nsmtp_enable; +char *var_psc_nsmtp_action; +int var_psc_nsmtp_ttl; + +bool var_psc_barlf_enable; +char *var_psc_barlf_action; +int var_psc_barlf_ttl; + +int var_psc_cmd_count; +int var_psc_cmd_time; + +char *var_dnsblog_service; +char *var_tlsproxy_service; + +char *var_smtpd_rej_footer; +char *var_psc_rej_footer; +char *var_psc_rej_ftr_maps; + +int var_smtpd_cconn_limit; +int var_psc_cconn_limit; + +char *var_smtpd_exp_filter; +char *var_psc_exp_filter; + +char *var_psc_allist_if; +char *var_psc_uproxy_proto; +int var_psc_uproxy_tmout; + + /* + * Global variables. + */ +int psc_check_queue_length; /* connections being checked */ +int psc_post_queue_length; /* being sent to real SMTPD */ +DICT_CACHE *psc_cache_map; /* cache table handle */ +VSTRING *psc_temp; /* scratchpad */ +char *psc_smtpd_service_name; /* path to real SMTPD */ +int psc_pregr_action; /* PSC_ACT_DROP/ENFORCE/etc */ +int psc_dnsbl_action; /* PSC_ACT_DROP/ENFORCE/etc */ +int psc_pipel_action; /* PSC_ACT_DROP/ENFORCE/etc */ +int psc_nsmtp_action; /* PSC_ACT_DROP/ENFORCE/etc */ +int psc_barlf_action; /* PSC_ACT_DROP/ENFORCE/etc */ +int psc_min_ttl; /* Update with new tests! */ +STRING_LIST *psc_forbid_cmds; /* CONNECT GET POST */ +int psc_stress_greet_wait; /* stressed greet wait */ +int psc_normal_greet_wait; /* stressed greet wait */ +int psc_stress_cmd_time_limit; /* stressed command limit */ +int psc_normal_cmd_time_limit; /* normal command time limit */ +int psc_stress; /* stress level */ +int psc_lowat_check_queue_length; /* stress low-water mark */ +int psc_hiwat_check_queue_length; /* stress high-water mark */ +DICT *psc_dnsbl_reply; /* DNSBL name mapper */ +HTABLE *psc_client_concurrency; /* per-client concurrency */ + + /* + * Local variables and functions. + */ +static ARGV *psc_acl; /* permanent allow/denylist */ +static int psc_dnlist_action; /* PSC_ACT_DROP/ENFORCE/etc */ +static ADDR_MATCH_LIST *psc_allist_if; /* allowlist interfaces */ + +static void psc_endpt_lookup_done(int, VSTREAM *, + MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *, + MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *); + +/* psc_dump - dump some statistics before exit */ + +static void psc_dump(char *unused_service, char **unused_argv) +{ + + /* + * Dump preliminary cache cleanup statistics when the process commits + * suicide while a cache cleanup run is in progress. We can't currently + * distinguish between "postfix reload" (we should restart) or "maximal + * idle time reached" (we could finish the cache cleanup first). + */ + if (psc_cache_map) { + dict_cache_close(psc_cache_map); + psc_cache_map = 0; + } +} + +/* psc_drain - delayed exit after "postfix reload" */ + +static void psc_drain(char *unused_service, char **unused_argv) +{ + int count; + + /* + * After "postfix reload", complete work-in-progress in the background, + * instead of dropping already-accepted connections on the floor. + * + * Unfortunately we must close all writable tables, so we can't store or + * look up reputation information. The reason is that we don't have any + * multi-writer safety guarantees. We also can't use the single-writer + * proxywrite service, because its latency guarantees are too weak. + * + * All error retry counts shall be limited. Instead of blocking here, we + * could retry failed fork() operations in the event call-back routines, + * but we don't need perfection. The host system is severely overloaded + * and service levels are already way down. + * + * XXX Some Berkeley DB versions break with close-after-fork. Every new + * version is an improvement over its predecessor. + * + * XXX Don't assume that it is OK to share the same LMDB lockfile descriptor + * between different processes. + */ + if (psc_cache_map != 0 /* XXX && psc_cache_map + requires locking */ ) { + dict_cache_close(psc_cache_map); + psc_cache_map = 0; + } + for (count = 0; /* see below */ ; count++) { + if (count >= 5) { + msg_fatal("fork: %m"); + } else if (event_server_drain() != 0) { + msg_warn("fork: %m"); + sleep(1); + continue; + } else { + return; + } + } +} + +/* psc_service - handle new client connection */ + +static void psc_service(VSTREAM *smtp_client_stream, + char *unused_service, + char **unused_argv) +{ + + /* + * For sanity, require that at least one of INET or INET6 is enabled. + * Otherwise, we can't look up interface information, and we can't + * convert names or addresses. + */ + if (inet_proto_info()->ai_family_list[0] == 0) + msg_fatal("all network protocols are disabled (%s = %s)", + VAR_INET_PROTOCOLS, var_inet_protocols); + + /* + * This program handles all incoming connections, so it must not block. + * We use event-driven code for all operations that introduce latency. + * + * Note: instead of using VSTREAM-level timeouts, we enforce limits on the + * total amount of time to receive a complete SMTP command line. + */ + non_blocking(vstream_fileno(smtp_client_stream), NON_BLOCKING); + + /* + * Look up the remote SMTP client address and port. + */ + psc_endpt_lookup(smtp_client_stream, psc_endpt_lookup_done); +} + +/* psc_warn_compat_respectful_logging - compatibility warning */ + +static void psc_warn_compat_respectful_logging(PSC_STATE *state) +{ + msg_info("using backwards-compatible default setting " + VAR_RESPECTFUL_LOGGING "=no for client [%s]:%s", + PSC_CLIENT_ADDR_PORT(state)); + warn_compat_respectful_logging = 0; +} + +/* psc_endpt_lookup_done - endpoint lookup completed */ + +static void psc_endpt_lookup_done(int endpt_status, + VSTREAM *smtp_client_stream, + MAI_HOSTADDR_STR *smtp_client_addr, + MAI_SERVPORT_STR *smtp_client_port, + MAI_HOSTADDR_STR *smtp_server_addr, + MAI_SERVPORT_STR *smtp_server_port) +{ + const char *myname = "psc_endpt_lookup_done"; + PSC_STATE *state; + const char *stamp_str; + int saved_flags; + + /* + * Best effort - if this non-blocking write(2) fails, so be it. + */ + if (endpt_status < 0) { + (void) write(vstream_fileno(smtp_client_stream), + "421 4.3.2 No system resources\r\n", + sizeof("421 4.3.2 No system resources\r\n") - 1); + event_server_disconnect(smtp_client_stream); + return; + } + if (msg_verbose > 1) + msg_info("%s: sq=%d cq=%d connect from [%s]:%s", + myname, psc_post_queue_length, psc_check_queue_length, + smtp_client_addr->buf, smtp_client_port->buf); + + msg_info("CONNECT from [%s]:%s to [%s]:%s", + smtp_client_addr->buf, smtp_client_port->buf, + smtp_server_addr->buf, smtp_server_port->buf); + + /* + * Bundle up all the loose session pieces. This zeroes all flags and time + * stamps. + */ + state = psc_new_session_state(smtp_client_stream, smtp_client_addr->buf, + smtp_client_port->buf, + smtp_server_addr->buf, + smtp_server_port->buf); + + /* + * Reply with 421 when the client has too many open connections. + */ + if (var_psc_cconn_limit > 0 + && state->client_info->concurrency > var_psc_cconn_limit) { + msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: too many connections", + state->smtp_client_addr, state->smtp_client_port); + PSC_DROP_SESSION_STATE(state, + "421 4.7.0 Error: too many connections\r\n"); + return; + } + + /* + * Reply with 421 when we can't forward more connections. + */ + if (var_psc_post_queue_limit > 0 + && psc_post_queue_length >= var_psc_post_queue_limit) { + msg_info("NOQUEUE: reject: CONNECT from [%s]:%s: all server ports busy", + state->smtp_client_addr, state->smtp_client_port); + PSC_DROP_SESSION_STATE(state, + "421 4.3.2 All server ports are busy\r\n"); + return; + } + + /* + * The permanent allow/denylist has highest precedence. + */ + if (psc_acl != 0) { + switch (psc_acl_eval(state, psc_acl, VAR_PSC_ACL)) { + + /* + * Permanently denylisted. + */ + case PSC_ACL_ACT_DENYLIST: + msg_info("%sLISTED [%s]:%s", + var_respectful_logging ? "DENY" : "BLACK", + PSC_CLIENT_ADDR_PORT(state)); + if (warn_compat_respectful_logging) + psc_warn_compat_respectful_logging(state); + PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNLIST_FAIL); + switch (psc_dnlist_action) { + case PSC_ACT_DROP: + PSC_DROP_SESSION_STATE(state, + "521 5.3.2 Service currently unavailable\r\n"); + return; + case PSC_ACT_ENFORCE: + PSC_ENFORCE_SESSION_STATE(state, + "550 5.3.2 Service currently unavailable\r\n"); + break; + case PSC_ACT_IGNORE: + PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNLIST_FAIL); + + /* + * Not: PSC_PASS_SESSION_STATE. Repeat this test the next + * time. + */ + break; + default: + msg_panic("%s: unknown denylist action value %d", + myname, psc_dnlist_action); + } + break; + + /* + * Permanently allowlisted. + */ + case PSC_ACL_ACT_ALLOWLIST: + msg_info("%sLISTED [%s]:%s", + var_respectful_logging ? "ALLOW" : "WHITE", + PSC_CLIENT_ADDR_PORT(state)); + if (warn_compat_respectful_logging) + psc_warn_compat_respectful_logging(state); + psc_conclude(state); + return; + + /* + * Other: dunno (don't know) or error. + */ + default: + break; + } + } + + /* + * The temporary allowlist (i.e. the postscreen cache) has the lowest + * precedence. This cache contains information about the results of prior + * tests. Allowlist the client when all enabled test results are still + * valid. + */ + if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0 + && state->client_info->concurrency == 1 + && psc_cache_map != 0 + && (stamp_str = psc_cache_lookup(psc_cache_map, state->smtp_client_addr)) != 0) { + saved_flags = state->flags; + psc_parse_tests(state, stamp_str, event_time()); + state->flags |= saved_flags; + if (msg_verbose) + msg_info("%s: cached + recent flags: %s", + myname, psc_print_state_flags(state->flags, myname)); + if ((state->flags & PSC_STATE_MASK_ANY_TODO_FAIL) == 0) { + msg_info("PASS OLD [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); + psc_conclude(state); + return; + } + } else if (state->client_info->concurrency > 1) { + saved_flags = state->flags; + psc_todo_tests(state, event_time()); + state->flags |= saved_flags; + if (msg_verbose) + msg_info("%s: new + recent flags: %s", + myname, psc_print_state_flags(state->flags, myname)); + } else { + saved_flags = state->flags; + psc_new_tests(state); + state->flags |= saved_flags; + if (msg_verbose) + msg_info("%s: new + recent flags: %s", + myname, psc_print_state_flags(state->flags, myname)); + } + + /* + * Don't allowlist clients that connect to backup MX addresses. Fail + * "closed" on error. + */ + if (addr_match_list_match(psc_allist_if, smtp_server_addr->buf) == 0) { + state->flags |= (PSC_STATE_FLAG_ALLIST_FAIL | PSC_STATE_FLAG_NOFORWARD); + msg_info("%sLIST VETO [%s]:%s", var_respectful_logging ? + "ALLOW" : "WHITE", PSC_CLIENT_ADDR_PORT(state)); + if (warn_compat_respectful_logging) + psc_warn_compat_respectful_logging(state); + } + + /* + * Reply with 421 when we can't analyze more connections. That also means + * no deep protocol tests when the noforward flag is raised. + */ + if (var_psc_pre_queue_limit > 0 + && psc_check_queue_length - psc_post_queue_length + >= var_psc_pre_queue_limit) { + msg_info("reject: connect from [%s]:%s: all screening ports busy", + state->smtp_client_addr, state->smtp_client_port); + PSC_DROP_SESSION_STATE(state, + "421 4.3.2 All screening ports are busy\r\n"); + return; + } + + /* + * If the client has no up-to-date results for some tests, do those tests + * first. Otherwise, skip the tests and hand off the connection. + */ + if (state->flags & PSC_STATE_MASK_EARLY_TODO) + psc_early_tests(state); + else if (state->flags & (PSC_STATE_MASK_SMTPD_TODO | PSC_STATE_FLAG_NOFORWARD)) + psc_smtpd_tests(state); + else + psc_conclude(state); +} + +/* psc_cache_validator - validate one cache entry */ + +static int psc_cache_validator(const char *client_addr, + const char *stamp_str, + void *unused_context) +{ + PSC_STATE dummy_state; + PSC_CLIENT_INFO dummy_client_info; + + /* + * This function is called by the cache cleanup pseudo thread. + * + * When an entry is removed from the cache, the client will be reported as + * "NEW" in the next session where it passes all tests again. To avoid + * silly logging we remove the cache entry only after all tests have + * expired longer ago than the cache retention time. + */ + dummy_state.client_info = &dummy_client_info; + psc_parse_tests(&dummy_state, stamp_str, event_time() - var_psc_cache_ret); + return ((dummy_state.flags & PSC_STATE_MASK_ANY_TODO) == 0); +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + VSTRING *redirect; + + /* + * Open read-only maps before dropping privilege, for consistency with + * other Postfix daemons. + */ + psc_acl_pre_jail_init(var_mynetworks, VAR_PSC_ACL); + if (*var_psc_acl) + psc_acl = psc_acl_parse(var_psc_acl, VAR_PSC_ACL); + /* Ignore smtpd_forbid_cmds lookup errors. Non-critical feature. */ + if (*var_psc_forbid_cmds) + psc_forbid_cmds = string_list_init(VAR_PSC_FORBID_CMDS, + MATCH_FLAG_RETURN, + var_psc_forbid_cmds); + if (*var_psc_dnsbl_reply) + psc_dnsbl_reply = dict_open(var_psc_dnsbl_reply, O_RDONLY, + DICT_FLAG_DUP_WARN); + + /* + * Never, ever, get killed by a master signal, as that would corrupt the + * database when we're in the middle of an update. + */ + if (setsid() < 0) + msg_warn("setsid: %m"); + + /* + * Security: don't create root-owned files that contain untrusted data. + * And don't create Postfix-owned files in root-owned directories, + * either. We want a correct relationship between (file or directory) + * ownership and (file or directory) content. To open files before going + * to jail, temporarily drop root privileges. + */ + SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid); + redirect = vstring_alloc(100); + + /* + * Keep state in persistent external map. As a safety measure we sync the + * database on each update. This hurts on LINUX file systems that sync + * all dirty disk blocks whenever any application invokes fsync(). + * + * Start the cache maintenance pseudo thread after dropping privileges. + */ +#define PSC_DICT_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | \ + DICT_FLAG_OPEN_LOCK) + + if (*var_psc_cache_map) + psc_cache_map = + dict_cache_open(data_redirect_map(redirect, var_psc_cache_map), + O_CREAT | O_RDWR, PSC_DICT_OPEN_FLAGS); + + /* + * Clean up and restore privilege. + */ + vstring_free(redirect); + RESTORE_SAVED_EUGID(); + + /* + * Initialize the dummy SMTP engine. + */ + psc_smtpd_pre_jail_init(); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + static time_t last_event_time; + time_t new_event_time; + const char *name; + + /* + * If some table has changed then stop accepting new connections. Don't + * check the tables more than once a second. + */ + new_event_time = event_time(); + if (new_event_time >= last_event_time + 1 + && (name = dict_changed_name()) != 0) { + msg_info("table %s has changed - finishing in the background", name); + event_server_drain(); + } else { + last_event_time = new_event_time; + } +} + +/* post_jail_init - post-jail initialization */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + const NAME_CODE actions[] = { + PSC_NAME_ACT_DROP, PSC_ACT_DROP, + PSC_NAME_ACT_ENFORCE, PSC_ACT_ENFORCE, + PSC_NAME_ACT_IGNORE, PSC_ACT_IGNORE, + PSC_NAME_ACT_CONT, PSC_ACT_IGNORE, /* compatibility */ + 0, -1, + }; + int cache_flags; + const char *tmp; + + /* + * This routine runs after the skeleton code has entered the chroot jail. + * Prevent automatic process suicide after a limited number of client + * requests. It is OK to terminate after a limited amount of idle time. + */ + var_use_limit = 0; + + /* + * Workaround for parameters whose values may contain "$", and that have + * a default of "$parametername". Not sure if it would be a good idea to + * always to this in the mail_conf_raw(3) module. + */ + if (*var_psc_rej_footer == '$' + && mail_conf_lookup(var_psc_rej_footer + 1)) { + tmp = mail_conf_eval_once(var_psc_rej_footer); + myfree(var_psc_rej_footer); + var_psc_rej_footer = mystrdup(tmp); + } + if (*var_psc_exp_filter == '$' + && mail_conf_lookup(var_psc_exp_filter + 1)) { + tmp = mail_conf_eval_once(var_psc_exp_filter); + myfree(var_psc_exp_filter); + var_psc_exp_filter = mystrdup(tmp); + } + + /* + * Other one-time initialization. + */ + psc_temp = vstring_alloc(10); + vstring_sprintf(psc_temp, "%s/%s", MAIL_CLASS_PRIVATE, var_smtpd_service); + psc_smtpd_service_name = mystrdup(STR(psc_temp)); + psc_dnsbl_init(); + psc_early_init(); + psc_smtpd_init(); + + if ((psc_dnlist_action = name_code(actions, NAME_CODE_FLAG_NONE, + var_psc_dnlist_action)) < 0) + msg_fatal("bad %s value: %s", VAR_PSC_DNLIST_ACTION, + var_psc_dnlist_action); + if ((psc_dnsbl_action = name_code(actions, NAME_CODE_FLAG_NONE, + var_psc_dnsbl_action)) < 0) + msg_fatal("bad %s value: %s", VAR_PSC_DNSBL_ACTION, + var_psc_dnsbl_action); + if ((psc_pregr_action = name_code(actions, NAME_CODE_FLAG_NONE, + var_psc_pregr_action)) < 0) + msg_fatal("bad %s value: %s", VAR_PSC_PREGR_ACTION, + var_psc_pregr_action); + if ((psc_pipel_action = name_code(actions, NAME_CODE_FLAG_NONE, + var_psc_pipel_action)) < 0) + msg_fatal("bad %s value: %s", VAR_PSC_PIPEL_ACTION, + var_psc_pipel_action); + if ((psc_nsmtp_action = name_code(actions, NAME_CODE_FLAG_NONE, + var_psc_nsmtp_action)) < 0) + msg_fatal("bad %s value: %s", VAR_PSC_NSMTP_ACTION, + var_psc_nsmtp_action); + if ((psc_barlf_action = name_code(actions, NAME_CODE_FLAG_NONE, + var_psc_barlf_action)) < 0) + msg_fatal("bad %s value: %s", VAR_PSC_BARLF_ACTION, + var_psc_barlf_action); + /* Fail "closed" on error. */ + psc_allist_if = addr_match_list_init(VAR_PSC_ALLIST_IF, MATCH_FLAG_RETURN, + var_psc_allist_if); + + /* + * Start the cache maintenance pseudo thread last. Early cleanup makes + * verbose logging more informative (we get positive confirmation that + * the cleanup thread runs). + */ + cache_flags = DICT_CACHE_FLAG_STATISTICS; + if (msg_verbose > 1) + cache_flags |= DICT_CACHE_FLAG_VERBOSE; + if (psc_cache_map != 0 && var_psc_cache_scan > 0) + dict_cache_control(psc_cache_map, + CA_DICT_CACHE_CTL_FLAGS(cache_flags), + CA_DICT_CACHE_CTL_INTERVAL(var_psc_cache_scan), + CA_DICT_CACHE_CTL_VALIDATOR(psc_cache_validator), + CA_DICT_CACHE_CTL_CONTEXT((void *) 0), + CA_DICT_CACHE_CTL_END); + + /* + * Pre-compute the minimal and maximal TTL. + */ + psc_min_ttl = + PSC_MIN(PSC_MIN(var_psc_pregr_ttl, var_psc_dnsbl_min_ttl), + PSC_MIN(PSC_MIN(var_psc_pipel_ttl, var_psc_nsmtp_ttl), + var_psc_barlf_ttl)); + + /* + * Pre-compute the stress and normal command time limits. + */ + mail_conf_update(VAR_STRESS, "yes"); + psc_stress_cmd_time_limit = + get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0); + psc_stress_greet_wait = + get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0); + + mail_conf_update(VAR_STRESS, ""); + psc_normal_cmd_time_limit = + get_mail_conf_time(VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, 1, 0); + psc_normal_greet_wait = + get_mail_conf_time(VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, 1, 0); + + psc_lowat_check_queue_length = .7 * var_psc_pre_queue_limit; + psc_hiwat_check_queue_length = .9 * var_psc_pre_queue_limit; + if (msg_verbose) + msg_info(VAR_PSC_CMD_TIME ": stress=%d normal=%d lowat=%d hiwat=%d", + psc_stress_cmd_time_limit, psc_normal_cmd_time_limit, + psc_lowat_check_queue_length, psc_hiwat_check_queue_length); + + if (psc_lowat_check_queue_length == 0) + msg_panic("compiler error: 0.7 * %d = %d", var_psc_pre_queue_limit, + psc_lowat_check_queue_length); + if (psc_hiwat_check_queue_length == 0) + msg_panic("compiler error: 0.9 * %d = %d", var_psc_pre_queue_limit, + psc_hiwat_check_queue_length); + + /* + * Per-client concurrency. + */ + psc_client_concurrency = htable_create(var_psc_pre_queue_limit); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the multi-threaded skeleton */ + +int main(int argc, char **argv) +{ + + /* + * List smtpd(8) parameters before any postscreen(8) parameters that have + * defaults dependencies on them. + */ + static const CONFIG_STR_TABLE str_table[] = { + VAR_SMTPD_SERVICE, DEF_SMTPD_SERVICE, &var_smtpd_service, 1, 0, + VAR_SMTPD_BANNER, DEF_SMTPD_BANNER, &var_smtpd_banner, 1, 0, + VAR_SMTPD_FORBID_CMDS, DEF_SMTPD_FORBID_CMDS, &var_smtpd_forbid_cmds, 0, 0, + VAR_SMTPD_EHLO_DIS_WORDS, DEF_SMTPD_EHLO_DIS_WORDS, &var_smtpd_ehlo_dis_words, 0, 0, + VAR_SMTPD_EHLO_DIS_MAPS, DEF_SMTPD_EHLO_DIS_MAPS, &var_smtpd_ehlo_dis_maps, 0, 0, + VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0, + VAR_SMTPD_CMD_FILTER, DEF_SMTPD_CMD_FILTER, &var_smtpd_cmd_filter, 0, 0, + VAR_PSC_CACHE_MAP, DEF_PSC_CACHE_MAP, &var_psc_cache_map, 0, 0, + VAR_PSC_PREGR_BANNER, DEF_PSC_PREGR_BANNER, &var_psc_pregr_banner, 0, 0, + VAR_PSC_PREGR_ACTION, DEF_PSC_PREGR_ACTION, &var_psc_pregr_action, 1, 0, + VAR_PSC_DNSBL_SITES, DEF_PSC_DNSBL_SITES, &var_psc_dnsbl_sites, 0, 0, + VAR_PSC_DNSBL_ACTION, DEF_PSC_DNSBL_ACTION, &var_psc_dnsbl_action, 1, 0, + VAR_PSC_PIPEL_ACTION, DEF_PSC_PIPEL_ACTION, &var_psc_pipel_action, 1, 0, + VAR_PSC_NSMTP_ACTION, DEF_PSC_NSMTP_ACTION, &var_psc_nsmtp_action, 1, 0, + VAR_PSC_BARLF_ACTION, DEF_PSC_BARLF_ACTION, &var_psc_barlf_action, 1, 0, + VAR_PSC_ACL, DEF_PSC_ACL, &var_psc_acl, 0, 0, + VAR_PSC_DNLIST_ACTION, DEF_PSC_DNLIST_ACTION, &var_psc_dnlist_action, 1, 0, + VAR_PSC_FORBID_CMDS, DEF_PSC_FORBID_CMDS, &var_psc_forbid_cmds, 0, 0, + VAR_PSC_EHLO_DIS_WORDS, DEF_PSC_EHLO_DIS_WORDS, &var_psc_ehlo_dis_words, 0, 0, + VAR_PSC_EHLO_DIS_MAPS, DEF_PSC_EHLO_DIS_MAPS, &var_psc_ehlo_dis_maps, 0, 0, + VAR_PSC_DNSBL_REPLY, DEF_PSC_DNSBL_REPLY, &var_psc_dnsbl_reply, 0, 0, + VAR_PSC_TLS_LEVEL, DEF_PSC_TLS_LEVEL, &var_psc_tls_level, 0, 0, + VAR_PSC_CMD_FILTER, DEF_PSC_CMD_FILTER, &var_psc_cmd_filter, 0, 0, + VAR_DNSBLOG_SERVICE, DEF_DNSBLOG_SERVICE, &var_dnsblog_service, 1, 0, + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, + VAR_PSC_ALLIST_IF, DEF_PSC_ALLIST_IF, &var_psc_allist_if, 0, 0, + VAR_PSC_UPROXY_PROTO, DEF_PSC_UPROXY_PROTO, &var_psc_uproxy_proto, 0, 0, + VAR_PSC_REJ_FTR_MAPS, DEF_PSC_REJ_FTR_MAPS, &var_psc_rej_ftr_maps, 0, 0, + 0, + }; + static const CONFIG_INT_TABLE int_table[] = { + VAR_PSC_DNSBL_THRESH, DEF_PSC_DNSBL_THRESH, &var_psc_dnsbl_thresh, 1, 0, + VAR_PSC_CMD_COUNT, DEF_PSC_CMD_COUNT, &var_psc_cmd_count, 1, 0, + VAR_SMTPD_CCONN_LIMIT, DEF_SMTPD_CCONN_LIMIT, &var_smtpd_cconn_limit, 0, 0, + 0, + }; + static const CONFIG_NINT_TABLE nint_table[] = { + VAR_PSC_POST_QLIMIT, DEF_PSC_POST_QLIMIT, &var_psc_post_queue_limit, 5, 0, + VAR_PSC_PRE_QLIMIT, DEF_PSC_PRE_QLIMIT, &var_psc_pre_queue_limit, 10, 0, + VAR_PSC_CCONN_LIMIT, DEF_PSC_CCONN_LIMIT, &var_psc_cconn_limit, 0, 0, + VAR_PSC_DNSBL_ALTHRESH, DEF_PSC_DNSBL_ALTHRESH, &var_psc_dnsbl_althresh, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_PSC_CMD_TIME, DEF_PSC_CMD_TIME, &var_psc_cmd_time, 1, 0, + VAR_PSC_GREET_WAIT, DEF_PSC_GREET_WAIT, &var_psc_greet_wait, 1, 0, + VAR_PSC_PREGR_TTL, DEF_PSC_PREGR_TTL, &var_psc_pregr_ttl, 1, 0, + VAR_PSC_DNSBL_MIN_TTL, DEF_PSC_DNSBL_MIN_TTL, &var_psc_dnsbl_min_ttl, 1, 0, + VAR_PSC_DNSBL_MAX_TTL, DEF_PSC_DNSBL_MAX_TTL, &var_psc_dnsbl_max_ttl, 1, 0, + VAR_PSC_PIPEL_TTL, DEF_PSC_PIPEL_TTL, &var_psc_pipel_ttl, 1, 0, + VAR_PSC_NSMTP_TTL, DEF_PSC_NSMTP_TTL, &var_psc_nsmtp_ttl, 1, 0, + VAR_PSC_BARLF_TTL, DEF_PSC_BARLF_TTL, &var_psc_barlf_ttl, 1, 0, + VAR_PSC_CACHE_RET, DEF_PSC_CACHE_RET, &var_psc_cache_ret, 1, 0, + VAR_PSC_CACHE_SCAN, DEF_PSC_CACHE_SCAN, &var_psc_cache_scan, 0, 0, + VAR_PSC_WATCHDOG, DEF_PSC_WATCHDOG, &var_psc_watchdog, 10, 0, + VAR_PSC_UPROXY_TMOUT, DEF_PSC_UPROXY_TMOUT, &var_psc_uproxy_tmout, 1, 0, + VAR_PSC_DNSBL_TMOUT, DEF_PSC_DNSBL_TMOUT, &var_psc_dnsbl_tmout, 1, 0, + + 0, + }; + static const CONFIG_BOOL_TABLE bool_table[] = { + VAR_HELO_REQUIRED, DEF_HELO_REQUIRED, &var_helo_required, + VAR_DISABLE_VRFY_CMD, DEF_DISABLE_VRFY_CMD, &var_disable_vrfy_cmd, + VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls, + VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls, + VAR_PSC_PIPEL_ENABLE, DEF_PSC_PIPEL_ENABLE, &var_psc_pipel_enable, + VAR_PSC_NSMTP_ENABLE, DEF_PSC_NSMTP_ENABLE, &var_psc_nsmtp_enable, + VAR_PSC_BARLF_ENABLE, DEF_PSC_BARLF_ENABLE, &var_psc_barlf_enable, + 0, + }; + static const CONFIG_RAW_TABLE raw_table[] = { + VAR_SMTPD_REJ_FOOTER, DEF_SMTPD_REJ_FOOTER, &var_smtpd_rej_footer, 0, 0, + VAR_PSC_REJ_FOOTER, DEF_PSC_REJ_FOOTER, &var_psc_rej_footer, 0, 0, + VAR_SMTPD_EXP_FILTER, DEF_SMTPD_EXP_FILTER, &var_smtpd_exp_filter, 1, 0, + VAR_PSC_EXP_FILTER, DEF_PSC_EXP_FILTER, &var_psc_exp_filter, 1, 0, + 0, + }; + static const CONFIG_NBOOL_TABLE nbool_table[] = { + VAR_PSC_HELO_REQUIRED, DEF_PSC_HELO_REQUIRED, &var_psc_helo_required, + VAR_PSC_DISABLE_VRFY, DEF_PSC_DISABLE_VRFY, &var_psc_disable_vrfy, + VAR_PSC_USE_TLS, DEF_PSC_USE_TLS, &var_psc_use_tls, + VAR_PSC_ENFORCE_TLS, DEF_PSC_ENFORCE_TLS, &var_psc_enforce_tls, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + event_server_main(argc, argv, psc_service, + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_NINT_TABLE(nint_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_BOOL_TABLE(bool_table), + CA_MAIL_SERVER_RAW_TABLE(raw_table), + CA_MAIL_SERVER_NBOOL_TABLE(nbool_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_SOLITARY, + CA_MAIL_SERVER_SLOW_EXIT(psc_drain), + CA_MAIL_SERVER_EXIT(psc_dump), + CA_MAIL_SERVER_WATCHDOG(&var_psc_watchdog), + 0); +} diff --git a/src/postscreen/postscreen.h b/src/postscreen/postscreen.h new file mode 100644 index 0000000..69a5e17 --- /dev/null +++ b/src/postscreen/postscreen.h @@ -0,0 +1,599 @@ +/*++ +/* NAME +/* postscreen 3h +/* SUMMARY +/* postscreen internal interfaces +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include + + /* + * Preliminary stuff, to be fixed. + */ +#define PSC_READ_BUF_SIZE 1024 + + /* + * Numeric indices and symbolic names for tests whose time stamps and status + * flags can be accessed by numeric index. + */ +#define PSC_TINDX_PREGR 0 /* pregreet */ +#define PSC_TINDX_DNSBL 1 /* dnsbl */ +#define PSC_TINDX_PIPEL 2 /* pipelining */ +#define PSC_TINDX_NSMTP 3 /* non-smtp command */ +#define PSC_TINDX_BARLF 4 /* bare newline */ +#define PSC_TINDX_COUNT 5 /* number of tests */ + +#define PSC_TNAME_PREGR "pregreet" +#define PSC_TNAME_DNSBL "dnsbl" +#define PSC_TNAME_PIPEL "pipelining" +#define PSC_TNAME_NSMTP "non-smtp command" +#define PSC_TNAME_BARLF "bare newline" + +#define PSC_TINDX_BYTNAME(tname) (PSC_TINDX_ ## tname) + + /* + * Per-client shared state. + */ +typedef struct { + int concurrency; /* per-client */ + int pass_new_count; /* per-client */ + time_t expire_time[PSC_TINDX_COUNT]; /* per-test expiration */ +} PSC_CLIENT_INFO; + + /* + * Per-session state. + */ +typedef struct { + int flags; /* see below */ + /* Socket state. */ + VSTREAM *smtp_client_stream; /* remote SMTP client */ + int smtp_server_fd; /* real SMTP server */ + char *smtp_client_addr; /* client address */ + char *smtp_client_port; /* client port */ + char *smtp_server_addr; /* server address */ + char *smtp_server_port; /* server port */ + const char *final_reply; /* cause for hanging up */ + VSTRING *send_buf; /* pending output */ + /* Test context. */ + struct timeval start_time; /* start of current test */ + const char *test_name; /* name of current test */ + PSC_CLIENT_INFO *client_info; /* shared client state */ + VSTRING *dnsbl_reply; /* dnsbl reject text */ + int dnsbl_score; /* saved DNSBL score */ + int dnsbl_ttl; /* saved DNSBL TTL */ + const char *dnsbl_name; /* DNSBL name with largest weight */ + int dnsbl_index; /* dnsbl request index */ + const char *rcpt_reply; /* how to reject recipients */ + int command_count; /* error + junk command count */ + const char *protocol; /* SMTP or ESMTP */ + char *helo_name; /* SMTP helo/ehlo */ + char *sender; /* MAIL FROM */ + VSTRING *cmd_buffer; /* command read buffer */ + int read_state; /* command read state machine */ + /* smtpd(8) compatibility */ + int ehlo_discard_mask; /* EHLO filter */ + VSTRING *expand_buf; /* macro expansion */ + const char *where; /* SMTP protocol state */ +} PSC_STATE; + + /* + * Special expiration time values. + */ +#define PSC_TIME_STAMP_NEW (0) /* test was never passed */ +#define PSC_TIME_STAMP_DISABLED (1) /* never passed but disabled */ +#define PSC_TIME_STAMP_INVALID (-1) /* must not be cached */ + + /* + * Status flags. + */ +#define PSC_STATE_FLAG_NOFORWARD (1<<0) /* don't forward this session */ +#define PSC_STATE_FLAG_USING_TLS (1<<1) /* using the TLS proxy */ +#define PSC_STATE_FLAG_UNUSED2 (1<<2) /* use me! */ +#define PSC_STATE_FLAG_NEW (1<<3) /* some test was never passed */ +#define PSC_STATE_FLAG_DNLIST_FAIL (1<<4) /* denylisted */ +#define PSC_STATE_FLAG_HANGUP (1<<5) /* NOT a test failure */ +#define PSC_STATE_FLAG_SMTPD_X21 (1<<6) /* hang up after command */ +#define PSC_STATE_FLAG_ALLIST_FAIL (1<<7) /* do not allowlist */ +#define PSC_STATE_FLAG_TEST_BASE (8) /* start of indexable flags */ + + /* + * Tests whose flags and expiration time can be accessed by numerical index. + * + * Important: every MUMBLE_TODO flag must have a MUMBLE_PASS flag, such that + * MUMBLE_PASS == PSC_STATE_FLAGS_TODO_TO_PASS(MUMBLE_TODO). + * + * MUMBLE_TODO flags must not be cleared once raised. The _TODO_TO_PASS and + * _TODO_TO_DONE macros depend on this to decide that a group of tests is + * passed or completed. + * + * MUMBLE_DONE flags are used for "early" tests that have final results. + * + * MUMBLE_SKIP flags are used for "deep" tests where the client messed up. + * These flags look like MUMBLE_DONE but they are different. Deep tests can + * tentatively pass, but can still fail later in a session. The "ignore" + * action introduces an additional complication. MUMBLE_PASS indicates + * either that a deep test passed tentatively, or that the test failed but + * the result was ignored. MUMBLE_FAIL, on the other hand, is always final. + * We use MUMBLE_SKIP to indicate that a decision was either "fail" or + * forced "pass". + * + * The difference between DONE and SKIP is in the beholder's eye. These flags + * share the same bit. + */ +#define PSC_STATE_FLAGS_TODO_TO_PASS(todo_flags) ((todo_flags) >> 1) +#define PSC_STATE_FLAGS_TODO_TO_DONE(todo_flags) ((todo_flags) << 1) + +#define PSC_STATE_FLAG_SHIFT_FAIL (0) /* failed test */ +#define PSC_STATE_FLAG_SHIFT_PASS (1) /* passed test */ +#define PSC_STATE_FLAG_SHIFT_TODO (2) /* expired test */ +#define PSC_STATE_FLAG_SHIFT_DONE (3) /* decision is final */ +#define PSC_STATE_FLAG_SHIFT_SKIP (3) /* action is already logged */ +#define PSC_STATE_FLAG_SHIFT_STRIDE (4) /* nr of flags per test */ + +#define PSC_STATE_FLAG_SHIFT_BYFNAME(fname) (PSC_STATE_FLAG_SHIFT_ ## fname) + + /* + * Indexable per-test flags. These are used for DNS allowlisting multiple + * tests, without needing per-test ad-hoc code. + */ +#define PSC_STATE_FLAG_BYTINDX_FNAME(tindx, fname) \ + (1U << (PSC_STATE_FLAG_TEST_BASE \ + + PSC_STATE_FLAG_SHIFT_STRIDE * (tindx) \ + + PSC_STATE_FLAG_SHIFT_BYFNAME(fname))) + +#define PSC_STATE_FLAG_BYTINDX_FAIL(tindx) \ + PSC_STATE_FLAG_BYTINDX_FNAME((tindx), FAIL) +#define PSC_STATE_FLAG_BYTINDX_PASS(tindx) \ + PSC_STATE_FLAG_BYTINDX_FNAME((tindx), PASS) +#define PSC_STATE_FLAG_BYTINDX_TODO(tindx) \ + PSC_STATE_FLAG_BYTINDX_FNAME((tindx), TODO) +#define PSC_STATE_FLAG_BYTINDX_DONE(tindx) \ + PSC_STATE_FLAG_BYTINDX_FNAME((tindx), DONE) +#define PSC_STATE_FLAG_BYTINDX_SKIP(tindx) \ + PSC_STATE_FLAG_BYTINDX_FNAME((tindx), SKIP) + + /* + * Flags with distinct names. These are used in the per-test ad-hoc code. + */ +#define PSC_STATE_FLAG_BYTNAME_FNAME(tname, fname) \ + (1U << (PSC_STATE_FLAG_TEST_BASE \ + + PSC_STATE_FLAG_SHIFT_STRIDE * PSC_TINDX_BYTNAME(tname) \ + + PSC_STATE_FLAG_SHIFT_BYFNAME(fname))) + +#define PSC_STATE_FLAG_PREGR_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, FAIL) +#define PSC_STATE_FLAG_PREGR_PASS PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, PASS) +#define PSC_STATE_FLAG_PREGR_TODO PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, TODO) +#define PSC_STATE_FLAG_PREGR_DONE PSC_STATE_FLAG_BYTNAME_FNAME(PREGR, DONE) + +#define PSC_STATE_FLAG_DNSBL_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, FAIL) +#define PSC_STATE_FLAG_DNSBL_PASS PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, PASS) +#define PSC_STATE_FLAG_DNSBL_TODO PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, TODO) +#define PSC_STATE_FLAG_DNSBL_DONE PSC_STATE_FLAG_BYTNAME_FNAME(DNSBL, DONE) + +#define PSC_STATE_FLAG_PIPEL_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, FAIL) +#define PSC_STATE_FLAG_PIPEL_PASS PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, PASS) +#define PSC_STATE_FLAG_PIPEL_TODO PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, TODO) +#define PSC_STATE_FLAG_PIPEL_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(PIPEL, SKIP) + +#define PSC_STATE_FLAG_NSMTP_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, FAIL) +#define PSC_STATE_FLAG_NSMTP_PASS PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, PASS) +#define PSC_STATE_FLAG_NSMTP_TODO PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, TODO) +#define PSC_STATE_FLAG_NSMTP_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(NSMTP, SKIP) + +#define PSC_STATE_FLAG_BARLF_FAIL PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, FAIL) +#define PSC_STATE_FLAG_BARLF_PASS PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, PASS) +#define PSC_STATE_FLAG_BARLF_TODO PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, TODO) +#define PSC_STATE_FLAG_BARLF_SKIP PSC_STATE_FLAG_BYTNAME_FNAME(BARLF, SKIP) + + /* + * Aggregates for individual tests. + */ +#define PSC_STATE_MASK_PREGR_TODO_FAIL \ + (PSC_STATE_FLAG_PREGR_TODO | PSC_STATE_FLAG_PREGR_FAIL) +#define PSC_STATE_MASK_DNSBL_TODO_FAIL \ + (PSC_STATE_FLAG_DNSBL_TODO | PSC_STATE_FLAG_DNSBL_FAIL) +#define PSC_STATE_MASK_PIPEL_TODO_FAIL \ + (PSC_STATE_FLAG_PIPEL_TODO | PSC_STATE_FLAG_PIPEL_FAIL) +#define PSC_STATE_MASK_NSMTP_TODO_FAIL \ + (PSC_STATE_FLAG_NSMTP_TODO | PSC_STATE_FLAG_NSMTP_FAIL) +#define PSC_STATE_MASK_BARLF_TODO_FAIL \ + (PSC_STATE_FLAG_BARLF_TODO | PSC_STATE_FLAG_BARLF_FAIL) + +#define PSC_STATE_MASK_PREGR_TODO_DONE \ + (PSC_STATE_FLAG_PREGR_TODO | PSC_STATE_FLAG_PREGR_DONE) +#define PSC_STATE_MASK_PIPEL_TODO_SKIP \ + (PSC_STATE_FLAG_PIPEL_TODO | PSC_STATE_FLAG_PIPEL_SKIP) +#define PSC_STATE_MASK_NSMTP_TODO_SKIP \ + (PSC_STATE_FLAG_NSMTP_TODO | PSC_STATE_FLAG_NSMTP_SKIP) +#define PSC_STATE_MASK_BARLF_TODO_SKIP \ + (PSC_STATE_FLAG_BARLF_TODO | PSC_STATE_FLAG_BARLF_SKIP) + +#define PSC_STATE_MASK_PREGR_FAIL_DONE \ + (PSC_STATE_FLAG_PREGR_FAIL | PSC_STATE_FLAG_PREGR_DONE) + +#define PSC_STATE_MASK_PIPEL_TODO_PASS_FAIL \ + (PSC_STATE_MASK_PIPEL_TODO_FAIL | PSC_STATE_FLAG_PIPEL_PASS) +#define PSC_STATE_MASK_NSMTP_TODO_PASS_FAIL \ + (PSC_STATE_MASK_NSMTP_TODO_FAIL | PSC_STATE_FLAG_NSMTP_PASS) +#define PSC_STATE_MASK_BARLF_TODO_PASS_FAIL \ + (PSC_STATE_MASK_BARLF_TODO_FAIL | PSC_STATE_FLAG_BARLF_PASS) + + /* + * Separate aggregates for early tests and deep tests. + */ +#define PSC_STATE_MASK_EARLY_DONE \ + (PSC_STATE_FLAG_PREGR_DONE | PSC_STATE_FLAG_DNSBL_DONE) +#define PSC_STATE_MASK_EARLY_TODO \ + (PSC_STATE_FLAG_PREGR_TODO | PSC_STATE_FLAG_DNSBL_TODO) +#define PSC_STATE_MASK_EARLY_PASS \ + (PSC_STATE_FLAG_PREGR_PASS | PSC_STATE_FLAG_DNSBL_PASS) +#define PSC_STATE_MASK_EARLY_FAIL \ + (PSC_STATE_FLAG_PREGR_FAIL | PSC_STATE_FLAG_DNSBL_FAIL) + +#define PSC_STATE_MASK_SMTPD_TODO \ + (PSC_STATE_FLAG_PIPEL_TODO | PSC_STATE_FLAG_NSMTP_TODO | \ + PSC_STATE_FLAG_BARLF_TODO) +#define PSC_STATE_MASK_SMTPD_PASS \ + (PSC_STATE_FLAG_PIPEL_PASS | PSC_STATE_FLAG_NSMTP_PASS | \ + PSC_STATE_FLAG_BARLF_PASS) +#define PSC_STATE_MASK_SMTPD_FAIL \ + (PSC_STATE_FLAG_PIPEL_FAIL | PSC_STATE_FLAG_NSMTP_FAIL | \ + PSC_STATE_FLAG_BARLF_FAIL) + + /* + * Super-aggregates for all tests combined. + */ +#define PSC_STATE_MASK_ANY_FAIL \ + (PSC_STATE_FLAG_DNLIST_FAIL | \ + PSC_STATE_MASK_EARLY_FAIL | PSC_STATE_MASK_SMTPD_FAIL | \ + PSC_STATE_FLAG_ALLIST_FAIL) + +#define PSC_STATE_MASK_ANY_PASS \ + (PSC_STATE_MASK_EARLY_PASS | PSC_STATE_MASK_SMTPD_PASS) + +#define PSC_STATE_MASK_ANY_TODO \ + (PSC_STATE_MASK_EARLY_TODO | PSC_STATE_MASK_SMTPD_TODO) + +#define PSC_STATE_MASK_ANY_TODO_FAIL \ + (PSC_STATE_MASK_ANY_TODO | PSC_STATE_MASK_ANY_FAIL) + +#define PSC_STATE_MASK_ANY_UPDATE \ + (PSC_STATE_MASK_ANY_PASS) + + /* + * Meta-commands for state->where that reflect the initial command processor + * state and commands that aren't implemented. + */ +#define PSC_SMTPD_CMD_CONNECT "CONNECT" +#define PSC_SMTPD_CMD_UNIMPL "UNIMPLEMENTED" + + /* + * See log_adhoc.c for discussion. + */ +typedef struct { + int dt_sec; /* make sure it's signed */ + int dt_usec; /* make sure it's signed */ +} DELTA_TIME; + +#define PSC_CALC_DELTA(x, y, z) \ + do { \ + (x).dt_sec = (y).tv_sec - (z).tv_sec; \ + (x).dt_usec = (y).tv_usec - (z).tv_usec; \ + while ((x).dt_usec < 0) { \ + (x).dt_usec += 1000000; \ + (x).dt_sec -= 1; \ + } \ + while ((x).dt_usec >= 1000000) { \ + (x).dt_usec -= 1000000; \ + (x).dt_sec += 1; \ + } \ + if ((x).dt_sec < 0) \ + (x).dt_sec = (x).dt_usec = 0; \ + } while (0) + +#define SIG_DIGS 2 + + /* + * Event management. + */ + +/* PSC_READ_EVENT_REQUEST - prepare for transition to next state */ + +#define PSC_READ_EVENT_REQUEST(fd, action, context, timeout) do { \ + if (msg_verbose > 1) \ + msg_info("%s: read-request fd=%d", myname, (fd)); \ + event_enable_read((fd), (action), (context)); \ + event_request_timer((action), (context), (timeout)); \ + } while (0) + +#define PSC_READ_EVENT_REQUEST2(fd, read_act, time_act, context, timeout) do { \ + if (msg_verbose > 1) \ + msg_info("%s: read-request fd=%d", myname, (fd)); \ + event_enable_read((fd), (read_act), (context)); \ + event_request_timer((time_act), (context), (timeout)); \ + } while (0) + +/* PSC_CLEAR_EVENT_REQUEST - complete state transition */ + +#define PSC_CLEAR_EVENT_REQUEST(fd, time_act, context) do { \ + if (msg_verbose > 1) \ + msg_info("%s: clear-request fd=%d", myname, (fd)); \ + event_disable_readwrite(fd); \ + event_cancel_timer((time_act), (context)); \ + } while (0) + + /* + * Failure enforcement policies. + */ +#define PSC_NAME_ACT_DROP "drop" +#define PSC_NAME_ACT_ENFORCE "enforce" +#define PSC_NAME_ACT_IGNORE "ignore" +#define PSC_NAME_ACT_CONT "continue" + +#define PSC_ACT_DROP 1 +#define PSC_ACT_ENFORCE 2 +#define PSC_ACT_IGNORE 3 + + /* + * Global variables. + */ +extern int psc_check_queue_length; /* connections being checked */ +extern int psc_post_queue_length; /* being sent to real SMTPD */ +extern DICT_CACHE *psc_cache_map; /* cache table handle */ +extern VSTRING *psc_temp; /* scratchpad */ +extern char *psc_smtpd_service_name; /* path to real SMTPD */ +extern int psc_pregr_action; /* PSC_ACT_DROP etc. */ +extern int psc_dnsbl_action; /* PSC_ACT_DROP etc. */ +extern int psc_pipel_action; /* PSC_ACT_DROP etc. */ +extern int psc_nsmtp_action; /* PSC_ACT_DROP etc. */ +extern int psc_barlf_action; /* PSC_ACT_DROP etc. */ +extern int psc_min_ttl; /* Update with new tests! */ +extern STRING_LIST *psc_forbid_cmds; /* CONNECT GET POST */ +extern int psc_stress_greet_wait; /* stressed greet wait */ +extern int psc_normal_greet_wait; /* stressed greet wait */ +extern int psc_stress_cmd_time_limit; /* stressed command limit */ +extern int psc_normal_cmd_time_limit; /* normal command time limit */ +extern int psc_stress; /* stress level */ +extern int psc_lowat_check_queue_length;/* stress low-water mark */ +extern int psc_hiwat_check_queue_length;/* stress high-water mark */ +extern DICT *psc_dnsbl_reply; /* DNSBL name mapper */ +extern HTABLE *psc_client_concurrency; /* per-client concurrency */ + +#define PSC_EFF_GREET_WAIT \ + (psc_stress ? psc_stress_greet_wait : psc_normal_greet_wait) +#define PSC_EFF_CMD_TIME_LIMIT \ + (psc_stress ? psc_stress_cmd_time_limit : psc_normal_cmd_time_limit) + + /* + * String plumbing macros. + */ +#define PSC_STRING_UPDATE(str, text) do { \ + if (str) myfree(str); \ + (str) = ((text) ? mystrdup(text) : 0); \ + } while (0) + +#define PSC_STRING_RESET(str) do { \ + if (str) { \ + myfree(str); \ + (str) = 0; \ + } \ + } while (0) + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + + /* + * postscreen_state.c + */ +#define PSC_CLIENT_ADDR_PORT(state) \ + (state)->smtp_client_addr, (state)->smtp_client_port + +#define PSC_PASS_SESSION_STATE(state, what, bits) do { \ + if (msg_verbose) \ + msg_info("PASS %s [%s]:%s", (what), PSC_CLIENT_ADDR_PORT(state)); \ + (state)->flags |= (bits); \ + } while (0) +#define PSC_FAIL_SESSION_STATE(state, bits) do { \ + if (msg_verbose) \ + msg_info("FAIL [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \ + (state)->flags |= (bits); \ + } while (0) +#define PSC_SKIP_SESSION_STATE(state, what, bits) do { \ + if (msg_verbose) \ + msg_info("SKIP %s [%s]:%s", (what), PSC_CLIENT_ADDR_PORT(state)); \ + (state)->flags |= (bits); \ + } while (0) +#define PSC_DROP_SESSION_STATE(state, reply) do { \ + if (msg_verbose) \ + msg_info("DROP [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \ + (state)->flags |= PSC_STATE_FLAG_NOFORWARD; \ + (state)->final_reply = (reply); \ + psc_conclude(state); \ + } while (0) +#define PSC_ENFORCE_SESSION_STATE(state, reply) do { \ + if (msg_verbose) \ + msg_info("ENFORCE [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \ + (state)->rcpt_reply = (reply); \ + (state)->flags |= PSC_STATE_FLAG_NOFORWARD; \ + } while (0) +#define PSC_UNPASS_SESSION_STATE(state, bits) do { \ + if (msg_verbose) \ + msg_info("UNPASS [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \ + (state)->flags &= ~(bits); \ + } while (0) +#define PSC_UNFAIL_SESSION_STATE(state, bits) do { \ + if (msg_verbose) \ + msg_info("UNFAIL [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); \ + (state)->flags &= ~(bits); \ + } while (0) +#define PSC_ADD_SERVER_STATE(state, fd) do { \ + (state)->smtp_server_fd = (fd); \ + psc_post_queue_length++; \ + } while (0) +#define PSC_DEL_SERVER_STATE(state) do { \ + close((state)->smtp_server_fd); \ + (state)->smtp_server_fd = (-1); \ + psc_post_queue_length--; \ + } while (0) +#define PSC_DEL_CLIENT_STATE(state) do { \ + event_server_disconnect((state)->smtp_client_stream); \ + (state)->smtp_client_stream = 0; \ + psc_check_queue_length--; \ + } while (0) +extern PSC_STATE *psc_new_session_state(VSTREAM *, const char *, const char *, const char *, const char *); +extern void psc_free_session_state(PSC_STATE *); +extern const char *psc_print_state_flags(int, const char *); + + /* + * postscreen_dict.c + */ +extern int psc_addr_match_list_match(ADDR_MATCH_LIST *, const char *); +extern const char *psc_cache_lookup(DICT_CACHE *, const char *); +extern void psc_cache_update(DICT_CACHE *, const char *, const char *); +const char *psc_dict_get(DICT *, const char *); +const char *psc_maps_find(MAPS *, const char *, int); + + /* + * postscreen_dnsbl.c + */ +extern void psc_dnsbl_init(void); +extern int psc_dnsbl_retrieve(const char *, const char **, int, int *); +extern int psc_dnsbl_request(const char *, void (*) (int, void *), void *); + + /* + * postscreen_tests.c + */ +#define PSC_INIT_TESTS(dst) do { \ + time_t *_it_stamp_p; \ + (dst)->flags = 0; \ + for (_it_stamp_p = (dst)->client_info->expire_time; \ + _it_stamp_p < (dst)->client_info->expire_time + PSC_TINDX_COUNT; \ + _it_stamp_p++) \ + *_it_stamp_p = PSC_TIME_STAMP_INVALID; \ + } while (0) +#define PSC_INIT_TEST_FLAGS_ONLY(dst) do { \ + (dst)->flags = 0; \ + } while (0) +#define PSC_BEGIN_TESTS(state, name) do { \ + (state)->test_name = (name); \ + GETTIMEOFDAY(&(state)->start_time); \ + } while (0) +extern void psc_new_tests(PSC_STATE *); +extern void psc_parse_tests(PSC_STATE *, const char *, time_t); +extern void psc_todo_tests(PSC_STATE *, time_t); +extern char *psc_print_tests(VSTRING *, PSC_STATE *); +extern char *psc_print_grey_key(VSTRING *, const char *, const char *, + const char *, const char *); +extern const char *psc_test_name(int); + +#define PSC_MIN(x, y) ((x) < (y) ? (x) : (y)) +#define PSC_MAX(x, y) ((x) > (y) ? (x) : (y)) + + /* + * postscreen_early.c + */ +extern void psc_early_tests(PSC_STATE *); +extern void psc_early_init(void); + + /* + * postscreen_smtpd.c + */ +extern void psc_smtpd_tests(PSC_STATE *); +extern void psc_smtpd_init(void); +extern void psc_smtpd_pre_jail_init(void); + +#define PSC_SMTPD_X21(state, reply) do { \ + (state)->flags |= PSC_STATE_FLAG_SMTPD_X21; \ + (state)->final_reply = (reply); \ + psc_smtpd_tests(state); \ + } while (0) + + /* + * postscreen_misc.c + */ +extern char *psc_format_delta_time(VSTRING *, struct timeval, DELTA_TIME *); +extern void psc_conclude(PSC_STATE *); +extern void psc_hangup_event(PSC_STATE *); + + /* + * postscreen_send.c + */ +#define PSC_SEND_REPLY psc_send_reply /* legacy macro */ +extern void pcs_send_pre_jail_init(void); +extern int psc_send_reply(PSC_STATE *, const char *); +extern void psc_send_socket(PSC_STATE *); + + /* + * postscreen_starttls.c + */ +extern void psc_starttls_open(PSC_STATE *, EVENT_NOTIFY_FN); + + /* + * postscreen_expand.c + */ +extern VSTRING *psc_expand_filter; +extern void psc_expand_init(void); +extern const char *psc_expand_lookup(const char *, int, void *); + + /* + * postscreen_endpt.c + */ +typedef void (*PSC_ENDPT_LOOKUP_FN) (int, VSTREAM *, + MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *, + MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *); +extern void psc_endpt_lookup(VSTREAM *, PSC_ENDPT_LOOKUP_FN); +extern void psc_endpt_local_lookup(VSTREAM *, PSC_ENDPT_LOOKUP_FN); + + /* + * postscreen_access emulation. + */ +#define PSC_ACL_ACT_ALLOWLIST SERVER_ACL_ACT_PERMIT +#define PSC_ACL_ACT_DUNNO SERVER_ACL_ACT_DUNNO +#define PSC_ACL_ACT_DENYLIST SERVER_ACL_ACT_REJECT +#define PSC_ACL_ACT_ERROR SERVER_ACL_ACT_ERROR + +#define psc_acl_pre_jail_init server_acl_pre_jail_init +#define psc_acl_parse server_acl_parse +#define psc_acl_eval(s,a,p) server_acl_eval((s)->smtp_client_addr, (a), (p)) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/postscreen/postscreen_dict.c b/src/postscreen/postscreen_dict.c new file mode 100644 index 0000000..131ff81 --- /dev/null +++ b/src/postscreen/postscreen_dict.c @@ -0,0 +1,182 @@ +/*++ +/* NAME +/* postscreen_dict 3 +/* SUMMARY +/* postscreen table access wrappers +/* SYNOPSIS +/* #include +/* +/* int psc_addr_match_list_match(match_list, client_addr) +/* ADDR_MATCH_LIST *match_list; +/* const char *client_addr; +/* +/* const char *psc_cache_lookup(DICT_CACHE *cache, const char *key) +/* DICT_CACHE *cache; +/* const char *key; +/* +/* void psc_cache_update(cache, key, value) +/* DICT_CACHE *cache; +/* const char *key; +/* const char *value; +/* +/* void psc_dict_get(dict, key) +/* DICT *dict; +/* const char *key; +/* +/* void psc_maps_find(maps, key, flags) +/* MAPS *maps; +/* const char *key; +/* int flags; +/* DESCRIPTION +/* This module implements wrappers around time-critical table +/* access functions. The functions log a warning when table +/* access takes a non-trivial amount of time. +/* +/* psc_addr_match_list_match() is a wrapper around +/* addr_match_list_match(). +/* +/* psc_cache_lookup() and psc_cache_update() are wrappers around +/* the corresponding dict_cache() methods. +/* +/* psc_dict_get() and psc_maps_find() are wrappers around +/* dict_get() and maps_find(), respectively. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include + + /* + * Monitor time-critical operations. + * + * XXX Averaging support was added during a stable release candidate, so it + * provides only the absolute minimum necessary. A complete implementation + * should maintain separate statistics for each table, and it should not + * complain when the access latency is less than the time between accesses. + */ +#define PSC_GET_TIME_BEFORE_LOOKUP { \ + struct timeval _before, _after; \ + DELTA_TIME _delta; \ + double _new_delta_ms; \ + GETTIMEOFDAY(&_before); + +#define PSC_DELTA_MS(d) ((d).dt_sec * 1000.0 + (d).dt_usec / 1000.0) + +#define PSC_AVERAGE(new, old) (0.1 * (new) + 0.9 * (old)) + +#ifndef PSC_THRESHOLD_MS +#define PSC_THRESHOLD_MS 100 /* nag if latency > 100ms */ +#endif + +#ifndef PSC_WARN_LOCKOUT_S +#define PSC_WARN_LOCKOUT_S 60 /* don't nag for 60s */ +#endif + + /* + * Shared warning lock, so that we don't spam the logfile when the system + * becomes slow. + */ +static time_t psc_last_warn = 0; + +#define PSC_CHECK_TIME_AFTER_LOOKUP(table, action, average) \ + GETTIMEOFDAY(&_after); \ + PSC_CALC_DELTA(_delta, _after, _before); \ + _new_delta_ms = PSC_DELTA_MS(_delta); \ + if ((average = PSC_AVERAGE(_new_delta_ms, average)) > PSC_THRESHOLD_MS \ + && psc_last_warn < _after.tv_sec - PSC_WARN_LOCKOUT_S) { \ + msg_warn("%s: %s %s average delay is %.0f ms", \ + myname, (table), (action), average); \ + psc_last_warn = _after.tv_sec; \ + } \ +} + +/* psc_addr_match_list_match - time-critical address list lookup */ + +int psc_addr_match_list_match(ADDR_MATCH_LIST *addr_list, + const char *addr_str) +{ + const char *myname = "psc_addr_match_list_match"; + int result; + static double latency_ms; + + PSC_GET_TIME_BEFORE_LOOKUP; + result = addr_match_list_match(addr_list, addr_str); + PSC_CHECK_TIME_AFTER_LOOKUP("address list", "lookup", latency_ms); + return (result); +} + +/* psc_cache_lookup - time-critical cache lookup */ + +const char *psc_cache_lookup(DICT_CACHE *cache, const char *key) +{ + const char *myname = "psc_cache_lookup"; + const char *result; + static double latency_ms; + + PSC_GET_TIME_BEFORE_LOOKUP; + result = dict_cache_lookup(cache, key); + PSC_CHECK_TIME_AFTER_LOOKUP(dict_cache_name(cache), "lookup", latency_ms); + return (result); +} + +/* psc_cache_update - time-critical cache update */ + +void psc_cache_update(DICT_CACHE *cache, const char *key, const char *value) +{ + const char *myname = "psc_cache_update"; + static double latency_ms; + + PSC_GET_TIME_BEFORE_LOOKUP; + dict_cache_update(cache, key, value); + PSC_CHECK_TIME_AFTER_LOOKUP(dict_cache_name(cache), "update", latency_ms); +} + +/* psc_dict_get - time-critical table lookup */ + +const char *psc_dict_get(DICT *dict, const char *key) +{ + const char *myname = "psc_dict_get"; + const char *result; + static double latency_ms; + + PSC_GET_TIME_BEFORE_LOOKUP; + result = dict_get(dict, key); + PSC_CHECK_TIME_AFTER_LOOKUP(dict->name, "lookup", latency_ms); + return (result); +} + +/* psc_maps_find - time-critical table lookup */ + +const char *psc_maps_find(MAPS *maps, const char *key, int flags) +{ + const char *myname = "psc_maps_find"; + const char *result; + static double latency_ms; + + PSC_GET_TIME_BEFORE_LOOKUP; + result = maps_find(maps, key, flags); + PSC_CHECK_TIME_AFTER_LOOKUP(maps->title, "lookup", latency_ms); + return (result); +} diff --git a/src/postscreen/postscreen_dnsbl.c b/src/postscreen/postscreen_dnsbl.c new file mode 100644 index 0000000..7d9a5e9 --- /dev/null +++ b/src/postscreen/postscreen_dnsbl.c @@ -0,0 +1,624 @@ +/*++ +/* NAME +/* postscreen_dnsbl 3 +/* SUMMARY +/* postscreen DNSBL support +/* SYNOPSIS +/* #include +/* +/* void psc_dnsbl_init(void) +/* +/* int psc_dnsbl_request(client_addr, callback, context) +/* char *client_addr; +/* void (*callback)(int, char *); +/* char *context; +/* +/* int psc_dnsbl_retrieve(client_addr, dnsbl_name, dnsbl_index, +/* dnsbl_ttl) +/* char *client_addr; +/* const char **dnsbl_name; +/* int dnsbl_index; +/* int *dnsbl_ttl; +/* DESCRIPTION +/* This module implements preliminary support for DNSBL lookups. +/* Multiple requests for the same information are handled with +/* reference counts. +/* +/* psc_dnsbl_init() initializes this module, and must be called +/* once before any of the other functions in this module. +/* +/* psc_dnsbl_request() requests a blocklist score for the +/* specified client IP address and increments the reference +/* count. The request completes in the background. The client +/* IP address must be in inet_ntop(3) output format. The +/* callback argument specifies a function that is called when +/* the requested result is available. The context is passed +/* on to the callback function. The callback should ignore its +/* first argument (it exists for compatibility with Postfix +/* generic event infrastructure). +/* The result value is the index for the psc_dnsbl_retrieve() +/* call. +/* +/* psc_dnsbl_retrieve() retrieves the result score and reply +/* TTL requested with psc_dnsbl_request(), and decrements the +/* reference count. The reply TTL value is clamped to +/* postscreen_dnsbl_min_ttl and postscreen_dnsbl_max_ttl. It +/* is an error to retrieve a score without requesting it first. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include /* AF_INET */ +#include /* inet_pton() */ +#include /* inet_pton() */ +#include /* sscanf */ +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include + + /* + * Talking to the DNSBLOG service. + */ +static char *psc_dnsbl_service; + + /* + * Per-DNSBL filters and weights. + * + * The postscreen_dnsbl_sites parameter specifies zero or more DNSBL domains. + * We provide multiple access methods, one for quick iteration when sending + * queries to all DNSBL servers, and one for quick location when receiving a + * reply from one DNSBL server. + * + * Each DNSBL domain can be specified more than once, each time with a + * different (filter, weight) pair. We group (filter, weight) pairs in a + * linked list under their DNSBL domain name. The list head has a reference + * to a "safe name" for the DNSBL, in case the name includes a password. + */ +static HTABLE *dnsbl_site_cache; /* indexed by DNSBNL domain */ +static HTABLE_INFO **dnsbl_site_list; /* flattened cache */ + +typedef struct { + const char *safe_dnsbl; /* from postscreen_dnsbl_reply_map */ + struct PSC_DNSBL_SITE *first; /* list of (filter, weight) tuples */ +} PSC_DNSBL_HEAD; + +typedef struct PSC_DNSBL_SITE { + char *filter; /* printable filter (default: null) */ + char *byte_codes; /* encoded filter (default: null) */ + int weight; /* reply weight (default: 1) */ + struct PSC_DNSBL_SITE *next; /* linked list */ +} PSC_DNSBL_SITE; + + /* + * Per-client DNSBL scores. + * + * Some SMTP clients make parallel connections. This can trigger parallel + * blocklist score requests when the pre-handshake delays of the connections + * overlap. + * + * We combine requests for the same score under the client IP address in a + * single reference-counted entry. The reference count goes up with each + * request for a score, and it goes down with each score retrieval. Each + * score has one or more requestors that need to be notified when the result + * is ready, so that postscreen can terminate a pre-handshake delay when all + * pre-handshake tests are completed. + */ +static HTABLE *dnsbl_score_cache; /* indexed by client address */ + +typedef struct { + void (*callback) (int, void *); /* generic call-back routine */ + void *context; /* generic call-back argument */ +} PSC_CALL_BACK_ENTRY; + +typedef struct { + const char *dnsbl_name; /* DNSBL with largest contribution */ + int dnsbl_weight; /* weight of largest contribution */ + int total; /* combined allow+denylist score */ + int fail_ttl; /* combined reply TTL */ + int pass_ttl; /* combined reply TTL */ + int refcount; /* score reference count */ + int pending_lookups; /* nr of DNS requests in flight */ + int request_id; /* duplicate suppression */ + /* Call-back table support. */ + int index; /* next table index */ + int limit; /* last valid index */ + PSC_CALL_BACK_ENTRY table[1]; /* actually a bunch */ +} PSC_DNSBL_SCORE; + +#define PSC_CALL_BACK_INIT(sp) do { \ + (sp)->limit = 0; \ + (sp)->index = 0; \ + } while (0) + +#define PSC_CALL_BACK_INDEX_OF_LAST(sp) ((sp)->index - 1) + +#define PSC_CALL_BACK_CANCEL(sp, idx) do { \ + PSC_CALL_BACK_ENTRY *_cb_; \ + if ((idx) < 0 || (idx) >= (sp)->index) \ + msg_panic("%s: index %d must be >= 0 and < %d", \ + myname, (idx), (sp)->index); \ + _cb_ = (sp)->table + (idx); \ + event_cancel_timer(_cb_->callback, _cb_->context); \ + _cb_->callback = 0; \ + _cb_->context = 0; \ + } while (0) + +#define PSC_CALL_BACK_EXTEND(hp, sp) do { \ + if ((sp)->index >= (sp)->limit) { \ + int _count_ = ((sp)->limit ? (sp)->limit * 2 : 5); \ + (hp)->value = myrealloc((void *) (sp), sizeof(*(sp)) + \ + _count_ * sizeof((sp)->table)); \ + (sp) = (PSC_DNSBL_SCORE *) (hp)->value; \ + (sp)->limit = _count_; \ + } \ + } while (0) + +#define PSC_CALL_BACK_ENTER(sp, fn, ctx) do { \ + PSC_CALL_BACK_ENTRY *_cb_ = (sp)->table + (sp)->index++; \ + _cb_->callback = (fn); \ + _cb_->context = (ctx); \ + } while (0) + +#define PSC_CALL_BACK_NOTIFY(sp, ev) do { \ + PSC_CALL_BACK_ENTRY *_cb_; \ + for (_cb_ = (sp)->table; _cb_ < (sp)->table + (sp)->index; _cb_++) \ + if (_cb_->callback != 0) \ + _cb_->callback((ev), _cb_->context); \ + } while (0) + +#define PSC_NULL_EVENT (0) + + /* + * Per-request state. + * + * This implementation stores the client IP address and DNSBL domain in the + * DNSBLOG query/reply stream. This simplifies code, and allows the DNSBLOG + * server to produce more informative logging. + */ +static VSTRING *reply_client; /* client address in DNSBLOG reply */ +static VSTRING *reply_dnsbl; /* domain in DNSBLOG reply */ +static VSTRING *reply_addr; /* address list in DNSBLOG reply */ + +/* psc_dnsbl_add_site - add DNSBL site information */ + +static void psc_dnsbl_add_site(const char *site) +{ + const char *myname = "psc_dnsbl_add_site"; + char *saved_site = mystrdup(site); + VSTRING *byte_codes = 0; + PSC_DNSBL_HEAD *head; + PSC_DNSBL_SITE *new_site; + char junk; + const char *weight_text; + char *pattern_text; + int weight; + HTABLE_INFO *ht; + char *parse_err; + const char *safe_dnsbl; + + /* + * Parse the required DNSBL domain name, the optional reply filter and + * the optional reply weight factor. + */ +#define DO_GRIPE 1 + + /* Negative weight means allowlist. */ + if ((weight_text = split_at(saved_site, '*')) != 0) { + if (sscanf(weight_text, "%d%c", &weight, &junk) != 1) + msg_fatal("bad DNSBL weight factor \"%s\" in \"%s\"", + weight_text, site); + } else { + weight = 1; + } + /* Reply filter. */ + if ((pattern_text = split_at(saved_site, '=')) != 0) { + byte_codes = vstring_alloc(100); + if ((parse_err = ip_match_parse(byte_codes, pattern_text)) != 0) + msg_fatal("bad DNSBL filter syntax: %s", parse_err); + } + if (valid_hostname(saved_site, DO_GRIPE) == 0) + msg_fatal("bad DNSBL domain name \"%s\" in \"%s\"", + saved_site, site); + + if (msg_verbose > 1) + msg_info("%s: \"%s\" -> domain=\"%s\" pattern=\"%s\" weight=%d", + myname, site, saved_site, pattern_text ? pattern_text : + "null", weight); + + /* + * Look up or create the (filter, weight) list head for this DNSBL domain + * name. + */ + if ((head = (PSC_DNSBL_HEAD *) + htable_find(dnsbl_site_cache, saved_site)) == 0) { + head = (PSC_DNSBL_HEAD *) mymalloc(sizeof(*head)); + ht = htable_enter(dnsbl_site_cache, saved_site, (void *) head); + /* Translate the DNSBL name into a safe name if available. */ + if (psc_dnsbl_reply == 0 + || (safe_dnsbl = dict_get(psc_dnsbl_reply, saved_site)) == 0) + safe_dnsbl = ht->key; + head->safe_dnsbl = mystrdup(safe_dnsbl); + if (psc_dnsbl_reply && psc_dnsbl_reply->error) + msg_fatal("%s:%s lookup error", psc_dnsbl_reply->type, + psc_dnsbl_reply->name); + head->first = 0; + } + + /* + * Append the new (filter, weight) node to the list for this DNSBL domain + * name. + */ + new_site = (PSC_DNSBL_SITE *) mymalloc(sizeof(*new_site)); + new_site->filter = (pattern_text ? mystrdup(pattern_text) : 0); + new_site->byte_codes = (byte_codes ? ip_match_save(byte_codes) : 0); + new_site->weight = weight; + new_site->next = head->first; + head->first = new_site; + + myfree(saved_site); + if (byte_codes) + vstring_free(byte_codes); +} + +/* psc_dnsbl_match - match DNSBL reply filter */ + +static int psc_dnsbl_match(const char *filter, ARGV *reply) +{ + char addr_buf[MAI_HOSTADDR_STRSIZE]; + char **cpp; + + /* + * Run the replies through the pattern-matching engine. + */ + for (cpp = reply->argv; *cpp != 0; cpp++) { + if (inet_pton(AF_INET, *cpp, addr_buf) != 1) + msg_warn("address conversion error for %s -- ignoring this reply", + *cpp); + if (ip_match_execute(filter, addr_buf)) + return (1); + } + return (0); +} + +/* psc_dnsbl_retrieve - retrieve blocklist score, decrement reference count */ + +int psc_dnsbl_retrieve(const char *client_addr, const char **dnsbl_name, + int dnsbl_index, int *dnsbl_ttl) +{ + const char *myname = "psc_dnsbl_retrieve"; + PSC_DNSBL_SCORE *score; + int result_score; + int result_ttl; + + /* + * Sanity check. + */ + if ((score = (PSC_DNSBL_SCORE *) + htable_find(dnsbl_score_cache, client_addr)) == 0) + msg_panic("%s: no blocklist score for %s", myname, client_addr); + + /* + * Disable callbacks. + */ + PSC_CALL_BACK_CANCEL(score, dnsbl_index); + + /* + * Reads are destructive. + */ + result_score = score->total; + *dnsbl_name = score->dnsbl_name; + result_ttl = (result_score > 0) ? score->fail_ttl : score->pass_ttl; + /* As with dnsblog(8), a value < 0 means no reply TTL. */ + if (result_ttl < var_psc_dnsbl_min_ttl) + result_ttl = var_psc_dnsbl_min_ttl; + if (result_ttl > var_psc_dnsbl_max_ttl) + result_ttl = var_psc_dnsbl_max_ttl; + *dnsbl_ttl = result_ttl; + if (msg_verbose) + msg_info("%s: addr=%s score=%d ttl=%d", + myname, client_addr, result_score, result_ttl); + score->refcount -= 1; + if (score->refcount < 1) { + if (msg_verbose > 1) + msg_info("%s: delete blocklist score for %s", myname, client_addr); + htable_delete(dnsbl_score_cache, client_addr, myfree); + } + return (result_score); +} + +/* psc_dnsbl_receive - receive DNSBL reply, update blocklist score */ + +static void psc_dnsbl_receive(int event, void *context) +{ + const char *myname = "psc_dnsbl_receive"; + VSTREAM *stream = (VSTREAM *) context; + PSC_DNSBL_SCORE *score; + PSC_DNSBL_HEAD *head; + PSC_DNSBL_SITE *site; + ARGV *reply_argv; + int request_id; + int dnsbl_ttl; + + PSC_CLEAR_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive, context); + + /* + * Receive the DNSBL lookup result. + * + * This is preliminary code to explore the field. Later, DNSBL lookup will + * be handled by an UDP-based DNS client that is built directly into some + * Postfix daemon. + * + * Don't bother looking up the blocklist score when the client IP address is + * not listed at the DNSBL. + * + * Don't panic when the blocklist score no longer exists. It may be deleted + * when the client triggers a "drop" action after pregreet, when the + * client does not pregreet and the DNSBL reply arrives late, or when the + * client triggers a "drop" action after hanging up. + */ + if (event == EVENT_READ + && attr_scan(stream, + ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, reply_dnsbl), + RECV_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, reply_client), + RECV_ATTR_INT(MAIL_ATTR_LABEL, &request_id), + RECV_ATTR_STR(MAIL_ATTR_RBL_ADDR, reply_addr), + RECV_ATTR_INT(MAIL_ATTR_TTL, &dnsbl_ttl), + ATTR_TYPE_END) == 5 + && (score = (PSC_DNSBL_SCORE *) + htable_find(dnsbl_score_cache, STR(reply_client))) != 0 + && score->request_id == request_id) { + + /* + * Run this response past all applicable DNSBL filters and update the + * blocklist score for this client IP address. + * + * Don't panic when the DNSBL domain name is not found. The DNSBLOG + * server may be messed up. + */ + if (msg_verbose > 1) + msg_info("%s: client=\"%s\" score=%d domain=\"%s\" reply=\"%d %s\"", + myname, STR(reply_client), score->total, + STR(reply_dnsbl), dnsbl_ttl, STR(reply_addr)); + head = (PSC_DNSBL_HEAD *) + htable_find(dnsbl_site_cache, STR(reply_dnsbl)); + if (head == 0) { + /* Bogus domain. Do nothing. */ + } else if (*STR(reply_addr) != 0) { + /* DNS reputation record(s) found. */ + reply_argv = 0; + for (site = head->first; site != 0; site = site->next) { + if (site->byte_codes == 0 + || psc_dnsbl_match(site->byte_codes, reply_argv ? reply_argv : + (reply_argv = argv_split(STR(reply_addr), " ")))) { + if (score->dnsbl_name == 0 + || score->dnsbl_weight < site->weight) { + score->dnsbl_name = head->safe_dnsbl; + score->dnsbl_weight = site->weight; + } + score->total += site->weight; + if (msg_verbose > 1) + msg_info("%s: filter=\"%s\" weight=%d score=%d", + myname, site->filter ? site->filter : "null", + site->weight, score->total); + } + /* As with dnsblog(8), a value < 0 means no reply TTL. */ + if (site->weight > 0) { + if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl) + score->fail_ttl = dnsbl_ttl; + } else { + if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl) + score->pass_ttl = dnsbl_ttl; + } + } + if (reply_argv != 0) + argv_free(reply_argv); + } else { + /* No DNS reputation record found. */ + for (site = head->first; site != 0; site = site->next) { + /* As with dnsblog(8), a value < 0 means no reply TTL. */ + if (site->weight > 0) { + if (score->pass_ttl < 0 || score->pass_ttl > dnsbl_ttl) + score->pass_ttl = dnsbl_ttl; + } else { + if (score->fail_ttl < 0 || score->fail_ttl > dnsbl_ttl) + score->fail_ttl = dnsbl_ttl; + } + } + } + + /* + * Notify the requestor(s) that the result is ready to be picked up. + * If this call isn't made, clients have to sit out the entire + * pre-handshake delay. + */ + score->pending_lookups -= 1; + if (score->pending_lookups == 0) + PSC_CALL_BACK_NOTIFY(score, PSC_NULL_EVENT); + } else if (event == EVENT_TIME) { + msg_warn("dnsblog reply timeout %ds for %s", + var_psc_dnsbl_tmout, (char *) vstream_context(stream)); + } + /* Here, score may be a null pointer. */ + vstream_fclose(stream); +} + +/* psc_dnsbl_request - send dnsbl query, increment reference count */ + +int psc_dnsbl_request(const char *client_addr, + void (*callback) (int, void *), + void *context) +{ + const char *myname = "psc_dnsbl_request"; + int fd; + VSTREAM *stream; + HTABLE_INFO **ht; + PSC_DNSBL_SCORE *score; + HTABLE_INFO *hash_node; + static int request_count; + + /* + * Some spambots make several connections at nearly the same time, + * causing their pregreet delays to overlap. Such connections can share + * the efforts of DNSBL lookup. + * + * We store a reference-counted DNSBL score under its client IP address. We + * increment the reference count with each score request, and decrement + * the reference count with each score retrieval. + * + * Do not notify the requestor NOW when the DNS replies are already in. + * Reason: we must not make a backwards call while we are still in the + * middle of executing the corresponding forward call. Instead we create + * a zero-delay timer request and call the notification function from + * there. + * + * psc_dnsbl_request() could instead return a result value to indicate that + * the DNSBL score is already available, but that would complicate the + * caller with two different notification code paths: one asynchronous + * code path via the callback invocation, and one synchronous code path + * via the psc_dnsbl_request() result value. That would be a source of + * future bugs. + */ + if ((hash_node = htable_locate(dnsbl_score_cache, client_addr)) != 0) { + score = (PSC_DNSBL_SCORE *) hash_node->value; + score->refcount += 1; + PSC_CALL_BACK_EXTEND(hash_node, score); + PSC_CALL_BACK_ENTER(score, callback, context); + if (msg_verbose > 1) + msg_info("%s: reuse blocklist score for %s refcount=%d pending=%d", + myname, client_addr, score->refcount, + score->pending_lookups); + if (score->pending_lookups == 0) + event_request_timer(callback, context, EVENT_NULL_DELAY); + return (PSC_CALL_BACK_INDEX_OF_LAST(score)); + } + if (msg_verbose > 1) + msg_info("%s: create blocklist score for %s", myname, client_addr); + score = (PSC_DNSBL_SCORE *) mymalloc(sizeof(*score)); + score->request_id = request_count++; + score->dnsbl_name = 0; + score->dnsbl_weight = 0; + /* As with dnsblog(8), a value < 0 means no reply TTL. */ + score->pass_ttl = -1; + score->fail_ttl = -1; + score->total = 0; + score->refcount = 1; + score->pending_lookups = 0; + PSC_CALL_BACK_INIT(score); + PSC_CALL_BACK_ENTER(score, callback, context); + (void) htable_enter(dnsbl_score_cache, client_addr, (void *) score); + + /* + * Send a query to all DNSBL servers. Later, DNSBL lookup will be done + * with an UDP-based DNS client that is built directly into Postfix code. + * We therefore do not optimize the maximum out of this temporary + * implementation. + */ + for (ht = dnsbl_site_list; *ht; ht++) { + if ((fd = LOCAL_CONNECT(psc_dnsbl_service, NON_BLOCKING, 1)) < 0) { + msg_warn("%s: connect to %s service: %m", + myname, psc_dnsbl_service); + continue; + } + stream = vstream_fdopen(fd, O_RDWR); + vstream_control(stream, + CA_VSTREAM_CTL_CONTEXT(ht[0]->key), + CA_VSTREAM_CTL_END); + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_RBL_DOMAIN, ht[0]->key), + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, client_addr), + SEND_ATTR_INT(MAIL_ATTR_LABEL, score->request_id), + ATTR_TYPE_END); + if (vstream_fflush(stream) != 0) { + msg_warn("%s: error sending to %s service: %m", + myname, psc_dnsbl_service); + vstream_fclose(stream); + continue; + } + PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_dnsbl_receive, + (void *) stream, var_psc_dnsbl_tmout); + score->pending_lookups += 1; + } + return (PSC_CALL_BACK_INDEX_OF_LAST(score)); +} + +/* psc_dnsbl_init - initialize */ + +void psc_dnsbl_init(void) +{ + const char *myname = "psc_dnsbl_init"; + ARGV *dnsbl_site = argv_split(var_psc_dnsbl_sites, CHARS_COMMA_SP); + char **cpp; + + /* + * Sanity check. + */ + if (dnsbl_site_cache != 0) + msg_panic("%s: called more than once", myname); + + /* + * pre-compute the DNSBLOG socket name. + */ + psc_dnsbl_service = concatenate(MAIL_CLASS_PRIVATE, "/", + var_dnsblog_service, (char *) 0); + + /* + * Prepare for quick iteration when sending out queries to all DNSBL + * servers, and for quick lookup when a reply arrives from a specific + * DNSBL server. + */ + dnsbl_site_cache = htable_create(13); + for (cpp = dnsbl_site->argv; *cpp; cpp++) + psc_dnsbl_add_site(*cpp); + argv_free(dnsbl_site); + dnsbl_site_list = htable_list(dnsbl_site_cache); + + /* + * The per-client blocklist score. + */ + dnsbl_score_cache = htable_create(13); + + /* + * Space for ad-hoc DNSBLOG server request/reply parameters. + */ + reply_client = vstring_alloc(100); + reply_dnsbl = vstring_alloc(100); + reply_addr = vstring_alloc(100); +} diff --git a/src/postscreen/postscreen_early.c b/src/postscreen/postscreen_early.c new file mode 100644 index 0000000..c9d8faf --- /dev/null +++ b/src/postscreen/postscreen_early.c @@ -0,0 +1,377 @@ +/*++ +/* NAME +/* postscreen_early 3 +/* SUMMARY +/* postscreen pre-handshake tests +/* SYNOPSIS +/* #include +/* +/* void psc_early_init(void) +/* +/* void psc_early_tests(state) +/* PSC_STATE *state; +/* DESCRIPTION +/* psc_early_tests() performs protocol tests before the SMTP +/* handshake: the pregreet test and the DNSBL test. Control +/* is passed to the psc_smtpd_tests() routine as appropriate. +/* +/* psc_early_init() performs one-time initialization. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include + +static char *psc_teaser_greeting; +static VSTRING *psc_escape_buf; + +/* psc_allowlist_non_dnsbl - allowlist pending non-dnsbl tests */ + +static void psc_allowlist_non_dnsbl(PSC_STATE *state) +{ + time_t now; + int tindx; + + /* + * If no tests failed (we can't undo those), and if the allowlist + * threshold is met, flag non-dnsbl tests that are pending or disabled as + * successfully completed, and set their expiration times equal to the + * DNSBL expiration time, except for tests that would expire later. + * + * Why flag disabled tests as passed? When a disabled test is turned on, + * postscreen should not apply that test to clients that are already + * allowlisted based on their combined DNSBL score. + */ + if ((state->flags & PSC_STATE_MASK_ANY_FAIL) == 0 + && state->dnsbl_score < var_psc_dnsbl_thresh + && var_psc_dnsbl_althresh < 0 + && state->dnsbl_score <= var_psc_dnsbl_althresh) { + now = event_time(); + for (tindx = 0; tindx < PSC_TINDX_COUNT; tindx++) { + if (tindx == PSC_TINDX_DNSBL) + continue; + if ((state->flags & PSC_STATE_FLAG_BYTINDX_TODO(tindx)) + && !(state->flags & PSC_STATE_FLAG_BYTINDX_PASS(tindx))) { + if (msg_verbose) + msg_info("skip %s test for [%s]:%s", + psc_test_name(tindx), PSC_CLIENT_ADDR_PORT(state)); + /* Wrong for deep protocol tests, but we disable those. */ + state->flags |= PSC_STATE_FLAG_BYTINDX_DONE(tindx); + /* This also disables pending deep protocol tests. */ + state->flags |= PSC_STATE_FLAG_BYTINDX_PASS(tindx); + } + /* Update expiration even if the test was completed or disabled. */ + if (state->client_info->expire_time[tindx] < now + state->dnsbl_ttl) + state->client_info->expire_time[tindx] = now + state->dnsbl_ttl; + } + } +} + +/* psc_early_event - handle pre-greet, EOF, and DNSBL results. */ + +static void psc_early_event(int event, void *context) +{ + const char *myname = "psc_early_event"; + PSC_STATE *state = (PSC_STATE *) context; + time_t *expire_time = state->client_info->expire_time; + char read_buf[PSC_READ_BUF_SIZE]; + int read_count; + DELTA_TIME elapsed; + + if (msg_verbose > 1) + msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s", + myname, psc_post_queue_length, psc_check_queue_length, + event, vstream_fileno(state->smtp_client_stream), + state->smtp_client_addr, state->smtp_client_port, + psc_print_state_flags(state->flags, myname)); + + PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream), + psc_early_event, context); + + /* + * XXX Be sure to empty the DNSBL lookup buffer otherwise we have a + * memory leak. + * + * XXX We can avoid "forgetting" to do this by keeping a pointer to the + * DNSBL lookup buffer in the PSC_STATE structure. This also allows us to + * shave off a hash table lookup when retrieving the DNSBL result. + * + * A direct pointer increases the odds of dangling pointers. Hash-table + * lookup is safer, and that is why it's done that way. + */ + switch (event) { + + /* + * We either reached the end of the early tests time limit, or all + * early tests completed before the pregreet timer would go off. + */ + case EVENT_TIME: + + /* + * Check if the SMTP client spoke before its turn. + */ + if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0 + && (state->flags & PSC_STATE_MASK_PREGR_FAIL_DONE) == 0) { + expire_time[PSC_TINDX_PREGR] = event_time() + var_psc_pregr_ttl; + PSC_PASS_SESSION_STATE(state, "pregreet test", + PSC_STATE_FLAG_PREGR_PASS); + } + if ((state->flags & PSC_STATE_FLAG_PREGR_FAIL) + && psc_pregr_action == PSC_ACT_IGNORE) { + PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL); + /* Not: PSC_PASS_SESSION_STATE. Repeat this test the next time. */ + } + + /* + * Collect the DNSBL score, and allowlist other tests if applicable. + * Note: this score will be partial when some DNS lookup did not + * complete before the pregreet timer expired. + * + * If the client is DNS blocklisted, drop the connection, send the + * client to a dummy protocol engine, or continue to the next test. + */ +#define PSC_DNSBL_FORMAT \ + "%s 5.7.1 Service unavailable; client [%s] blocked using %s\r\n" +#define NO_DNSBL_SCORE INT_MAX + + if (state->flags & PSC_STATE_FLAG_DNSBL_TODO) { + if (state->dnsbl_score == NO_DNSBL_SCORE) { + state->dnsbl_score = + psc_dnsbl_retrieve(state->smtp_client_addr, + &state->dnsbl_name, + state->dnsbl_index, + &state->dnsbl_ttl); + if (var_psc_dnsbl_althresh < 0) + psc_allowlist_non_dnsbl(state); + } + if (state->dnsbl_score < var_psc_dnsbl_thresh) { + expire_time[PSC_TINDX_DNSBL] = event_time() + state->dnsbl_ttl; + PSC_PASS_SESSION_STATE(state, "dnsbl test", + PSC_STATE_FLAG_DNSBL_PASS); + } else { + msg_info("DNSBL rank %d for [%s]:%s", + state->dnsbl_score, PSC_CLIENT_ADDR_PORT(state)); + PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL); + switch (psc_dnsbl_action) { + case PSC_ACT_DROP: + state->dnsbl_reply = vstring_sprintf(vstring_alloc(100), + PSC_DNSBL_FORMAT, "521", + state->smtp_client_addr, + state->dnsbl_name); + PSC_DROP_SESSION_STATE(state, STR(state->dnsbl_reply)); + return; + case PSC_ACT_ENFORCE: + state->dnsbl_reply = vstring_sprintf(vstring_alloc(100), + PSC_DNSBL_FORMAT, "550", + state->smtp_client_addr, + state->dnsbl_name); + PSC_ENFORCE_SESSION_STATE(state, STR(state->dnsbl_reply)); + break; + case PSC_ACT_IGNORE: + PSC_UNFAIL_SESSION_STATE(state, PSC_STATE_FLAG_DNSBL_FAIL); + /* Not: PSC_PASS_SESSION_STATE. Repeat this test. */ + break; + default: + msg_panic("%s: unknown dnsbl action value %d", + myname, psc_dnsbl_action); + + } + } + } + + /* + * Pass the connection to a real SMTP server, or enter the dummy + * engine for deep tests. + */ + if ((state->flags & PSC_STATE_FLAG_NOFORWARD) != 0 + || ((state->flags & PSC_STATE_MASK_SMTPD_PASS) + != PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_SMTPD_TODO))) + psc_smtpd_tests(state); + else + psc_conclude(state); + return; + + /* + * EOF, or the client spoke before its turn. We simply drop the + * connection, or we continue waiting and allow DNS replies to + * trickle in. + */ + default: + if ((read_count = recv(vstream_fileno(state->smtp_client_stream), + read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) { + /* Avoid memory leak. */ + if (state->dnsbl_score == NO_DNSBL_SCORE + && (state->flags & PSC_STATE_FLAG_DNSBL_TODO)) + (void) psc_dnsbl_retrieve(state->smtp_client_addr, + &state->dnsbl_name, + state->dnsbl_index, + &state->dnsbl_ttl); + /* XXX Wait for DNS replies to come in. */ + psc_hangup_event(state); + return; + } + read_buf[read_count] = 0; + escape(psc_escape_buf, read_buf, read_count); + msg_info("PREGREET %d after %s from [%s]:%s: %.100s", read_count, + psc_format_delta_time(psc_temp, state->start_time, &elapsed), + PSC_CLIENT_ADDR_PORT(state), STR(psc_escape_buf)); + PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PREGR_FAIL); + switch (psc_pregr_action) { + case PSC_ACT_DROP: + /* Avoid memory leak. */ + if (state->dnsbl_score == NO_DNSBL_SCORE + && (state->flags & PSC_STATE_FLAG_DNSBL_TODO)) + (void) psc_dnsbl_retrieve(state->smtp_client_addr, + &state->dnsbl_name, + state->dnsbl_index, + &state->dnsbl_ttl); + PSC_DROP_SESSION_STATE(state, "521 5.5.1 Protocol error\r\n"); + return; + case PSC_ACT_ENFORCE: + /* We call psc_dnsbl_retrieve() when the timer expires. */ + PSC_ENFORCE_SESSION_STATE(state, "550 5.5.1 Protocol error\r\n"); + break; + case PSC_ACT_IGNORE: + /* We call psc_dnsbl_retrieve() when the timer expires. */ + /* We must handle this case after the timer expires. */ + break; + default: + msg_panic("%s: unknown pregreet action value %d", + myname, psc_pregr_action); + } + + /* + * Terminate the greet delay if we're just waiting for the pregreet + * test to complete. It is safe to call psc_early_event directly, + * since we are already in that function. + * + * XXX After this code passes all tests, swap around the two blocks in + * this switch statement and fall through from EVENT_READ into + * EVENT_TIME, instead of calling psc_early_event recursively. + */ + state->flags |= PSC_STATE_FLAG_PREGR_DONE; + if (elapsed.dt_sec >= PSC_EFF_GREET_WAIT + || ((state->flags & PSC_STATE_MASK_EARLY_DONE) + == PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO))) + psc_early_event(EVENT_TIME, context); + else + event_request_timer(psc_early_event, context, + PSC_EFF_GREET_WAIT - elapsed.dt_sec); + return; + } +} + +/* psc_early_dnsbl_event - cancel pregreet timer if waiting for DNS only */ + +static void psc_early_dnsbl_event(int unused_event, void *context) +{ + const char *myname = "psc_early_dnsbl_event"; + PSC_STATE *state = (PSC_STATE *) context; + + if (msg_verbose) + msg_info("%s: notify [%s]:%s", myname, PSC_CLIENT_ADDR_PORT(state)); + + /* + * Collect the DNSBL score, and allowlist other tests if applicable. + */ + state->dnsbl_score = + psc_dnsbl_retrieve(state->smtp_client_addr, &state->dnsbl_name, + state->dnsbl_index, &state->dnsbl_ttl); + if (var_psc_dnsbl_althresh < 0) + psc_allowlist_non_dnsbl(state); + + /* + * Terminate the greet delay if we're just waiting for DNSBL lookup to + * complete. Don't call psc_early_event directly, that would result in a + * dangling pointer. + */ + state->flags |= PSC_STATE_FLAG_DNSBL_DONE; + if ((state->flags & PSC_STATE_MASK_EARLY_DONE) + == PSC_STATE_FLAGS_TODO_TO_DONE(state->flags & PSC_STATE_MASK_EARLY_TODO)) + event_request_timer(psc_early_event, context, EVENT_NULL_DELAY); +} + +/* psc_early_tests - start the early (before protocol) tests */ + +void psc_early_tests(PSC_STATE *state) +{ + const char *myname = "psc_early_tests"; + + /* + * Report errors and progress in the context of this test. + */ + PSC_BEGIN_TESTS(state, "tests before SMTP handshake"); + + /* + * Run a PREGREET test. Send half the greeting banner, by way of teaser, + * then wait briefly to see if the client speaks before its turn. + */ + if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0 + && psc_teaser_greeting != 0 + && PSC_SEND_REPLY(state, psc_teaser_greeting) != 0) { + psc_hangup_event(state); + return; + } + + /* + * Run a DNS blocklist query. + */ + if ((state->flags & PSC_STATE_FLAG_DNSBL_TODO) != 0) + state->dnsbl_index = + psc_dnsbl_request(state->smtp_client_addr, psc_early_dnsbl_event, + (void *) state); + else + state->dnsbl_index = -1; + state->dnsbl_score = NO_DNSBL_SCORE; + + /* + * Wait for the client to respond or for DNS lookup to complete. + */ + if ((state->flags & PSC_STATE_FLAG_PREGR_TODO) != 0) + PSC_READ_EVENT_REQUEST(vstream_fileno(state->smtp_client_stream), + psc_early_event, (void *) state, PSC_EFF_GREET_WAIT); + else + event_request_timer(psc_early_event, (void *) state, PSC_EFF_GREET_WAIT); +} + +/* psc_early_init - initialize early tests */ + +void psc_early_init(void) +{ + if (*var_psc_pregr_banner) { + vstring_sprintf(psc_temp, "220-%s\r\n", var_psc_pregr_banner); + psc_teaser_greeting = mystrdup(STR(psc_temp)); + psc_escape_buf = vstring_alloc(100); + } +} diff --git a/src/postscreen/postscreen_endpt.c b/src/postscreen/postscreen_endpt.c new file mode 100644 index 0000000..36949e3 --- /dev/null +++ b/src/postscreen/postscreen_endpt.c @@ -0,0 +1,232 @@ +/*++ +/* NAME +/* postscreen_endpt 3 +/* SUMMARY +/* look up connection endpoint information +/* SYNOPSIS +/* #include +/* +/* void psc_endpt_lookup(smtp_client_stream, lookup_done) +/* VSTREAM *smtp_client_stream; +/* void (*lookup_done)(status, smtp_client_stream, +/* smtp_client_addr, smtp_client_port, +/* smtp_server_addr, smtp_server_port) +/* int status; +/* MAI_HOSTADDR_STR *smtp_client_addr; +/* MAI_SERVPORT_STR *smtp_client_port; +/* MAI_HOSTADDR_STR *smtp_server_addr; +/* MAI_SERVPORT_STR *smtp_server_port; +/* AUXILIARY METHODS +/* void psc_endpt_local_lookup(smtp_client_stream, lookup_done) +/* VSTREAM *smtp_client_stream; +/* void (*lookup_done)(status, smtp_client_stream, +/* smtp_client_addr, smtp_client_port, +/* smtp_server_addr, smtp_server_port) +/* int status; +/* MAI_HOSTADDR_STR *smtp_client_addr; +/* MAI_SERVPORT_STR *smtp_client_port; +/* MAI_HOSTADDR_STR *smtp_server_addr; +/* MAI_SERVPORT_STR *smtp_server_port; +/* DESCRIPTION +/* psc_endpt_lookup() looks up remote and local connection +/* endpoint information, either through local system calls, +/* or through an adapter for an up-stream proxy protocol. +/* +/* The following summarizes what the postscreen(8) server +/* expects from a proxy protocol adapter routine. +/* .IP \(bu +/* Accept the same arguments as psc_endpt_lookup(). +/* .IP \(bu +/* Call psc_endpt_local_lookup() to look up connection info +/* when the upstream proxy indicates that the connection is +/* not proxied (e.g., health check probe). +/* .IP \(bu +/* Validate protocol, address and port syntax. Permit only +/* protocols that are configured with the main.cf:inet_protocols +/* setting. +/* .IP \(bu +/* Convert IPv4-in-IPv6 address syntax to IPv4 syntax when +/* both IPv6 and IPv4 support are enabled with main.cf:inet_protocols. +/* .IP \(bu +/* Log a clear warning message that explains why a request +/* fails. +/* .IP \(bu +/* Never talk to the remote SMTP client. +/* .PP +/* Arguments: +/* .IP client_stream +/* A brand-new stream that is connected to the remote client. +/* This argument MUST be passed to psc_endpt_local_lookup() +/* if the up-stream proxy indicates that a connection is not +/* proxied. +/* .IP lookup +/* Call-back routine that reports the result status, address +/* and port information. The result status is -1 in case of +/* error, 0 in case of success. This MUST NOT be called directly +/* if the up-stream proxy indicates that a connection is not +/* proxied; instead this MUST be called indirectly by +/* psc_endpt_local_lookup(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include +#include + +static const INET_PROTO_INFO *proto_info; + +/* psc_sockaddr_to_hostaddr - transform endpoint address and port to string */ + +static int psc_sockaddr_to_hostaddr(struct sockaddr *addr_storage, + SOCKADDR_SIZE addr_storage_len, + MAI_HOSTADDR_STR *addr_buf, + MAI_SERVPORT_STR *port_buf, + int socktype) +{ + int aierr; + + if ((aierr = sockaddr_to_hostaddr(addr_storage, addr_storage_len, + addr_buf, port_buf, socktype)) == 0 + && strncasecmp("::ffff:", addr_buf->buf, 7) == 0 + && strchr((char *) proto_info->sa_family_list, AF_INET) != 0) + memmove(addr_buf->buf, addr_buf->buf + 7, + sizeof(addr_buf->buf) - 7); + return (aierr); +} + +/* psc_endpt_local_lookup - look up local system connection information */ + +void psc_endpt_local_lookup(VSTREAM *smtp_client_stream, + PSC_ENDPT_LOOKUP_FN lookup_done) +{ + struct sockaddr_storage addr_storage; + SOCKADDR_SIZE addr_storage_len = sizeof(addr_storage); + int status; + MAI_HOSTADDR_STR smtp_client_addr; + MAI_SERVPORT_STR smtp_client_port; + MAI_HOSTADDR_STR smtp_server_addr; + MAI_SERVPORT_STR smtp_server_port; + int aierr; + + /* + * Look up the remote SMTP client address and port. + */ + if (getpeername(vstream_fileno(smtp_client_stream), (struct sockaddr *) + &addr_storage, &addr_storage_len) < 0) { + msg_warn("getpeername: %m -- dropping this connection"); + status = -1; + } + + /* + * Convert the remote SMTP client address and port to printable form for + * logging and access control. + */ + else if ((aierr = psc_sockaddr_to_hostaddr( + (struct sockaddr *) &addr_storage, + addr_storage_len, &smtp_client_addr, + &smtp_client_port, SOCK_STREAM)) != 0) { + msg_warn("cannot convert client address/port to string: %s" + " -- dropping this connection", + MAI_STRERROR(aierr)); + status = -1; + } + + /* + * Look up the local SMTP server address and port. + */ + else if (getsockname(vstream_fileno(smtp_client_stream), + (struct sockaddr *) &addr_storage, + &addr_storage_len) < 0) { + msg_warn("getsockname: %m -- dropping this connection"); + status = -1; + } + + /* + * Convert the local SMTP server address and port to printable form for + * logging. + */ + else if ((aierr = psc_sockaddr_to_hostaddr( + (struct sockaddr *) &addr_storage, + addr_storage_len, &smtp_server_addr, + &smtp_server_port, SOCK_STREAM)) != 0) { + msg_warn("cannot convert server address/port to string: %s" + " -- dropping this connection", + MAI_STRERROR(aierr)); + status = -1; + } else { + status = 0; + } + lookup_done(status, smtp_client_stream, + &smtp_client_addr, &smtp_client_port, + &smtp_server_addr, &smtp_server_port); +} + + /* + * Lookup table for available proxy protocols. + */ +typedef struct { + const char *name; + void (*endpt_lookup) (VSTREAM *, PSC_ENDPT_LOOKUP_FN); +} PSC_ENDPT_LOOKUP_INFO; + +static const PSC_ENDPT_LOOKUP_INFO psc_endpt_lookup_info[] = { + NOPROXY_PROTO_NAME, psc_endpt_local_lookup, + HAPROXY_PROTO_NAME, psc_endpt_haproxy_lookup, + 0, +}; + +/* psc_endpt_lookup - look up connection endpoint information */ + +void psc_endpt_lookup(VSTREAM *smtp_client_stream, + PSC_ENDPT_LOOKUP_FN notify) +{ + const PSC_ENDPT_LOOKUP_INFO *pp; + + if (proto_info == 0) + proto_info = inet_proto_info(); + + for (pp = psc_endpt_lookup_info; /* see below */ ; pp++) { + if (pp->name == 0) + msg_fatal("unsupported %s value: %s", + VAR_PSC_UPROXY_PROTO, var_psc_uproxy_proto); + if (strcmp(var_psc_uproxy_proto, pp->name) == 0) { + pp->endpt_lookup(smtp_client_stream, notify); + return; + } + } +} diff --git a/src/postscreen/postscreen_expand.c b/src/postscreen/postscreen_expand.c new file mode 100644 index 0000000..ecda543 --- /dev/null +++ b/src/postscreen/postscreen_expand.c @@ -0,0 +1,141 @@ +/*++ +/* NAME +/* postscreen_expand 3 +/* SUMMARY +/* SMTP server macro expansion +/* SYNOPSIS +/* #include +/* +/* void psc_expand_init() +/* +/* VSTRING *psc_expand_filter; +/* +/* const char *psc_expand_lookup(name, unused_mode, context) +/* const char *name; +/* int unused_mode; +/* char *context; +/* DESCRIPTION +/* This module expands session-related macros. +/* +/* psc_expand_init() performs one-time initialization +/* of the psc_expand_filter buffer. +/* +/* The psc_expand_filter buffer contains the characters +/* that are allowed in macro expansion, as specified with the +/* psc_expand_filter configuration parameter. +/* +/* psc_expand_lookup() returns the value of the named +/* macro or a null pointer. +/* +/* Arguments: +/* .IP name +/* Macro name. +/* .IP context +/* Call-back context (a PSC_STATE pointer). +/* DIAGNOSTICS +/* Panic: interface violations. Fatal errors: out of memory. +/* internal protocol errors. postscreen_expand() returns the +/* binary OR of MAC_PARSE_ERROR (syntax error) and MAC_PARSE_UNDEF +/* (undefined macro name). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include + + /* + * Pre-parsed expansion filter. + */ +VSTRING *psc_expand_filter; + +/* psc_expand_init - initialize once during process lifetime */ + +void psc_expand_init(void) +{ + + /* + * Expand the expansion filter :-) + */ + psc_expand_filter = vstring_alloc(10); + unescape(psc_expand_filter, var_psc_exp_filter); +} + +/* psc_expand_lookup - generic SMTP attribute $name expansion */ + +const char *psc_expand_lookup(const char *name, int unused_mode, + void *context) +{ + PSC_STATE *state = (PSC_STATE *) context; + time_t now; + struct tm *lt; + + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(10); + + if (msg_verbose > 1) + msg_info("psc_expand_lookup: ${%s}", name); + +#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0) +#define STREQN(x,y,n) (*(x) == *(y) && strncmp((x), (y), (n)) == 0) +#define CONST_LEN(x) (sizeof(x) - 1) + + /* + * Don't query main.cf parameters, as the result of expansion could + * reveal system-internal information in server replies. + * + * XXX: This said, multiple servers may be behind a single client-visible + * name or IP address, and each may generate its own logs. Therefore, it + * may be useful to expose the replying MTA id (myhostname) in the + * contact footer, to identify the right logs. So while we don't expose + * the raw configuration dictionary, we do expose "$myhostname" as + * expanded in var_myhostname. + * + * Return NULL only for non-existent names. + */ + if (STREQ(name, MAIL_ATTR_SERVER_NAME)) { + return (var_myhostname); + } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_ADDR)) { + return (state->smtp_client_addr); + } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_PORT)) { + return (state->smtp_client_port); + } if (STREQ(name, MAIL_ATTR_LOCALTIME)) { + if (time(&now) == (time_t) -1) + msg_fatal("time lookup failed: %m"); + lt = localtime(&now); + VSTRING_RESET(state->expand_buf); + do { + VSTRING_SPACE(state->expand_buf, 100); + } while (strftime(STR(state->expand_buf), + vstring_avail(state->expand_buf), + "%b %d %H:%M:%S", lt) == 0); + return (STR(state->expand_buf)); + } else { + msg_warn("unknown macro name \"%s\" in expansion request", name); + return (0); + } +} diff --git a/src/postscreen/postscreen_haproxy.c b/src/postscreen/postscreen_haproxy.c new file mode 100644 index 0000000..45c6b9a --- /dev/null +++ b/src/postscreen/postscreen_haproxy.c @@ -0,0 +1,137 @@ +/*++ +/* NAME +/* postscreen_haproxy 3 +/* SUMMARY +/* haproxy protocol adapter +/* SYNOPSIS +/* #include +/* +/* void psc_endpt_haproxy_lookup(smtp_client_stream, lookup_done) +/* VSTRING *smtp_client_stream; +/* void (*lookup_done)(status, smtp_client_stream, +/* smtp_client_addr, smtp_client_port, +/* smtp_server_addr, smtp_server_port) +/* int status; +/* MAI_HOSTADDR_STR *smtp_client_addr; +/* MAI_SERVPORT_STR *smtp_client_port; +/* MAI_HOSTADDR_STR *smtp_server_addr; +/* MAI_SERVPORT_STR *smtp_server_port; +/* DESCRIPTION +/* psc_endpt_haproxy_lookup() looks up connection endpoint +/* information via the haproxy protocol, or looks up local +/* information if the haproxy handshake indicates that a +/* connection is not proxied. Arguments and results conform +/* to the postscreen_endpt(3) API. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include +#include + + /* + * Per-session state. + */ +typedef struct { + VSTREAM *stream; + PSC_ENDPT_LOOKUP_FN notify; +} PSC_HAPROXY_STATE; + +/* psc_endpt_haproxy_event - read or time event */ + +static void psc_endpt_haproxy_event(int event, void *context) +{ + const char *myname = "psc_endpt_haproxy_event"; + PSC_HAPROXY_STATE *state = (PSC_HAPROXY_STATE *) context; + int status = 0; + MAI_HOSTADDR_STR smtp_client_addr; + MAI_SERVPORT_STR smtp_client_port; + MAI_HOSTADDR_STR smtp_server_addr; + MAI_SERVPORT_STR smtp_server_port; + int non_proxy = 0; + + switch (event) { + case EVENT_TIME: + msg_warn("haproxy read: time limit exceeded"); + status = -1; + break; + case EVENT_READ: + status = haproxy_srvr_receive(vstream_fileno(state->stream), &non_proxy, + &smtp_client_addr, &smtp_client_port, + &smtp_server_addr, &smtp_server_port); + } + + /* + * Terminate this pseudo thread, and notify the caller. + */ + PSC_CLEAR_EVENT_REQUEST(vstream_fileno(state->stream), + psc_endpt_haproxy_event, context); + if (status == 0 && non_proxy) + psc_endpt_local_lookup(state->stream, state->notify); + else + state->notify(status, state->stream, + &smtp_client_addr, &smtp_client_port, + &smtp_server_addr, &smtp_server_port); + /* Note: the stream may be closed at this point. */ + myfree((void *) state); +} + +/* psc_endpt_haproxy_lookup - event-driven haproxy client */ + +void psc_endpt_haproxy_lookup(VSTREAM *stream, + PSC_ENDPT_LOOKUP_FN notify) +{ + const char *myname = "psc_endpt_haproxy_lookup"; + PSC_HAPROXY_STATE *state; + + /* + * Prepare the per-session state. XXX To improve overload behavior, + * maintain a pool of these so that we can reduce memory allocator + * activity. + */ + state = (PSC_HAPROXY_STATE *) mymalloc(sizeof(*state)); + state->stream = stream; + state->notify = notify; + + /* + * Read the haproxy line. + */ + PSC_READ_EVENT_REQUEST(vstream_fileno(stream), psc_endpt_haproxy_event, + (void *) state, var_psc_uproxy_tmout); +} diff --git a/src/postscreen/postscreen_haproxy.h b/src/postscreen/postscreen_haproxy.h new file mode 100644 index 0000000..7557fb3 --- /dev/null +++ b/src/postscreen/postscreen_haproxy.h @@ -0,0 +1,30 @@ +/*++ +/* NAME +/* postscreen_haproxy 3h +/* SUMMARY +/* postscreen haproxy protocol support +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * haproxy protocol interface. + */ +extern void psc_endpt_haproxy_lookup(VSTREAM *, PSC_ENDPT_LOOKUP_FN); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/postscreen/postscreen_misc.c b/src/postscreen/postscreen_misc.c new file mode 100644 index 0000000..f07c9ac --- /dev/null +++ b/src/postscreen/postscreen_misc.c @@ -0,0 +1,164 @@ +/*++ +/* NAME +/* postscreen_misc 3 +/* SUMMARY +/* postscreen misc routines +/* SYNOPSIS +/* #include +/* +/* char *psc_format_delta_time(buf, tv, delta) +/* VSTRING *buf; +/* struct timeval tv; +/* DELTA_TIME *delta; +/* +/* void psc_conclude(state) +/* PSC_STATE *state; +/* +/* void psc_hangup_event(state) +/* PSC_STATE *state; +/* DESCRIPTION +/* psc_format_delta_time() computes the time difference between +/* tv (past) and the present, formats the time difference with +/* sub-second resolution in a human-readable way, and returns +/* the integer time difference in seconds through the delta +/* argument. +/* +/* psc_conclude() logs when a client passes all necessary tests, +/* updates the postscreen cache for any testes that were passed, +/* and either forwards the connection to a real SMTP server or +/* replies with the text in state->error_reply and hangs up the +/* connection (by default, state->error_reply is set to a +/* default 421 reply). +/* +/* psc_hangup_event() cleans up after a client connection breaks +/* unexpectedly. If logs the test where the break happened, +/* and how much time as spent in that test before the connection +/* broke. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include + +/* psc_format_delta_time - pretty-formatted delta time */ + +char *psc_format_delta_time(VSTRING *buf, struct timeval tv, + DELTA_TIME *delta) +{ + DELTA_TIME pdelay; + struct timeval now; + + GETTIMEOFDAY(&now); + PSC_CALC_DELTA(pdelay, now, tv); + VSTRING_RESET(buf); + format_tv(buf, pdelay.dt_sec, pdelay.dt_usec, SIG_DIGS, var_delay_max_res); + *delta = pdelay; + return (STR(buf)); +} + +/* psc_conclude - bring this session to a conclusion */ + +void psc_conclude(PSC_STATE *state) +{ + const char *myname = "psc_conclude"; + + if (msg_verbose) + msg_info("flags for %s: %s", + myname, psc_print_state_flags(state->flags, myname)); + + /* + * Handle clients that passed at least one test other than permanent + * allowlisting, and that didn't fail any test including permanent + * denylisting. There may still be unfinished tests; those tests will + * need to be completed when the client returns in a later session. + */ + if (state->flags & PSC_STATE_MASK_ANY_FAIL) + state->flags &= ~PSC_STATE_MASK_ANY_PASS; + + /* + * Log our final blessing when all unfinished tests were completed. + */ + if ((state->flags & PSC_STATE_MASK_ANY_PASS) != 0 + && (state->flags & PSC_STATE_MASK_ANY_PASS) == + PSC_STATE_FLAGS_TODO_TO_PASS(state->flags & PSC_STATE_MASK_ANY_TODO)) + msg_info("PASS %s [%s]:%s", (state->flags & PSC_STATE_FLAG_NEW) == 0 + || state->client_info->pass_new_count++ > 0 ? + "OLD" : "NEW", PSC_CLIENT_ADDR_PORT(state)); + + /* + * Update the postscreen cache. This still supports a scenario where a + * client gets allowlisted in the course of multiple sessions, as long as + * that client does not "fail" any test. Don't try to optimize away cache + * updates; we want cached information to be up-to-date even if a test + * result is renewed during overlapping SMTP sessions, and even if + * 'postfix reload' happens in the middle of that. + */ + if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) != 0 + && psc_cache_map != 0) { + psc_print_tests(psc_temp, state); + psc_cache_update(psc_cache_map, state->smtp_client_addr, STR(psc_temp)); + } + + /* + * Either hand off the socket to a real SMTP engine, or say bye-bye. + */ + if ((state->flags & PSC_STATE_FLAG_NOFORWARD) == 0) { + psc_send_socket(state); + } else { + if ((state->flags & PSC_STATE_FLAG_HANGUP) == 0) + (void) PSC_SEND_REPLY(state, state->final_reply); + msg_info("DISCONNECT [%s]:%s", PSC_CLIENT_ADDR_PORT(state)); + psc_free_session_state(state); + } +} + +/* psc_hangup_event - handle unexpected disconnect */ + +void psc_hangup_event(PSC_STATE *state) +{ + DELTA_TIME elapsed; + + /* + * Sessions can break at any time, even after the client passes all tests + * (some MTAs including Postfix don't send QUIT when connection reuse is + * enabled). This must not be treated as a protocol test failure. + * + * Log the current test phase, and the elapsed time after the start of that + * phase. + */ + state->flags |= PSC_STATE_FLAG_HANGUP; + msg_info("HANGUP after %s from [%s]:%s in %s", + psc_format_delta_time(psc_temp, state->start_time, &elapsed), + PSC_CLIENT_ADDR_PORT(state), state->test_name); + state->flags |= PSC_STATE_FLAG_NOFORWARD; + psc_conclude(state); +} diff --git a/src/postscreen/postscreen_send.c b/src/postscreen/postscreen_send.c new file mode 100644 index 0000000..53714b1 --- /dev/null +++ b/src/postscreen/postscreen_send.c @@ -0,0 +1,293 @@ +/*++ +/* NAME +/* postscreen_send 3 +/* SUMMARY +/* postscreen low-level output +/* SYNOPSIS +/* #include +/* +/* void pcs_send_pre_jail_init(void) +/* +/* int psc_send_reply(state, text) +/* PSC_STATE *state; +/* const char *text; +/* +/* int PSC_SEND_REPLY(state, text) +/* PSC_STATE *state; +/* const char *text; +/* +/* void psc_send_socket(state) +/* PSC_STATE *state; +/* DESCRIPTION +/* pcs_send_pre_jail_init() performs one-time initialization. +/* +/* psc_send_reply() sends the specified text to the specified +/* remote SMTP client. In case of an immediate error, it logs +/* a warning (except EPIPE) with the client address and port, +/* and returns a non-zero result (all errors including EPIPE). +/* +/* psc_send_reply() does a best effort to send the reply, but +/* it won't block when the output is throttled by a hostile +/* peer. +/* +/* PSC_SEND_REPLY() is a legacy wrapper for psc_send_reply(). +/* It will eventually be replaced by its expansion. +/* +/* psc_send_socket() sends the specified socket to the real +/* Postfix SMTP server. The socket is delivered in the background. +/* This function must be called after all other session-related +/* work is finished including postscreen cache updates. +/* +/* In case of an immediate error, psc_send_socket() sends a 421 +/* reply to the remote SMTP client and closes the connection. +/* If the 220- greeting was sent, sending 421 would be invalid; +/* instead, the client is redirected to the dummy SMTP engine +/* which sends the 421 reply at the first legitimate opportunity. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include + +static MAPS *psc_rej_ftr_maps; + + /* + * This program screens all inbound SMTP connections, so it better not waste + * time. + */ +#define PSC_SEND_SOCK_CONNECT_TIMEOUT 1 +#define PSC_SEND_SOCK_NOTIFY_TIMEOUT 100 + +/* pcs_send_pre_jail_init - initialize */ + +void pcs_send_pre_jail_init(void) +{ + static int init_count = 0; + + if (init_count++ != 0) + msg_panic("pcs_send_pre_jail_init: multiple calls"); + + /* + * SMTP server reject footer. + */ + if (*var_psc_rej_ftr_maps) + psc_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS, + var_psc_rej_ftr_maps, + DICT_FLAG_LOCK); +} + +/* psc_get_footer - find that footer */ + +static const char *psc_get_footer(const char *text, ssize_t text_len) +{ + static VSTRING *footer_buf = 0; + + if (footer_buf == 0) + footer_buf = vstring_alloc(100); + /* Strip the \r\n for consistency with smtpd. */ + vstring_strncpy(footer_buf, text, text_len); + return (psc_maps_find(psc_rej_ftr_maps, STR(footer_buf), 0)); +} + +/* psc_send_reply - send reply to remote SMTP client */ + +int psc_send_reply(PSC_STATE *state, const char *text) +{ + ssize_t start; + int ret; + const char *footer; + ssize_t text_len = strlen(text) - 2; + + if (msg_verbose) + msg_info("> [%s]:%s: %.*s", state->smtp_client_addr, + state->smtp_client_port, (int) text_len, text); + + /* + * Append the new text to earlier text that could not be sent because the + * output was throttled. + */ + start = VSTRING_LEN(state->send_buf); + vstring_strcat(state->send_buf, text); + + /* + * For soft_bounce support, we also fix the REJECT logging before the + * dummy SMTP engine calls the psc_send_reply() output routine. We do + * some double work, but it is for debugging only. + */ + if (var_soft_bounce) { + if (text[0] == '5') + STR(state->send_buf)[start + 0] = '4'; + if (text[4] == '5') + STR(state->send_buf)[start + 4] = '4'; + } + + /* + * Append the optional reply footer. + */ + if ((*text == '4' || *text == '5') + && ((psc_rej_ftr_maps != 0 + && (footer = psc_get_footer(text, text_len)) != 0) + || *(footer = var_psc_rej_footer) != 0)) + smtp_reply_footer(state->send_buf, start, footer, + STR(psc_expand_filter), psc_expand_lookup, + (void *) state); + + /* + * Do a best effort sending text, but don't block when the output is + * throttled by a hostile peer. + */ + ret = write(vstream_fileno(state->smtp_client_stream), + STR(state->send_buf), LEN(state->send_buf)); + if (ret > 0) + vstring_truncate(state->send_buf, ret - LEN(state->send_buf)); + if (ret < 0 && errno != EAGAIN && errno != EPIPE && errno != ECONNRESET) + msg_warn("write [%s]:%s: %m", state->smtp_client_addr, + state->smtp_client_port); + return (ret < 0 && errno != EAGAIN); +} + +/* psc_send_socket_close_event - file descriptor has arrived or timeout */ + +static void psc_send_socket_close_event(int event, void *context) +{ + const char *myname = "psc_send_socket_close_event"; + PSC_STATE *state = (PSC_STATE *) context; + + if (msg_verbose > 1) + msg_info("%s: sq=%d cq=%d event %d on send socket %d from [%s]:%s", + myname, psc_post_queue_length, psc_check_queue_length, + event, state->smtp_server_fd, state->smtp_client_addr, + state->smtp_client_port); + + /* + * The real SMTP server has closed the local IPC channel, or we have + * reached the limit of our patience. In the latter case it is still + * possible that the real SMTP server will receive the socket so we + * should not interfere. + */ + PSC_CLEAR_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event, + context); + if (event == EVENT_TIME) + msg_warn("timeout sending connection to service %s", + psc_smtpd_service_name); + psc_free_session_state(state); +} + +/* psc_send_socket - send socket to real SMTP server process */ + +void psc_send_socket(PSC_STATE *state) +{ + const char *myname = "psc_send_socket"; + int server_fd; + int pass_err; + VSTREAM *fp; + + if (msg_verbose > 1) + msg_info("%s: sq=%d cq=%d send socket %d from [%s]:%s", + myname, psc_post_queue_length, psc_check_queue_length, + vstream_fileno(state->smtp_client_stream), + state->smtp_client_addr, state->smtp_client_port); + + /* + * Connect to the real SMTP service over a local IPC channel, send the + * file descriptor, and close the file descriptor to save resources. + * Experience has shown that some systems will discard information when + * we close a channel immediately after writing. Thus, we waste resources + * waiting for the remote side to close the local IPC channel first. The + * good side of waiting is that we learn when the real SMTP server is + * falling behind. + * + * This is where we would forward the connection to an SMTP server that + * provides an appropriate level of service for this client class. For + * example, a server that is more forgiving, or one that is more + * suspicious. Alternatively, we could send attributes along with the + * socket with client reputation information, making everything even more + * Postfix-specific. + */ + if ((server_fd = + LOCAL_CONNECT(psc_smtpd_service_name, NON_BLOCKING, + PSC_SEND_SOCK_CONNECT_TIMEOUT)) < 0) { + msg_warn("cannot connect to service %s: %m", psc_smtpd_service_name); + if (state->flags & PSC_STATE_FLAG_PREGR_TODO) { + PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n"); + } else { + PSC_SEND_REPLY(state, "421 4.3.2 All server ports are busy\r\n"); + psc_free_session_state(state); + } + return; + } + /* XXX Note: no dummy read between LOCAL_SEND_FD() and attr_print(). */ + fp = vstream_fdopen(server_fd, O_RDWR); + pass_err = + (LOCAL_SEND_FD(server_fd, + vstream_fileno(state->smtp_client_stream)) < 0 + || (attr_print(fp, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->smtp_client_addr), + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->smtp_client_port), + SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR, state->smtp_server_addr), + SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT, state->smtp_server_port), + ATTR_TYPE_END) || vstream_fflush(fp))); + /* XXX Note: no read between attr_print() and vstream_fdclose(). */ + (void) vstream_fdclose(fp); + if (pass_err != 0) { + msg_warn("cannot pass connection to service %s: %m", + psc_smtpd_service_name); + (void) close(server_fd); + if (state->flags & PSC_STATE_FLAG_PREGR_TODO) { + PSC_SMTPD_X21(state, "421 4.3.2 No system resources\r\n"); + } else { + PSC_SEND_REPLY(state, "421 4.3.2 No system resources\r\n"); + psc_free_session_state(state); + } + return; + } else { + + /* + * Closing the smtp_client_fd here triggers a FreeBSD 7.1 kernel bug + * where smtp-source sometimes sees the connection being closed after + * it has already received the real SMTP server's 220 greeting! + */ +#if 0 + PSC_DEL_CLIENT_STATE(state); +#endif + PSC_ADD_SERVER_STATE(state, server_fd); + PSC_READ_EVENT_REQUEST(state->smtp_server_fd, psc_send_socket_close_event, + (void *) state, PSC_SEND_SOCK_NOTIFY_TIMEOUT); + return; + } +} diff --git a/src/postscreen/postscreen_smtpd.c b/src/postscreen/postscreen_smtpd.c new file mode 100644 index 0000000..edd5d71 --- /dev/null +++ b/src/postscreen/postscreen_smtpd.c @@ -0,0 +1,1339 @@ +/*++ +/* NAME +/* postscreen_smtpd 3 +/* SUMMARY +/* postscreen built-in SMTP server engine +/* SYNOPSIS +/* #include +/* +/* void psc_smtpd_pre_jail_init(void) +/* +/* void psc_smtpd_init(void) +/* +/* void psc_smtpd_tests(state) +/* PSC_STATE *state; +/* +/* void PSC_SMTPD_X21(state, final_reply) +/* PSC_STATE *state; +/* const char *final_reply; +/* DESCRIPTION +/* psc_smtpd_pre_jail_init() performs one-time per-process +/* initialization during the "before chroot" execution phase. +/* +/* psc_smtpd_init() performs one-time per-process initialization. +/* +/* psc_smtpd_tests() starts up an SMTP server engine for deep +/* protocol tests and for collecting helo/sender/recipient +/* information. +/* +/* PSC_SMTPD_X21() redirects the SMTP client to an SMTP server +/* engine, which sends the specified final reply at the first +/* legitimate opportunity without doing any protocol tests. +/* +/* Unlike the Postfix SMTP server, this engine does not announce +/* PIPELINING support. This exposes spambots that pipeline +/* their commands anyway. Like the Postfix SMTP server, this +/* engine will accept input with bare newline characters. To +/* pass the "pipelining" and "bare newline" test, the client +/* has to properly speak SMTP all the way to the RCPT TO +/* command. These tests fail if the client violates the protocol +/* at any stage. +/* +/* No support is announced for AUTH, XCLIENT or XFORWARD. +/* Clients that need this should be allowlisted or should talk +/* directly to the submission service. +/* +/* The engine rejects RCPT TO and VRFY commands with the +/* state->rcpt_reply response which depends on program history, +/* rejects ETRN with a generic response, and closes the +/* connection after QUIT. +/* +/* Since this engine defers or rejects all non-junk commands, +/* there is no point maintaining separate counters for "error" +/* commands and "junk" commands. Instead, the engine maintains +/* a per-session command counter, and terminates the session +/* with a 421 reply when the command count exceeds the limit. +/* +/* We limit the command count, as well as the total time to +/* receive a command. This limits the time per client more +/* effectively than would be possible with read() timeouts. +/* +/* There is no concern about getting blocked on output. The +/* psc_send() routine uses non-blocking output, and discards +/* output that the client is not willing to receive. +/* PROTOCOL INSPECTION VERSUS CONTENT INSPECTION +/* The goal of postscreen is to keep spambots away from Postfix. +/* To recognize spambots, postscreen measures properties of +/* the client IP address and of the client SMTP protocol +/* implementation. These client properties don't change with +/* each delivery attempt. Therefore it is possible to make a +/* long-term decision after a single measurement. For example, +/* allow a good client to skip the DNSBL test for 24 hours, +/* or to skip the pipelining test for one week. +/* +/* If postscreen were to measure properties of message content +/* (MIME compliance, etc.) then it would measure properties +/* that may change with each delivery attempt. Here, it would +/* be wrong to make a long-term decision after a single +/* measurement. Instead, postscreen would need to develop a +/* ranking based on the content of multiple messages from the +/* same client. +/* +/* Many spambots avoid spamming the same site repeatedly. +/* Thus, postscreen must make decisions after a single +/* measurement. Message content is not a good indicator for +/* making long-term decisions after single measurements, and +/* that is why postscreen does not inspect message content. +/* REJECTING RCPT TO VERSUS SENDING LIVE SOCKETS TO SMTPD(8) +/* When post-handshake protocol tests are enabled, postscreen +/* rejects the RCPT TO command from a good client, and forces +/* it to deliver mail in a later session. This is why +/* post-handshake protocol tests have a longer expiration time +/* than pre-handshake tests. +/* +/* Instead, postscreen could send the network socket to smtpd(8) +/* and ship the session history (including TLS and other SMTP +/* or non-SMTP attributes) as auxiliary data. The Postfix SMTP +/* server would then use new code to replay the session history, +/* and would use existing code to validate the client, helo, +/* sender and recipient address. +/* +/* Such an approach would increase the implementation and +/* maintenance effort, because: +/* +/* 1) New replay code would be needed in smtpd(8), such that +/* the HELO, EHLO, and MAIL command handlers can delay their +/* error responses until the RCPT TO reply. +/* +/* 2) postscreen(8) would have to implement more of smtpd(8)'s +/* syntax checks, to avoid confusing delayed "syntax error" +/* and other error responses syntax error responses while +/* replaying history. +/* +/* 3) New code would be needed in postscreen(8) and smtpd(8) +/* to send and receive the session history (including TLS and +/* other SMTP or non-SMTP attributes) as auxiliary data while +/* sending the network socket from postscreen(8) to smtpd(8). +/* REJECTING RCPT TO VERSUS PROXYING LIVE SESSIONS TO SMTPD(8) +/* An alternative would be to proxy the session history to a +/* real Postfix SMTP process, presumably passing TLS and other +/* attributes via an extended XCLIENT implementation. That +/* would require all the work described in 2) above, plus +/* duplication of all the features of the smtpd(8) TLS engine, +/* plus additional XCLIENT support for a lot more attributes. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* TLS library. */ + +#include + +/* Application-specific. */ + +#include + + /* + * Plan for future body processing. See smtp-sink.c. For now, we have no + * per-session push-back except for the single-character push-back that + * VSTREAM guarantees after we read one character. + */ +#define PSC_SMTPD_HAVE_PUSH_BACK(state) (0) +#define PSC_SMTPD_PUSH_BACK_CHAR(state, ch) \ + vstream_ungetc((state)->smtp_client_stream, (ch)) +#define PSC_SMTPD_NEXT_CHAR(state) \ + VSTREAM_GETC((state)->smtp_client_stream) + +#define PSC_SMTPD_BUFFER_EMPTY(state) \ + (!PSC_SMTPD_HAVE_PUSH_BACK(state) \ + && vstream_peek((state)->smtp_client_stream) <= 0) + +#define PSC_SMTPD_PEEK_DATA(state) \ + vstream_peek_data((state)->smtp_client_stream) +#define PSC_SMTPD_PEEK_LEN(state) \ + vstream_peek((state)->smtp_client_stream) + + /* + * Dynamic reply strings. To minimize overhead we format these once. + */ +static char *psc_smtpd_greeting; /* smtp banner */ +static char *psc_smtpd_helo_reply; /* helo reply */ +static char *psc_smtpd_ehlo_reply_plain;/* multi-line ehlo reply, non-TLS */ +static char *psc_smtpd_ehlo_reply_tls; /* multi-line ehlo reply, with TLS */ +static char *psc_smtpd_timeout_reply; /* timeout reply */ +static char *psc_smtpd_421_reply; /* generic final_reply value */ + + /* + * Forward declaration, needed by PSC_CLEAR_EVENT_REQUEST. + */ +static void psc_smtpd_time_event(int, void *); +static void psc_smtpd_read_event(int, void *); + + /* + * Encapsulation. The STARTTLS, EHLO and AUTH command handlers temporarily + * suspend SMTP command events, send an asynchronous proxy request, and + * resume SMTP command events after receiving the asynchronous proxy + * response (the EHLO handler must asynchronously talk to the auth server + * before it can announce the SASL mechanism list; the list can depend on + * the client IP address and on the presence on TLS encryption). + */ +#define PSC_RESUME_SMTP_CMD_EVENTS(state) do { \ + PSC_READ_EVENT_REQUEST2(vstream_fileno((state)->smtp_client_stream), \ + psc_smtpd_read_event, psc_smtpd_time_event, \ + (void *) (state), PSC_EFF_CMD_TIME_LIMIT); \ + if (!PSC_SMTPD_BUFFER_EMPTY(state)) \ + psc_smtpd_read_event(EVENT_READ, (void *) state); \ + } while (0) + +#define PSC_SUSPEND_SMTP_CMD_EVENTS(state) \ + PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \ + psc_smtpd_time_event, (void *) (state)); + + /* + * Make control characters and other non-text visible. + */ +#define PSC_SMTPD_ESCAPE_TEXT(dest, src, src_len, max_len) do { \ + ssize_t _s_len = (src_len); \ + ssize_t _m_len = (max_len); \ + (void) escape((dest), (src), _s_len < _m_len ? _s_len : _m_len); \ + } while (0) + + /* + * Command parser support. + */ +#define PSC_SMTPD_NEXT_TOKEN(ptr) mystrtok(&(ptr), " ") + + /* + * EHLO keyword filter + */ +static MAPS *psc_ehlo_discard_maps; +static int psc_ehlo_discard_mask; + + /* + * Command editing filter. + */ +static DICT *psc_cmd_filter; + + /* + * Encapsulation. We must not forget turn off input/timer events when we + * terminate the SMTP protocol engine. + * + * It would be safer to turn off input/timer events after each event, and to + * turn on input/timer events again when we want more input. But experience + * with the Postfix smtp-source and smtp-sink tools shows that this would + * noticeably increase the run-time cost. + */ +#define PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, event, reply) do { \ + PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \ + (event), (void *) (state)); \ + PSC_DROP_SESSION_STATE((state), (reply)); \ + } while (0) + +#define PSC_CLEAR_EVENT_HANGUP(state, event) do { \ + PSC_CLEAR_EVENT_REQUEST(vstream_fileno((state)->smtp_client_stream), \ + (event), (void *) (state)); \ + psc_hangup_event(state); \ + } while (0) + +/* psc_helo_cmd - record HELO and respond */ + +static int psc_helo_cmd(PSC_STATE *state, char *args) +{ + char *helo_name = PSC_SMTPD_NEXT_TOKEN(args); + + /* + * smtpd(8) incompatibility: we ignore extra words; smtpd(8) saves them. + */ + if (helo_name == 0) + return (PSC_SEND_REPLY(state, "501 Syntax: HELO hostname\r\n")); + + PSC_STRING_UPDATE(state->helo_name, helo_name); + PSC_STRING_RESET(state->sender); + /* Don't downgrade state->protocol, in case some test depends on this. */ + return (PSC_SEND_REPLY(state, psc_smtpd_helo_reply)); +} + +/* psc_smtpd_format_ehlo_reply - format EHLO response */ + +static void psc_smtpd_format_ehlo_reply(VSTRING *buf, int discard_mask + /* , const char *sasl_mechanism_list */ ) +{ + const char *myname = "psc_smtpd_format_ehlo_reply"; + int saved_len = 0; + + if (msg_verbose) + msg_info("%s: discard_mask %s", myname, str_ehlo_mask(discard_mask)); + +#define PSC_EHLO_APPEND(save, buf, fmt) do { \ + (save) = LEN(buf); \ + vstring_sprintf_append((buf), (fmt)); \ + } while (0) + +#define PSC_EHLO_APPEND1(save, buf, fmt, arg1) do { \ + (save) = LEN(buf); \ + vstring_sprintf_append((buf), (fmt), (arg1)); \ + } while (0) + + vstring_sprintf(psc_temp, "250-%s\r\n", var_myhostname); + if ((discard_mask & EHLO_MASK_SIZE) == 0) { + if (ENFORCING_SIZE_LIMIT(var_message_limit)) + PSC_EHLO_APPEND1(saved_len, psc_temp, "250-SIZE %lu\r\n", + (unsigned long) var_message_limit); + else + PSC_EHLO_APPEND(saved_len, psc_temp, "250-SIZE\r\n"); + } + if ((discard_mask & EHLO_MASK_VRFY) == 0 && var_disable_vrfy_cmd == 0) + PSC_EHLO_APPEND(saved_len, psc_temp, "250-VRFY\r\n"); + if ((discard_mask & EHLO_MASK_ETRN) == 0) + PSC_EHLO_APPEND(saved_len, psc_temp, "250-ETRN\r\n"); + if ((discard_mask & EHLO_MASK_STARTTLS) == 0 && var_psc_use_tls) + PSC_EHLO_APPEND(saved_len, psc_temp, "250-STARTTLS\r\n"); +#ifdef TODO_SASL_AUTH + if ((discard_mask & EHLO_MASK_AUTH) == 0 && sasl_mechanism_list + && (!var_psc_tls_auth_only || (discard_mask & EHLO_MASK_STARTTLS))) { + PSC_EHLO_APPEND1(saved_len, psc_temp, "AUTH %s", sasl_mechanism_list); + if (var_broken_auth_clients) + PSC_EHLO_APPEND1(saved_len, psc_temp, "AUTH=%s", sasl_mechanism_list); + } +#endif + if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0) + PSC_EHLO_APPEND(saved_len, psc_temp, "250-ENHANCEDSTATUSCODES\r\n"); + if ((discard_mask & EHLO_MASK_8BITMIME) == 0) + PSC_EHLO_APPEND(saved_len, psc_temp, "250-8BITMIME\r\n"); + if ((discard_mask & EHLO_MASK_DSN) == 0) + PSC_EHLO_APPEND(saved_len, psc_temp, "250-DSN\r\n"); + /* Fix 20140708: announce SMTPUTF8. */ + if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0) + PSC_EHLO_APPEND(saved_len, psc_temp, "250-SMTPUTF8\r\n"); + if ((discard_mask & EHLO_MASK_CHUNKING) == 0) + PSC_EHLO_APPEND(saved_len, psc_temp, "250-CHUNKING\r\n"); + STR(psc_temp)[saved_len + 3] = ' '; +} + +/* psc_ehlo_cmd - record EHLO and respond */ + +static int psc_ehlo_cmd(PSC_STATE *state, char *args) +{ + char *helo_name = PSC_SMTPD_NEXT_TOKEN(args); + const char *ehlo_words; + int discard_mask; + char *reply; + + /* + * smtpd(8) incompatibility: we ignore extra words; smtpd(8) saves them. + */ + if (helo_name == 0) + return (PSC_SEND_REPLY(state, "501 Syntax: EHLO hostname\r\n")); + + PSC_STRING_UPDATE(state->helo_name, helo_name); + PSC_STRING_RESET(state->sender); + state->protocol = MAIL_PROTO_ESMTP; + + /* + * smtpd(8) compatibility: dynamic reply filtering. + */ + if (psc_ehlo_discard_maps != 0 + && (ehlo_words = psc_maps_find(psc_ehlo_discard_maps, + state->smtp_client_addr, 0)) != 0 + && (discard_mask = ehlo_mask(ehlo_words)) != psc_ehlo_discard_mask) { + if (discard_mask && !(discard_mask & EHLO_MASK_SILENT)) + msg_info("[%s]%s: discarding EHLO keywords: %s", + PSC_CLIENT_ADDR_PORT(state), str_ehlo_mask(discard_mask)); + if (state->flags & PSC_STATE_FLAG_USING_TLS) + discard_mask |= EHLO_MASK_STARTTLS; + psc_smtpd_format_ehlo_reply(psc_temp, discard_mask); + reply = STR(psc_temp); + state->ehlo_discard_mask = discard_mask; + } else if (psc_ehlo_discard_maps && psc_ehlo_discard_maps->error) { + msg_fatal("%s lookup error for %s", + psc_ehlo_discard_maps->title, state->smtp_client_addr); + } else if (state->flags & PSC_STATE_FLAG_USING_TLS) { + reply = psc_smtpd_ehlo_reply_tls; + state->ehlo_discard_mask = psc_ehlo_discard_mask | EHLO_MASK_STARTTLS; + } else { + reply = psc_smtpd_ehlo_reply_plain; + state->ehlo_discard_mask = psc_ehlo_discard_mask; + } + return (PSC_SEND_REPLY(state, reply)); +} + +/* psc_starttls_resume - resume the SMTP protocol after tlsproxy activation */ + +static void psc_starttls_resume(int unused_event, void *context) +{ + const char *myname = "psc_starttls_resume"; + PSC_STATE *state = (PSC_STATE *) context; + + /* + * Reset SMTP server state if STARTTLS was successful. + */ + if (state->flags & PSC_STATE_FLAG_USING_TLS) { + /* Purge the push-back buffer, when implemented. */ + PSC_STRING_RESET(state->helo_name); + PSC_STRING_RESET(state->sender); +#ifdef TODO_SASL_AUTH + /* Reset SASL AUTH state. Dovecot responses may change. */ +#endif + } + + /* + * Resume read/timeout events. If we still have unread input, resume the + * command processor immediately. + */ + PSC_RESUME_SMTP_CMD_EVENTS(state); +} + +/* psc_starttls_cmd - activate the tlsproxy server */ + +static int psc_starttls_cmd(PSC_STATE *state, char *args) +{ + const char *myname = "psc_starttls_cmd"; + + /* + * smtpd(8) incompatibility: we can't send a 4XX reply that TLS is + * unavailable when tlsproxy(8) detects the problem too late. + */ + if (PSC_SMTPD_NEXT_TOKEN(args) != 0) + return (PSC_SEND_REPLY(state, "501 Syntax: EHLO hostname\r\n")); + if (state->flags & PSC_STATE_FLAG_USING_TLS) + return (PSC_SEND_REPLY(state, + "554 5.5.1 Error: TLS already active\r\n")); + if (var_psc_use_tls == 0 || (state->ehlo_discard_mask & EHLO_MASK_STARTTLS)) + return (PSC_SEND_REPLY(state, + "502 5.5.1 Error: command not implemented\r\n")); + + /* + * Suspend the SMTP protocol until psc_starttls_resume() is called. + */ + PSC_SUSPEND_SMTP_CMD_EVENTS(state); + psc_starttls_open(state, psc_starttls_resume); + return (0); +} + +/* psc_extract_addr - extract MAIL/RCPT address, unquoted form */ + +static char *psc_extract_addr(VSTRING *result, const char *string) +{ + const unsigned char *cp = (const unsigned char *) string; + char *addr; + char *colon; + int stop_at; + int inquote = 0; + + /* + * smtpd(8) incompatibility: we allow more invalid address forms, and we + * don't validate recipients. We are not going to deliver them so we + * won't have to worry about deliverability. This may have to change when + * we pass the socket to a real SMTP server and replay message envelope + * commands. + */ + + /* Skip SP characters. */ + while (*cp && *cp == ' ') + cp++; + + /* Choose the terminator for or bare addr. */ + if (*cp == '<') { + cp++; + stop_at = '>'; + } else { + stop_at = ' '; + } + + /* Skip to terminator or end. */ + VSTRING_RESET(result); + for ( /* void */ ; *cp; cp++) { + if (!inquote && *cp == stop_at) + break; + if (*cp == '"') { + inquote = !inquote; + } else { + if (*cp == '\\' && *++cp == 0) + break; + VSTRING_ADDCH(result, *cp); + } + } + VSTRING_TERMINATE(result); + + /* + * smtpd(8) compatibility: truncate deprecated route address form. This + * is primarily to simplify logfile analysis. + */ + addr = STR(result); + if (*addr == '@' && (colon = strchr(addr, ':')) != 0) + addr = colon + 1; + return (addr); +} + +/* psc_mail_cmd - record MAIL and respond */ + +static int psc_mail_cmd(PSC_STATE *state, char *args) +{ + char *colon; + char *addr; + + /* + * smtpd(8) incompatibility: we never reject the sender, and we ignore + * additional arguments. + */ + if (var_psc_helo_required && state->helo_name == 0) + return (PSC_SEND_REPLY(state, + "503 5.5.1 Error: send HELO/EHLO first\r\n")); + if (state->sender != 0) + return (PSC_SEND_REPLY(state, + "503 5.5.1 Error: nested MAIL command\r\n")); + if (args == 0 || (colon = strchr(args, ':')) == 0) + return (PSC_SEND_REPLY(state, + "501 5.5.4 Syntax: MAIL FROM:
\r\n")); + if ((addr = psc_extract_addr(psc_temp, colon + 1)) == 0) + return (PSC_SEND_REPLY(state, + "501 5.1.7 Bad sender address syntax\r\n")); + PSC_STRING_UPDATE(state->sender, addr); + return (PSC_SEND_REPLY(state, "250 2.1.0 Ok\r\n")); +} + +/* psc_soften_reply - copy and soft-bounce a reply */ + +static char *psc_soften_reply(const char *reply) +{ + static VSTRING *buf = 0; + + if (buf == 0) + buf = vstring_alloc(100); + vstring_strcpy(buf, reply); + if (reply[0] == '5') + STR(buf)[0] = '4'; + if (reply[4] == '5') + STR(buf)[4] = '4'; + return (STR(buf)); +} + +/* psc_rcpt_cmd record RCPT and respond */ + +static int psc_rcpt_cmd(PSC_STATE *state, char *args) +{ + char *colon; + char *addr; + + /* + * smtpd(8) incompatibility: we reject all recipients, and ignore + * additional arguments. + */ + if (state->sender == 0) + return (PSC_SEND_REPLY(state, + "503 5.5.1 Error: need MAIL command\r\n")); + if (args == 0 || (colon = strchr(args, ':')) == 0) + return (PSC_SEND_REPLY(state, + "501 5.5.4 Syntax: RCPT TO:
\r\n")); + if ((addr = psc_extract_addr(psc_temp, colon + 1)) == 0) + return (PSC_SEND_REPLY(state, + "501 5.1.3 Bad recipient address syntax\r\n")); + msg_info("NOQUEUE: reject: RCPT from [%s]:%s: %.*s; " + "from=<%s>, to=<%s>, proto=%s, helo=<%s>", + PSC_CLIENT_ADDR_PORT(state), + (int) strlen(state->rcpt_reply) - 2, + var_soft_bounce == 0 ? state->rcpt_reply : + psc_soften_reply(state->rcpt_reply), + info_log_addr_form_sender(state->sender), + info_log_addr_form_recipient(addr), state->protocol, + state->helo_name ? state->helo_name : ""); + return (PSC_SEND_REPLY(state, state->rcpt_reply)); +} + +/* psc_data_cmd - respond to DATA and disconnect */ + +static int psc_data_cmd(PSC_STATE *state, char *args) +{ + const char myname[] = "psc_data_cmd"; + + /* + * smtpd(8) incompatibility: postscreen(8) drops the connection, instead + * of waiting for the next command. Justification: postscreen(8) should + * never see DATA from a legitimate client, because 1) the server rejects + * every recipient, and 2) the server does not announce PIPELINING. + */ + msg_info("DATA without valid RCPT from [%s]:%s", + PSC_CLIENT_ADDR_PORT(state)); + if (PSC_SMTPD_NEXT_TOKEN(args) != 0) + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "501 5.5.4 Syntax: DATA\r\n"); + else if (state->sender == 0) + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "503 5.5.1 Error: need RCPT command\r\n"); + else + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "554 5.5.1 Error: no valid recipients\r\n"); + /* Caution: state is now a dangling pointer. */ + return (0); +} + +/* psc_bdat_cmd - respond to BDAT and disconnect */ + +static int psc_bdat_cmd(PSC_STATE *state, char *args) +{ + const char *myname = "psc_bdat_cmd"; + + /* + * smtpd(8) incompatibility: postscreen(8) drops the connection, instead + * of reading the entire BDAT chunk and staying in sync with the client. + * Justification: postscreen(8) should never see BDAT from a legitimate + * client, because 1) the server rejects every recipient, and 2) the + * server does not announce PIPELINING. + */ + msg_info("BDAT without valid RCPT from [%s]:%s", + PSC_CLIENT_ADDR_PORT(state)); + if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING) + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "502 5.5.1 Error: command not implemented\r\n"); + else if (PSC_SMTPD_NEXT_TOKEN(args) == 0) + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "501 5.5.4 Syntax: BDAT count [LAST]\r\n"); + else if (state->sender == 0) + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "554 5.5.1 Error: need MAIL command\r\n"); + else + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "554 5.5.1 Error: no valid recipients\r\n"); + /* Caution: state is now a dangling pointer. */ + return (0); +} + +/* psc_rset_cmd - reset, send 250 OK */ + +static int psc_rset_cmd(PSC_STATE *state, char *unused_args) +{ + PSC_STRING_RESET(state->sender); + return (PSC_SEND_REPLY(state, "250 2.0.0 Ok\r\n")); +} + +/* psc_noop_cmd - respond to something */ + +static int psc_noop_cmd(PSC_STATE *state, char *unused_args) +{ + return (PSC_SEND_REPLY(state, "250 2.0.0 Ok\r\n")); +} + +/* psc_vrfy_cmd - respond to VRFY */ + +static int psc_vrfy_cmd(PSC_STATE *state, char *args) +{ + + /* + * smtpd(8) incompatibility: we reject all requests, and ignore + * additional arguments. + */ + if (PSC_SMTPD_NEXT_TOKEN(args) == 0) + return (PSC_SEND_REPLY(state, + "501 5.5.4 Syntax: VRFY address\r\n")); + if (var_psc_disable_vrfy) + return (PSC_SEND_REPLY(state, + "502 5.5.1 VRFY command is disabled\r\n")); + return (PSC_SEND_REPLY(state, state->rcpt_reply)); +} + +/* psc_etrn_cmd - reset, send 250 OK */ + +static int psc_etrn_cmd(PSC_STATE *state, char *args) +{ + + /* + * smtpd(8) incompatibility: we reject all requests, and ignore + * additional arguments. + */ + if (var_psc_helo_required && state->helo_name == 0) + return (PSC_SEND_REPLY(state, + "503 5.5.1 Error: send HELO/EHLO first\r\n")); + if (PSC_SMTPD_NEXT_TOKEN(args) == 0) + return (PSC_SEND_REPLY(state, + "500 Syntax: ETRN domain\r\n")); + return (PSC_SEND_REPLY(state, "458 Unable to queue messages\r\n")); +} + +/* psc_quit_cmd - respond to QUIT and disconnect */ + +static int psc_quit_cmd(PSC_STATE *state, char *unused_args) +{ + const char *myname = "psc_quit_cmd"; + + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event, + "221 2.0.0 Bye\r\n"); + /* Caution: state is now a dangling pointer. */ + return (0); +} + +/* psc_smtpd_time_event - handle per-session time limit */ + +static void psc_smtpd_time_event(int event, void *context) +{ + const char *myname = "psc_smtpd_time_event"; + PSC_STATE *state = (PSC_STATE *) context; + + if (msg_verbose > 1) + msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s", + myname, psc_post_queue_length, psc_check_queue_length, + event, vstream_fileno(state->smtp_client_stream), + state->smtp_client_addr, state->smtp_client_port, + psc_print_state_flags(state->flags, myname)); + + msg_info("COMMAND TIME LIMIT from [%s]:%s after %s", + PSC_CLIENT_ADDR_PORT(state), state->where); + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event, + psc_smtpd_timeout_reply); +} + + /* + * The table of all SMTP commands that we know. + */ +typedef struct { + const char *name; + int (*action) (PSC_STATE *, char *); + int flags; /* see below */ +} PSC_SMTPD_COMMAND; + +#define PSC_SMTPD_CMD_FLAG_NONE (0) /* no flags (i.e. disabled) */ +#define PSC_SMTPD_CMD_FLAG_ENABLE (1<<0) /* command is enabled */ +#define PSC_SMTPD_CMD_FLAG_DESTROY (1<<1) /* dangling pointer alert */ +#define PSC_SMTPD_CMD_FLAG_PRE_TLS (1<<2) /* allowed with mandatory TLS */ +#define PSC_SMTPD_CMD_FLAG_SUSPEND (1<<3) /* suspend command engine */ +#define PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD (1<<4) /* command has payload */ + +static const PSC_SMTPD_COMMAND command_table[] = { + "HELO", psc_helo_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS, + "EHLO", psc_ehlo_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS, + "STARTTLS", psc_starttls_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS | PSC_SMTPD_CMD_FLAG_SUSPEND, + "XCLIENT", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE, + "XFORWARD", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE, + "AUTH", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_NONE, + "MAIL", psc_mail_cmd, PSC_SMTPD_CMD_FLAG_ENABLE, + "RCPT", psc_rcpt_cmd, PSC_SMTPD_CMD_FLAG_ENABLE, + "DATA", psc_data_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY, + /* ".", psc_dot_cmd, PSC_SMTPD_CMD_FLAG_NONE, */ + "BDAT", psc_bdat_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY | PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD, + "RSET", psc_rset_cmd, PSC_SMTPD_CMD_FLAG_ENABLE, + "NOOP", psc_noop_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_PRE_TLS, + "VRFY", psc_vrfy_cmd, PSC_SMTPD_CMD_FLAG_ENABLE, + "ETRN", psc_etrn_cmd, PSC_SMTPD_CMD_FLAG_ENABLE, + "QUIT", psc_quit_cmd, PSC_SMTPD_CMD_FLAG_ENABLE | PSC_SMTPD_CMD_FLAG_DESTROY | PSC_SMTPD_CMD_FLAG_PRE_TLS, + 0, +}; + +/* psc_smtpd_read_event - pseudo responder */ + +static void psc_smtpd_read_event(int event, void *context) +{ + const char *myname = "psc_smtpd_read_event"; + PSC_STATE *state = (PSC_STATE *) context; + time_t *expire_time = state->client_info->expire_time; + int ch; + struct cmd_trans { + int state; + int want; + int next_state; + }; + const char *saved_where; + +#define PSC_SMTPD_CMD_ST_ANY 0 +#define PSC_SMTPD_CMD_ST_CR 1 +#define PSC_SMTPD_CMD_ST_CR_LF 2 + + static const struct cmd_trans cmd_trans[] = { + PSC_SMTPD_CMD_ST_ANY, '\r', PSC_SMTPD_CMD_ST_CR, + PSC_SMTPD_CMD_ST_CR, '\n', PSC_SMTPD_CMD_ST_CR_LF, + 0, 0, 0, + }; + const struct cmd_trans *transp; + char *cmd_buffer_ptr; + char *command; + const PSC_SMTPD_COMMAND *cmdp; + int write_stat; + + if (msg_verbose > 1) + msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s", + myname, psc_post_queue_length, psc_check_queue_length, + event, vstream_fileno(state->smtp_client_stream), + state->smtp_client_addr, state->smtp_client_port, + psc_print_state_flags(state->flags, myname)); + + /* + * Basic liveness requirements. + * + * Drain all input in the VSTREAM buffer, otherwise this socket will not + * receive further read event notification until the client disconnects! + * + * To suspend this loop temporarily before the buffer is drained, use the + * PSC_SUSPEND_SMTP_CMD_EVENTS() and PSC_RESUME_SMTP_CMD_EVENTS() macros, + * and set the PSC_SMTPD_CMD_FLAG_SUSPEND flag in the command table. + * + * Don't try to read input before it has arrived, otherwise we would starve + * the pseudo threads of other sessions. Get out of here as soon as the + * VSTREAM read buffer dries up. Do not look for more input in kernel + * buffers. That input wasn't likely there when psc_smtpd_read_event() + * was called. Also, yielding the pseudo thread will improve fairness for + * other pseudo threads. + */ + + /* + * Note: on entry into this function the VSTREAM buffer may or may not be + * empty, so we test the "no more input" condition at the bottom of the + * loops. + */ + for (;;) { + + /* + * Read one command line, possibly one fragment at a time. + */ + for (;;) { + + if ((ch = PSC_SMTPD_NEXT_CHAR(state)) == VSTREAM_EOF) { + PSC_CLEAR_EVENT_HANGUP(state, psc_smtpd_time_event); + return; + } + + /* + * Sanity check. We don't want to store infinitely long commands. + */ + if (state->read_state == PSC_SMTPD_CMD_ST_ANY + && VSTRING_LEN(state->cmd_buffer) >= var_line_limit) { + msg_info("COMMAND LENGTH LIMIT from [%s]:%s after %s", + PSC_CLIENT_ADDR_PORT(state), state->where); + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event, + psc_smtpd_421_reply); + return; + } + VSTRING_ADDCH(state->cmd_buffer, ch); + + /* + * Try to match the current character desired by the state + * machine. If that fails, try to restart the machine with a + * match for its first state. Like smtpd(8), we understand lines + * ending in and bare . Unlike smtpd(8), we may + * treat lines ending in bare as an offense. + */ + for (transp = cmd_trans; transp->state != state->read_state; transp++) + if (transp->want == 0) + msg_panic("%s: command_read: unknown state: %d", + myname, state->read_state); + if (ch == transp->want) + state->read_state = transp->next_state; + else if (ch == cmd_trans[0].want) + state->read_state = cmd_trans[0].next_state; + else + state->read_state = PSC_SMTPD_CMD_ST_ANY; + if (state->read_state == PSC_SMTPD_CMD_ST_CR_LF) { + vstring_truncate(state->cmd_buffer, + VSTRING_LEN(state->cmd_buffer) - 2); + break; + } + + /* + * Bare newline test. + */ + if (ch == '\n') { + if ((state->flags & PSC_STATE_MASK_BARLF_TODO_SKIP) + == PSC_STATE_FLAG_BARLF_TODO) { + PSC_SMTPD_ESCAPE_TEXT(psc_temp, STR(state->cmd_buffer), + VSTRING_LEN(state->cmd_buffer) - 1, 100); + msg_info("BARE NEWLINE from [%s]:%s after %s", + PSC_CLIENT_ADDR_PORT(state), STR(psc_temp)); + PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_BARLF_FAIL); + PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_BARLF_PASS); + expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_DISABLED; /* XXX */ + /* Skip this test for the remainder of this session. */ + PSC_SKIP_SESSION_STATE(state, "bare newline test", + PSC_STATE_FLAG_BARLF_SKIP); + switch (psc_barlf_action) { + case PSC_ACT_DROP: + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "521 5.5.1 Protocol error\r\n"); + return; + case PSC_ACT_ENFORCE: + PSC_ENFORCE_SESSION_STATE(state, + "550 5.5.1 Protocol error\r\n"); + break; + case PSC_ACT_IGNORE: + PSC_UNFAIL_SESSION_STATE(state, + PSC_STATE_FLAG_BARLF_FAIL); + /* Temporarily allowlist until something expires. */ + PSC_PASS_SESSION_STATE(state, "bare newline test", + PSC_STATE_FLAG_BARLF_PASS); + expire_time[PSC_TINDX_BARLF] = event_time() + psc_min_ttl; + break; + default: + msg_panic("%s: unknown bare_newline action value %d", + myname, psc_barlf_action); + } + } + vstring_truncate(state->cmd_buffer, + VSTRING_LEN(state->cmd_buffer) - 1); + break; + } + + /* + * Yield this pseudo thread when the VSTREAM buffer is empty in + * the middle of a command. + * + * XXX Do not reset the read timeout. The entire command must be + * received within the time limit. + */ + if (PSC_SMTPD_BUFFER_EMPTY(state)) + return; + } + + /* + * Avoid complaints from Postfix maps about malformed content. + */ +#define PSC_BAD_UTF8(str, len) \ + (var_smtputf8_enable && !valid_utf8_string((str), (len))) + + /* + * Terminate the command buffer, and apply the last-resort command + * editing workaround. + */ + VSTRING_TERMINATE(state->cmd_buffer); + if (psc_cmd_filter != 0 && !PSC_BAD_UTF8(STR(state->cmd_buffer), + LEN(state->cmd_buffer))) { + const char *cp; + + for (cp = STR(state->cmd_buffer); *cp && IS_SPACE_TAB(*cp); cp++) + /* void */ ; + if ((cp = psc_dict_get(psc_cmd_filter, cp)) != 0) { + msg_info("[%s]:%s: replacing command \"%.100s\" with \"%.100s\"", + state->smtp_client_addr, state->smtp_client_port, + STR(state->cmd_buffer), cp); + vstring_strcpy(state->cmd_buffer, cp); + } else if (psc_cmd_filter->error != 0) { + msg_fatal("%s:%s lookup error for \"%.100s\"", + psc_cmd_filter->type, psc_cmd_filter->name, + STR(state->cmd_buffer)); + } + } + + /* + * Reset the command buffer write pointer and state machine in + * preparation for the next command. For this to work as expected, + * VSTRING_RESET() must be non-destructive. We just can't ask for the + * VSTRING_LEN() and vstring_end() results. + */ + state->read_state = PSC_SMTPD_CMD_ST_ANY; + VSTRING_RESET(state->cmd_buffer); + + /* + * Process the command line. + * + * Caution: some command handlers terminate the session and destroy the + * session state structure. When this happens we must leave the SMTP + * engine to avoid a dangling pointer problem. + */ + cmd_buffer_ptr = STR(state->cmd_buffer); + if (msg_verbose) + msg_info("< [%s]:%s: %s", state->smtp_client_addr, + state->smtp_client_port, cmd_buffer_ptr); + + /* Parse the command name. */ + if ((command = PSC_SMTPD_NEXT_TOKEN(cmd_buffer_ptr)) == 0) + command = ""; + + /* + * The non-SMTP, PIPELINING and command COUNT tests depend on the + * client command handler. + * + * Caution: cmdp->name and cmdp->action may be null on loop exit. + */ + saved_where = state->where; + state->where = PSC_SMTPD_CMD_UNIMPL; + for (cmdp = command_table; cmdp->name != 0; cmdp++) { + if (strcasecmp(command, cmdp->name) == 0) { + state->where = cmdp->name; + break; + } + } + + if ((state->flags & PSC_STATE_FLAG_SMTPD_X21) + && cmdp->action != psc_quit_cmd) { + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event, + state->final_reply); + return; + } + /* Non-SMTP command test. */ + if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_SKIP) + == PSC_STATE_FLAG_NSMTP_TODO && cmdp->name == 0 + && (is_header(command) + || PSC_BAD_UTF8(command, strlen(command)) + /* Ignore forbid_cmds lookup errors. Non-critical feature. */ + || (*var_psc_forbid_cmds + && string_list_match(psc_forbid_cmds, command)))) { + printable(command, '?'); + PSC_SMTPD_ESCAPE_TEXT(psc_temp, cmd_buffer_ptr, + strlen(cmd_buffer_ptr), 100); + msg_info("NON-SMTP COMMAND from [%s]:%s after %s: %.100s %s", + PSC_CLIENT_ADDR_PORT(state), saved_where, + command, STR(psc_temp)); + PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_FAIL); + PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_PASS); + expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED; /* XXX */ + /* Skip this test for the remainder of this SMTP session. */ + PSC_SKIP_SESSION_STATE(state, "non-smtp test", + PSC_STATE_FLAG_NSMTP_SKIP); + switch (psc_nsmtp_action) { + case PSC_ACT_DROP: + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "521 5.7.0 Error: I can break rules, too. Goodbye.\r\n"); + return; + case PSC_ACT_ENFORCE: + PSC_ENFORCE_SESSION_STATE(state, + "550 5.5.1 Protocol error\r\n"); + break; + case PSC_ACT_IGNORE: + PSC_UNFAIL_SESSION_STATE(state, + PSC_STATE_FLAG_NSMTP_FAIL); + /* Temporarily allowlist until something else expires. */ + PSC_PASS_SESSION_STATE(state, "non-smtp test", + PSC_STATE_FLAG_NSMTP_PASS); + expire_time[PSC_TINDX_NSMTP] = event_time() + psc_min_ttl; + break; + default: + msg_panic("%s: unknown non_smtp_command action value %d", + myname, psc_nsmtp_action); + } + } + /* Command PIPELINING test. */ + if ((cmdp->flags & PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD) == 0 + && (state->flags & PSC_STATE_MASK_PIPEL_TODO_SKIP) + == PSC_STATE_FLAG_PIPEL_TODO && !PSC_SMTPD_BUFFER_EMPTY(state)) { + printable(command, '?'); + PSC_SMTPD_ESCAPE_TEXT(psc_temp, PSC_SMTPD_PEEK_DATA(state), + PSC_SMTPD_PEEK_LEN(state), 100); + msg_info("COMMAND PIPELINING from [%s]:%s after %.100s: %s", + PSC_CLIENT_ADDR_PORT(state), command, STR(psc_temp)); + PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_FAIL); + PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_PASS); + expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED; /* XXX */ + /* Skip this test for the remainder of this SMTP session. */ + PSC_SKIP_SESSION_STATE(state, "pipelining test", + PSC_STATE_FLAG_PIPEL_SKIP); + switch (psc_pipel_action) { + case PSC_ACT_DROP: + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "521 5.5.1 Protocol error\r\n"); + return; + case PSC_ACT_ENFORCE: + PSC_ENFORCE_SESSION_STATE(state, + "550 5.5.1 Protocol error\r\n"); + break; + case PSC_ACT_IGNORE: + PSC_UNFAIL_SESSION_STATE(state, + PSC_STATE_FLAG_PIPEL_FAIL); + /* Temporarily allowlist until something else expires. */ + PSC_PASS_SESSION_STATE(state, "pipelining test", + PSC_STATE_FLAG_PIPEL_PASS); + expire_time[PSC_TINDX_PIPEL] = event_time() + psc_min_ttl; + break; + default: + msg_panic("%s: unknown pipelining action value %d", + myname, psc_pipel_action); + } + } + + /* + * The following tests don't pass until the client gets all the way + * to the RCPT TO command. However, the client can still fail these + * tests with some later command. + */ + if (cmdp->action == psc_rcpt_cmd) { + if ((state->flags & PSC_STATE_MASK_BARLF_TODO_PASS_FAIL) + == PSC_STATE_FLAG_BARLF_TODO) { + PSC_PASS_SESSION_STATE(state, "bare newline test", + PSC_STATE_FLAG_BARLF_PASS); + /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */ + expire_time[PSC_TINDX_BARLF] = event_time() + var_psc_barlf_ttl; + } + if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_PASS_FAIL) + == PSC_STATE_FLAG_NSMTP_TODO) { + PSC_PASS_SESSION_STATE(state, "non-smtp test", + PSC_STATE_FLAG_NSMTP_PASS); + /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */ + expire_time[PSC_TINDX_NSMTP] = event_time() + var_psc_nsmtp_ttl; + } + if ((state->flags & PSC_STATE_MASK_PIPEL_TODO_PASS_FAIL) + == PSC_STATE_FLAG_PIPEL_TODO) { + PSC_PASS_SESSION_STATE(state, "pipelining test", + PSC_STATE_FLAG_PIPEL_PASS); + /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */ + expire_time[PSC_TINDX_PIPEL] = event_time() + var_psc_pipel_ttl; + } + } + /* Command COUNT limit test. */ + if (++state->command_count > var_psc_cmd_count + && cmdp->action != psc_quit_cmd) { + msg_info("COMMAND COUNT LIMIT from [%s]:%s after %s", + PSC_CLIENT_ADDR_PORT(state), saved_where); + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event, + psc_smtpd_421_reply); + return; + } + /* Finally, execute the command. */ + if (cmdp->name == 0 || (cmdp->flags & PSC_SMTPD_CMD_FLAG_ENABLE) == 0) { + write_stat = PSC_SEND_REPLY(state, + "502 5.5.2 Error: command not recognized\r\n"); + } else if (var_psc_enforce_tls + && (state->flags & PSC_STATE_FLAG_USING_TLS) == 0 + && (cmdp->flags & PSC_SMTPD_CMD_FLAG_PRE_TLS) == 0) { + write_stat = PSC_SEND_REPLY(state, + "530 5.7.0 Must issue a STARTTLS command first\r\n"); + } else { + write_stat = cmdp->action(state, cmd_buffer_ptr); + if (cmdp->flags & PSC_SMTPD_CMD_FLAG_DESTROY) + return; + } + + /* + * Terminate the session after a write error. + */ + if (write_stat < 0) { + PSC_CLEAR_EVENT_HANGUP(state, psc_smtpd_time_event); + return; + } + + /* + * We're suspended, waiting for some external event to happen. + * Hopefully, someone will call us back to process the remainder of + * the pending input, otherwise we could hang. + */ + if (cmdp->flags & PSC_SMTPD_CMD_FLAG_SUSPEND) + return; + + /* + * Reset the command read timeout before reading the next command. + */ + event_request_timer(psc_smtpd_time_event, (void *) state, + PSC_EFF_CMD_TIME_LIMIT); + + /* + * Yield this pseudo thread when the VSTREAM buffer is empty. + */ + if (PSC_SMTPD_BUFFER_EMPTY(state)) + return; + } +} + +/* psc_smtpd_tests - per-session deep protocol test initialization */ + +void psc_smtpd_tests(PSC_STATE *state) +{ + static char *myname = "psc_smtpd_tests"; + + /* + * Report errors and progress in the context of this test. + */ + PSC_BEGIN_TESTS(state, "tests after SMTP handshake"); + + /* + * Initialize per-session state that is used only by the dummy engine: + * the command read buffer and the command read state machine. + */ + state->cmd_buffer = vstring_alloc(100); + state->read_state = PSC_SMTPD_CMD_ST_ANY; + + /* + * Disable all after-220 tests when we need to reply with 421 and hang up + * after reading the next SMTP client command. + * + * Opportunistically make postscreen more useful, by turning on all + * after-220 tests when a bad client failed a before-220 test. + * + * Otherwise, only apply the explicitly-configured after-220 tests. + */ + if (state->flags & PSC_STATE_FLAG_SMTPD_X21) { + state->flags &= ~PSC_STATE_MASK_SMTPD_TODO; + } else if (state->flags & PSC_STATE_MASK_ANY_FAIL) { + state->flags |= PSC_STATE_MASK_SMTPD_TODO; + } + + /* + * Send no SMTP banner to pregreeting clients. This eliminates a lot of + * "NON-SMTP COMMAND" events, and improves sender/recipient logging. + */ + if ((state->flags & PSC_STATE_FLAG_PREGR_FAIL) == 0 + && PSC_SEND_REPLY(state, psc_smtpd_greeting) != 0) { + psc_hangup_event(state); + return; + } + + /* + * Wait for the client to respond. + */ + PSC_READ_EVENT_REQUEST2(vstream_fileno(state->smtp_client_stream), + psc_smtpd_read_event, psc_smtpd_time_event, + (void *) state, PSC_EFF_CMD_TIME_LIMIT); +} + +/* psc_smtpd_init - per-process deep protocol test initialization */ + +void psc_smtpd_init(void) +{ + + /* + * Initialize the server banner. + */ + vstring_sprintf(psc_temp, "220 %s\r\n", var_smtpd_banner); + psc_smtpd_greeting = mystrdup(STR(psc_temp)); + + /* + * Initialize the HELO reply. + */ + vstring_sprintf(psc_temp, "250 %s\r\n", var_myhostname); + psc_smtpd_helo_reply = mystrdup(STR(psc_temp)); + + /* + * STARTTLS support. Note the complete absence of #ifdef USE_TLS + * throughout the postscreen(8) source code. If Postfix is built without + * TLS support, then the TLS proxy will simply report that TLS is not + * available, and conventional error handling will take care of the + * issue. + * + * Legacy code copied from smtpd(8). The pre-fabricated EHLO reply depends + * on this. + */ + if (*var_psc_tls_level) { + switch (tls_level_lookup(var_psc_tls_level)) { + default: + msg_fatal("Invalid TLS level \"%s\"", var_psc_tls_level); + /* NOTREACHED */ + break; + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_FPRINT: + msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"", + VAR_PSC_TLS_LEVEL, var_psc_tls_level); + /* FALLTHROUGH */ + case TLS_LEV_ENCRYPT: + var_psc_enforce_tls = var_psc_use_tls = 1; + break; + case TLS_LEV_MAY: + var_psc_enforce_tls = 0; + var_psc_use_tls = 1; + break; + case TLS_LEV_NONE: + var_psc_enforce_tls = var_psc_use_tls = 0; + break; + } + } + var_psc_use_tls = var_psc_use_tls || var_psc_enforce_tls; +#ifdef TODO_SASL_AUTH + var_psc_tls_auth_only = var_psc_tls_auth_only || var_psc_enforce_tls; +#endif + + /* + * Initialize the EHLO reply. Once for plaintext sessions, and once for + * TLS sessions. + */ + psc_smtpd_format_ehlo_reply(psc_temp, psc_ehlo_discard_mask); + psc_smtpd_ehlo_reply_plain = mystrdup(STR(psc_temp)); + + psc_smtpd_format_ehlo_reply(psc_temp, + psc_ehlo_discard_mask | EHLO_MASK_STARTTLS); + psc_smtpd_ehlo_reply_tls = mystrdup(STR(psc_temp)); + + /* + * Initialize the 421 timeout reply. + */ + vstring_sprintf(psc_temp, "421 4.4.2 %s Error: timeout exceeded\r\n", + var_myhostname); + psc_smtpd_timeout_reply = mystrdup(STR(psc_temp)); + + /* + * Initialize the generic 421 reply. + */ + vstring_sprintf(psc_temp, "421 %s Service unavailable - try again later\r\n", + var_myhostname); + psc_smtpd_421_reply = mystrdup(STR(psc_temp)); + + /* + * Initialize the reply footer. + */ + if (*var_psc_rej_footer || *var_psc_rej_ftr_maps) + psc_expand_init(); +} + +/* psc_smtpd_pre_jail_init - per-process deep protocol test initialization */ + +void psc_smtpd_pre_jail_init(void) +{ + + /* + * Determine what server ESMTP features to suppress, typically to avoid + * inter-operability problems. We do the default filter here, and + * determine client-dependent filtering on the fly. + * + * XXX Bugger. This means we have to restart when the table changes! + */ + if (*var_psc_ehlo_dis_maps) + psc_ehlo_discard_maps = maps_create(VAR_PSC_EHLO_DIS_MAPS, + var_psc_ehlo_dis_maps, + DICT_FLAG_LOCK); + psc_ehlo_discard_mask = ehlo_mask(var_psc_ehlo_dis_words); + + /* + * Last-resort command editing support. + */ + if (*var_psc_cmd_filter) + psc_cmd_filter = dict_open(var_psc_cmd_filter, O_RDONLY, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + + /* + * SMTP server reply footer. + */ + if (*var_psc_rej_ftr_maps) + pcs_send_pre_jail_init(); +} diff --git a/src/postscreen/postscreen_starttls.c b/src/postscreen/postscreen_starttls.c new file mode 100644 index 0000000..4036a3d --- /dev/null +++ b/src/postscreen/postscreen_starttls.c @@ -0,0 +1,317 @@ +/*++ +/* NAME +/* postscreen_starttls 3 +/* SUMMARY +/* postscreen TLS proxy support +/* SYNOPSIS +/* #include +/* +/* int psc_starttls_open(state, resume_event) +/* PSC_STATE *state; +/* void (*resume_event)(int unused_event, char *context); +/* DESCRIPTION +/* This module inserts the tlsproxy(8) proxy between the +/* postscreen(8) server and the remote SMTP client. The entire +/* process happens in the background, including notification +/* of completion to the remote SMTP client and to the calling +/* application. +/* +/* Before calling psc_starttls_open() the caller must turn off +/* all pending timer and I/O event requests on the SMTP client +/* stream. +/* +/* psc_starttls_open() starts the first transaction in the +/* tlsproxy(8) hand-off protocol, and sets up event handlers +/* for the successive protocol stages. +/* +/* Upon completion, the event handlers call resume_event() +/* which must reset the SMTP helo/sender/etc. state when the +/* PSC_STATE_FLAG_USING_TLS is set, and set up timer and read +/* event requests to receive the next SMTP command. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include /* concatenate() */ +#include + +/* Global library. */ + +#include +#include + +/* TLS library. */ + +#include + +/* Application-specific. */ + +#include + + /* + * For now, this code is built into the postscreen(8) daemon. In the future + * it may be abstracted into a reusable library module for use by other + * event-driven programs (perhaps smtp-source and smtp-sink). + */ + + /* + * Transient state for the postscreen(8)-to-tlsproxy(8) hand-off protocol. + */ +typedef struct { + VSTREAM *tlsproxy_stream; /* hand-off negotiation */ + EVENT_NOTIFY_FN resume_event; /* call-back handler */ + PSC_STATE *smtp_state; /* SMTP session state */ +} PSC_STARTTLS; + +#define TLSPROXY_INIT_TIMEOUT 10 + +static char *psc_tlsp_service = 0; + +/* Resume the dummy SMTP engine after an event handling error */ + +#define PSC_STARTTLS_EVENT_ERR_RESUME_RETURN() do { \ + event_disable_readwrite(vstream_fileno(tlsproxy_stream)); \ + PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state); \ + } while (0); + +/* Resume the dummy SMTP engine, possibly after swapping streams */ + +#define PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state) do { \ + vstream_fclose(tlsproxy_stream); \ + starttls_state->resume_event(event, (void *) smtp_state); \ + myfree((void *) starttls_state); \ + return; \ + } while (0) + +/* psc_starttls_finish - complete negotiation with TLS proxy */ + +static void psc_starttls_finish(int event, void *context) +{ + const char *myname = "psc_starttls_finish"; + PSC_STARTTLS *starttls_state = (PSC_STARTTLS *) context; + PSC_STATE *smtp_state = starttls_state->smtp_state; + VSTREAM *tlsproxy_stream = starttls_state->tlsproxy_stream; + int status; + + if (msg_verbose) + msg_info("%s: send client handle on proxy socket %d" + " for smtp socket %d from [%s]:%s flags=%s", + myname, vstream_fileno(tlsproxy_stream), + vstream_fileno(smtp_state->smtp_client_stream), + smtp_state->smtp_client_addr, smtp_state->smtp_client_port, + psc_print_state_flags(smtp_state->flags, myname)); + + /* + * We leave read-event notification enabled on the postscreen to TLS + * proxy stream, to avoid two kqueue/epoll/etc. system calls: one here, + * and one when resuming the dummy SMTP engine. + */ + if (event != EVENT_TIME) + event_cancel_timer(psc_starttls_finish, (void *) starttls_state); + + /* + * Receive the "TLS is available" indication. + * + * This may seem out of order, but we must have a read transaction between + * sending the request attributes and sending the SMTP client file + * descriptor. We can't assume UNIX-domain socket semantics here. + */ + if (event != EVENT_READ + || attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + ATTR_TYPE_END) != 1 || status == 0) { + + /* + * The TLS proxy reports that the TLS engine is not available (due to + * configuration error, or other causes). + */ + msg_warn("%s receiving status from %s service", + event == EVENT_TIME ? "timeout" : "problem", psc_tlsp_service); + PSC_SEND_REPLY(smtp_state, + "454 4.7.0 TLS not available due to local problem\r\n"); + PSC_STARTTLS_EVENT_ERR_RESUME_RETURN(); + } + + /* + * Send the remote SMTP client file descriptor. + */ + else if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream), + vstream_fileno(smtp_state->smtp_client_stream)) < 0) { + + /* + * Some error: drop the TLS proxy stream. + */ + msg_warn("problem sending file handle to %s service", psc_tlsp_service); + PSC_SEND_REPLY(smtp_state, + "454 4.7.0 TLS not available due to local problem\r\n"); + PSC_STARTTLS_EVENT_ERR_RESUME_RETURN(); + } + + /* + * After we send the plaintext 220 greeting, the client-side TLS engine + * is supposed to talk first, then the server-side TLS engine. However, + * postscreen(8) will not participate in that conversation. + */ + else { + PSC_SEND_REPLY(smtp_state, "220 2.0.0 Ready to start TLS\r\n"); + + /* + * Swap the SMTP client stream and the TLS proxy stream, and close + * the direct connection to the SMTP client. The TLS proxy will talk + * directly to the SMTP client, and once the TLS handshake is + * completed, the TLS proxy will talk plaintext to postscreen(8). + * + * Swap the file descriptors from under the VSTREAM so that we don't + * have to worry about loss of user-configurable VSTREAM attributes. + */ + vstream_fpurge(smtp_state->smtp_client_stream, VSTREAM_PURGE_BOTH); + vstream_control(smtp_state->smtp_client_stream, + CA_VSTREAM_CTL_SWAP_FD(tlsproxy_stream), + CA_VSTREAM_CTL_END); + smtp_state->flags |= PSC_STATE_FLAG_USING_TLS; + PSC_STARTTLS_EVENT_RESUME_RETURN(starttls_state); + } +} + +/* psc_starttls_first - start negotiation with TLS proxy */ + +static void psc_starttls_first(int event, void *context) +{ + const char *myname = "psc_starttls_first"; + PSC_STARTTLS *starttls_state = (PSC_STARTTLS *) context; + PSC_STATE *smtp_state = starttls_state->smtp_state; + VSTREAM *tlsproxy_stream = starttls_state->tlsproxy_stream; + static VSTRING *remote_endpt = 0; + + if (msg_verbose) + msg_info("%s: receive server protocol on proxy socket %d" + " for smtp socket %d from [%s]:%s flags=%s", + myname, vstream_fileno(tlsproxy_stream), + vstream_fileno(smtp_state->smtp_client_stream), + smtp_state->smtp_client_addr, smtp_state->smtp_client_port, + psc_print_state_flags(smtp_state->flags, myname)); + + /* + * We leave read-event notification enabled on the postscreen to TLS + * proxy stream, to avoid two kqueue/epoll/etc. system calls: one here, + * and one when resuming the dummy SMTP engine. + */ + if (event != EVENT_TIME) + event_cancel_timer(psc_starttls_first, (void *) starttls_state); + + /* + * Receive and verify the server protocol. + */ + if (event != EVENT_READ + || attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSPROXY), + ATTR_TYPE_END) != 0) { + msg_warn("%s receiving %s attribute from %s service: %m", + event == EVENT_TIME ? "timeout" : "problem", + MAIL_ATTR_PROTO, psc_tlsp_service); + PSC_SEND_REPLY(smtp_state, + "454 4.7.0 TLS not available due to local problem\r\n"); + PSC_STARTTLS_EVENT_ERR_RESUME_RETURN(); + } + + /* + * Send the data attributes now, and send the client file descriptor in a + * later transaction. We report all errors asynchronously, to avoid + * having to maintain multiple error delivery paths. + * + * XXX The formatted endpoint should be a state member. Then, we can + * simplify all the format strings throughout the program. + */ + if (remote_endpt == 0) + remote_endpt = vstring_alloc(20); + vstring_sprintf(remote_endpt, "[%s]:%s", smtp_state->smtp_client_addr, + smtp_state->smtp_client_port); + attr_print(tlsproxy_stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, STR(remote_endpt)), + SEND_ATTR_INT(TLS_ATTR_FLAGS, TLS_PROXY_FLAG_ROLE_SERVER), + SEND_ATTR_INT(TLS_ATTR_TIMEOUT, psc_normal_cmd_time_limit), + SEND_ATTR_INT(TLS_ATTR_TIMEOUT, psc_normal_cmd_time_limit), + SEND_ATTR_STR(TLS_ATTR_SERVERID, MAIL_SERVICE_SMTPD), /* XXX */ + ATTR_TYPE_END); + if (vstream_fflush(tlsproxy_stream) != 0) { + msg_warn("error sending request to %s service: %m", psc_tlsp_service); + PSC_SEND_REPLY(smtp_state, + "454 4.7.0 TLS not available due to local problem\r\n"); + PSC_STARTTLS_EVENT_ERR_RESUME_RETURN(); + } + + /* + * Set up a read event for the next phase of the TLS proxy handshake. + */ + PSC_READ_EVENT_REQUEST(vstream_fileno(tlsproxy_stream), psc_starttls_finish, + (void *) starttls_state, TLSPROXY_INIT_TIMEOUT); +} + +/* psc_starttls_open - open negotiations with TLS proxy */ + +void psc_starttls_open(PSC_STATE *smtp_state, EVENT_NOTIFY_FN resume_event) +{ + const char *myname = "psc_starttls_open"; + PSC_STARTTLS *starttls_state; + VSTREAM *tlsproxy_stream; + int fd; + + if (psc_tlsp_service == 0) { + psc_tlsp_service = concatenate(MAIL_CLASS_PRIVATE "/", + var_tlsproxy_service, (char *) 0); + } + + /* + * Connect to the tlsproxy(8) daemon. We report all errors + * asynchronously, to avoid having to maintain multiple delivery paths. + */ + if ((fd = LOCAL_CONNECT(psc_tlsp_service, NON_BLOCKING, 1)) < 0) { + msg_warn("connect to %s service: %m", psc_tlsp_service); + PSC_SEND_REPLY(smtp_state, + "454 4.7.0 TLS not available due to local problem\r\n"); + event_request_timer(resume_event, (void *) smtp_state, 0); + return; + } + if (msg_verbose) + msg_info("%s: connecting to proxy socket %d" + " for smtp socket %d from [%s]:%s flags=%s", + myname, fd, vstream_fileno(smtp_state->smtp_client_stream), + smtp_state->smtp_client_addr, smtp_state->smtp_client_port, + psc_print_state_flags(smtp_state->flags, myname)); + + tlsproxy_stream = vstream_fdopen(fd, O_RDWR); + vstream_control(tlsproxy_stream, + VSTREAM_CTL_PATH, psc_tlsp_service, + VSTREAM_CTL_END); + + /* + * Set up a read event for the next phase of the TLS proxy handshake. + */ + starttls_state = (PSC_STARTTLS *) mymalloc(sizeof(*starttls_state)); + starttls_state->tlsproxy_stream = tlsproxy_stream; + starttls_state->resume_event = resume_event; + starttls_state->smtp_state = smtp_state; + PSC_READ_EVENT_REQUEST(vstream_fileno(tlsproxy_stream), psc_starttls_first, + (void *) starttls_state, TLSPROXY_INIT_TIMEOUT); +} diff --git a/src/postscreen/postscreen_state.c b/src/postscreen/postscreen_state.c new file mode 100644 index 0000000..2b5db3c --- /dev/null +++ b/src/postscreen/postscreen_state.c @@ -0,0 +1,317 @@ +/*++ +/* NAME +/* postscreen_state 3 +/* SUMMARY +/* postscreen session state and queue length management +/* SYNOPSIS +/* #include +/* +/* PSC_STATE *psc_new_session_state(stream, client_addr, client_port, +/* server_addr, server_port) +/* VSTREAM *stream; +/* const char *client_addr; +/* const char *client_port; +/* const char *server_addr; +/* const char *server_port; +/* +/* void psc_free_session_state(state) +/* PSC_STATE *state; +/* +/* char *psc_print_state_flags(flags, context) +/* int flags; +/* const char *context; +/* +/* void PSC_ADD_SERVER_STATE(state, server_fd) +/* PSC_STATE *state; +/* int server_fd; +/* +/* void PSC_DEL_SERVER_STATE(state) +/* PSC_STATE *state; +/* +/* void PSC_DEL_CLIENT_STATE(state) +/* PSC_STATE *state; +/* +/* void PSC_DROP_SESSION_STATE(state, final_reply) +/* PSC_STATE *state; +/* const char *final_reply; +/* +/* void PSC_ENFORCE_SESSION_STATE(state, rcpt_reply) +/* PSC_STATE *state; +/* const char *rcpt_reply; +/* +/* void PSC_PASS_SESSION_STATE(state, testname, pass_flag) +/* PSC_STATE *state; +/* const char *testname; +/* int pass_flag; +/* +/* void PSC_FAIL_SESSION_STATE(state, fail_flag) +/* PSC_STATE *state; +/* int fail_flag; +/* +/* void PSC_UNFAIL_SESSION_STATE(state, fail_flag) +/* PSC_STATE *state; +/* int fail_flag; +/* DESCRIPTION +/* This module maintains per-client session state, and two +/* global file descriptor counters: +/* .IP psc_check_queue_length +/* The total number of remote SMTP client sockets. +/* .IP psc_post_queue_length +/* The total number of server file descriptors that are currently +/* in use for client file descriptor passing. This number +/* equals the number of client file descriptors in transit. +/* .PP +/* psc_new_session_state() creates a new session state object +/* for the specified client stream, and increments the +/* psc_check_queue_length counter. The flags and per-test time +/* stamps are initialized with PSC_INIT_TESTS(), or for concurrent +/* sessions, with PSC_INIT_TEST_FLAGS_ONLY(). The addr and +/* port arguments are null-terminated strings with the remote +/* SMTP client endpoint. The _reply members are set to +/* polite "try again" SMTP replies. The protocol member is set +/* to "SMTP". +/* +/* The psc_stress variable is set to non-zero when +/* psc_check_queue_length passes over a high-water mark. +/* +/* psc_free_session_state() destroys the specified session state +/* object, closes the applicable I/O channels, and decrements +/* the applicable file descriptor counters: psc_check_queue_length +/* and psc_post_queue_length. +/* +/* The psc_stress variable is reset to zero when psc_check_queue_length +/* passes under a low-water mark. +/* +/* psc_print_state_flags() converts per-session flags into +/* human-readable form. The context is for error reporting. +/* The result is overwritten upon each call. +/* +/* PSC_ADD_SERVER_STATE() updates the specified session state +/* object with the specified server file descriptor, and +/* increments the global psc_post_queue_length file descriptor +/* counter. +/* +/* PSC_DEL_SERVER_STATE() closes the specified session state +/* object's server file descriptor, and decrements the global +/* psc_post_queue_length file descriptor counter. +/* +/* PSC_DEL_CLIENT_STATE() updates the specified session state +/* object, closes the client stream, and decrements the global +/* psc_check_queue_length file descriptor counter. +/* +/* PSC_DROP_SESSION_STATE() updates the specified session state +/* object and closes the client stream after sending the +/* specified SMTP reply. +/* +/* PSC_ENFORCE_SESSION_STATE() updates the specified session +/* state object. It arranges that the built-in SMTP engine +/* logs sender/recipient information and rejects all RCPT TO +/* commands with the specified SMTP reply. +/* +/* PSC_PASS_SESSION_STATE() sets the specified "pass" flag. +/* The testname is used for debug logging. +/* +/* PSC_FAIL_SESSION_STATE() sets the specified "fail" flag. +/* +/* PSC_UNFAIL_SESSION_STATE() unsets the specified "fail" flag. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Master server protocols. */ + +#include + +/* Application-specific. */ + +#include + +/* psc_new_session_state - fill in connection state for event processing */ + +PSC_STATE *psc_new_session_state(VSTREAM *stream, + const char *client_addr, + const char *client_port, + const char *server_addr, + const char *server_port) +{ + PSC_STATE *state; + + state = (PSC_STATE *) mymalloc(sizeof(*state)); + if ((state->smtp_client_stream = stream) != 0) + psc_check_queue_length++; + state->smtp_server_fd = (-1); + state->smtp_client_addr = mystrdup(client_addr); + state->smtp_client_port = mystrdup(client_port); + state->smtp_server_addr = mystrdup(server_addr); + state->smtp_server_port = mystrdup(server_port); + state->send_buf = vstring_alloc(100); + state->test_name = "TEST NAME HERE"; + state->dnsbl_reply = 0; + state->final_reply = "421 4.3.2 Service currently unavailable\r\n"; + state->rcpt_reply = "450 4.3.2 Service currently unavailable\r\n"; + state->command_count = 0; + state->protocol = MAIL_PROTO_SMTP; + state->helo_name = 0; + state->sender = 0; + state->cmd_buffer = 0; + state->read_state = 0; + state->ehlo_discard_mask = 0; /* XXX Should be ~0 */ + state->expand_buf = 0; + state->where = PSC_SMTPD_CMD_CONNECT; + + /* + * Update the stress level. + */ + if (psc_stress == 0 + && psc_check_queue_length >= psc_hiwat_check_queue_length) { + psc_stress = 1; + msg_info("entering STRESS mode with %d connections", + psc_check_queue_length); + } + + /* + * Update the per-client session count. + */ + if ((state->client_info = (PSC_CLIENT_INFO *) + htable_find(psc_client_concurrency, client_addr)) == 0) { + state->client_info = (PSC_CLIENT_INFO *) + mymalloc(sizeof(state->client_info[0])); + (void) htable_enter(psc_client_concurrency, client_addr, + (void *) state->client_info); + PSC_INIT_TESTS(state); + state->client_info->concurrency = 1; + state->client_info->pass_new_count = 0; + } else { + PSC_INIT_TEST_FLAGS_ONLY(state); + state->client_info->concurrency += 1; + } + + return (state); +} + +/* psc_free_session_state - destroy connection state including connections */ + +void psc_free_session_state(PSC_STATE *state) +{ + const char *myname = "psc_free_session_state"; + HTABLE_INFO *ht; + + /* + * Update the per-client session count. + */ + if ((ht = htable_locate(psc_client_concurrency, + state->smtp_client_addr)) == 0) + msg_panic("%s: unknown client address: %s", + myname, state->smtp_client_addr); + if (--(state->client_info->concurrency) == 0) + htable_delete(psc_client_concurrency, state->smtp_client_addr, myfree); + + if (state->smtp_client_stream != 0) { + PSC_DEL_CLIENT_STATE(state); + } + if (state->smtp_server_fd >= 0) { + PSC_DEL_SERVER_STATE(state); + } + if (state->send_buf != 0) + state->send_buf = vstring_free(state->send_buf); + myfree(state->smtp_client_addr); + myfree(state->smtp_client_port); + myfree(state->smtp_server_addr); + myfree(state->smtp_server_port); + if (state->dnsbl_reply) + vstring_free(state->dnsbl_reply); + if (state->helo_name) + myfree(state->helo_name); + if (state->sender) + myfree(state->sender); + if (state->cmd_buffer) + vstring_free(state->cmd_buffer); + if (state->expand_buf) + vstring_free(state->expand_buf); + myfree((void *) state); + + if (psc_check_queue_length < 0 || psc_post_queue_length < 0) + msg_panic("bad queue length: check_queue=%d, post_queue=%d", + psc_check_queue_length, psc_post_queue_length); + + /* + * Update the stress level. + */ + if (psc_stress != 0 + && psc_check_queue_length <= psc_lowat_check_queue_length) { + psc_stress = 0; + msg_info("leaving STRESS mode with %d connections", + psc_check_queue_length); + } +} + +/* psc_print_state_flags - format state flags */ + +const char *psc_print_state_flags(int flags, const char *context) +{ + static const NAME_MASK flags_mask[] = { + "NOFORWARD", PSC_STATE_FLAG_NOFORWARD, + "USING_TLS", PSC_STATE_FLAG_USING_TLS, + "NEW", PSC_STATE_FLAG_NEW, + "DNLIST_FAIL", PSC_STATE_FLAG_DNLIST_FAIL, + "HANGUP", PSC_STATE_FLAG_HANGUP, + /* unused */ + "ALLIST_FAIL", PSC_STATE_FLAG_ALLIST_FAIL, + + "PREGR_FAIL", PSC_STATE_FLAG_PREGR_FAIL, + "PREGR_PASS", PSC_STATE_FLAG_PREGR_PASS, + "PREGR_TODO", PSC_STATE_FLAG_PREGR_TODO, + "PREGR_DONE", PSC_STATE_FLAG_PREGR_DONE, + + "DNSBL_FAIL", PSC_STATE_FLAG_DNSBL_FAIL, + "DNSBL_PASS", PSC_STATE_FLAG_DNSBL_PASS, + "DNSBL_TODO", PSC_STATE_FLAG_DNSBL_TODO, + "DNSBL_DONE", PSC_STATE_FLAG_DNSBL_DONE, + + "PIPEL_FAIL", PSC_STATE_FLAG_PIPEL_FAIL, + "PIPEL_PASS", PSC_STATE_FLAG_PIPEL_PASS, + "PIPEL_TODO", PSC_STATE_FLAG_PIPEL_TODO, + "PIPEL_SKIP", PSC_STATE_FLAG_PIPEL_SKIP, + + "NSMTP_FAIL", PSC_STATE_FLAG_NSMTP_FAIL, + "NSMTP_PASS", PSC_STATE_FLAG_NSMTP_PASS, + "NSMTP_TODO", PSC_STATE_FLAG_NSMTP_TODO, + "NSMTP_SKIP", PSC_STATE_FLAG_NSMTP_SKIP, + + "BARLF_FAIL", PSC_STATE_FLAG_BARLF_FAIL, + "BARLF_PASS", PSC_STATE_FLAG_BARLF_PASS, + "BARLF_TODO", PSC_STATE_FLAG_BARLF_TODO, + "BARLF_SKIP", PSC_STATE_FLAG_BARLF_SKIP, + 0, + }; + + return (str_name_mask_opt((VSTRING *) 0, context, flags_mask, flags, + NAME_MASK_PIPE | NAME_MASK_NUMBER)); +} diff --git a/src/postscreen/postscreen_tests.c b/src/postscreen/postscreen_tests.c new file mode 100644 index 0000000..5e18622 --- /dev/null +++ b/src/postscreen/postscreen_tests.c @@ -0,0 +1,341 @@ +/*++ +/* NAME +/* postscreen_tests 3 +/* SUMMARY +/* postscreen tests timestamp/flag bulk support +/* SYNOPSIS +/* #include +/* +/* void PSC_INIT_TESTS(state) +/* PSC_STATE *state; +/* +/* void psc_new_tests(state) +/* PSC_STATE *state; +/* +/* void psc_parse_tests(state, stamp_text, time_value) +/* PSC_STATE *state; +/* const char *stamp_text; +/* time_t time_value; +/* +/* void psc_todo_tests(state, time_value) +/* PSC_STATE *state; +/* const char *stamp_text; +/* time_t time_value; +/* +/* char *psc_print_tests(buffer, state) +/* VSTRING *buffer; +/* PSC_STATE *state; +/* +/* char *psc_print_grey_key(buffer, client, helo, sender, rcpt) +/* VSTRING *buffer; +/* const char *client; +/* const char *helo; +/* const char *sender; +/* const char *rcpt; +/* +/* const char *psc_test_name(tindx) +/* int tindx; +/* DESCRIPTION +/* The functions in this module overwrite the per-test expiration +/* time stamps and all flags bits. Some functions are implemented +/* as unsafe macros, meaning they evaluate one or more arguments +/* multiple times. +/* +/* PSC_INIT_TESTS() is an unsafe macro that sets the per-test +/* expiration time stamps to PSC_TIME_STAMP_INVALID, and that +/* zeroes all the flags bits. These values are not meant to +/* be stored into the postscreen(8) cache. +/* +/* PSC_INIT_TEST_FLAGS_ONLY() zeroes all the flag bits. It +/* should be used when the time stamps are already initialized. +/* +/* psc_new_tests() sets all test expiration time stamps to +/* PSC_TIME_STAMP_NEW, and invokes psc_todo_tests(). +/* +/* psc_parse_tests() parses a cache file record and invokes +/* psc_todo_tests(). +/* +/* psc_todo_tests() overwrites all per-session flag bits, and +/* populates the flags based on test expiration time stamp +/* information. Tests are considered "expired" when they +/* would be expired at the specified time value. Only enabled +/* tests are flagged as "expired"; the object is flagged as +/* "new" if some enabled tests have "new" time stamps. +/* +/* psc_print_tests() creates a cache file record for the +/* specified flags and per-test expiration time stamps. +/* This may modify the time stamps for disabled tests. +/* +/* psc_print_grey_key() prints a greylist lookup key. +/* +/* psc_test_name() returns the name for the specified text +/* index. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include /* sscanf */ + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include + + /* + * Kludge to detect if some test is enabled. + */ +#define PSC_PREGR_TEST_ENABLE() (*var_psc_pregr_banner != 0) +#define PSC_DNSBL_TEST_ENABLE() (*var_psc_dnsbl_sites != 0) + + /* + * Format of a persistent cache entry (which is almost but not quite the + * same as the in-memory representation). + * + * Each cache entry has one time stamp for each test. + * + * - A time stamp of PSC_TIME_STAMP_INVALID must never appear in the cache. It + * is reserved for in-memory objects that are still being initialized. + * + * - A time stamp of PSC_TIME_STAMP_NEW indicates that the test never passed. + * Postscreen will log the client with "pass new" when it passes the final + * test. + * + * - A time stamp of PSC_TIME_STAMP_DISABLED indicates that the test never + * passed, and that the test was disabled when the cache entry was written. + * + * - Otherwise, the test was passed, and the time stamp indicates when that + * test result expires. + * + * A cache entry is expired when the time stamps of all passed tests are + * expired. + */ + +/* psc_new_tests - initialize new test results from scratch */ + +void psc_new_tests(PSC_STATE *state) +{ + time_t *expire_time = state->client_info->expire_time; + + /* + * Give all tests a PSC_TIME_STAMP_NEW time stamp, so that we can later + * recognize cache entries that haven't passed all enabled tests. When we + * write a cache entry to the database, any new-but-disabled tests will + * get a PSC_TIME_STAMP_DISABLED time stamp. + */ + expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_NEW; + expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_NEW; + expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_NEW; + expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_NEW; + expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_NEW; + + /* + * Determine what tests need to be completed. + */ + psc_todo_tests(state, PSC_TIME_STAMP_NEW + 1); +} + +/* psc_parse_tests - parse test results from cache */ + +void psc_parse_tests(PSC_STATE *state, + const char *stamp_str, + time_t time_value) +{ + const char *start = stamp_str; + char *cp; + time_t *time_stamps = state->client_info->expire_time; + time_t *sp; + + /* + * Parse the cache entry, and allow for older postscreen versions that + * implemented fewer tests. We pretend that the newer tests were disabled + * at the time that the cache entry was written. + */ + for (sp = time_stamps; sp < time_stamps + PSC_TINDX_COUNT; sp++) { + *sp = sane_strtoul(start, &cp, 10); + if (*start == 0 || (*cp != '\0' && *cp != ';') || errno == ERANGE) + *sp = PSC_TIME_STAMP_DISABLED; + if (msg_verbose) + msg_info("%s -> %lu", start, (unsigned long) *sp); + if (*cp == ';') + start = cp + 1; + else + start = cp; + } + + /* + * Determine what tests need to be completed. + */ + psc_todo_tests(state, time_value); +} + +/* psc_todo_tests - determine what tests to perform */ + +void psc_todo_tests(PSC_STATE *state, time_t time_value) +{ + time_t *expire_time = state->client_info->expire_time; + time_t *sp; + + /* + * Reset all per-session flags. + */ + state->flags = 0; + + /* + * Flag the tests as "new" when the cache entry has fields for all + * enabled tests, but the remote SMTP client has not yet passed all those + * tests. + */ + for (sp = expire_time; sp < expire_time + PSC_TINDX_COUNT; sp++) { + if (*sp == PSC_TIME_STAMP_NEW) + state->flags |= PSC_STATE_FLAG_NEW; + } + + /* + * Don't flag disabled tests as "todo", because there would be no way to + * make those bits go away. + */ + if (PSC_PREGR_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_PREGR]) + state->flags |= PSC_STATE_FLAG_PREGR_TODO; + if (PSC_DNSBL_TEST_ENABLE() && time_value > expire_time[PSC_TINDX_DNSBL]) + state->flags |= PSC_STATE_FLAG_DNSBL_TODO; + if (var_psc_pipel_enable && time_value > expire_time[PSC_TINDX_PIPEL]) + state->flags |= PSC_STATE_FLAG_PIPEL_TODO; + if (var_psc_nsmtp_enable && time_value > expire_time[PSC_TINDX_NSMTP]) + state->flags |= PSC_STATE_FLAG_NSMTP_TODO; + if (var_psc_barlf_enable && time_value > expire_time[PSC_TINDX_BARLF]) + state->flags |= PSC_STATE_FLAG_BARLF_TODO; + + /* + * If any test has expired, proactively refresh tests that will expire + * soon. This can increase the occurrence of client-visible delays, but + * avoids questions about why a client can pass some test and then fail + * within seconds. The proactive refresh time is really a surrogate for + * the user's curiosity level, and therefore hard to choose optimally. + */ +#ifdef VAR_PSC_REFRESH_TIME + if ((state->flags & PSC_STATE_MASK_ANY_TODO) != 0 + && var_psc_refresh_time > 0) { + time_t refresh_time = time_value + var_psc_refresh_time; + + if (PSC_PREGR_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_PREGR]) + state->flags |= PSC_STATE_FLAG_PREGR_TODO; + if (PSC_DNSBL_TEST_ENABLE() && refresh_time > expire_time[PSC_TINDX_DNSBL]) + state->flags |= PSC_STATE_FLAG_DNSBL_TODO; + if (var_psc_pipel_enable && refresh_time > expire_time[PSC_TINDX_PIPEL]) + state->flags |= PSC_STATE_FLAG_PIPEL_TODO; + if (var_psc_nsmtp_enable && refresh_time > expire_time[PSC_TINDX_NSMTP]) + state->flags |= PSC_STATE_FLAG_NSMTP_TODO; + if (var_psc_barlf_enable && refresh_time > expire_time[PSC_TINDX_BARLF]) + state->flags |= PSC_STATE_FLAG_BARLF_TODO; + } +#endif + + /* + * Gratuitously make postscreen logging more useful by turning on all + * enabled pre-handshake tests when any pre-handshake test is turned on. + * + * XXX Don't enable PREGREET gratuitously before the test expires. With a + * short TTL for DNSBL allowlisting, turning on PREGREET would force a + * full postscreen_greet_wait too frequently. + */ +#if 0 + if (state->flags & PSC_STATE_MASK_EARLY_TODO) { + if (PSC_PREGR_TEST_ENABLE()) + state->flags |= PSC_STATE_FLAG_PREGR_TODO; + if (PSC_DNSBL_TEST_ENABLE()) + state->flags |= PSC_STATE_FLAG_DNSBL_TODO; + } +#endif +} + +/* psc_print_tests - print postscreen cache record */ + +char *psc_print_tests(VSTRING *buf, PSC_STATE *state) +{ + const char *myname = "psc_print_tests"; + time_t *expire_time = state->client_info->expire_time; + + /* + * Sanity check. + */ + if ((state->flags & PSC_STATE_MASK_ANY_UPDATE) == 0) + msg_panic("%s: attempt to save a no-update record", myname); + + /* + * Give disabled tests a dummy time stamp so that we don't log a client + * with "pass new" when some disabled test becomes enabled at some later + * time. + */ + if (PSC_PREGR_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_PREGR] == PSC_TIME_STAMP_NEW) + expire_time[PSC_TINDX_PREGR] = PSC_TIME_STAMP_DISABLED; + if (PSC_DNSBL_TEST_ENABLE() == 0 && expire_time[PSC_TINDX_DNSBL] == PSC_TIME_STAMP_NEW) + expire_time[PSC_TINDX_DNSBL] = PSC_TIME_STAMP_DISABLED; + if (var_psc_pipel_enable == 0 && expire_time[PSC_TINDX_PIPEL] == PSC_TIME_STAMP_NEW) + expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED; + if (var_psc_nsmtp_enable == 0 && expire_time[PSC_TINDX_NSMTP] == PSC_TIME_STAMP_NEW) + expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED; + if (var_psc_barlf_enable == 0 && expire_time[PSC_TINDX_BARLF] == PSC_TIME_STAMP_NEW) + expire_time[PSC_TINDX_BARLF] = PSC_TIME_STAMP_DISABLED; + + vstring_sprintf(buf, "%lu;%lu;%lu;%lu;%lu", + (unsigned long) expire_time[PSC_TINDX_PREGR], + (unsigned long) expire_time[PSC_TINDX_DNSBL], + (unsigned long) expire_time[PSC_TINDX_PIPEL], + (unsigned long) expire_time[PSC_TINDX_NSMTP], + (unsigned long) expire_time[PSC_TINDX_BARLF]); + return (STR(buf)); +} + +/* psc_print_grey_key - print postscreen cache record */ + +char *psc_print_grey_key(VSTRING *buf, const char *client, + const char *helo, const char *sender, + const char *rcpt) +{ + return (STR(vstring_sprintf(buf, "%s/%s/%s/%s", + client, helo, sender, rcpt))); +} + +/* psc_test_name - map test index to symbolic name */ + +const char *psc_test_name(int tindx) +{ + const char *myname = "psc_test_name"; + const NAME_CODE test_name_map[] = { + PSC_TNAME_PREGR, PSC_TINDX_PREGR, + PSC_TNAME_DNSBL, PSC_TINDX_DNSBL, + PSC_TNAME_PIPEL, PSC_TINDX_PIPEL, + PSC_TNAME_NSMTP, PSC_TINDX_NSMTP, + PSC_TNAME_BARLF, PSC_TINDX_BARLF, + 0, -1, + }; + const char *result; + + if ((result = str_name_code(test_name_map, tindx)) == 0) + msg_panic("%s: bad index %d", myname, tindx); + return (result); +} diff --git a/src/postsuper/.indent.pro b/src/postsuper/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/postsuper/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/postsuper/.printfck b/src/postsuper/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/postsuper/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/postsuper/Makefile.in b/src/postsuper/Makefile.in new file mode 100644 index 0000000..30b540a --- /dev/null +++ b/src/postsuper/Makefile.in @@ -0,0 +1,90 @@ +SHELL = /bin/sh +SRCS = postsuper.c +OBJS = postsuper.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = postsuper +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postsuper.o: ../../include/argv.h +postsuper.o: ../../include/check_arg.h +postsuper.o: ../../include/clean_env.h +postsuper.o: ../../include/file_id.h +postsuper.o: ../../include/mail_conf.h +postsuper.o: ../../include/mail_open_ok.h +postsuper.o: ../../include/mail_params.h +postsuper.o: ../../include/mail_parm_split.h +postsuper.o: ../../include/mail_queue.h +postsuper.o: ../../include/mail_task.h +postsuper.o: ../../include/mail_version.h +postsuper.o: ../../include/maillog_client.h +postsuper.o: ../../include/msg.h +postsuper.o: ../../include/msg_vstream.h +postsuper.o: ../../include/mymalloc.h +postsuper.o: ../../include/myrand.h +postsuper.o: ../../include/name_mask.h +postsuper.o: ../../include/safe.h +postsuper.o: ../../include/safe_open.h +postsuper.o: ../../include/safe_ultostr.h +postsuper.o: ../../include/sane_fsops.h +postsuper.o: ../../include/scan_dir.h +postsuper.o: ../../include/set_ugid.h +postsuper.o: ../../include/sys_defs.h +postsuper.o: ../../include/vbuf.h +postsuper.o: ../../include/vstream.h +postsuper.o: ../../include/vstring.h +postsuper.o: ../../include/vstring_vstream.h +postsuper.o: ../../include/warn_stat.h +postsuper.o: postsuper.c diff --git a/src/postsuper/postsuper.c b/src/postsuper/postsuper.c new file mode 100644 index 0000000..d3f2d5b --- /dev/null +++ b/src/postsuper/postsuper.c @@ -0,0 +1,1604 @@ +/*++ +/* NAME +/* postsuper 1 +/* SUMMARY +/* Postfix superintendent +/* SYNOPSIS +/* .fi +/* \fBpostsuper\fR [\fB-psSv\fR] +/* [\fB-c \fIconfig_dir\fR] [\fB-d \fIqueue_id\fR] +/* [\fB-e \fIqueue_id\fR] [\fB-f \fIqueue_id\fR] +/* [\fB-h \fIqueue_id\fR] [\fB-H \fIqueue_id\fR] +/* [\fB-r \fIqueue_id\fR] [\fIdirectory ...\fR] +/* DESCRIPTION +/* The \fBpostsuper\fR(1) command does maintenance jobs on the Postfix +/* queue. Use of the command is restricted to the superuser. +/* See the \fBpostqueue\fR(1) command for unprivileged queue operations +/* such as listing or flushing the mail queue. +/* +/* By default, \fBpostsuper\fR(1) performs the operations +/* requested with the +/* \fB-s\fR and \fB-p\fR command-line options on all Postfix queue +/* directories - this includes the \fBincoming\fR, \fBactive\fR, +/* \fBdeferred\fR, and \fBhold\fR directories with message +/* files and the \fBbounce\fR, +/* \fBdefer\fR, \fBtrace\fR and \fBflush\fR directories with log files. +/* +/* Options: +/* .IP "\fB-c \fIconfig_dir\fR" +/* The \fBmain.cf\fR configuration file is in the named directory +/* instead of the default configuration directory. See also the +/* MAIL_CONFIG environment setting below. +/* .IP "\fB-d \fIqueue_id\fR" +/* Delete one message with the named queue ID from the named +/* mail queue(s) (default: \fBhold\fR, \fBincoming\fR, \fBactive\fR and +/* \fBdeferred\fR). +/* +/* To delete multiple files, specify the \fB-d\fR option multiple +/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs +/* from standard input. For example, to delete all mail +/* with exactly one recipient \fBuser@example.com\fR: +/* .sp +/* .nf +/* postqueue -j | jq -r ' +/* # See JSON OBJECT FORMAT section in the postqueue(1) manpage +/* select(.recipients[0].address == "user@example.com") +/* | select(.recipients[1].address == null) +/* | .queue_id +/* ' | postsuper -d - +/* .fi +/* .sp +/* (note the "jq -r" option), or the historical form: +/* .sp +/* .nf +/* mailq | tail -n +2 | grep -v '^ *(' | awk 'BEGIN { RS = "" } +/* # $7=sender, $8=recipient1, $9=recipient2 +/* { if ($8 == "user@example.com" && $9 == "") +/* print $1 } +/* ' | tr -d '*!' | postsuper -d - +/* .fi +/* .sp +/* Specify "\fB-d ALL\fR" to remove all messages; for example, specify +/* "\fB-d ALL deferred\fR" to delete all mail in the \fBdeferred\fR queue. +/* As a safety measure, the word \fBALL\fR must be specified in upper +/* case. +/* .sp +/* Warning: Postfix queue IDs are reused (always with Postfix +/* <= 2.8; and with Postfix >= 2.9 when enable_long_queue_ids=no). +/* There is a very small possibility that postsuper deletes the +/* wrong message file when it is executed while the Postfix mail +/* system is delivering mail. +/* .sp +/* The scenario is as follows: +/* .RS +/* .IP 1) +/* The Postfix queue manager deletes the message that \fBpostsuper\fR(1) +/* is asked to delete, because Postfix is finished with the +/* message (it is delivered, or it is returned to the sender). +/* .IP 2) +/* New mail arrives, and the new message is given the same queue ID +/* as the message that \fBpostsuper\fR(1) is supposed to delete. +/* The probability for reusing a deleted queue ID is about 1 in 2**15 +/* (the number of different microsecond values that the system clock +/* can distinguish within a second). +/* .IP 3) +/* \fBpostsuper\fR(1) deletes the new message, instead of the old +/* message that it should have deleted. +/* .RE +/* .IP "\fB-e \fIqueue_id\fR" +/* .IP "\fB-f \fIqueue_id\fR" +/* Request forced expiration for one message with the named +/* queue ID in the named mail queue(s) (default: \fBhold\fR, +/* \fBincoming\fR, \fBactive\fR and \fBdeferred\fR). +/* .RS +/* .IP \(bu +/* The message will be returned to the sender when the queue +/* manager attempts to deliver that message (note that Postfix +/* will never deliver messages in the \fBhold\fR queue). +/* .IP \(bu +/* The \fB-e\fR and \fB-f\fR options both request forced +/* expiration. The difference is that \fB-f\fR will also release +/* a message if it is in the \fBhold\fR queue. With \fB-e\fR, such +/* a message would not be returned to the sender until it is +/* released with \fB-f\fR or \fB-H\fR. +/* .IP \(bu +/* When a deferred message is force-expired, the return message +/* will state the reason for the delay. Otherwise, the reason +/* will be "message is administratively expired". +/* .RE +/* .IP +/* To expire multiple files, specify the \fB-e\fR or \fB-f\fR +/* option multiple times, or specify a \fIqueue_id\fR of \fB-\fR +/* to read queue IDs from standard input (see the \fB-d\fR option +/* above for an example, but be sure to replace \fB-d\fR in +/* the example). +/* .sp +/* Specify "\fB-e ALL\fR" or "\fB-f ALL\fR" to expire all +/* messages; for example, specify "\fB-e ALL deferred\fR" to +/* expire all mail in the \fBdeferred\fR queue. As a safety +/* measure, the word \fBALL\fR must be specified in upper case. +/* .sp +/* These features are available in Postfix 3.5 and later. +/* .IP "\fB-h \fIqueue_id\fR" +/* Put mail "on hold" so that no attempt is made to deliver it. +/* Move one message with the named queue ID from the named +/* mail queue(s) (default: \fBincoming\fR, \fBactive\fR and +/* \fBdeferred\fR) to the \fBhold\fR queue. +/* +/* To hold multiple files, specify the \fB-h\fR option multiple +/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs +/* from standard input. +/* .sp +/* Specify "\fB-h ALL\fR" to hold all messages; for example, specify +/* "\fB-h ALL deferred\fR" to hold all mail in the \fBdeferred\fR queue. +/* As a safety measure, the word \fBALL\fR must be specified in upper +/* case. +/* .sp +/* Note: while mail is "on hold" it will not expire when its +/* time in the queue exceeds the \fBmaximal_queue_lifetime\fR +/* or \fBbounce_queue_lifetime\fR setting. It becomes subject to +/* expiration after it is released from "hold". +/* .sp +/* This feature is available in Postfix 2.0 and later. +/* .IP "\fB-H \fIqueue_id\fR" +/* Release mail that was put "on hold". +/* Move one message with the named queue ID from the named +/* mail queue(s) (default: \fBhold\fR) to the \fBdeferred\fR queue. +/* +/* To release multiple files, specify the \fB-H\fR option multiple +/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs +/* from standard input. +/* .sp +/* Note: specify "\fBpostsuper -r\fR" to release mail that was kept on +/* hold for a significant fraction of \fB$maximal_queue_lifetime\fR +/* or \fB$bounce_queue_lifetime\fR, or longer. +/* .sp +/* Specify "\fB-H ALL\fR" to release all mail that is "on hold". +/* As a safety measure, the word \fBALL\fR must be specified in upper +/* case. +/* .sp +/* This feature is available in Postfix 2.0 and later. +/* .IP \fB-p\fR +/* Purge old temporary files that are left over after system or +/* software crashes. +/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done +/* before other operations. +/* .IP "\fB-r \fIqueue_id\fR" +/* Requeue the message with the named queue ID from the named +/* mail queue(s) (default: \fBhold\fR, \fBincoming\fR, \fBactive\fR and +/* \fBdeferred\fR). +/* +/* To requeue multiple files, specify the \fB-r\fR option multiple +/* times, or specify a \fIqueue_id\fR of \fB-\fR to read queue IDs +/* from standard input. +/* .sp +/* Specify "\fB-r ALL\fR" to requeue all messages. As a safety +/* measure, the word \fBALL\fR must be specified in upper case. +/* .sp +/* A requeued message is moved to the \fBmaildrop\fR queue, +/* from where it is copied by the \fBpickup\fR(8) and +/* \fBcleanup\fR(8) daemons to a new queue file. In many +/* respects its handling differs from that of a new local +/* submission. +/* .RS +/* .IP \(bu +/* The message is not subjected to the smtpd_milters or +/* non_smtpd_milters settings. When mail has passed through +/* an external content filter, this would produce incorrect +/* results with Milter applications that depend on original +/* SMTP connection state information. +/* .IP \(bu +/* The message is subjected again to mail address rewriting +/* and substitution. This is useful when rewriting rules or +/* virtual mappings have changed. +/* .sp +/* The address rewriting context (local or remote) is the same +/* as when the message was received. +/* .IP \(bu +/* The message is subjected to the same content_filter settings +/* (if any) as used for new local mail submissions. This is +/* useful when content_filter settings have changed. +/* .RE +/* .IP +/* Warning: Postfix queue IDs are reused (always with Postfix +/* <= 2.8; and with Postfix >= 2.9 when enable_long_queue_ids=no). +/* There is a very small possibility that \fBpostsuper\fR(1) requeues +/* the wrong message file when it is executed while the Postfix mail +/* system is running, but no harm should be done. +/* .sp +/* This feature is available in Postfix 1.1 and later. +/* .IP \fB-s\fR +/* Structure check and structure repair. This should be done once +/* before Postfix startup. +/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done +/* before other operations. +/* .RS +/* .IP \(bu +/* Rename files whose name does not match the message file inode +/* number. This operation is necessary after restoring a mail +/* queue from a different machine or from backup, when queue +/* files were created with Postfix <= 2.8 or with +/* "enable_long_queue_ids = no". +/* .IP \(bu +/* Move queue files that are in the wrong place in the file system +/* hierarchy and remove subdirectories that are no longer needed. +/* File position rearrangements are necessary after a change in the +/* \fBhash_queue_names\fR and/or \fBhash_queue_depth\fR +/* configuration parameters. +/* .IP \(bu +/* Rename queue files created with "enable_long_queue_ids = +/* yes" to short names, for migration to Postfix <= 2.8. The +/* procedure is as follows: +/* .sp +/* .nf +/* .na +/* # postfix stop +/* # postconf enable_long_queue_ids=no +/* # postsuper +/* .ad +/* .fi +/* .sp +/* Run \fBpostsuper\fR(1) repeatedly until it stops reporting +/* file name changes. +/* .RE +/* .IP \fB-S\fR +/* A redundant version of \fB-s\fR that requires that long +/* file names also match the message file inode number. This +/* option exists for testing purposes, and is available with +/* Postfix 2.9 and later. +/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done +/* before other operations. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream and to +/* \fBsyslogd\fR(8) or \fBpostlogd\fR(8). +/* +/* \fBpostsuper\fR(1) reports the number of messages deleted +/* with \fB-d\fR, the number of messages expired with \fB-e\fR, +/* the number of messages expired or released with \fB-f\fR, +/* the number of messages held or released with \fB-h\fR or +/* \fB-H\fR, the number of messages requeued with \fB-r\fR, +/* and the number of messages whose queue file name was fixed +/* with \fB-s\fR. The report is written to the standard error +/* stream and to \fBsyslogd\fR(8) or \fBpostlogd\fR(8). +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP MAIL_CONFIG +/* Directory with the \fBmain.cf\fR file. +/* BUGS +/* Mail that is not sanitized by Postfix (i.e. mail in the \fBmaildrop\fR +/* queue) cannot be placed "on hold". +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBhash_queue_depth (1)\fR" +/* The number of subdirectory levels for queue directories listed with +/* the hash_queue_names parameter. +/* .IP "\fBhash_queue_names (deferred, defer)\fR" +/* The names of queue directories that are split across multiple +/* subdirectory levels. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment parameters that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBenable_long_queue_ids (no)\fR" +/* Enable long, non-repeating, queue IDs (queue file names). +/* SEE ALSO +/* sendmail(1), Sendmail-compatible user interface +/* postqueue(1), unprivileged queue operations +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include /* remove() */ +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#define MAIL_QUEUE_INTERNAL +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#define MAX_TEMP_AGE (7 * 60 * 60 * 24) /* temp file maximal age */ +#define STR vstring_str /* silly little macro */ + +#define ACTION_STRUCT (1<<0) /* fix file organization */ +#define ACTION_PURGE (1<<1) /* purge old temp files */ +#define ACTION_DELETE_ONE (1<<2) /* delete named queue file(s) */ +#define ACTION_DELETE_ALL (1<<3) /* delete all queue file(s) */ +#define ACTION_REQUEUE_ONE (1<<4) /* requeue named queue file(s) */ +#define ACTION_REQUEUE_ALL (1<<5) /* requeue all queue file(s) */ +#define ACTION_HOLD_ONE (1<<6) /* put named queue file(s) on hold */ +#define ACTION_HOLD_ALL (1<<7) /* put all messages on hold */ +#define ACTION_RELEASE_ONE (1<<8) /* release named queue file(s) */ +#define ACTION_RELEASE_ALL (1<<9) /* release all "on hold" mail */ +#define ACTION_STRUCT_RED (1<<10) /* fix long queue ID inode fields */ +#define ACTION_EXPIRE_ONE (1<<11) /* expire named queue file(s) */ +#define ACTION_EXPIRE_ALL (1<<12) /* expire all queue file(s) */ +#define ACTION_EXP_REL_ONE (1<<13) /* expire+release named queue file(s) */ +#define ACTION_EXP_REL_ALL (1<<14) /* expire+release all queue file(s) */ + +#define ACTION_DEFAULT (ACTION_STRUCT | ACTION_PURGE) + + /* + * Actions that operate on individually named queue files. These must never + * be done after fixing queue file names to match their inode number because + * the target file may have been replaced. Actions that move files are safe + * only when queue file names match their inode number, otherwise mail can + * be lost due to filename collisions. + */ +#define ACTIONS_BY_QUEUE_ID (ACTION_DELETE_ONE | ACTION_REQUEUE_ONE \ + | ACTION_HOLD_ONE | ACTION_RELEASE_ONE \ + | ACTION_EXPIRE_ONE | ACTION_EXP_REL_ONE) + + /* + * Mass actions. Actions that move files are safe only when queue file names + * match their inode number, otherwise mail can be lost due to filename + * collisions. + */ +#define ACTIONS_BY_WILDCARD (ACTION_DELETE_ALL | ACTION_REQUEUE_ALL \ + | ACTION_HOLD_ALL | ACTION_RELEASE_ALL \ + | ACTION_EXPIRE_ALL | ACTION_EXP_REL_ALL) + +#define ACTIONS_FOR_REPAIR (ACTION_PURGE | ACTION_STRUCT \ + | ACTION_STRUCT_RED) + + /* + * Information about queue directories and what we expect to do there. If a + * file has unexpected owner permissions and is older than some threshold, + * the file is discarded. We don't step into maildrop subdirectories - if + * maildrop is writable, we might end up in the wrong place, deleting the + * wrong information. + */ +struct queue_info { + char *name; /* directory name */ + int perms; /* expected permissions */ + int flags; /* see below */ +}; + +#define RECURSE (1<<0) /* step into subdirectories */ +#define DONT_RECURSE 0 /* don't step into directories */ + +static struct queue_info queue_info[] = { + MAIL_QUEUE_MAILDROP, MAIL_QUEUE_STAT_READY, DONT_RECURSE, + MAIL_QUEUE_INCOMING, MAIL_QUEUE_STAT_READY, RECURSE, + MAIL_QUEUE_ACTIVE, MAIL_QUEUE_STAT_READY, RECURSE, + MAIL_QUEUE_DEFERRED, MAIL_QUEUE_STAT_READY, RECURSE, + MAIL_QUEUE_HOLD, MAIL_QUEUE_STAT_READY, RECURSE, + MAIL_QUEUE_TRACE, 0600, RECURSE, + MAIL_QUEUE_DEFER, 0600, RECURSE, + MAIL_QUEUE_BOUNCE, 0600, RECURSE, + MAIL_QUEUE_FLUSH, 0600, RECURSE, + 0, +}; + + /* + * Directories with per-message meta files. + */ +const char *log_queue_names[] = { + MAIL_QUEUE_BOUNCE, + MAIL_QUEUE_DEFER, + MAIL_QUEUE_TRACE, + 0, +}; + + /* + * Cruft that we append to a file name when a queue ID is named after the + * message file inode number. This cruft must not pass mail_queue_id_ok() so + * that the queue manager will ignore it, should people be so unwise as to + * run this operation on a live mail system. + */ +#define SUFFIX "#FIX" +#define SUFFIX_LEN 4 + + /* + * Grr. These counters are global, because C only has clumsy ways to return + * multiple results from a function. + */ +static int message_requeued = 0; /* requeued messages */ +static int message_held = 0; /* messages put on hold */ +static int message_released = 0; /* messages released from hold */ +static int message_deleted = 0; /* deleted messages */ +static int message_expired = 0; /* expired messages */ +static int inode_fixed = 0; /* queue id matched to inode number */ +static int inode_mismatch = 0; /* queue id inode mismatch */ +static int position_mismatch = 0; /* file position mismatch */ + + /* + * Silly little macros. These translate arcane expressions into something + * more at a conceptual level. + */ +#define MESSAGE_QUEUE(qp) ((qp)->perms == MAIL_QUEUE_STAT_READY) +#define READY_MESSAGE(st) (((st).st_mode & S_IRWXU) == MAIL_QUEUE_STAT_READY) + +/* find_queue_info - look up expected permissions field by queue name */ + +static struct queue_info *find_queue_info(const char *queue_name) +{ + struct queue_info *qp; + + for (qp = queue_info; qp->name; qp++) + if (strcmp(queue_name, qp->name) == 0) + return (qp); + msg_fatal("invalid directory name: %s", queue_name); +} + +/* postremove - remove file with extreme prejudice */ + +static int postremove(const char *path) +{ + int ret; + + if ((ret = remove(path)) < 0) { + if (errno != ENOENT) + msg_fatal("remove file %s: %m", path); + } else { + if (msg_verbose) + msg_info("removed file %s", path); + } + return (ret); +} + +/* postexpire - expire file, setting the group execute permission */ + +static int postexpire(const char *path) +{ + static VSTRING *why = 0; + VSTREAM *fp; + struct stat st; + + /* + * Initialize. + */ + if (why == 0) + why = vstring_alloc(100); + + /* + * We don't actually verify the file content, therefore safe_open() the + * queue file so that we won't add group execute permission to some file + * outside of the mail queue. + */ + if ((fp = safe_open(path, O_RDWR, 0, &st, -1, -1, why)) == 0) { + if (errno != ENOENT) + msg_warn("expire file %s: %s", path, vstring_str(why)); + return (-1); + } +#define POSTEXPIRE_RETURN(x) do { \ + (void) vstream_fclose(fp); \ + return (x); \ + } while (0) + + if (!READY_MESSAGE(st)) + POSTEXPIRE_RETURN(-1); /* must not expire */ + if ((st.st_mode & MAIL_QUEUE_STAT_EXPIRE) != 0) + POSTEXPIRE_RETURN(-1); /* already expired */ + if (fchmod(vstream_fileno(fp), + (st.st_mode | MAIL_QUEUE_STAT_EXPIRE) & ~S_IFMT) < 0) { + msg_warn("expire file %s: cannot set permission: %m", path); + POSTEXPIRE_RETURN(-1); + } + POSTEXPIRE_RETURN(0); +} + +/* postrename - rename file with extreme prejudice */ + +static int postrename(const char *old, const char *new) +{ + int ret; + + if ((ret = sane_rename(old, new)) < 0) { + if (errno != ENOENT + || mail_queue_mkdirs(new) < 0 + || (ret = sane_rename(old, new)) < 0) + if (errno != ENOENT) + msg_fatal("rename file %s as %s: %m", old, new); + } else { + if (msg_verbose) + msg_info("renamed file %s as %s", old, new); + } + return (ret); +} + +/* postrmdir - remove directory with extreme prejudice */ + +static int postrmdir(const char *path) +{ + int ret; + + if ((ret = rmdir(path)) < 0) { + if (errno != ENOENT) + msg_fatal("remove directory %s: %m", path); + } else { + if (msg_verbose) + msg_info("remove directory %s", path); + } + return (ret); +} + +/* delete_one - delete one message instance and all its associated files */ + +static void delete_one(const char **queue_names, const char *queue_id) +{ + struct stat st; + const char **msg_qpp; + const char **log_qpp; + const char *msg_path; + VSTRING *log_path_buf; + int found; + int tries; + + /* + * Sanity check. No early returns beyond this point. + */ + if (!mail_queue_id_ok(queue_id)) { + msg_warn("invalid mail queue id: %s", queue_id); + return; + } + log_path_buf = vstring_alloc(100); + + /* + * Skip meta file directories. Delete trace/defer/bounce logfiles before + * deleting the corresponding message file, and only if the message file + * exists. This minimizes but does not eliminate a race condition with + * queue ID reuse which results in deleting the wrong files. + */ + for (found = 0, tries = 0; found == 0 && tries < 2; tries++) { + for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) { + if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp))) + continue; + if (mail_open_ok(*msg_qpp, queue_id, &st, &msg_path) != MAIL_OPEN_YES) + continue; + for (log_qpp = log_queue_names; *log_qpp != 0; log_qpp++) + postremove(mail_queue_path(log_path_buf, *log_qpp, queue_id)); + if (postremove(msg_path) == 0) { + found = 1; + msg_info("%s: removed", queue_id); + break; + } /* else: maybe lost a race */ + } + } + vstring_free(log_path_buf); + message_deleted += found; +} + +/* requeue_one - requeue one message instance and delete its logfiles */ + +static void requeue_one(const char **queue_names, const char *queue_id) +{ + struct stat st; + const char **msg_qpp; + const char *old_path; + VSTRING *new_path_buf; + int found; + int tries; + struct utimbuf tbuf; + + /* + * Sanity check. No early returns beyond this point. + */ + if (!mail_queue_id_ok(queue_id)) { + msg_warn("invalid mail queue id: %s", queue_id); + return; + } + new_path_buf = vstring_alloc(100); + + /* + * Skip meta file directories. Like the mass requeue operation, we not + * delete defer or bounce logfiles, to avoid losing a race where the + * queue manager decides to bounce mail after all recipients have been + * tried. + */ + for (found = 0, tries = 0; found == 0 && tries < 2; tries++) { + for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) { + if (strcmp(*msg_qpp, MAIL_QUEUE_MAILDROP) == 0) + continue; + if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp))) + continue; + if (mail_open_ok(*msg_qpp, queue_id, &st, &old_path) != MAIL_OPEN_YES) + continue; + (void) mail_queue_path(new_path_buf, MAIL_QUEUE_MAILDROP, queue_id); + if (postrename(old_path, STR(new_path_buf)) == 0) { + tbuf.actime = tbuf.modtime = time((time_t *) 0); + if (utime(STR(new_path_buf), &tbuf) < 0) + msg_warn("%s: reset time stamps: %m", STR(new_path_buf)); + msg_info("%s: requeued", queue_id); + found = 1; + break; + } /* else: maybe lost a race */ + } + } + vstring_free(new_path_buf); + message_requeued += found; +} + +/* hold_one - put "on hold" one message instance */ + +static void hold_one(const char **queue_names, const char *queue_id) +{ + struct stat st; + const char **msg_qpp; + const char *old_path; + VSTRING *new_path_buf; + int found; + int tries; + + /* + * Sanity check. No early returns beyond this point. + */ + if (!mail_queue_id_ok(queue_id)) { + msg_warn("invalid mail queue id: %s", queue_id); + return; + } + new_path_buf = vstring_alloc(100); + + /* + * Skip meta file directories. Like the mass requeue operation, we not + * delete defer or bounce logfiles, to avoid losing a race where the + * queue manager decides to bounce mail after all recipients have been + * tried. + * + * XXX We must not put maildrop mail on hold because that would mix already + * sanitized mail with mail that still needs to be sanitized. + */ + for (found = 0, tries = 0; found == 0 && tries < 2; tries++) { + for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) { + if (strcmp(*msg_qpp, MAIL_QUEUE_MAILDROP) == 0) + continue; + if (strcmp(*msg_qpp, MAIL_QUEUE_HOLD) == 0) + continue; + if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp))) + continue; + if (mail_open_ok(*msg_qpp, queue_id, &st, &old_path) != MAIL_OPEN_YES) + continue; + (void) mail_queue_path(new_path_buf, MAIL_QUEUE_HOLD, queue_id); + if (postrename(old_path, STR(new_path_buf)) == 0) { + msg_info("%s: placed on hold", queue_id); + found = 1; + break; + } /* else: maybe lost a race */ + } + } + vstring_free(new_path_buf); + message_held += found; +} + +/* release_one - release one message instance that was placed "on hold" */ + +static void release_one(const char **queue_names, const char *queue_id) +{ + struct stat st; + const char **msg_qpp; + const char *old_path; + VSTRING *new_path_buf; + int found; + + /* + * Sanity check. No early returns beyond this point. + */ + if (!mail_queue_id_ok(queue_id)) { + msg_warn("invalid mail queue id: %s", queue_id); + return; + } + new_path_buf = vstring_alloc(100); + + /* + * Skip inapplicable directories. This can happen when -H is combined + * with other operations. + */ + found = 0; + for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) { + if (strcmp(*msg_qpp, MAIL_QUEUE_HOLD) != 0) + continue; + if (mail_open_ok(*msg_qpp, queue_id, &st, &old_path) != MAIL_OPEN_YES) + continue; + (void) mail_queue_path(new_path_buf, MAIL_QUEUE_DEFERRED, queue_id); + if (postrename(old_path, STR(new_path_buf)) == 0) { + msg_info("%s: released from hold", queue_id); + found = 1; + break; + } + } + vstring_free(new_path_buf); + message_released += found; +} + +/* expire_one - expire one message instance */ + +static void expire_one(const char **queue_names, const char *queue_id) +{ + struct stat st; + const char **msg_qpp; + const char *msg_path; + int found; + int tries; + + /* + * Sanity check. No early returns beyond this point. + */ + if (!mail_queue_id_ok(queue_id)) { + msg_warn("invalid mail queue id: %s", queue_id); + return; + } + + /* + * Skip meta file directories. + */ + for (found = 0, tries = 0; found == 0 && tries < 2; tries++) { + for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) { + if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp))) + continue; + if (strcmp(*msg_qpp, MAIL_QUEUE_MAILDROP) == 0) + continue; + if (mail_open_ok(*msg_qpp, queue_id, &st, &msg_path) != MAIL_OPEN_YES) + continue; + if (postexpire(msg_path) == 0) { + found = 1; + msg_info("%s: expired", queue_id); + break; + } /* else: maybe lost a race */ + } + } + message_expired += found; +} + +/* exp_rel_one - expire or release one message instance */ + +static void exp_rel_one(const char **queue_names, const char *queue_id) +{ + expire_one(queue_names, queue_id); + release_one(queue_names, queue_id); +} + +/* operate_stream - operate on queue IDs given on stream */ + +static void operate_stream(VSTREAM *fp, + void (*operator) (const char **, const char *), + const char **queues) +{ + VSTRING *buf = vstring_alloc(20); + + while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) + operator(queues, STR(buf)); + + vstring_free(buf); +} + +/* fix_queue_id - make message queue ID match inode number */ + +static int fix_queue_id(const char *actual_path, const char *actual_queue, + const char *actual_id, struct stat * st) +{ + VSTRING *old_path = vstring_alloc(10); + VSTRING *new_path = vstring_alloc(10); + VSTRING *new_id = vstring_alloc(10); + const char **log_qpp; + char *cp; + int ret; + + /* + * Create the new queue ID from the existing time digits and from the new + * inode number. Since we are renaming multiple files, the new name must + * be deterministic so that we can recover even when the renaming + * operation is interrupted in the middle. + */ + if (MQID_FIND_LG_INUM_SEPARATOR(cp, actual_id) == 0) { + /* Short->short queue ID. Replace the inode portion. */ + vstring_sprintf(new_id, "%.*s%s", + MQID_SH_USEC_PAD, actual_id, + get_file_id_st(st, 0)); + } else if (var_long_queue_ids) { + /* Long->long queue ID. Replace the inode portion. */ + vstring_sprintf(new_id, "%.*s%c%s", + (int) (cp - actual_id), actual_id, MQID_LG_INUM_SEP, + get_file_id_st(st, 1)); + } else { + /* Long->short queue ID. Reformat time and replace inode portion. */ + MQID_LG_GET_HEX_USEC(new_id, cp); + vstring_strcat(new_id, get_file_id_st(st, 0)); + } + + /* + * Rename logfiles before renaming the message file, so that we can + * recover when a previous attempt was interrupted. + */ + for (log_qpp = log_queue_names; *log_qpp; log_qpp++) { + mail_queue_path(old_path, *log_qpp, actual_id); + mail_queue_path(new_path, *log_qpp, STR(new_id)); + vstring_strcat(new_path, SUFFIX); + postrename(STR(old_path), STR(new_path)); + } + + /* + * Rename the message file last, so that we know that we are done with + * this message and with all its logfiles. + */ + mail_queue_path(new_path, actual_queue, STR(new_id)); + vstring_strcat(new_path, SUFFIX); + ret = postrename(actual_path, STR(new_path)); + + /* + * Clean up. + */ + vstring_free(old_path); + vstring_free(new_path); + vstring_free(new_id); + + return (ret); +} + +/* super - check queue structure, clean up, do wild-card operations */ + +static void super(const char **queues, int action) +{ + ARGV *hash_queue_names = argv_split(var_hash_queue_names, CHARS_COMMA_SP); + VSTRING *actual_path = vstring_alloc(10); + VSTRING *wanted_path = vstring_alloc(10); + struct stat st; + const char *queue_name; + SCAN_DIR *info; + char *path; + int actual_depth; + int wanted_depth; + char **cpp; + struct queue_info *qp; + unsigned long inum; + int long_name; + int error; + + /* + * This routine was originally written to do multiple mass operations in + * one pass. However this hard-coded the order of operations which became + * difficult to explain. As of Postfix 3.5 this routine is called for one + * mass operation at a time, in the user-specified order. The exception + * is that repair operations (purging stale files, queue hashing, and + * file-inode match) are combined and done before other mass operations. + */ + + /* + * Make sure every file is in the right place, clean out stale files, and + * remove non-file/non-directory objects. + */ + while ((queue_name = *queues++) != 0) { + + if (msg_verbose) + msg_info("queue: %s", queue_name); + + /* + * Look up queue-specific properties: desired hashing depth, what + * file permissions to look for, and whether or not it is desirable + * to step into subdirectories. + */ + qp = find_queue_info(queue_name); + for (cpp = hash_queue_names->argv; /* void */ ; cpp++) { + if (*cpp == 0) { + wanted_depth = 0; + break; + } + if (strcmp(*cpp, queue_name) == 0) { + wanted_depth = var_hash_queue_depth; + break; + } + } + + /* + * Sanity check. Some queues just cannot be recursive. + */ + if (wanted_depth > 0 && (qp->flags & RECURSE) == 0) + msg_fatal("%s queue must not be hashed", queue_name); + + /* + * Other per-directory initialization. + */ + info = scan_dir_open(queue_name); + actual_depth = 0; + + for (;;) { + + /* + * If we reach the end of a subdirectory, return to its parent. + * Delete subdirectories that are no longer needed. + */ + if ((path = scan_dir_next(info)) == 0) { + if (actual_depth == 0) + break; + if (actual_depth > wanted_depth) + postrmdir(scan_dir_path(info)); + scan_dir_pop(info); + actual_depth--; + continue; + } + + /* + * If we stumble upon a subdirectory, enter it, if it is + * considered safe to do so. Otherwise, try to remove the + * subdirectory at a later stage. + */ + if (strlen(path) == 1 && (qp->flags & RECURSE) != 0) { + actual_depth++; + scan_dir_push(info, path); + continue; + } + + /* + * From here on we need to keep track of operations that + * invalidate or revalidate the actual_path and path variables, + * otherwise we can hit the wrong files. + */ + vstring_sprintf(actual_path, "%s/%s", scan_dir_path(info), path); + if (lstat(STR(actual_path), &st) < 0) + continue; + + /* + * Remove alien directories. If maildrop is compromised, then we + * cannot abort just because we cannot remove someone's + * directory. + */ + if (S_ISDIR(st.st_mode)) { + if (rmdir(STR(actual_path)) < 0) { + if (errno != ENOENT) + msg_warn("remove subdirectory %s: %m", STR(actual_path)); + } else { + if (msg_verbose) + msg_info("remove subdirectory %s", STR(actual_path)); + } + /* No further work on this object is possible. */ + continue; + } + + /* + * Mass deletion. We count the deletion of mail that this system + * has taken responsibility for. XXX This option does not use + * mail_queue_remove(), so that it can avoid having to first move + * queue files to the "right" subdirectory level. + */ + if (action & ACTION_DELETE_ALL) { + if (postremove(STR(actual_path)) == 0) + if (MESSAGE_QUEUE(qp) && READY_MESSAGE(st)) + message_deleted++; + /* No further work on this object is possible. */ + continue; + } + + /* + * Remove non-file objects and old temporary files. Be careful + * not to delete bounce or defer logs just because they are more + * than a couple days old. + */ + if (!S_ISREG(st.st_mode) + || ((action & ACTION_PURGE) != 0 + && MESSAGE_QUEUE(qp) + && !READY_MESSAGE(st) + && time((time_t *) 0) > st.st_mtime + MAX_TEMP_AGE)) { + (void) postremove(STR(actual_path)); + /* No further work on this object is possible. */ + continue; + } + + /* + * Fix queueid#FIX names that were left from a previous pass over + * the queue where message queue file names were matched to their + * inode number. We strip the suffix and move the file into the + * proper subdirectory level. Make sure that the name minus + * suffix is well formed and that the name matches the file inode + * number. + */ + if ((action & ACTION_STRUCT) + && strcmp(path + (strlen(path) - SUFFIX_LEN), SUFFIX) == 0) { + path[strlen(path) - SUFFIX_LEN] = 0; /* XXX */ + if (!mail_queue_id_ok(path)) { + msg_warn("bogus file name: %s", STR(actual_path)); + continue; + } + if (MESSAGE_QUEUE(qp)) { + MQID_GET_INUM(path, inum, long_name, error); + if (error) { + msg_warn("bogus file name: %s", STR(actual_path)); + continue; + } + if (inum != (unsigned long) st.st_ino) { + msg_warn("name/inode mismatch: %s", STR(actual_path)); + continue; + } + } + (void) mail_queue_path(wanted_path, queue_name, path); + if (postrename(STR(actual_path), STR(wanted_path)) < 0) { + /* No further work on this object is possible. */ + continue; + } else { + if (MESSAGE_QUEUE(qp)) + inode_fixed++; + vstring_strcpy(actual_path, STR(wanted_path)); + /* At this point, path and actual_path are revalidated. */ + } + } + + /* + * Skip over files with illegal names. The library routines + * refuse to operate on them. + */ + if (!mail_queue_id_ok(path)) { + msg_warn("bogus file name: %s", STR(actual_path)); + continue; + } + + /* + * See if the file name matches the file inode number. Skip meta + * file directories. This option requires that meta files be put + * into their proper place before queue files, so that we can + * rename queue files and meta files at the same time. Mis-named + * files are renamed to newqueueid#FIX on the first pass, and + * upon the second pass the #FIX is stripped off the name. Of + * course we have to be prepared that the program is interrupted + * before it completes, so any left-over newqueueid#FIX files + * have to be handled properly. XXX This option cannot use + * mail_queue_rename(), because the queue file name violates + * normal queue file syntax. + * + * By design there is no need to "fix" non-repeating names. What + * follows is applicable only when reverting from long names to + * short names, or when migrating short names from one queue to + * another. + */ + if ((action & ACTION_STRUCT) != 0 && MESSAGE_QUEUE(qp)) { + MQID_GET_INUM(path, inum, long_name, error); + if (error) { + msg_warn("bogus file name: %s", STR(actual_path)); + continue; + } + if ((long_name != 0 && var_long_queue_ids == 0) + || (inum != (unsigned long) st.st_ino + && (long_name == 0 || (action & ACTION_STRUCT_RED)))) { + inode_mismatch++; /* before we fix */ + action &= ~ACTIONS_BY_WILDCARD; + fix_queue_id(STR(actual_path), queue_name, path, &st); + /* At this point, path and actual_path are invalidated. */ + continue; + } + } + + /* + * Mass requeuing. The pickup daemon will copy requeued mail to a + * new queue file, so that address rewriting is applied again. + * XXX This option does not use mail_queue_rename(), so that it + * can avoid having to first move queue files to the "right" + * subdirectory level. Like the requeue_one() routine, this code + * does not touch logfiles. + */ + if ((action & ACTION_REQUEUE_ALL) + && MESSAGE_QUEUE(qp) + && strcmp(queue_name, MAIL_QUEUE_MAILDROP) != 0) { + (void) mail_queue_path(wanted_path, MAIL_QUEUE_MAILDROP, path); + if (postrename(STR(actual_path), STR(wanted_path)) == 0) + message_requeued++; + /* At this point, path and actual_path are invalidated. */ + continue; + } + + /* + * Many of the following actions may move queue files. To avoid + * loss of email due to file name collisions. we should do such + * actions only when the queue file names are known to match + * their inode number. Even with non-repeating queue IDs a name + * collision may happen when different queues are merged. + */ + + /* + * Mass expiration. We count the expiration of mail that this + * system has taken responsibility for. + */ + if ((action & (ACTION_EXPIRE_ALL | ACTION_EXP_REL_ALL)) + && MESSAGE_QUEUE(qp) && READY_MESSAGE(st) + && strcmp(queue_name, MAIL_QUEUE_MAILDROP) != 0 + && postexpire(STR(actual_path)) == 0) + message_expired++; + + /* + * Mass renaming to the "on hold" queue. XXX This option does not + * use mail_queue_rename(), so that it can avoid having to first + * move queue files to the "right" subdirectory level. Like the + * hold_one() routine, this code does not touch logfiles, and + * must not touch files in the maildrop queue, because maildrop + * files contain data that has not yet been sanitized and + * therefore must not be mixed with already sanitized mail. + */ + if ((action & ACTION_HOLD_ALL) + && MESSAGE_QUEUE(qp) + && strcmp(queue_name, MAIL_QUEUE_MAILDROP) != 0 + && strcmp(queue_name, MAIL_QUEUE_HOLD) != 0) { + (void) mail_queue_path(wanted_path, MAIL_QUEUE_HOLD, path); + if (postrename(STR(actual_path), STR(wanted_path)) == 0) + message_held++; + /* At this point, path and actual_path are invalidated. */ + continue; + } + + /* + * Mass release from the "on hold" queue. XXX This option does + * not use mail_queue_rename(), so that it can avoid having to + * first move queue files to the "right" subdirectory level. Like + * the release_one() routine, this code must not touch logfiles. + */ + if ((action & (ACTION_RELEASE_ALL | ACTION_EXP_REL_ALL)) + && strcmp(queue_name, MAIL_QUEUE_HOLD) == 0) { + (void) mail_queue_path(wanted_path, MAIL_QUEUE_DEFERRED, path); + if (postrename(STR(actual_path), STR(wanted_path)) == 0) + message_released++; + /* At this point, path and actual_path are invalidated. */ + continue; + } + + /* + * See if this file sits in the right place in the file system + * hierarchy. Its place may be wrong after a change to the + * hash_queue_{names,depth} parameter settings. This requires + * that the bounce/defer logfiles be at the right subdirectory + * level first, otherwise we would fail to properly rename + * bounce/defer logfiles. + */ + if (action & ACTION_STRUCT) { + (void) mail_queue_path(wanted_path, queue_name, path); + if (strcmp(STR(actual_path), STR(wanted_path)) != 0) { + position_mismatch++; /* before we fix */ + (void) postrename(STR(actual_path), STR(wanted_path)); + /* At this point, path and actual_path are invalidated. */ + continue; + } + } + } + scan_dir_close(info); + } + + /* + * Clean up. + */ + vstring_free(wanted_path); + vstring_free(actual_path); + argv_free(hash_queue_names); +} + +/* interrupted - signal handler */ + +static void interrupted(int sig) +{ + + /* + * This commands requires root privileges. We therefore do not worry + * about hostile signals, and report problems via msg_warn(). + * + * We use the in-kernel SIGINT handler address as an atomic variable to + * prevent nested interrupted() calls. For this reason, main() must + * configure interrupted() as SIGINT handler before other signal handlers + * are allowed to invoke interrupted(). See also similar code in + * postdrop. + */ + if (signal(SIGINT, SIG_IGN) != SIG_IGN) { + (void) signal(SIGQUIT, SIG_IGN); + (void) signal(SIGTERM, SIG_IGN); + (void) signal(SIGHUP, SIG_IGN); + if (inode_mismatch > 0 || inode_fixed > 0 || position_mismatch > 0) + msg_warn("OPERATION INCOMPLETE -- RERUN COMMAND TO FIX THE QUEUE FIRST"); + if (sig) + _exit(sig); + } +} + +/* fatal_warning - print warning if queue fix is incomplete */ + +static void fatal_warning(void) +{ + interrupted(0); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + int fd; + struct stat st; + char *slash; + int action = 0; + const char **queues; + int c; + ARGV *import_env; + int saved_optind; + + /* + * Defaults. The structural checks must fix the directory levels of "log + * file" directories (bounce, defer) before doing structural checks on + * the "message file" directories, so that we can find the logfiles in + * the right place when message files need to be renamed to match their + * inode number. + */ + static char *default_queues[] = { + MAIL_QUEUE_DEFER, /* before message directories */ + MAIL_QUEUE_BOUNCE, /* before message directories */ + MAIL_QUEUE_MAILDROP, + MAIL_QUEUE_INCOMING, + MAIL_QUEUE_ACTIVE, + MAIL_QUEUE_DEFERRED, + MAIL_QUEUE_HOLD, + MAIL_QUEUE_FLUSH, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Be consistent with file permissions. + */ + umask(022); + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Process this environment option as early as we can, to aid debugging. + */ + if (safe_getenv(CONF_ENV_VERB)) + msg_verbose = 1; + + /* + * Initialize logging. + */ + if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) + argv[0] = slash + 1; + msg_vstream_init(argv[0], VSTREAM_ERR); + maillog_client_init(mail_task(argv[0]), + MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Disallow unsafe practices, and refuse to run set-uid (or as the child + * of a set-uid process). Whenever a privileged wrapper program is + * needed, it must properly sanitize the real/effective/saved UID/GID, + * the secondary groups, the process environment, and so on. Otherwise, + * accidents can happen. If not with Postfix, then with other software. + */ + if (unsafe() != 0) + msg_fatal("this postfix command must not run as a set-uid process"); + if (getuid()) + msg_fatal("use of this command is reserved for the superuser"); + + /* + * Parse JCL. + * + * First, find out what kind of actions are requested, without executing + * them. Later, we execute actions in mostly user-specified order. + */ +#define GETOPT_LIST "c:d:e:f:h:H:pr:sSv" + + saved_optind = optind; + while ((c = GETOPT(argc, argv, GETOPT_LIST)) > 0) { + switch (c) { + default: + msg_fatal("usage: %s " + "[-c config_dir] " + "[-d queue_id (delete)] " + "[-e queue_id (expire)] " + "[-f queue_id (expire and/or un-hold)] " + "[-h queue_id (hold)] [-H queue_id (un-hold)] " + "[-p (purge temporary files)] [-r queue_id (requeue)] " + "[-s (structure fix)] [-S (redundant structure fix)]" + "[-v (verbose)] [queue...]", argv[0]); + case 'c': + if (*optarg != '/') + msg_fatal("-c requires absolute pathname"); + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("setenv: %m"); + break; + case 'd': + action |= (strcmp(optarg, "ALL") == 0 ? + ACTION_DELETE_ALL : ACTION_DELETE_ONE); + break; + case 'e': + action |= (strcmp(optarg, "ALL") == 0 ? + ACTION_EXPIRE_ALL : ACTION_EXPIRE_ONE); + break; + case 'f': + action |= (strcmp(optarg, "ALL") == 0 ? + ACTION_EXP_REL_ALL : ACTION_EXP_REL_ONE); + break; + case 'h': + action |= (strcmp(optarg, "ALL") == 0 ? + ACTION_HOLD_ALL : ACTION_HOLD_ONE); + break; + case 'H': + action |= (strcmp(optarg, "ALL") == 0 ? + ACTION_RELEASE_ALL : ACTION_RELEASE_ONE); + break; + case 'p': + action |= ACTION_PURGE; + break; + case 'r': + action |= (strcmp(optarg, "ALL") == 0 ? + ACTION_REQUEUE_ALL : ACTION_REQUEUE_ONE); + break; + case 'S': + action |= ACTION_STRUCT_RED; + /* FALLTHROUGH */ + case 's': + action |= ACTION_STRUCT; + break; + case 'v': + msg_verbose++; + break; + } + } + + /* + * Read the global configuration file and extract configuration + * information. The -c command option can override the default + * configuration directory location. + */ + mail_conf_read(); + /* Enforce consistent operation of different Postfix parts. */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + update_env(import_env->argv); + argv_free(import_env); + /* Re-evaluate mail_task() after reading main.cf. */ + maillog_client_init(mail_task(argv[0]), + MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK); + if (chdir(var_queue_dir)) + msg_fatal("chdir %s: %m", var_queue_dir); + + /* + * All file/directory updates must be done as the mail system owner. This + * is because Postfix daemons manipulate the queue with those same + * privileges, so directories must be created with the right ownership. + * + * Running as a non-root user is also required for security reasons. When + * the Postfix queue hierarchy is compromised, an attacker could trick us + * into entering other file hierarchies and afflicting damage. Running as + * a non-root user limits the damage to the already compromised mail + * owner. + */ + set_ugid(var_owner_uid, var_owner_gid); + + /* + * Be sure to log a warning if we do not finish structural repair. Maybe + * we should have an fsck-style "clean" flag so Postfix will not start + * with a broken queue. + * + * Set up signal handlers after permanently dropping super-user privileges, + * so that signal handlers will always run with the correct privileges. + * + * XXX Don't enable SIGHUP or SIGTERM if it was ignored by the parent. + * + * interrupted() uses the in-kernel SIGINT handler address as an atomic + * variable to prevent nested interrupted() calls. For this reason, the + * SIGINT handler must be configured before other signal handlers are + * allowed to invoke interrupted(). See also similar code in postdrop. + */ + signal(SIGINT, interrupted); + signal(SIGQUIT, interrupted); + if (signal(SIGTERM, SIG_IGN) == SIG_DFL) + signal(SIGTERM, interrupted); + if (signal(SIGHUP, SIG_IGN) == SIG_DFL) + signal(SIGHUP, interrupted); + msg_cleanup(fatal_warning); + + /* + * Execute the explicitly specified (or default) action, on the + * explicitly specified (or default) queues. + * + * XXX Work around gcc const brain damage. + * + * XXX The file name/inode number fix should always run over all message + * file directories, and should always be preceded by a subdirectory + * level check of the bounce and defer logfile directories. + */ + if (action == 0) + action = ACTION_DEFAULT; + if (argv[optind] != 0) + queues = (const char **) argv + optind; + else + queues = (const char **) default_queues; + + /* + * Basic queue maintenance, including mass name-to-inode fixing. This + * ensures that queue files are in the right place before any other + * operations are done. + */ + if (action & ACTIONS_FOR_REPAIR) + super(queues, action & ACTIONS_FOR_REPAIR); + + /* + * If any file names needed changing to match the message file inode + * number, those files were named newqeueid#FIX. We need a second pass to + * strip the suffix from the new queue ID, and to complete any requested + * operations that had to be skipped in the first pass. + */ + if (inode_mismatch > 0) + super(queues, action & ACTIONS_FOR_REPAIR); + + /* + * Don't do actions by queue file name if any queue files changed name + * because they did not match the queue file inode number. We could be + * acting on the wrong queue file and lose mail. + */ + if ((action & ACTIONS_BY_QUEUE_ID) + && (inode_mismatch > 0 || inode_fixed > 0)) { + msg_error("QUEUE FILE NAMES WERE CHANGED TO MATCH INODE NUMBERS"); + msg_fatal("CHECK YOUR QUEUE IDS AND RE-ISSUE THE COMMAND"); + } + + /* + * Execute actions by queue ID and by wildcard in the user-specified + * order. + */ + optind = saved_optind; + while ((c = GETOPT(argc, argv, GETOPT_LIST)) > 0) { + switch (c) { + default: + msg_panic("%s: unexpected option: %c", argv[0], c); + case 'c': + case 'p': + case 'S': + case 's': + case 'v': + /* Already handled. */ + break; + case 'd': + if (strcmp(optarg, "ALL") == 0) + super(queues, ACTION_DELETE_ALL); + else if (strcmp(optarg, "-") == 0) + operate_stream(VSTREAM_IN, delete_one, queues); + else + delete_one(queues, optarg); + break; + case 'e': + if (strcmp(optarg, "ALL") == 0) + super(queues, ACTION_EXPIRE_ALL); + else if (strcmp(optarg, "-") == 0) + operate_stream(VSTREAM_IN, expire_one, queues); + else + expire_one(queues, optarg); + break; + case 'f': + if (strcmp(optarg, "ALL") == 0) + super(queues, ACTION_EXP_REL_ALL); + else if (strcmp(optarg, "-") == 0) + operate_stream(VSTREAM_IN, exp_rel_one, queues); + else + exp_rel_one(queues, optarg); + break; + case 'h': + if (strcmp(optarg, "ALL") == 0) + super(queues, ACTION_HOLD_ALL); + else if (strcmp(optarg, "-") == 0) + operate_stream(VSTREAM_IN, hold_one, queues); + else + hold_one(queues, optarg); + break; + case 'H': + if (strcmp(optarg, "ALL") == 0) + super(queues, ACTION_RELEASE_ALL); + else if (strcmp(optarg, "-") == 0) + operate_stream(VSTREAM_IN, release_one, queues); + else + release_one(queues, optarg); + break; + case 'r': + if (strcmp(optarg, "ALL") == 0) + super(queues, ACTION_REQUEUE_ALL); + else if (strcmp(optarg, "-") == 0) + operate_stream(VSTREAM_IN, requeue_one, queues); + else + requeue_one(queues, optarg); + break; + } + } + + /* + * Report. + */ + if (action & (ACTION_REQUEUE_ONE | ACTION_REQUEUE_ALL)) + msg_info("Requeued: %d message%s", message_requeued, + message_requeued != 1 ? "s" : ""); + if (action & (ACTION_DELETE_ONE | ACTION_DELETE_ALL)) + msg_info("Deleted: %d message%s", message_deleted, + message_deleted != 1 ? "s" : ""); + if (action & (ACTION_EXPIRE_ONE | ACTION_EXPIRE_ALL + | ACTION_EXP_REL_ONE | ACTION_EXP_REL_ALL)) + msg_info("Force-expired: %d message%s", message_expired, + message_expired != 1 ? "s" : ""); + if (action & (ACTION_HOLD_ONE | ACTION_HOLD_ALL)) + msg_info("Placed on hold: %d message%s", + message_held, message_held != 1 ? "s" : ""); + if (action & (ACTION_RELEASE_ONE | ACTION_RELEASE_ALL + | ACTION_EXP_REL_ONE | ACTION_EXP_REL_ALL)) + msg_info("Released from hold: %d message%s", + message_released, message_released != 1 ? "s" : ""); + if (inode_fixed > 0) + msg_info("Renamed to match inode number: %d message%s", inode_fixed, + inode_fixed != 1 ? "s" : ""); + if (inode_mismatch > 0 || inode_fixed > 0) + msg_warn("QUEUE FILE NAMES WERE CHANGED TO MATCH INODE NUMBERS"); + + exit(0); +} diff --git a/src/posttls-finger/.indent.pro b/src/posttls-finger/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/posttls-finger/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/posttls-finger/Makefile.in b/src/posttls-finger/Makefile.in new file mode 100644 index 0000000..d246303 --- /dev/null +++ b/src/posttls-finger/Makefile.in @@ -0,0 +1,117 @@ +SHELL = /bin/sh +SRCS = posttls-finger.c tlsmgrmem.c +OBJS = posttls-finger.o tlsmgrmem.o +HDRS = tlsmgrmem.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +INC_DIR = ../../include +PROG = posttls-finger +LIBS = ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +all: $(PROG) + +$(OBJS): ../../conf/makedefs.out $(HDRS) + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +posttls-finger: $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/posttls-finger + +../../bin/posttls-finger: posttls-finger + cp $? $@ + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +posttls-finger.o: ../../include/argv.h +posttls-finger.o: ../../include/attr.h +posttls-finger.o: ../../include/check_arg.h +posttls-finger.o: ../../include/chroot_uid.h +posttls-finger.o: ../../include/clean_env.h +posttls-finger.o: ../../include/dns.h +posttls-finger.o: ../../include/dsn.h +posttls-finger.o: ../../include/dsn_buf.h +posttls-finger.o: ../../include/host_port.h +posttls-finger.o: ../../include/htable.h +posttls-finger.o: ../../include/inet_proto.h +posttls-finger.o: ../../include/iostuff.h +posttls-finger.o: ../../include/known_tcp_ports.h +posttls-finger.o: ../../include/mail_conf.h +posttls-finger.o: ../../include/mail_params.h +posttls-finger.o: ../../include/mail_parm_split.h +posttls-finger.o: ../../include/mail_proto.h +posttls-finger.o: ../../include/mail_server.h +posttls-finger.o: ../../include/midna_domain.h +posttls-finger.o: ../../include/msg.h +posttls-finger.o: ../../include/msg_vstream.h +posttls-finger.o: ../../include/myaddrinfo.h +posttls-finger.o: ../../include/mymalloc.h +posttls-finger.o: ../../include/name_code.h +posttls-finger.o: ../../include/name_mask.h +posttls-finger.o: ../../include/nvtable.h +posttls-finger.o: ../../include/sane_connect.h +posttls-finger.o: ../../include/smtp_stream.h +posttls-finger.o: ../../include/sock_addr.h +posttls-finger.o: ../../include/stringops.h +posttls-finger.o: ../../include/sys_defs.h +posttls-finger.o: ../../include/timed_connect.h +posttls-finger.o: ../../include/tls.h +posttls-finger.o: ../../include/tls_proxy.h +posttls-finger.o: ../../include/vbuf.h +posttls-finger.o: ../../include/vstream.h +posttls-finger.o: ../../include/vstring.h +posttls-finger.o: ../../include/vstring_vstream.h +posttls-finger.o: posttls-finger.c +posttls-finger.o: tlsmgrmem.h +tlsmgrmem.o: ../../include/argv.h +tlsmgrmem.o: ../../include/check_arg.h +tlsmgrmem.o: ../../include/dict.h +tlsmgrmem.o: ../../include/htable.h +tlsmgrmem.o: ../../include/myflock.h +tlsmgrmem.o: ../../include/sys_defs.h +tlsmgrmem.o: ../../include/tls_mgr.h +tlsmgrmem.o: ../../include/tls_scache.h +tlsmgrmem.o: ../../include/vbuf.h +tlsmgrmem.o: ../../include/vstream.h +tlsmgrmem.o: ../../include/vstring.h +tlsmgrmem.o: tlsmgrmem.c +tlsmgrmem.o: tlsmgrmem.h diff --git a/src/posttls-finger/posttls-finger.c b/src/posttls-finger/posttls-finger.c new file mode 100644 index 0000000..502645c --- /dev/null +++ b/src/posttls-finger/posttls-finger.c @@ -0,0 +1,2165 @@ +/*++ +/* NAME +/* posttls-finger 1 +/* SUMMARY +/* Probe the TLS properties of an ESMTP or LMTP server. +/* SYNOPSIS +/* \fBposttls-finger\fR [\fIoptions\fR] [\fBinet:\fR]\fIdomain\fR[:\fIport\fR] [\fImatch ...\fR] +/* .br +/* \fBposttls-finger\fR -S [\fIoptions\fR] \fBunix:\fIpathname\fR [\fImatch ...\fR] +/* DESCRIPTION +/* \fBposttls-finger\fR(1) connects to the specified destination +/* and reports TLS-related information about the server. With SMTP, the +/* destination is a domainname; with LMTP it is either a domainname +/* prefixed with \fBinet:\fR or a pathname prefixed with \fBunix:\fR. If +/* Postfix is built without TLS support, the resulting \fBposttls-finger\fR(1) +/* program has very limited functionality, and only the \fB-a\fR, \fB-c\fR, +/* \fB-h\fR, \fB-o\fR, \fB-S\fR, \fB-t\fR, \fB-T\fR and \fB-v\fR options +/* are available. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* For SMTP servers that don't support ESMTP, only the greeting banner +/* and the negative EHLO response are reported. Otherwise, the reported +/* EHLO response details further server capabilities. +/* +/* If TLS support is enabled when \fBposttls-finger\fR(1) is compiled, and +/* the server supports \fBSTARTTLS\fR, a TLS handshake is attempted. +/* +/* If DNSSEC support is available, the connection TLS security level +/* (\fB-l\fR option) defaults to \fBdane\fR; see TLS_README for +/* details. Otherwise, it defaults to \fBsecure\fR. This setting +/* determines the certificate matching policy. +/* +/* If TLS negotiation succeeds, the TLS protocol and cipher details are +/* reported. The server certificate is then verified in accordance with +/* the policy at the chosen (or default) security level. With public +/* CA-based trust, when the \fB-L\fR option includes \fBcertmatch\fR, +/* (true by default) name matching is performed even if the certificate +/* chain is not trusted. This logs the names found in the remote SMTP +/* server certificate and which if any would match, were the certificate +/* chain trusted. +/* +/* Note: \fBposttls-finger\fR(1) does not perform any table lookups, so +/* the TLS policy table and obsolete per-site tables are not consulted. +/* It does not communicate with the \fBtlsmgr\fR(8) daemon (or any other +/* Postfix daemons); its TLS session cache is held in private memory, and +/* disappears when the process exits. +/* +/* With the \fB-r \fIdelay\fR option, if the server assigns a TLS +/* session id, the TLS session is cached. The connection is then closed +/* and re-opened after the specified delay, and \fBposttls-finger\fR(1) +/* then reports whether the cached TLS session was re-used. +/* +/* When the destination is a load balancer, it may be distributing +/* load between multiple server caches. Typically, each server returns +/* its unique name in its EHLO response. If, upon reconnecting with +/* \fB-r\fR, a new server name is detected, another session is cached +/* for the new server, and the reconnect is repeated up to a maximum +/* number of times (default 5) that can be specified via the \fB-m\fR +/* option. +/* +/* The choice of SMTP or LMTP (\fB-S\fR option) determines the syntax of +/* the destination argument. With SMTP, one can specify a service on a +/* non-default port as \fIhost\fR:\fIservice\fR, and disable MX (mail +/* exchanger) DNS lookups with [\fIhost\fR] or [\fIhost\fR]:\fIport\fR. +/* The [] form is required when you specify an IP address instead of a +/* hostname. An IPv6 address takes the form [\fBipv6:\fIaddress\fR]. +/* The default port for SMTP is taken from the \fBsmtp/tcp\fR entry in +/* /etc/services, defaulting to 25 if the entry is not found. +/* +/* With LMTP, specify \fBunix:\fIpathname\fR to connect to a local server +/* listening on a unix-domain socket bound to the specified pathname; +/* otherwise, specify an optional \fBinet:\fR prefix followed by a +/* \fIdomain\fR and an optional port, with the same syntax as for +/* SMTP. The default TCP port for LMTP is 24. +/* +/* Arguments: +/* .IP "\fB-a\fR \fIfamily\fR (default: \fBany\fR)" +/* Address family preference: \fBipv4\fR, \fBipv6\fR or \fBany\fR. When +/* using \fBany\fR, \fBposttls-finger\fR(1) will randomly select one of +/* the two as the more preferred, and exhaust all MX preferences for the +/* first address family before trying any addresses for the other. +/* .IP "\fB-A\fR \fItrust-anchor.pem\fR (default: none)" +/* A list of PEM trust-anchor files that overrides CAfile and CApath +/* trust chain verification. Specify the option multiple times to +/* specify multiple files. See the main.cf documentation for +/* smtp_tls_trust_anchor_file for details. +/* .IP "\fB-c\fR" +/* Disable SMTP chat logging; only TLS-related information is logged. +/* .IP "\fB-C\fR" +/* Print the remote SMTP server certificate trust chain in PEM format. +/* The issuer DN, subject DN, certificate and public key fingerprints +/* (see \fB-d \fImdalg\fR option below) are printed above each PEM +/* certificate block. If you specify \fB-F \fICAfile\fR or +/* \fB-P \fICApath\fR, the OpenSSL library may augment the chain with +/* missing issuer certificates. To see the actual chain sent by the +/* remote SMTP server leave \fICAfile\fR and \fICApath\fR unset. +/* .IP "\fB-d \fImdalg\fR (default: \fB$smtp_tls_fingerprint_digest\fR)" +/* The message digest algorithm to use for reporting remote SMTP server +/* fingerprints and matching against user provided certificate +/* fingerprints (with DANE TLSA records the algorithm is specified +/* in the DNS). In Postfix versions prior to 3.6, the default value +/* was "md5". +/* .IP "\fB-f\fR" +/* Lookup the associated DANE TLSA RRset even when a hostname is not an +/* alias and its address records lie in an unsigned zone. See +/* smtp_tls_force_insecure_host_tlsa_lookup for details. +/* .IP "\fB-F \fICAfile.pem\fR (default: none)" +/* The PEM formatted CAfile for remote SMTP server certificate +/* verification. By default no CAfile is used and no public CAs +/* are trusted. +/* .IP "\fB-g \fIgrade\fR (default: medium)" +/* The minimum TLS cipher grade used by \fBposttls-finger\fR(1). +/* See smtp_tls_mandatory_ciphers for details. +/* .IP "\fB-h \fIhost_lookup\fR (default: \fBdns\fR)" +/* The hostname lookup methods used for the connection. See the +/* documentation of smtp_host_lookup for syntax and semantics. +/* .IP "\fB-H \fIchainfiles\fR (default: \fInone\fR)\fR" +/* List of files with a sequence PEM-encoded TLS client certificate +/* chains. The list can be built-up incrementally, by specifying +/* the option multiple times, or all at once via a comma or +/* whitespace separated list of filenames. Each chain starts with +/* a private key, which is followed immediately by the +/* corresponding certificate, and optionally by additional issuer +/* certificates. Each new key begins a new chain for the +/* corresponding algorithm. This option is mutually exclusive with +/* the below \fB-k\fR and \fB-K\fR options. +/* .IP "\fB-k \fIcertfile\fR (default: \fIkeyfile\fR)\fR" +/* File with PEM-encoded TLS client certificate chain. This +/* defaults to \fIkeyfile\fR if one is specified. +/* .IP "\fB-K \fIkeyfile\fR (default: \fIcertfile\fR)" +/* File with PEM-encoded TLS client private key. +/* This defaults to \fIcertfile\fR if one is specified. +/* .IP "\fB-l \fIlevel\fR (default: \fBdane\fR or \fBsecure\fR)" +/* The security level for the connection, default \fBdane\fR or +/* \fBsecure\fR depending on whether DNSSEC is available. For syntax +/* and semantics, see the documentation of smtp_tls_security_level. +/* When \fBdane\fR or \fBdane-only\fR is supported and selected, if no +/* TLSA records are found, or all the records found are unusable, the +/* \fIsecure\fR level will be used instead. The \fBfingerprint\fR +/* security level allows you to test certificate or public-key +/* fingerprint matches before you deploy them in the policy table. +/* .IP +/* Note, since \fBposttls-finger\fR(1) does not actually deliver any email, +/* the \fBnone\fR, \fBmay\fR and \fBencrypt\fR security levels are not +/* very useful. Since \fBmay\fR and \fBencrypt\fR don't require peer +/* certificates, they will often negotiate anonymous TLS ciphersuites, +/* so you won't learn much about the remote SMTP server's certificates +/* at these levels if it also supports anonymous TLS (though you may +/* learn that the server supports anonymous TLS). +/* .IP "\fB-L \fIlogopts\fR (default: \fBroutine,certmatch\fR)" +/* Fine-grained TLS logging options. To tune the TLS features logged +/* during the TLS handshake, specify one or more of: +/* .RS +/* .IP "\fB0, none\fR" +/* These yield no TLS logging; you'll generally want more, but this +/* is handy if you just want the trust chain: +/* .RS +/* .ad +/* .nf +/* $ posttls-finger -cC -L none destination +/* .fi +/* .RE +/* .IP "\fB1, routine, summary\fR" +/* These synonymous values yield a normal one-line summary of the TLS +/* connection. +/* .IP "\fB2, debug\fR" +/* These synonymous values combine routine, ssl-debug, cache and verbose. +/* .IP "\fB3, ssl-expert\fR" +/* These synonymous values combine debug with ssl-handshake-packet-dump. +/* For experts only. +/* .IP "\fB4, ssl-developer\fR" +/* These synonymous values combine ssl-expert with ssl-session-packet-dump. +/* For experts only, and in most cases, use wireshark instead. +/* .IP "\fBssl-debug\fR" +/* Turn on OpenSSL logging of the progress of the SSL handshake. +/* .IP "\fBssl-handshake-packet-dump\fR" +/* Log hexadecimal packet dumps of the SSL handshake; for experts only. +/* .IP "\fBssl-session-packet-dump\fR" +/* Log hexadecimal packet dumps of the entire SSL session; only useful +/* to those who can debug SSL protocol problems from hex dumps. +/* .IP "\fBuntrusted\fR" +/* Logs trust chain verification problems. This is turned on +/* automatically at security levels that use peer names signed +/* by Certification Authorities to validate certificates. So while +/* this setting is recognized, you should never need to set it +/* explicitly. +/* .IP "\fBpeercert\fR" +/* This logs a one line summary of the remote SMTP server certificate +/* subject, issuer, and fingerprints. +/* .IP "\fBcertmatch\fR" +/* This logs remote SMTP server certificate matching, showing the CN +/* and each subjectAltName and which name matched. With DANE, logs +/* matching of TLSA record trust-anchor and end-entity certificates. +/* .IP "\fBcache\fR" +/* This logs session cache operations, showing whether session caching +/* is effective with the remote SMTP server. Automatically used when +/* reconnecting with the \fB-r\fR option; rarely needs to be set +/* explicitly. +/* .IP "\fBverbose\fR" +/* Enables verbose logging in the Postfix TLS driver; includes all of +/* peercert..cache and more. +/* .RE +/* .IP +/* The default is \fBroutine,certmatch\fR. After a reconnect, +/* \fBpeercert\fR, \fBcertmatch\fR and \fBverbose\fR are automatically +/* disabled while \fBcache\fR and \fBsummary\fR are enabled. +/* .IP "\fB-m \fIcount\fR (default: \fB5\fR)" +/* When the \fB-r \fIdelay\fR option is specified, the \fB-m\fR option +/* determines the maximum number of reconnect attempts to use with +/* a server behind a load balancer, to see whether connection caching +/* is likely to be effective for this destination. Some MTAs +/* don't expose the underlying server identity in their EHLO +/* response; with these servers there will never be more than +/* 1 reconnection attempt. +/* .IP "\fB-M \fIinsecure_mx_policy\fR (default: \fBdane\fR)" +/* The TLS policy for MX hosts with "secure" TLSA records when the +/* nexthop destination security level is \fBdane\fR, but the MX +/* record was found via an "insecure" MX lookup. See the main.cf +/* documentation for smtp_tls_dane_insecure_mx_policy for details. +/* .IP "\fB-o \fIname=value\fR" +/* Specify zero or more times to override the value of the main.cf +/* parameter \fIname\fR with \fIvalue\fR. Possible use-cases include +/* overriding the values of TLS library parameters, or "myhostname" to +/* configure the SMTP EHLO name sent to the remote server. +/* .IP "\fB-p \fIprotocols\fR (default: >=TLSv1)" +/* TLS protocols that \fBposttls-finger\fR(1) will exclude or include. See +/* smtp_tls_mandatory_protocols for details. +/* .IP "\fB-P \fICApath/\fR (default: none)" +/* The OpenSSL CApath/ directory (indexed via c_rehash(1)) for remote +/* SMTP server certificate verification. By default no CApath is used +/* and no public CAs are trusted. +/* .IP "\fB-r \fIdelay\fR" +/* With a cacheable TLS session, disconnect and reconnect after \fIdelay\fR +/* seconds. Report whether the session is re-used. Retry if a new server +/* is encountered, up to 5 times or as specified with the \fB-m\fR option. +/* By default reconnection is disabled, specify a positive delay to +/* enable this behavior. +/* .IP "\fB-s \fIservername\fR" +/* The server name to send with the TLS Server Name Indication (SNI) +/* extension. When the server has DANE TLSA records, this parameter +/* is ignored and the TLSA base domain is used instead. Otherwise, SNI is +/* not used by default, but can be enabled by specifying the desired value +/* with this option. +/* .IP "\fB-S\fR" +/* Disable SMTP; that is, connect to an LMTP server. The default port for +/* LMTP over TCP is 24. Alternative ports can specified by appending +/* "\fI:servicename\fR" or ":\fIportnumber\fR" to the destination +/* argument. +/* .IP "\fB-t \fItimeout\fR (default: \fB30\fR)" +/* The TCP connection timeout to use. This is also the timeout for +/* reading the remote server's 220 banner. +/* .IP "\fB-T \fItimeout\fR (default: \fB30\fR)" +/* The SMTP/LMTP command timeout for EHLO/LHLO, STARTTLS and QUIT. +/* .IP "\fB-v\fR" +/* Enable verbose Postfix logging. Specify more than once to increase +/* the level of verbose logging. +/* .IP "\fB-w\fR" +/* Enable outgoing TLS wrapper mode, or SUBMISSIONS/SMTPS support. This +/* is typically provided on port 465 by servers that are compatible with +/* the SMTP-in-SSL protocol, rather than the STARTTLS protocol. +/* The destination \fIdomain\fR:\fIport\fR must of course provide such +/* a service. +/* .IP "\fB-X\fR" +/* Enable \fBtlsproxy\fR(8) mode. This is an unsupported mode, +/* for program development only. +/* .IP "[\fBinet:\fR]\fIdomain\fR[:\fIport\fR]" +/* Connect via TCP to domain \fIdomain\fR, port \fIport\fR. The default +/* port is \fBsmtp\fR (or 24 with LMTP). With SMTP an MX lookup is +/* performed to resolve the domain to a host, unless the domain is +/* enclosed in \fB[]\fR. If you want to connect to a specific MX host, +/* for instance \fImx1.example.com\fR, specify [\fImx1.example.com\fR] +/* as the destination and \fIexample.com\fR as a \fBmatch\fR argument. +/* When using DNS, the destination domain is assumed fully qualified +/* and no default domain or search suffixes are applied; you must use +/* fully-qualified names or also enable \fBnative\fR host lookups +/* (these don't support \fBdane\fR or \fBdane-only\fR as no DNSSEC +/* validation information is available via \fBnative\fR lookups). +/* .IP "\fBunix:\fIpathname\fR" +/* Connect to the UNIX-domain socket at \fIpathname\fR. LMTP only. +/* .IP "\fBmatch ...\fR" +/* With no match arguments specified, certificate peername matching uses +/* the compiled-in default strategies for each security level. If you +/* specify one or more arguments, these will be used as the list of +/* certificate or public-key digests to match for the \fBfingerprint\fR +/* level, or as the list of DNS names to match in the certificate at the +/* \fBverify\fR and \fBsecure\fR levels. If the security level is +/* \fBdane\fR, or \fBdane-only\fR the match names are ignored, and +/* \fBhostname, nexthop\fR strategies are used. +/* .ad +/* .fi +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP \fBMAIL_CONFIG\fR +/* Read configuration parameters from a non-default location. +/* .IP \fBMAIL_VERBOSE\fR +/* Same as \fB-v\fR option. +/* SEE ALSO +/* smtp-source(1), SMTP/LMTP message source +/* smtp-sink(1), SMTP/LMTP message dump +/* +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or "\fBpostconf +/* html_directory\fR" to locate this information. +/* .na +/* .nf +/* TLS_README, Postfix STARTTLS howto +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Viktor Dukhovni +/*--*/ + + /* + * System library. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STR(x) vstring_str(x) + + /* + * Global library. + */ +#include +#include +#include +#include +#include +#include + +/* DNS library. */ + +#include + + /* + * master library + */ +#include + + /* + * TLS Library + */ +#define TLS_INTERNAL +#include + +#ifdef USE_TLS +#include +#include +#endif + + /* + * Application specific + */ +#include "tlsmgrmem.h" + +static int conn_tmout = 30; +static int smtp_tmout = 30; + +#define HOST_FLAG_DNS (1<<0) +#define HOST_FLAG_NATIVE (1<<1) + +#define MISC_FLAG_PREF_IPV6 (1<<0) +#define MISC_FLAG_PREF_IPV4 (1<<1) + +static const NAME_MASK lookup_masks[] = { + "dns", HOST_FLAG_DNS, + "native", HOST_FLAG_NATIVE, + 0, +}; + +static const NAME_CODE addr_pref_map[] = { + INET_PROTO_NAME_IPV6, MISC_FLAG_PREF_IPV6, + INET_PROTO_NAME_IPV4, MISC_FLAG_PREF_IPV4, + INET_PROTO_NAME_ANY, 0, + 0, -1, +}; + +typedef struct OPTIONS { + char *logopts; + char *level; + ARGV *tas; + char *host_lookup; + char *addr_pref; +} OPTIONS; + + /* + * Per-session data structure with state. + * + * This software can maintain multiple parallel connections to the same SMTP + * server. However, it makes no more than one connection request at a time + * to avoid overwhelming the server with SYN packets and having to back off. + * Back-off would screw up the benchmark. Pending connection requests are + * kept in a linear list. + */ +typedef struct STATE { + int smtp; /* SMTP or LMTP? */ + int host_lookup; /* dns|native|dns,native */ + int addr_pref; /* v4, v6, both */ + int log_mask; /* via tls_log_mask() */ + int reconnect; /* -r option */ + int max_reconnect; /* -m option */ + int force_tlsa; /* -f option */ + unsigned port; /* TCP port */ + char *dest; /* Full destination spec */ + char *paddr; /* XXX printable addr for proxy */ + char *addrport; /* [addr]:port */ + char *namaddrport; /* name[addr]:port */ + char *nexthop; /* Nexthop domain for verification */ + char *hostname; /* Hostname for verification */ + DNS_RR *addr; /* IPv[46] Address to (re)connect to */ + DNS_RR *mx; /* MX RRset qname, rname, valid */ + int pass; /* Pass number, 2 for reconnect */ + int nochat; /* disable chat logging */ + char *helo; /* Server name from EHLO reply */ + DSN_BUF *why; /* SMTP-style error message */ + VSTRING *buffer; /* Response buffer */ + VSTREAM *stream; /* Open connection */ + int level; /* TLS security level */ + int wrapper_mode; /* SMTPS support */ +#ifdef USE_TLS + char *mdalg; /* fingerprint digest algorithm */ + char *CAfile; /* Trusted public CAs */ + char *CApath; /* Trusted public CAs */ + char *chains; /* TLS client certificate chain files */ + char *certfile; /* TLS client certificate file */ + char *keyfile; /* TLS client key file */ + char *sni; /* Server SNI name */ + ARGV *match; /* match arguments */ + int print_trust; /* -C option */ + BIO *tls_bio; /* BIO wrapper for stdout */ + TLS_APPL_STATE *tls_ctx; /* Application TLS context */ + TLS_SESS_STATE *tls_context; /* Session TLS context */ + TLS_DANE *dane; /* DANE TLSA validation structure */ + TLS_DANE *ddane; /* DANE TLSA from DNS */ + char *grade; /* Minimum cipher grade */ + char *protocols; /* Protocol inclusion/exclusion */ + int mxinsec_level; /* DANE for insecure MX RRs? */ + int tlsproxy_mode; +#endif + OPTIONS options; /* JCL */ +} STATE; + +static DNS_RR *host_addr(STATE *, const char *); + +#define HNAME(addr) (addr->qname) + + /* + * Structure with broken-up SMTP server response. + */ +typedef struct { /* server response */ + int code; /* status */ + char *str; /* text */ + VSTRING *buf; /* origin of text */ +} RESPONSE; + + +/* command - send an SMTP command */ + +static void PRINTFLIKE(3, 4) command(STATE *state, int verbose, char *fmt,...) +{ + VSTREAM *stream = state->stream; + VSTRING *buf; + va_list ap; + char *line; + + buf = vstring_alloc(100); + va_start(ap, fmt); + vstring_vsprintf(buf, fmt, ap); + va_end(ap); + line = vstring_str(buf); + + while (line && *line) { + char *nextline = strchr(line, '\n'); + + if (nextline) + *nextline++ = '\0'; + if (verbose && !state->nochat) + msg_info("> %s", line); + smtp_printf(stream, "%s", line); + line = nextline; + } + + vstring_free(buf); +} + +/* response - read and process SMTP server response */ + +static RESPONSE *response(STATE *state, int verbose) +{ + VSTREAM *stream = state->stream; + VSTRING *buf = state->buffer; + static RESPONSE rdata; + int more; + char *cp; + + /* + * Initialize the response data buffer. smtp_get() defends against a + * denial of service attack by limiting the amount of single-line text, + * and the loop below limits the amount of multi-line text that we are + * willing to store. + */ + if (rdata.buf == 0) + rdata.buf = vstring_alloc(100); + + /* + * Censor out non-printable characters in server responses. Concatenate + * multi-line server responses. Separate the status code from the text. + * Leave further parsing up to the application. + */ +#define BUF ((char *) vstring_str(buf)) + VSTRING_RESET(rdata.buf); + for (;;) { + smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP); + for (cp = BUF; *cp != 0; cp++) + if (!ISPRINT(*cp) && !ISSPACE(*cp)) + *cp = '?'; + cp = BUF; + if (verbose && !state->nochat) + msg_info("< %s", cp); + while (ISDIGIT(*cp)) + cp++; + rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0); + if ((more = (*cp == '-')) != 0) + cp++; + while (ISSPACE(*cp)) + cp++; + if (VSTRING_LEN(rdata.buf) < var_line_limit) + vstring_strcat(rdata.buf, cp); + if (more == 0) + break; + if (VSTRING_LEN(rdata.buf) < var_line_limit) + VSTRING_ADDCH(rdata.buf, '\n'); + } + VSTRING_TERMINATE(rdata.buf); + rdata.str = vstring_str(rdata.buf); + return (&rdata); +} + +/* exception_text - translate exceptions from the smtp_stream module */ + +static char *exception_text(int except) +{ + switch (except) { + case SMTP_ERR_EOF: + return ("lost connection"); + case SMTP_ERR_TIME: + return ("timeout"); + default: + msg_panic("exception_text: unknown exception %d", except); + } +} + +/* greeting - read server's 220 greeting */ + +static int greeting(STATE *state) +{ + VSTREAM *stream = state->stream; + int except; + RESPONSE *resp; + + /* + * Prepare for disaster. + */ + smtp_stream_setup(stream, conn_tmout, /* deadline */ 1, /* minrate */ 0); + if ((except = vstream_setjmp(stream)) != 0) { + msg_info("%s while reading server greeting", exception_text(except)); + return (1); + } + + /* + * Read and parse the server's SMTP greeting banner. + */ + if (((resp = response(state, 1))->code / 100) != 2) { + msg_info("SMTP service not available: %d %s", resp->code, resp->str); + return (1); + } + return (0); +} + +/* ehlo - send EHLO/LHLO */ + +static RESPONSE *ehlo(STATE *state) +{ + int except; + int verbose; + volatile char *ehlo = state->smtp ? "EHLO" : "LHLO"; + VSTREAM *stream = state->stream; + RESPONSE *resp; + +#ifdef USE_TLS + verbose = (state->pass == 1 && state->nochat == 0); +#else + verbose = 1; +#endif + + /* + * Send the standard greeting with our hostname + */ + smtp_stream_setup(stream, smtp_tmout, /* deadline */ 1, /* minrate */ 0); + if ((except = vstream_setjmp(stream)) != 0) { + msg_info("%s while sending %s", exception_text(except), ehlo); + return (0); + } + command(state, verbose, "%s %s", ehlo, var_myhostname); + + resp = response(state, verbose); + if (resp->code / 100 != 2) { + msg_info("%s rejected: %d %s", ehlo, resp->code, resp->str); + return (0); + } + return resp; +} + +#ifdef USE_TLS + +static void print_stack(STATE *state, x509_stack_t *sk, int trustout) +{ + int i; + + for (i = 0; i < sk_X509_num(sk); i++) { + X509 *cert = sk_X509_value(sk, i); + char buf[CCERT_BUFSIZ]; + X509_NAME *xn; + char *digest; + + if ((xn = X509_get_subject_name(cert)) != 0) { + X509_NAME_oneline(xn, buf, sizeof buf); + BIO_printf(state->tls_bio, "%2d subject: %s\n", i, buf); + } + if ((xn = X509_get_issuer_name(cert)) != 0) { + X509_NAME_oneline(xn, buf, sizeof buf); + BIO_printf(state->tls_bio, " issuer: %s\n", buf); + } + digest = tls_cert_fprint(cert, state->mdalg); + BIO_printf(state->tls_bio, " cert digest=%s\n", digest); + myfree(digest); + + digest = tls_pkey_fprint(cert, state->mdalg); + BIO_printf(state->tls_bio, " pkey digest=%s\n", digest); + myfree(digest); + + if (trustout) + PEM_write_bio_X509_AUX(state->tls_bio, cert); + else + PEM_write_bio_X509(state->tls_bio, cert); + } +} + +static void print_trust_info(STATE *state) +{ + x509_stack_t *sk = SSL_get_peer_cert_chain(state->tls_context->con); + + if (sk != 0) { + BIO_printf(state->tls_bio, "\n---\nCertificate chain\n"); + print_stack(state, sk, 0); + } +#ifdef dane_verify_debug + /* print internally constructed untrusted chain */ + if ((sk = state->tls_context->untrusted) != 0) { + BIO_printf(state->tls_bio, "\n---\nUntrusted chain\n"); + print_stack(state, sk, 0); + } + /* print associated root CA */ + if ((sk = state->tls_context->trusted) != 0) { + BIO_printf(state->tls_bio, "\n---\nTrusted chain\n"); + print_stack(state, sk, 1); + } +#endif +} + +/* starttls - SMTP STARTTLS handshake */ + +static int starttls(STATE *state) +{ + VSTRING *cipher_exclusions; + int except; + RESPONSE *resp; + VSTREAM *stream = state->stream; + TLS_CLIENT_START_PROPS start_props; + TLS_CLIENT_INIT_PROPS init_props; + VSTREAM *tlsproxy; + VSTRING *port_buf; + int cwd_fd; + + if (state->wrapper_mode == 0) { + /* SMTP stream with deadline timeouts */ + smtp_stream_setup(stream, smtp_tmout, /* deadline */ 1, /* minrate */ 0); + if ((except = vstream_setjmp(stream)) != 0) { + msg_fatal("%s while sending STARTTLS", exception_text(except)); + return (1); + } + command(state, state->pass == 1, "STARTTLS"); + + resp = response(state, state->pass == 1); + if (resp->code / 100 != 2) { + msg_info("STARTTLS rejected: %d %s", resp->code, resp->str); + return (1); + } + + /* + * Discard any plain-text data that may be piggybacked after the + * server's 220 STARTTLS reply. Should we abort the session instead? + */ + vstream_fpurge(stream, VSTREAM_PURGE_READ); + } +#define ADD_EXCLUDE(vstr, str) \ + do { \ + if (*(str)) \ + vstring_sprintf_append((vstr), "%s%s", \ + VSTRING_LEN(vstr) ? " " : "", (str)); \ + } while (0) + + cipher_exclusions = vstring_alloc(10); + ADD_EXCLUDE(cipher_exclusions, DEF_SMTP_TLS_EXCL_CIPH); + if (TLS_REQUIRED(state->level)) + ADD_EXCLUDE(cipher_exclusions, DEF_SMTP_TLS_MAND_EXCL); + + /* + * If we're authenticating suppress anonymous ciphersuites, otherwise at + * least encrypt, not much point in doing neither. + */ + if (TLS_MUST_MATCH(state->level)) + ADD_EXCLUDE(cipher_exclusions, "aNULL"); + else + ADD_EXCLUDE(cipher_exclusions, "eNULL"); + + smtp_stream_setup(stream, smtp_tmout, /* deadline */ 1, /* minrate */ 0); + if (state->tlsproxy_mode) { + TLS_CLIENT_PARAMS tls_params; + + /* + * Send all our wishes in one big request. + */ + TLS_PROXY_CLIENT_INIT_PROPS(&init_props, + log_param = "-L option", + log_level = state->options.logopts, + verifydepth = DEF_SMTP_TLS_SCERT_VD, + cache_type = TLS_MGR_SCACHE_SMTP, + chain_files = state->chains, + cert_file = state->certfile, + key_file = state->keyfile, + dcert_file = "", + dkey_file = "", + eccert_file = "", + eckey_file = "", + CAfile = state->CAfile, + CApath = state->CApath, + mdalg = state->mdalg); + TLS_PROXY_CLIENT_START_PROPS(&start_props, + timeout = smtp_tmout, + tls_level = state->level, + nexthop = state->nexthop, + host = state->hostname, + namaddr = state->namaddrport, + sni = state->sni, + serverid = state->addrport, + helo = state->helo ? state->helo : "", + protocols = state->protocols, + cipher_grade = state->grade, + cipher_exclusions + = vstring_str(cipher_exclusions), + matchargv = state->match, + mdalg = state->mdalg, + dane = state->ddane ? + state->ddane : state->dane); + +#define PROXY_OPEN_FLAGS \ + (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT) +#define var_tlsproxy_service + + if ((cwd_fd = open(".", O_RDONLY)) < 0) + msg_fatal("open(\".\", O_RDONLY): %m"); + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir(%s): %m", var_queue_dir); + port_buf = vstring_alloc(100); + vstring_sprintf(port_buf, "%d", ntohs(state->port)); + tlsproxy = + tls_proxy_open(DEF_TLSPROXY_SERVICE /* TODO */ , PROXY_OPEN_FLAGS, + state->stream, state->paddr, STR(port_buf), + smtp_tmout, smtp_tmout, state->addrport, + tls_proxy_client_param_from_config(&tls_params), + &init_props, &start_props); + vstring_free(port_buf); + if (fchdir(cwd_fd) < 0) + msg_fatal("fchdir: %m"); + (void) close(cwd_fd); + + /* + * To insert tlsproxy(8) between this process and the remote SMTP + * server, we swap the file descriptors between the tlsproxy and + * session->stream VSTREAMS, so that we don't lose all the + * user-configurable session->stream attributes (such as longjump + * buffers or timeouts). + * + * TODO: the tlsproxy RPCs should return more error detail than a "NO" + * result. + */ + if (tlsproxy == 0) { + state->tls_context = 0; + } else { + vstream_control(tlsproxy, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_END); + vstream_control(state->stream, + CA_VSTREAM_CTL_SWAP_FD(tlsproxy), + CA_VSTREAM_CTL_END); + (void) vstream_fclose(tlsproxy); /* direct-to-server stream! */ + + /* + * There must not be any pending data in the stream buffers + * before we read the TLS context attributes. + */ + vstream_fpurge(state->stream, VSTREAM_PURGE_BOTH); + + /* + * After plumbing the plaintext stream, receive the TLS context + * object. For this we use the same VSTREAM buffer that we also + * use to receive subsequent SMTP commands, therefore we must be + * prepared for the possibility that the remote SMTP server + * starts talking immediately. The tlsproxy implementation sends + * the TLS context before remote content. The attribute protocol + * is robust enough that an adversary cannot insert their own TLS + * context attributes. + */ + state->tls_context = tls_proxy_context_receive(state->stream); + if (state->tls_context) { + if (state->log_mask & + (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) + msg_info("%s: subject_CN=%s, issuer_CN=%s, " + "fingerprint=%s, pkey_fingerprint=%s", + state->namaddrport, state->tls_context->peer_CN, + state->tls_context->issuer_CN, + state->tls_context->peer_cert_fprint, + state->tls_context->peer_pkey_fprint); + tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW, + state->tls_context); + } else { + msg_warn("error receiving TLS proxy context"); + } + } + } else { /* tls_proxy_mode */ + state->tls_context = + TLS_CLIENT_START(&start_props, + ctx = state->tls_ctx, + stream = stream, + fd = -1, + timeout = smtp_tmout, + tls_level = state->level, + nexthop = state->nexthop, + host = state->hostname, + namaddr = state->namaddrport, + sni = state->sni, + serverid = state->addrport, + helo = state->helo ? state->helo : "", + protocols = state->protocols, + cipher_grade = state->grade, + cipher_exclusions + = vstring_str(cipher_exclusions), + matchargv = state->match, + mdalg = state->mdalg, + dane = state->ddane ? state->ddane : state->dane); + } /* tlsproxy_mode */ + vstring_free(cipher_exclusions); + if (state->helo) { + myfree(state->helo); + state->helo = 0; + } + if (state->tls_context == 0) { + /* We must avoid further I/O, the peer is in an undefined state. */ + (void) vstream_fpurge(stream, VSTREAM_PURGE_BOTH); + (void) vstream_fclose(stream); + state->stream = 0; + return (1); + } + if (state->wrapper_mode && greeting(state) != 0) + return (1); + + if (state->pass == 1) { + ehlo(state); + if (!TLS_CERT_IS_PRESENT(state->tls_context)) + msg_info("Server is anonymous"); + else if (state->tlsproxy_mode == 0) { + if (state->print_trust) + print_trust_info(state); + state->log_mask &= ~(TLS_LOG_CERTMATCH | TLS_LOG_PEERCERT | + TLS_LOG_VERBOSE | TLS_LOG_UNTRUSTED); + } + state->log_mask |= TLS_LOG_CACHE | TLS_LOG_SUMMARY; + tls_update_app_logmask(state->tls_ctx, state->log_mask); + } + return (0); +} + +#endif + +/* doproto - do SMTP handshake */ + +static int doproto(STATE *state) +{ + VSTREAM *stream = state->stream; + RESPONSE *resp; + int except; + int n; + char *lines; + char *words = 0; + char *word; + + if (!state->wrapper_mode) { + if (greeting(state) != 0) + return (1); + if ((resp = ehlo(state)) == 0) + return (1); + + lines = resp->str; + for (n = 0; (words = mystrtok(&lines, "\n")) != 0; ++n) { + if ((word = mystrtok(&words, " \t=")) != 0) { + if (n == 0) + state->helo = mystrdup(word); + if (strcasecmp(word, "STARTTLS") == 0) + break; + } + } + } +#ifdef USE_TLS + if ((state->wrapper_mode || words) && state->tls_ctx) + if (starttls(state)) + return (1); +#endif + + /* + * Prepare for disaster. + */ + smtp_stream_setup(stream, smtp_tmout, /* deadline */ 1, /* minrate */ 0); + if ((except = vstream_setjmp(stream)) != 0) { + msg_warn("%s while sending QUIT command", exception_text(except)); + return (0); + } + command(state, 1, "QUIT"); + (void) response(state, 1); + return (0); +} + +/* connect_sock - connect a socket over some transport */ + +static VSTREAM *connect_sock(int sock, struct sockaddr *sa, int salen, + const char *name, const char *addr, STATE *state) +{ + DSN_BUF *why = state->why; + int conn_stat; + int saved_errno; + VSTREAM *stream; + + if (conn_tmout > 0) { + non_blocking(sock, NON_BLOCKING); + conn_stat = timed_connect(sock, sa, salen, conn_tmout); + saved_errno = errno; + non_blocking(sock, BLOCKING); + errno = saved_errno; + } else { + conn_stat = sane_connect(sock, sa, salen); + } + if (conn_stat < 0) { + if (state->port) + dsb_simple(why, "4.4.1", "connect to %s[%s]:%d: %m", + name, addr, ntohs(state->port)); + else + dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr); + close(sock); + return (0); + } + stream = vstream_fdopen(sock, O_RDWR); + state->namaddrport = + vstring_export(state->port == 0 ? + vstring_sprintf(vstring_alloc(10), "%s[%s]", name, addr) : + vstring_sprintf(vstring_alloc(10), "%s[%s]:%u", + name, addr, ntohs(state->port))); + state->addrport = + vstring_export(state->port == 0 ? + vstring_sprintf(vstring_alloc(10), "%s", addr) : + vstring_sprintf(vstring_alloc(10), "[%s]:%u", + addr, ntohs(state->port))); + + state->paddr = mystrdup(addr); /* XXX for tlsproxy */ + + /* + * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. + */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) + vstream_tweak_tcp(stream); + + return (stream); +} + +/* connect_unix - connect to a unix-domain socket */ + +static VSTREAM *connect_unix(STATE *state, const char *path) +{ + static const char *myname = "connect_unix"; + DSN_BUF *why = state->why; + struct sockaddr_un sock_un; + int len = strlen(path); + int sock; + + if (!state->nexthop) + state->nexthop = mystrdup(var_myhostname); + state->hostname = mystrdup(var_myhostname); + + dsb_reset(why); /* Paranoia */ + + /* + * Sanity checks. + */ + if (len >= (int) sizeof(sock_un.sun_path)) { + dsb_simple(why, "4.3.5", "unix-domain name too long: %s", path); + return (0); + } + + /* + * Initialize. + */ + memset((void *) &sock_un, 0, sizeof(sock_un)); + sock_un.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sock_un.sun_len = len + 1; +#endif + memcpy(sock_un.sun_path, path, len + 1); + + /* + * Create a client socket. + */ + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + + /* + * Connect to the server. + */ + if (msg_verbose) + msg_info("%s: trying: %s...", myname, path); + + return (connect_sock(sock, (struct sockaddr *) &sock_un, sizeof(sock_un), + var_myhostname, path, state)); +} + +/* connect_addr - connect to explicit address */ + +static VSTREAM *connect_addr(STATE *state, DNS_RR *addr) +{ + static const char *myname = "connect_addr"; + DSN_BUF *why = state->why; + struct sockaddr_storage ss; /* remote */ + struct sockaddr *sa = (struct sockaddr *) &ss; + SOCKADDR_SIZE salen = sizeof(ss); + MAI_HOSTADDR_STR hostaddr; + int sock; + + dsb_reset(why); /* Paranoia */ + + /* + * Sanity checks. + */ + if (dns_rr_to_sa(addr, state->port, sa, &salen) != 0) { + msg_warn("%s: skip address type %s: %m", + myname, dns_strtype(addr->type)); + dsb_simple(why, "4.4.0", "network address conversion failed: %m"); + return (0); + } + + /* + * Initialize. + */ + if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + + if (inet_windowsize > 0) + set_inet_windowsize(sock, inet_windowsize); + + /* + * Connect to the server. + */ + SOCKADDR_TO_HOSTADDR(sa, salen, &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + if (msg_verbose) + msg_info("%s: trying: %s[%s] port %d...", + myname, HNAME(addr), hostaddr.buf, ntohs(state->port)); + + return (connect_sock(sock, sa, salen, HNAME(addr), hostaddr.buf, state)); +} + +#define HAS_DSN(why) (STR((why)->status)[0] != 0) +#define HAS_SOFT_DSN(why) (STR((why)->status)[0] == '4') +#define HAS_HARD_DSN(why) (STR((why)->status)[0] == '5') +#define HAS_LOOP_DSN(why) \ + (HAS_DSN(why) && strcmp(STR((why)->status) + 1, ".4.6") == 0) + +#define SET_SOFT_DSN(why) (STR((why)->status)[0] = '4') +#define SET_HARD_DSN(why) (STR((why)->status)[0] = '5') + +/* addr_one - address lookup for one host name */ + +static DNS_RR *addr_one(STATE *state, DNS_RR *addr_list, const char *host, + int res_opt, unsigned pref) +{ + static const char *myname = "addr_one"; + DSN_BUF *why = state->why; + DNS_RR *addr = 0; + DNS_RR *rr; + int aierr; + struct addrinfo *res0; + struct addrinfo *res; + const INET_PROTO_INFO *proto_info = inet_proto_info(); + int found; + + if (msg_verbose) + msg_info("%s: host %s", myname, host); + + /* + * Interpret a numerical name as an address. + */ + if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0 + && strchr((char *) proto_info->sa_family_list, res0->ai_family) != 0) { + if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0) + msg_fatal("host %s: conversion error for address family %d: %m", + host, ((struct sockaddr *) (res0->ai_addr))->sa_family); + addr_list = dns_rr_append(addr_list, addr); + freeaddrinfo(res0); + return (addr_list); + } + + /* + * Use DNS lookup, but keep the option open to use native name service. + * + * XXX A soft error dominates past and future hard errors. Therefore we + * should not clobber a soft error text and status code. + */ + if (state->host_lookup & HOST_FLAG_DNS) { + switch (dns_lookup_v(host, res_opt, &addr, (VSTRING *) 0, + why->reason, DNS_REQ_FLAG_NONE, + proto_info->dns_atype_list)) { + case DNS_OK: + for (rr = addr; rr; rr = rr->next) + rr->pref = pref; + addr_list = dns_rr_append(addr_list, addr); + return (addr_list); + default: + dsb_status(why, "4.4.3"); + return (addr_list); + case DNS_FAIL: + dsb_status(why, HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3"); + return (addr_list); + case DNS_INVAL: + dsb_status(why, HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); + return (addr_list); + case DNS_NOTFOUND: + dsb_status(why, HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); + /* maybe native naming service will succeed */ + break; + } + } + + /* + * Use the native name service which also looks in /etc/hosts. + * + * XXX A soft error dominates past and future hard errors. Therefore we + * should not clobber a soft error text and status code. + */ +#define RETRY_AI_ERROR(e) \ + ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM) +#ifdef EAI_NODATA +#define DSN_NOHOST(e) \ + ((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME) +#else +#define DSN_NOHOST(e) \ + ((e) == EAI_AGAIN || (e) == EAI_NONAME) +#endif + + if (state->host_lookup & HOST_FLAG_NATIVE) { + if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) { + dsb_simple(why, (HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ? + (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") : + (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"), + "unable to look up host %s: %s", + host, MAI_STRERROR(aierr)); + } else { + for (found = 0, res = res0; res != 0; res = res->ai_next) { + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + msg_info("skipping address family %d for host %s", + res->ai_family, host); + continue; + } + found++; + if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0) + msg_fatal("host %s: conversion error for address family %d: %m", + host, ((struct sockaddr *) (res0->ai_addr))->sa_family); + addr_list = dns_rr_append(addr_list, addr); + } + freeaddrinfo(res0); + if (found == 0) { + dsb_simple(why, HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4", + "%s: host not found", host); + } + return (addr_list); + } + } + + /* + * No further alternatives for host lookup. + */ + return (addr_list); +} + +/* mx_addr_list - address lookup for a list of mail exchangers */ + +static DNS_RR *mx_addr_list(STATE *state, DNS_RR *mx_names) +{ + static const char *myname = "mx_addr_list"; + DNS_RR *addr_list = 0; + DNS_RR *rr; + int res_opt = 0; + + if (mx_names->dnssec_valid) + res_opt = RES_USE_DNSSEC; +#ifdef USE_TLS + else if (state->mxinsec_level > TLS_LEV_MAY) + res_opt = RES_USE_DNSSEC; +#endif + + for (rr = mx_names; rr; rr = rr->next) { + if (rr->type != T_MX) + msg_panic("%s: bad resource type: %d", myname, rr->type); + addr_list = addr_one(state, addr_list, (char *) rr->data, res_opt, + rr->pref); + } + return (addr_list); +} + +/* smtp_domain_addr - mail exchanger address lookup */ + +static DNS_RR *domain_addr(STATE *state, char *domain) +{ + DNS_RR *mx_names; + DNS_RR *addr_list = 0; + int r = 0; /* Resolver flags */ + const char *aname; + + dsb_reset(state->why); + +#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0) + r |= RES_USE_DNSSEC; +#endif + + /* + * IDNA support. + */ +#ifndef NO_EAI + if (!allascii(domain) && (aname = midna_domain_to_ascii(domain)) != 0) { + msg_info("%s asciified to %s", domain, aname); + } else +#endif + aname = domain; + + switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0, + state->why->reason)) { + default: + dsb_status(state->why, "4.4.3"); + break; + case DNS_INVAL: + dsb_status(state->why, "5.4.4"); + break; + case DNS_NULLMX: + dsb_status(state->why, "5.1.0"); + break; + case DNS_FAIL: + dsb_status(state->why, "5.4.3"); + break; + case DNS_OK: + mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any); + addr_list = mx_addr_list(state, mx_names); + state->mx = dns_rr_copy(mx_names); + dns_rr_free(mx_names); + if (addr_list == 0) { + msg_warn("no MX host for %s has a valid address record", domain); + break; + } +#define COMPARE_ADDR(flags) \ + ((flags & MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \ + (flags & MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \ + dns_rr_compare_pref_any) + if (addr_list && addr_list->next) { + addr_list = dns_rr_shuffle(addr_list); + addr_list = dns_rr_sort(addr_list, COMPARE_ADDR(state->addr_pref)); + } + break; + case DNS_NOTFOUND: + addr_list = host_addr(state, domain); + break; + } + + return (addr_list); +} + +/* host_addr - direct host lookup */ + +static DNS_RR *host_addr(STATE *state, const char *host) +{ + DSN_BUF *why = state->why; + DNS_RR *addr_list; + int res_opt = 0; + const char *ahost; + + dsb_reset(why); /* Paranoia */ + +#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0) + res_opt |= RES_USE_DNSSEC; +#endif + + /* + * IDNA support. + */ +#ifndef NO_EAI + if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) { + msg_info("%s asciified to %s", host, ahost); + } else +#endif + ahost = host; + +#define PREF0 0 + addr_list = addr_one(state, (DNS_RR *) 0, ahost, res_opt, PREF0); + if (addr_list && addr_list->next) { + addr_list = dns_rr_shuffle(addr_list); + if (inet_proto_info()->ai_family_list[1] != 0) + addr_list = dns_rr_sort(addr_list, COMPARE_ADDR(state->addr_pref)); + } + return (addr_list); +} + +/* dane_host_level - candidate host "dane" or degraded security level */ + +static int dane_host_level(STATE *state, DNS_RR *addr) +{ + int level = state->level; + +#ifdef USE_TLS + if (TLS_DANE_BASED(level)) { + if (state->mx == 0 || state->mx->dnssec_valid || + state->mxinsec_level > TLS_LEV_MAY) { + + /* See addr loop in connect_remote() */ + if (state->ddane) + tls_dane_free(state->ddane); + + /* + * When TLSA lookups fail, next host. If unusable or not found, + * fallback to "secure" + */ + state->ddane = tls_dane_resolve(state->port, "tcp", addr, + state->force_tlsa); + if (!state->ddane) { + dsb_simple(state->why, "4.7.5", + "TLSA lookup error for %s:%u", + HNAME(addr), ntohs(state->port)); + level = TLS_LEV_INVALID; + } else if (tls_dane_notfound(state->ddane) + || tls_dane_unusable(state->ddane)) { + if (msg_verbose || level == TLS_LEV_DANE_ONLY) + msg_info("no %sTLSA records found, " + "resorting to \"secure\"", + tls_dane_unusable(state->ddane) ? + "usable " : ""); + level = TLS_LEV_SECURE; + } else if (state->ddane->tlsa == 0) { + msg_panic("DANE activated with no TLSA records to match"); + } else if (state->mx && !state->mx->dnssec_valid && + state->mxinsec_level == TLS_LEV_ENCRYPT) { + msg_info("TLSA RRs found, MX RRset insecure: just encrypt"); + tls_dane_free(state->ddane); + state->ddane = 0; + level = TLS_LEV_ENCRYPT; + } else { + if (state->match) + argv_free(state->match); + argv_add(state->match = argv_alloc(2), + state->ddane->base_domain, ARGV_END); + if (state->mx) { + if (!state->mx->dnssec_valid) { + msg_info("MX RRset insecure: log verified as trusted"); + level = TLS_LEV_HALF_DANE; + } + if (strcmp(state->mx->qname, state->mx->rname) == 0) + argv_add(state->match, state->mx->qname, ARGV_END); + else + argv_add(state->match, state->mx->rname, + state->mx->qname, ARGV_END); + } + } + } else if (state->mx && !state->mx->dnssec_valid && + state->mxinsec_level == TLS_LEV_MAY) { + msg_info("MX RRset is insecure: try to encrypt"); + level = TLS_LEV_MAY; + } else { + level = TLS_LEV_SECURE; + } + } +#endif + + return (level); +} + +/* parse_destination - parse host/port destination */ + +static char *parse_destination(char *destination, char *def_service, + char **hostp, unsigned *portp) +{ + char *buf = mystrdup(destination); + char *service; + struct servent *sp; + char *protocol = "tcp"; + unsigned port; + const char *err; + + if (msg_verbose) + msg_info("parse_destination: %s %s", destination, def_service); + + /* + * Parse the host/port information. We're working with a copy of the + * destination argument so the parsing can be destructive. + */ + if ((err = host_port(buf, hostp, (char *) 0, &service, def_service)) != 0) + msg_fatal("%s in server description: %s", err, destination); + + /* + * Convert service to port number, network byte order. + */ + service = (char *) filter_known_tcp_port(service); + if (alldig(service)) { + if ((port = atoi(service)) >= 65536 || port == 0) + msg_fatal("bad network port: %s for destination: %s", + service, destination); + *portp = htons(port); + } else { + if ((sp = getservbyname(service, protocol)) != 0) + *portp = sp->s_port; + else if (strcmp(service, "smtp") == 0) + *portp = htons(25); + else + msg_fatal("unknown service: %s/%s", service, protocol); + } + return (buf); +} + +/* connect_remote - connect to TCP destination or log an error */ + +static void connect_remote(STATE *state, char *dest) +{ + DNS_RR *addr; + char *buf; + char *domain; + + /* When reconnecting use IP address of previous session */ + if (state->addr == 0) { + buf = parse_destination(dest, state->smtp ? "smtp" : "24", + &domain, &state->port); + if (!state->nexthop) + state->nexthop = mystrdup(domain); + if (state->smtp == 0 || *dest == '[') + state->addr = host_addr(state, domain); + else + state->addr = domain_addr(state, domain); + myfree(buf); + + if (state->addr == 0) { + msg_info("Destination address lookup failed: %s", + vstring_str(state->why->reason)); + return; + } + } + for (addr = state->addr; addr; addr = addr->next) { + int level = dane_host_level(state, addr); + + if (level == TLS_LEV_INVALID + || (state->stream = connect_addr(state, addr)) == 0) { + msg_info("Failed to establish session to %s via %s: %s", + dest, HNAME(addr), vstring_str(state->why->reason)); + continue; + } + /* We have a connection */ + state->level = level; + state->hostname = mystrdup(HNAME(addr)); + + /* We use the same address when reconnecting, so flush the rest. */ + addr = dns_rr_copy(addr); + dns_rr_free(state->addr); + state->addr = addr; + break; + } +} + +/* connect_dest - connect to given inet: or unix: destination */ + +static int connect_dest(STATE *state) +{ + char *dest = state->dest; + + /* + * With LMTP we have direct-to-host delivery only. The destination may + * have multiple IP addresses. + */ + if (state->smtp == 0) { + if (strncmp(dest, "unix:", 5) == 0) { + state->stream = connect_unix(state, dest + 5); + if (!state->stream) + msg_info("Failed to establish session to %s: %s", + dest, vstring_str(state->why->reason)); + return (1); + } + if (strncmp(dest, "inet:", 5) == 0) + dest += 5; + } + connect_remote(state, dest); + + return (state->stream == 0); +} + +static void disconnect_dest(STATE *state) +{ +#ifdef USE_TLS + if (state->tls_context) { + if (state->tlsproxy_mode) { + tls_proxy_context_free(state->tls_context); + } else { + tls_client_stop(state->tls_ctx, state->stream, + smtp_tmout, 0, state->tls_context); + } + } + state->tls_context = 0; + if (state->ddane) + tls_dane_free(state->ddane); + state->ddane = 0; +#endif + + if (state->stream) + vstream_fclose(state->stream); + state->stream = 0; + + if (state->namaddrport) + myfree(state->namaddrport); + state->namaddrport = 0; + + if (state->addrport) + myfree(state->addrport); + state->addrport = 0; + + if (state->paddr) + myfree(state->paddr); + state->paddr = 0; + + /* Reused on reconnect */ + if (state->reconnect <= 0) { + if (state->addr) + dns_rr_free(state->addr); + state->addr = 0; + if (state->mx) + dns_rr_free(state->mx); + state->mx = 0; + + if (state->nexthop) + myfree(state->nexthop); + state->nexthop = 0; + } + if (state->hostname) + myfree(state->hostname); + state->hostname = 0; + + dsb_free(state->why); + vstring_free(state->buffer); +} + +static int finger(STATE *state) +{ + int err; + + /* + * smtp_get() makes sure the SMTP server cannot run us out of memory by + * sending never-ending lines of text. + */ + state->buffer = vstring_alloc(100); + state->why = dsb_create(); + + if (!(err = connect_dest(state))) { + if (state->pass == 1 && !state->nochat) + msg_info("Connected to %s", state->namaddrport); + err = doproto(state); + } + disconnect_dest(state); + + if (err != 0) + return (1); + +#ifdef USE_TLS + if (state->tlsproxy_mode == 0 && state->reconnect > 0) { + int cache_enabled; + int cache_count; + int cache_hits; + + tlsmgrmem_status(&cache_enabled, &cache_count, &cache_hits); + if (cache_enabled && cache_count == 0) { + msg_info("Server declined session caching. Done reconnecting."); + state->reconnect = 0; + } else if (cache_hits > 0 && (state->log_mask & TLS_LOG_CACHE) != 0) { + msg_info("Found a previously used server. Done reconnecting."); + state->reconnect = 0; + } else if (state->max_reconnect-- <= 0) { + msg_info("Maximum reconnect count reached."); + state->reconnect = 0; + } + } +#endif + + return (0); +} + +/* run - do what we were asked to do. */ + +static int run(STATE *state) +{ + + while (1) { + if (finger(state) != 0) + break; + if (state->reconnect <= 0) + break; + msg_info("Reconnecting after %d seconds", state->reconnect); + ++state->pass; + sleep(state->reconnect); + } + + return (0); +} + +/* cleanup - free memory allocated in main */ + +static void cleanup(STATE *state) +{ +#ifdef USE_TLS + if (state->tls_ctx != 0) + tls_free_app_context(state->tls_ctx); + if (state->tls_bio) + (void) BIO_free(state->tls_bio); + state->tls_bio = 0; + + myfree(state->mdalg); + myfree(state->CApath); + myfree(state->CAfile); + myfree(state->certfile); + myfree(state->keyfile); + myfree(state->sni); + if (state->options.level) + myfree(state->options.level); + myfree(state->options.logopts); + if (state->match) + argv_free(state->match); + if (state->options.tas) + argv_free(state->options.tas); + if (state->dane) + tls_dane_free(state->dane); + + /* Flush and free DANE TLSA cache */ + tls_dane_flush(); + /* Flush and free memory tlsmgr cache */ + tlsmgrmem_flush(); + myfree(state->grade); + myfree(state->protocols); +#endif + myfree(state->options.host_lookup); + myfree(state->dest); + + mail_conf_flush(); +} + +/* usage - explain */ + +static void usage(void) +{ +#ifdef USE_TLS + fprintf(stderr, "usage: %s %s \\\n\t%s \\\n\t%s \\\n\t%s \\\n\t%s" + " destination [match ...]\n", var_procname, + "[-acCfSvw] [-t conn_tmout] [-T cmd_tmout] [-L logopts]", + "[-h host_lookup] [-l level] [-d mdalg] [-g grade] [-p protocols]", + "[-A tafile] [-F CAfile.pem] [-P CApath/] [-s servername]", + "[ [-H chainfiles] | [-k certfile [-K keyfile]] ]", + "[-m count] [-r delay] [-o name=value]"); +#else + fprintf(stderr, "usage: %s [-acStTv] [-h host_lookup] [-o name=value] destination\n", + var_procname); +#endif + exit(1); +} + +/* tls_init - initialize application TLS library context */ + +static void tls_init(STATE *state) +{ +#ifdef USE_TLS + TLS_CLIENT_INIT_PROPS props; + + if (state->level <= TLS_LEV_NONE) + return; + + /* Needed for tls_dane_avail() and other DANE-related processing. */ + state->tls_ctx = + TLS_CLIENT_INIT(&props, + log_param = "-L option", + log_level = state->options.logopts, + verifydepth = DEF_SMTP_TLS_SCERT_VD, + cache_type = "memory", + chain_files = state->chains, + cert_file = state->certfile, + key_file = state->keyfile, + dcert_file = "", + dkey_file = "", + eccert_file = "", + eckey_file = "", + CAfile = state->CAfile, + CApath = state->CApath, + mdalg = state->mdalg); +#endif +} + +/* override - update main.cf parameter */ + +static void override(const char *nameval) +{ + char *param_name; + char *param_value; + char *save = mystrdup(nameval); + + if (split_nameval(save, ¶m_name, ¶m_value) != 0) + usage(); + mail_conf_update(param_name, param_value); + myfree(save); +} + +/* parse_options - (argc, argv) -> state */ + +static void parse_options(STATE *state, int argc, char *argv[]) +{ + int c; + + state->smtp = 1; + state->pass = 1; + state->reconnect = -1; + state->max_reconnect = 5; + state->wrapper_mode = 0; +#ifdef USE_TLS + state->protocols = mystrdup(">=TLSv1"); + state->grade = mystrdup("medium"); +#endif + memset((void *) &state->options, 0, sizeof(state->options)); + state->options.host_lookup = mystrdup("dns"); + +#define OPTS "a:ch:o:St:T:v" +#ifdef USE_TLS +#define TLSOPTS "A:Cd:fF:g:H:k:K:l:L:m:M:p:P:r:s:wX" + + state->mdalg = 0; + state->CApath = mystrdup(""); + state->CAfile = mystrdup(""); + state->chains = mystrdup(""); + state->certfile = mystrdup(""); + state->keyfile = mystrdup(""); + state->sni = mystrdup(""); + state->options.tas = argv_alloc(1); + state->options.logopts = 0; + state->level = TLS_LEV_DANE; + state->mxinsec_level = TLS_LEV_DANE; + state->tlsproxy_mode = 0; +#else +#define TLSOPTS "" + state->level = TLS_LEV_NONE; +#endif + + while ((c = GETOPT(argc, argv, OPTS TLSOPTS)) > 0) { + switch (c) { + default: + usage(); + break; + case 'a': + state->options.addr_pref = mystrdup(optarg); + break; + case 'c': + state->nochat = 1; + break; + case 'h': + myfree(state->options.host_lookup); + state->options.host_lookup = mystrdup(optarg); + break; + case 'o': + override(optarg); + break; + case 'S': + state->smtp = 0; + break; + case 't': + conn_tmout = atoi(optarg); + break; + case 'T': + smtp_tmout = atoi(optarg); + break; + case 'v': + msg_verbose++; + break; +#ifdef USE_TLS + case 'A': + argv_add(state->options.tas, optarg, ARGV_END); + break; + case 'C': + state->print_trust = 1; + break; + case 'd': + if (state->mdalg) + myfree(state->mdalg); + state->mdalg = mystrdup(optarg); + break; + case 'f': + state->force_tlsa = 1; + break; + case 'F': + myfree(state->CAfile); + state->CAfile = mystrdup(optarg); + break; + case 'g': + myfree(state->grade); + state->grade = mystrdup(optarg); + break; + case 'H': + { + char *tmp; + + if (*state->chains) + tmp = concatenate(state->chains, ", ", optarg, (char *) 0); + else + tmp = mystrdup(optarg); + myfree(state->chains); + state->chains = tmp; + } + break; + case 'k': + myfree(state->certfile); + state->certfile = mystrdup(optarg); + if (!*state->keyfile) { + myfree(state->keyfile); + state->keyfile = mystrdup(optarg); + } + break; + case 'K': + myfree(state->keyfile); + state->keyfile = mystrdup(optarg); + if (!*state->certfile) { + myfree(state->certfile); + state->certfile = mystrdup(optarg); + } + break; + case 'l': + if (state->options.level) + myfree(state->options.level); + state->options.level = mystrdup(optarg); + break; + case 'L': + if (state->options.logopts) + myfree(state->options.logopts); + state->options.logopts = mystrdup(optarg); + break; + case 'm': + state->max_reconnect = atoi(optarg); + break; + case 'M': + switch (state->mxinsec_level = tls_level_lookup(optarg)) { + case TLS_LEV_MAY: + case TLS_LEV_ENCRYPT: + case TLS_LEV_DANE: + break; + default: + msg_fatal("bad '-M' option value: %s", optarg); + } + break; + case 'p': + myfree(state->protocols); + state->protocols = mystrdup(optarg); + break; + case 'P': + myfree(state->CApath); + state->CApath = mystrdup(optarg); + break; + case 'r': + state->reconnect = atoi(optarg); + break; + case 's': + myfree(state->sni); + state->sni = mystrdup(optarg); + break; + case 'w': + state->wrapper_mode = 1; + break; + case 'X': + state->tlsproxy_mode = 1; + break; +#endif + } + } + + /* + * Address family preference. + */ + state->addr_pref = + name_code(addr_pref_map, NAME_CODE_FLAG_NONE, state->options.addr_pref ? + state->options.addr_pref : "any"); + if (state->addr_pref < 0) + msg_fatal("bad '-a' option value: %s", state->options.addr_pref); + +#ifdef USE_TLS + if (state->tlsproxy_mode && state->reconnect >= 0) + msg_fatal("The -X and -r options are mutually exclusive"); +#endif + + /* + * Select hostname lookup mechanisms. + */ + state->host_lookup = + name_mask("-h option", lookup_masks, state->options.host_lookup ? + state->options.host_lookup : "dns"); + +#ifdef USE_TLS + + if (*state->chains && *state->certfile) + msg_fatal("When the '-H' option is used, neither the '-k'," + " nor the '-K' options may be used"); + + if (state->reconnect < 0) + tlsmgrmem_disable(); + + if (state->options.logopts == 0) + state->options.logopts = mystrdup("routine,certmatch"); + state->log_mask = tls_log_mask("-L option", state->options.logopts); + tls_dane_loglevel("-L option", state->options.logopts); + + if (state->options.level) { + state->level = tls_level_lookup(state->options.level); + + switch (state->level) { + case TLS_LEV_NONE: + if (state->wrapper_mode) + msg_fatal("SSL wrapper mode requires that TLS not be disabled"); + return; + case TLS_LEV_INVALID: + msg_fatal("Invalid TLS level \"%s\"", state->options.level); + } + } +#endif +} + +/* parse_match - process match arguments */ + +static void parse_match(STATE *state, int argc, char *argv[]) +{ +#ifdef USE_TLS + int smtp_mode = 1; + + switch (state->level) { + case TLS_LEV_SECURE: + state->match = argv_alloc(2); + while (*argv) + argv_split_append(state->match, *argv++, ""); + if (state->match->argc == 0) + argv_add(state->match, "nexthop", "dot-nexthop", ARGV_END); + break; + case TLS_LEV_VERIFY: + state->match = argv_alloc(1); + while (*argv) + argv_split_append(state->match, *argv++, ""); + if (state->match->argc == 0) + argv_add(state->match, "hostname", ARGV_END); + break; + case TLS_LEV_FPRINT: + state->dane = tls_dane_alloc(); + while (*argv) + tls_dane_add_fpt_digests((TLS_DANE *) state->dane, *argv++, "", + smtp_mode); + break; + case TLS_LEV_DANE: + case TLS_LEV_DANE_ONLY: + state->match = argv_alloc(2); + argv_add(state->match, "nexthop", "hostname", ARGV_END); + break; + } +#endif +} + +/* parse_tas - process '-A' trust anchor file option */ + +static void parse_tas(STATE *state) +{ +#ifdef USE_TLS + char **file; + + if (!state->options.tas->argc) + return; + + switch (state->level) { + default: + return; + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + state->dane = tls_dane_alloc(); + for (file = state->options.tas->argv; *file; ++file) { + if (!tls_dane_load_trustfile((TLS_DANE *) state->dane, *file)) + break; + } + if (*file) + msg_fatal("Failed to load trust anchor file: %s", *file); + break; + } +#endif +} + + +int main(int argc, char *argv[]) +{ + static STATE state; + char *loopenv = getenv("VALGRINDLOOP"); + int loop = loopenv ? atoi(loopenv) : 1; + ARGV *import_env; + static char *var_smtp_tls_fpt_dgst; + static const CONFIG_STR_TABLE smtp_str_table[] = { +#ifdef USE_TLS + VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, +#endif + 0, + }; + + /* Don't die when a peer goes away unexpectedly. */ + signal(SIGPIPE, SIG_IGN); + + /* We're a diagnostic utility, so diagnostic messages go to stdout. */ + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + msg_vstream_init(var_procname, VSTREAM_OUT); + + /* + * Load main.cf, parse command-line options, then process main.cf + * settings plus any command-line "-o" overrides. + */ + mail_conf_suck(); + parse_options(&state, argc, argv); + mail_params_init(); + get_mail_conf_str_table(smtp_str_table); + parse_tas(&state); + +#ifdef USE_TLS + /* Less surprising to default to the same fingerprint digest as smtp(8) */ + if (state.mdalg) + warn_compat_break_smtp_tls_fpt_dgst = 0; + else + state.mdalg = mystrdup(var_smtp_tls_fpt_dgst); + + /* + * We first call tls_init(), which ultimately calls SSL_library_init(), + * since otherwise we can't tell whether we have the message digests + * required for DANE support. + */ + tls_init(&state); + if (TLS_DANE_BASED(state.level) && !tls_dane_avail()) { + msg_warn("DANE TLS support is not available, resorting to \"secure\""); + state.level = TLS_LEV_SECURE; + } + state.tls_bio = 0; + if (state.print_trust) + state.tls_bio = BIO_new_fp(stdout, BIO_NOCLOSE); +#endif + + /* Enforce consistent operation of different Postfix parts. */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + update_env(import_env->argv); + argv_free(import_env); + + argc -= optind; + argv += optind; + + /* The first non-option argument is the destination. */ + if (!argc) + usage(); + + state.dest = mystrdup(argv[0]); + parse_match(&state, --argc, ++argv); + + /* Don't talk to remote systems as root */ + if (!geteuid()) + chroot_uid(0, var_mail_owner); + + while (loop-- > 0) + run(&state); + + /* Be valgrind friendly and clean-up */ + cleanup(&state); + + return (0); +} diff --git a/src/posttls-finger/tlsmgrmem.c b/src/posttls-finger/tlsmgrmem.c new file mode 100644 index 0000000..bfbc3a1 --- /dev/null +++ b/src/posttls-finger/tlsmgrmem.c @@ -0,0 +1,143 @@ +/*++ +/* NAME +/* tlsmgrmem 3 +/* SUMMARY +/* Memory-based TLS manager interface for tlsfinger(1). +/* SYNOPSIS +/* #ifdef USE_TLS +/* #include +/* +/* void tlsmgrmem_disable() +/* +/* void tlsmgrmem_status(enable, count, hits) +/* int *enable; +/* int *count; +/* int *hits; +/* +/* void tlsmgrmem_flush() +/* #endif +/* DESCRIPTION +/* tlsmgrmem_disable() disables the in-memory TLS session cache. +/* +/* tlsmgrmem_status() reports whether the cache is enabled, the +/* number of entries in the cache, and the number of cache hits. +/* If any of the return pointers are null, that item is not reported. +/* +/* tlsmgrmem_flush() flushes any cached data and frees the cache. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Viktor Dukhovni +/*--*/ + +#include + +#ifdef USE_TLS +#include +#include +#include + +#include "tlsmgrmem.h" + +static HTABLE *tls_cache; +static int cache_enabled = 1; +static int cache_count; +static int cache_hits; +typedef void (*free_func) (void *); +static free_func free_value = (free_func) vstring_free; + +void tlsmgrmem_disable(void) +{ + cache_enabled = 0; +} + +void tlsmgrmem_flush(void) +{ + if (!tls_cache) + return; + htable_free(tls_cache, free_value); +} + +void tlsmgrmem_status(int *enabled, int *count, int *hits) +{ + if (enabled) + *enabled = cache_enabled; + if (count) + *count = cache_count; + if (hits) + *hits = cache_hits; +} + +/* tls_mgr_* - Local cache and stubs that do not talk to the TLS manager */ + +int tls_mgr_seed(VSTRING *buf, int len) +{ + return (TLS_MGR_STAT_OK); +} + +int tls_mgr_policy(const char *unused_type, int *cachable, int *timeout) +{ + if (cache_enabled && tls_cache == 0) + tls_cache = htable_create(1); + *cachable = cache_enabled; + *timeout = TLS_SESSION_LIFEMIN; + return (TLS_MGR_STAT_OK); +} + +int tls_mgr_lookup(const char *unused_type, const char *key, VSTRING *buf) +{ + VSTRING *s; + + if (tls_cache == 0) + return TLS_MGR_STAT_ERR; + + if ((s = (VSTRING *) htable_find(tls_cache, key)) == 0) + return TLS_MGR_STAT_ERR; + + vstring_memcpy(buf, vstring_str(s), VSTRING_LEN(s)); + + ++cache_hits; + return (TLS_MGR_STAT_OK); +} + +int tls_mgr_update(const char *unused_type, const char *key, + const char *buf, ssize_t len) +{ + HTABLE_INFO *ent; + VSTRING *s; + + if (tls_cache == 0) + return TLS_MGR_STAT_ERR; + + if ((ent = htable_locate(tls_cache, key)) == 0) { + s = vstring_alloc(len); + ent = htable_enter(tls_cache, key, (void *) s); + } else { + s = (VSTRING *) ent->value; + } + vstring_memcpy(s, buf, len); + + ++cache_count; + return (TLS_MGR_STAT_OK); +} + +int tls_mgr_delete(const char *unused_type, const char *key) +{ + if (tls_cache == 0) + return TLS_MGR_STAT_ERR; + + if (htable_locate(tls_cache, key)) { + htable_delete(tls_cache, key, free_value); + --cache_count; + } + return (TLS_MGR_STAT_OK); +} + +#endif diff --git a/src/posttls-finger/tlsmgrmem.h b/src/posttls-finger/tlsmgrmem.h new file mode 100644 index 0000000..706b206 --- /dev/null +++ b/src/posttls-finger/tlsmgrmem.h @@ -0,0 +1,28 @@ +/*++ +/* NAME +/* tlsmgrmem 3 +/* SUMMARY +/* Memory-based TLS manager interface for tlsfinger(1). +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern void tlsmgrmem_disable(void); +extern void tlsmgrmem_status(int *, int *, int *); +extern void tlsmgrmem_flush(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Viktor Dukhovni +/*--*/ diff --git a/src/proxymap/.indent.pro b/src/proxymap/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/proxymap/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/proxymap/Makefile.in b/src/proxymap/Makefile.in new file mode 100644 index 0000000..925f98f --- /dev/null +++ b/src/proxymap/Makefile.in @@ -0,0 +1,85 @@ +SHELL = /bin/sh +SRCS = proxymap.c +OBJS = proxymap.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = proxymap +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +proxymap.o: ../../include/argv.h +proxymap.o: ../../include/attr.h +proxymap.o: ../../include/check_arg.h +proxymap.o: ../../include/dict.h +proxymap.o: ../../include/dict_pipe.h +proxymap.o: ../../include/dict_proxy.h +proxymap.o: ../../include/dict_union.h +proxymap.o: ../../include/htable.h +proxymap.o: ../../include/iostuff.h +proxymap.o: ../../include/mail_conf.h +proxymap.o: ../../include/mail_params.h +proxymap.o: ../../include/mail_proto.h +proxymap.o: ../../include/mail_server.h +proxymap.o: ../../include/mail_version.h +proxymap.o: ../../include/msg.h +proxymap.o: ../../include/myflock.h +proxymap.o: ../../include/mymalloc.h +proxymap.o: ../../include/nvtable.h +proxymap.o: ../../include/stringops.h +proxymap.o: ../../include/sys_defs.h +proxymap.o: ../../include/vbuf.h +proxymap.o: ../../include/vstream.h +proxymap.o: ../../include/vstring.h +proxymap.o: proxymap.c diff --git a/src/proxymap/proxymap.c b/src/proxymap/proxymap.c new file mode 100644 index 0000000..abdcf3a --- /dev/null +++ b/src/proxymap/proxymap.c @@ -0,0 +1,851 @@ +/*++ +/* NAME +/* proxymap 8 +/* SUMMARY +/* Postfix lookup table proxy server +/* SYNOPSIS +/* \fBproxymap\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBproxymap\fR(8) server provides read-only or read-write +/* table lookup service to Postfix processes. These services are +/* implemented with distinct service names: \fBproxymap\fR and +/* \fBproxywrite\fR, respectively. The purpose of these services is: +/* .IP \(bu +/* To overcome chroot restrictions. For example, a chrooted SMTP +/* server needs access to the system passwd file in order to +/* reject mail for non-existent local addresses, but it is not +/* practical to maintain a copy of the passwd file in the chroot +/* jail. The solution: +/* .sp +/* .nf +/* local_recipient_maps = +/* proxy:unix:passwd.byname $alias_maps +/* .fi +/* .IP \(bu +/* To consolidate the number of open lookup tables by sharing +/* one open table among multiple processes. For example, making +/* mysql connections from every Postfix daemon process results +/* in "too many connections" errors. The solution: +/* .sp +/* .nf +/* virtual_alias_maps = +/* proxy:mysql:/etc/postfix/virtual_alias.cf +/* .fi +/* .sp +/* The total number of connections is limited by the number of +/* proxymap server processes. +/* .IP \(bu +/* To provide single-updater functionality for lookup tables +/* that do not reliably support multiple writers (i.e. all +/* file-based tables). +/* .PP +/* The \fBproxymap\fR(8) server implements the following requests: +/* .IP "\fBopen\fR \fImaptype:mapname flags\fR" +/* Open the table with type \fImaptype\fR and name \fImapname\fR, +/* as controlled by \fIflags\fR. The reply includes the \fImaptype\fR +/* dependent flags (to distinguish a fixed string table from a regular +/* expression table). +/* .IP "\fBlookup\fR \fImaptype:mapname flags key\fR" +/* Look up the data stored under the requested key. +/* The reply is the request completion status code and +/* the lookup result value. +/* The \fImaptype:mapname\fR and \fIflags\fR are the same +/* as with the \fBopen\fR request. +/* .IP "\fBupdate\fR \fImaptype:mapname flags key value\fR" +/* Update the data stored under the requested key. +/* The reply is the request completion status code. +/* The \fImaptype:mapname\fR and \fIflags\fR are the same +/* as with the \fBopen\fR request. +/* .sp +/* To implement single-updater maps, specify a process limit +/* of 1 in the master.cf file entry for the \fBproxywrite\fR +/* service. +/* .sp +/* This request is supported in Postfix 2.5 and later. +/* .IP "\fBdelete\fR \fImaptype:mapname flags key\fR" +/* Delete the data stored under the requested key. +/* The reply is the request completion status code. +/* The \fImaptype:mapname\fR and \fIflags\fR are the same +/* as with the \fBopen\fR request. +/* .sp +/* This request is supported in Postfix 2.5 and later. +/* .IP "\fBsequence\fR \fImaptype:mapname flags function\fR" +/* Iterate over the specified database. The \fIfunction\fR +/* is one of DICT_SEQ_FUN_FIRST or DICT_SEQ_FUN_NEXT. +/* The reply is the request completion status code and +/* a lookup key and result value, if found. +/* .sp +/* This request is supported in Postfix 2.9 and later. +/* .PP +/* The request completion status is one of OK, RETRY, NOKEY +/* (lookup failed because the key was not found), BAD (malformed +/* request) or DENY (the table is not approved for proxy read +/* or update access). +/* +/* There is no \fBclose\fR command, nor are tables implicitly closed +/* when a client disconnects. The purpose is to share tables among +/* multiple client processes. +/* SERVER PROCESS MANAGEMENT +/* .ad +/* .fi +/* \fBproxymap\fR(8) servers run under control by the Postfix +/* \fBmaster\fR(8) +/* server. Each server can handle multiple simultaneous connections. +/* When all servers are busy while a client connects, the \fBmaster\fR(8) +/* creates a new \fBproxymap\fR(8) server process, provided that the +/* process limit is not exceeded. +/* Each server terminates after serving at least \fB$max_use\fR clients +/* or after \fB$max_idle\fR seconds of idle time. +/* SECURITY +/* .ad +/* .fi +/* The \fBproxymap\fR(8) server opens only tables that are +/* approved via the \fBproxy_read_maps\fR or \fBproxy_write_maps\fR +/* configuration parameters, does not talk to +/* users, and can run at fixed low privilege, chrooted or not. +/* However, running the proxymap server chrooted severely limits +/* usability, because it can open only chrooted tables. +/* +/* The \fBproxymap\fR(8) server is not a trusted daemon process, and must +/* not be used to look up sensitive information such as UNIX user or +/* group IDs, mailbox file/directory names or external commands. +/* +/* In Postfix version 2.2 and later, the proxymap client recognizes +/* requests to access a table for security-sensitive purposes, +/* and opens the table directly. This allows the same main.cf +/* setting to be used by sensitive and non-sensitive processes. +/* +/* Postfix-writable data files should be stored under a dedicated +/* directory that is writable only by the Postfix mail system, +/* such as the Postfix-owned \fBdata_directory\fR. +/* +/* In particular, Postfix-writable files should never exist +/* in root-owned directories. That would open up a particular +/* type of security hole where ownership of a file or directory +/* does not match the provider of its content. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* The \fBproxymap\fR(8) server provides service to multiple clients, +/* and must therefore not be used for tables that have high-latency +/* lookups. +/* +/* The \fBproxymap\fR(8) read-write service does not explicitly +/* close lookup tables (even if it did, this could not be relied on, +/* because the process may be terminated between table updates). +/* The read-write service should therefore not be used with tables that +/* leave persistent storage in an inconsistent state between +/* updates (for example, CDB). Tables that support "sync on +/* update" should be safe (for example, Berkeley DB) as should +/* tables that are implemented by a real DBMS. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* On busy mail systems a long time may pass before +/* \fBproxymap\fR(8) relevant +/* changes to \fBmain.cf\fR are picked up. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdata_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix-writable data files (for example: +/* caches, pseudo-random numbers). +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBproxy_read_maps (see 'postconf -d' output)\fR" +/* The lookup tables that the \fBproxymap\fR(8) server is allowed to +/* access for the read-only service. +/* .PP +/* Available in Postfix 2.5 and later: +/* .IP "\fBdata_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix-writable data files (for example: +/* caches, pseudo-random numbers). +/* .IP "\fBproxy_write_maps (see 'postconf -d' output)\fR" +/* The lookup tables that the \fBproxymap\fR(8) server is allowed to +/* access for the read-write service. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* DATABASE_README, Postfix lookup table overview +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* The proxymap service was introduced with Postfix 2.0. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Server skeleton. */ + +#include + +/* Application-specific. */ + + /* + * XXX All but the last are needed here so that $name expansion dependencies + * aren't too broken. The fix is to gather all parameter default settings in + * one place. + */ +char *var_alias_maps; +char *var_local_rcpt_maps; +char *var_virt_alias_maps; +char *var_virt_alias_doms; +char *var_virt_mailbox_maps; +char *var_virt_mailbox_doms; +char *var_relay_rcpt_maps; +char *var_canonical_maps; +char *var_send_canon_maps; +char *var_rcpt_canon_maps; +char *var_relocated_maps; +char *var_transport_maps; +char *var_verify_map; +char *var_smtpd_snd_auth_maps; +char *var_psc_cache_map; +char *var_proxy_read_maps; +char *var_proxy_write_maps; + + /* + * The pre-approved, pre-parsed list of maps. + */ +static HTABLE *proxy_auth_maps; + + /* + * Shared and static to reduce memory allocation overhead. + */ +static VSTRING *request; +static VSTRING *request_map; +static VSTRING *request_key; +static VSTRING *request_value; +static VSTRING *map_type_name_flags; + + /* + * Are we a proxy writer or not? + */ +static int proxy_writer; + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define VSTREQ(x,y) (strcmp(STR(x),y) == 0) + +/* get_nested_dict_name - return nested dictionary name pointer, or null */ + +static char *get_nested_dict_name(char *type_name) +{ + const struct { + const char *type_col; + ssize_t type_col_len; + } *prefix, prefixes[] = { + DICT_TYPE_UNION ":", (sizeof(DICT_TYPE_UNION ":") - 1), + DICT_TYPE_PIPE ":", (sizeof(DICT_TYPE_PIPE ":") - 1), + }; + +#define COUNT_OF(x) (sizeof(x)/sizeof((x)[0])) + + for (prefix = prefixes; prefix < prefixes + COUNT_OF(prefixes); prefix++) { + if (strncmp(type_name, prefix->type_col, prefix->type_col_len) == 0) + return (type_name + prefix->type_col_len); + } + return (0); +} + +/* proxy_map_find - look up or open table */ + +static DICT *proxy_map_find(const char *map_type_name, int request_flags, + int *statp) +{ + DICT *dict; + +#define PROXY_COLON DICT_TYPE_PROXY ":" +#define PROXY_COLON_LEN (sizeof(PROXY_COLON) - 1) +#define READ_OPEN_FLAGS O_RDONLY +#define WRITE_OPEN_FLAGS (O_RDWR | O_CREAT) + + /* + * Canonicalize the map name. If the map is not on the approved list, + * deny the request. + */ +#define PROXY_MAP_FIND_ERROR_RETURN(x) { *statp = (x); return (0); } +#define PROXY_MAP_PARAM_NAME(proxy_writer) \ + ((proxy_writer) == 0 ? VAR_PROXY_READ_MAPS : VAR_PROXY_WRITE_MAPS) + + while (strncmp(map_type_name, PROXY_COLON, PROXY_COLON_LEN) == 0) + map_type_name += PROXY_COLON_LEN; + /* XXX The following breaks with maps that have ':' in their name. */ + if (strchr(map_type_name, ':') == 0) + PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_BAD); + if (htable_locate(proxy_auth_maps, map_type_name) == 0) { + msg_warn("request for unapproved table: \"%s\"", map_type_name); + msg_warn("to approve this table for %s access, list %s:%s in %s:%s", + proxy_writer == 0 ? "read-only" : "read-write", + DICT_TYPE_PROXY, map_type_name, MAIN_CONF_FILE, + PROXY_MAP_PARAM_NAME(proxy_writer)); + PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_DENY); + } + + /* + * Open one instance of a map for each combination of name+flags. + * + * Assume that a map instance can be shared among clients with different + * paranoia flag settings and with different map lookup flag settings. + * + * XXX The open() flags are passed implicitly, via the selection of the + * service name. For a more sophisticated interface, appropriate subsets + * of open() flags should be received directly from the client. + */ + vstring_sprintf(map_type_name_flags, "%s:%s", map_type_name, + dict_flags_str(request_flags & DICT_FLAG_INST_MASK)); + if (msg_verbose) + msg_info("proxy_map_find: %s", STR(map_type_name_flags)); + if ((dict = dict_handle(STR(map_type_name_flags))) == 0) { + dict = dict_open(map_type_name, proxy_writer ? + WRITE_OPEN_FLAGS : READ_OPEN_FLAGS, + request_flags); + if (dict == 0) + msg_panic("proxy_map_find: dict_open null result"); + dict_register(STR(map_type_name_flags), dict); + } + dict->error = 0; + return (dict); +} + +/* proxymap_sequence_service - remote sequence service */ + +static void proxymap_sequence_service(VSTREAM *client_stream) +{ + int request_flags; + DICT *dict; + int request_func; + const char *reply_key; + const char *reply_value; + int dict_status; + int reply_status; + + /* + * Process the request. + */ + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), + RECV_ATTR_INT(MAIL_ATTR_FUNC, &request_func), + ATTR_TYPE_END) != 3 + || (request_func != DICT_SEQ_FUN_FIRST + && request_func != DICT_SEQ_FUN_NEXT)) { + reply_status = PROXY_STAT_BAD; + reply_key = reply_value = ""; + } else if ((dict = proxy_map_find(STR(request_map), request_flags, + &reply_status)) == 0) { + reply_key = reply_value = ""; + } else { + dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) + | (request_flags & DICT_FLAG_RQST_MASK)); + dict_status = dict_seq(dict, request_func, &reply_key, &reply_value); + if (dict_status == 0) { + reply_status = PROXY_STAT_OK; + } else if (dict->error == 0) { + reply_status = PROXY_STAT_NOKEY; + reply_key = reply_value = ""; + } else { + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); + reply_key = reply_value = ""; + } + } + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), + SEND_ATTR_STR(MAIL_ATTR_KEY, reply_key), + SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value), + ATTR_TYPE_END); +} + +/* proxymap_lookup_service - remote lookup service */ + +static void proxymap_lookup_service(VSTREAM *client_stream) +{ + int request_flags; + DICT *dict; + const char *reply_value; + int reply_status; + + /* + * Process the request. + */ + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), + RECV_ATTR_STR(MAIL_ATTR_KEY, request_key), + ATTR_TYPE_END) != 3) { + reply_status = PROXY_STAT_BAD; + reply_value = ""; + } else if ((dict = proxy_map_find(STR(request_map), request_flags, + &reply_status)) == 0) { + reply_value = ""; + } else if (dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) + | (request_flags & DICT_FLAG_RQST_MASK)), + (reply_value = dict_get(dict, STR(request_key))) != 0) { + reply_status = PROXY_STAT_OK; + } else if (dict->error == 0) { + reply_status = PROXY_STAT_NOKEY; + reply_value = ""; + } else { + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); + reply_value = ""; + } + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), + SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value), + ATTR_TYPE_END); +} + +/* proxymap_update_service - remote update service */ + +static void proxymap_update_service(VSTREAM *client_stream) +{ + int request_flags; + DICT *dict; + int dict_status; + int reply_status; + + /* + * Process the request. + * + * XXX We don't close maps, so we must turn on synchronous update to ensure + * that the on-disk data is in a consistent state between updates. + * + * XXX We ignore duplicates, because the proxymap server would abort + * otherwise. + */ + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), + RECV_ATTR_STR(MAIL_ATTR_KEY, request_key), + RECV_ATTR_STR(MAIL_ATTR_VALUE, request_value), + ATTR_TYPE_END) != 4) { + reply_status = PROXY_STAT_BAD; + } else if (proxy_writer == 0) { + msg_warn("refusing %s update request on non-%s service", + STR(request_map), MAIL_SERVICE_PROXYWRITE); + reply_status = PROXY_STAT_DENY; + } else if ((dict = proxy_map_find(STR(request_map), request_flags, + &reply_status)) == 0) { + /* void */ ; + } else { + dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) + | (request_flags & DICT_FLAG_RQST_MASK) + | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE); + dict_status = dict_put(dict, STR(request_key), STR(request_value)); + if (dict_status == 0) { + reply_status = PROXY_STAT_OK; + } else if (dict->error == 0) { + reply_status = PROXY_STAT_NOKEY; + } else { + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); + } + } + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), + ATTR_TYPE_END); +} + +/* proxymap_delete_service - remote delete service */ + +static void proxymap_delete_service(VSTREAM *client_stream) +{ + int request_flags; + DICT *dict; + int dict_status; + int reply_status; + + /* + * Process the request. + * + * XXX We don't close maps, so we must turn on synchronous update to ensure + * that the on-disk data is in a consistent state between updates. + */ + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), + RECV_ATTR_STR(MAIL_ATTR_KEY, request_key), + ATTR_TYPE_END) != 3) { + reply_status = PROXY_STAT_BAD; + } else if (proxy_writer == 0) { + msg_warn("refusing %s delete request on non-%s service", + STR(request_map), MAIL_SERVICE_PROXYWRITE); + reply_status = PROXY_STAT_DENY; + } else if ((dict = proxy_map_find(STR(request_map), request_flags, + &reply_status)) == 0) { + /* void */ ; + } else { + dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK) + | (request_flags & DICT_FLAG_RQST_MASK) + | DICT_FLAG_SYNC_UPDATE); + dict_status = dict_del(dict, STR(request_key)); + if (dict_status == 0) { + reply_status = PROXY_STAT_OK; + } else if (dict->error == 0) { + reply_status = PROXY_STAT_NOKEY; + } else { + reply_status = (dict->error == DICT_ERR_RETRY ? + PROXY_STAT_RETRY : PROXY_STAT_CONFIG); + } + } + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), + ATTR_TYPE_END); +} + +/* proxymap_open_service - open remote lookup table */ + +static void proxymap_open_service(VSTREAM *client_stream) +{ + int request_flags; + DICT *dict; + int reply_status; + int reply_flags; + + /* + * Process the request. + */ + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), + RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), + ATTR_TYPE_END) != 2) { + reply_status = PROXY_STAT_BAD; + reply_flags = 0; + } else if ((dict = proxy_map_find(STR(request_map), request_flags, + &reply_status)) == 0) { + reply_flags = 0; + } else { + reply_status = PROXY_STAT_OK; + reply_flags = dict->flags; + } + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), + SEND_ATTR_INT(MAIL_ATTR_FLAGS, reply_flags), + ATTR_TYPE_END); +} + +/* proxymap_service - perform service for client */ + +static void proxymap_service(VSTREAM *client_stream, char *unused_service, + char **argv) +{ + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Deadline enforcement. + */ + if (vstream_fstat(client_stream, VSTREAM_FLAG_DEADLINE) == 0) + vstream_control(client_stream, + CA_VSTREAM_CTL_TIMEOUT(1), + CA_VSTREAM_CTL_END); + + /* + * This routine runs whenever a client connects to the socket dedicated + * to the proxymap service. All connection-management stuff is handled by + * the common code in multi_server.c. + */ + vstream_control(client_stream, + CA_VSTREAM_CTL_START_DEADLINE, + CA_VSTREAM_CTL_END); + if (attr_scan(client_stream, + ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_REQ, request), + ATTR_TYPE_END) == 1) { + if (VSTREQ(request, PROXY_REQ_LOOKUP)) { + proxymap_lookup_service(client_stream); + } else if (VSTREQ(request, PROXY_REQ_UPDATE)) { + proxymap_update_service(client_stream); + } else if (VSTREQ(request, PROXY_REQ_DELETE)) { + proxymap_delete_service(client_stream); + } else if (VSTREQ(request, PROXY_REQ_SEQUENCE)) { + proxymap_sequence_service(client_stream); + } else if (VSTREQ(request, PROXY_REQ_OPEN)) { + proxymap_open_service(client_stream); + } else { + msg_warn("unrecognized request: \"%s\", ignored", STR(request)); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, PROXY_STAT_BAD), + ATTR_TYPE_END); + } + } + vstream_control(client_stream, + CA_VSTREAM_CTL_START_DEADLINE, + CA_VSTREAM_CTL_END); + vstream_fflush(client_stream); +} + +/* dict_proxy_open - intercept remote map request from inside library */ + +DICT *dict_proxy_open(const char *map, int open_flags, int dict_flags) +{ + if (msg_verbose) + msg_info("dict_proxy_open(%s, 0%o, 0%o) called from internal routine", + map, open_flags, dict_flags); + while (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) == 0) + map += PROXY_COLON_LEN; + return (dict_open(map, open_flags, dict_flags)); +} + +/* authorize_proxied_maps - recursively authorize maps */ + +static void authorize_proxied_maps(char *bp) +{ + const char *sep = CHARS_COMMA_SP; + const char *parens = CHARS_BRACE; + char *type_name; + + while ((type_name = mystrtokq(&bp, sep, parens)) != 0) { + char *nested_info; + + /* Maybe { maptype:mapname attr=value... } */ + if (*type_name == parens[0]) { + char *err; + + /* Warn about blatant syntax error. */ + if ((err = extpar(&type_name, parens, EXTPAR_FLAG_NONE)) != 0) { + msg_warn("bad %s parameter value: %s", + PROXY_MAP_PARAM_NAME(proxy_writer), err); + myfree(err); + continue; + } + /* Don't try to second-guess the semantics of { }. */ + if ((type_name = mystrtokq(&type_name, sep, parens)) == 0) + continue; + } + /* Recurse into nested map (pipemap, unionmap). */ + if ((nested_info = get_nested_dict_name(type_name)) != 0) { + char *err; + + if (*nested_info != parens[0]) + continue; + /* Warn about blatant syntax error. */ + if ((err = extpar(&nested_info, parens, EXTPAR_FLAG_NONE)) != 0) { + msg_warn("bad %s parameter value: %s", + PROXY_MAP_PARAM_NAME(proxy_writer), err); + myfree(err); + continue; + } + authorize_proxied_maps(nested_info); + continue; + } + if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN)) + continue; + do { + type_name += PROXY_COLON_LEN; + } while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN)); + if (strchr(type_name, ':') != 0 + && htable_locate(proxy_auth_maps, type_name) == 0) { + (void) htable_enter(proxy_auth_maps, type_name, (void *) 0); + if (msg_verbose) + msg_info("allowlisting %s from %s", type_name, + PROXY_MAP_PARAM_NAME(proxy_writer)); + } + } +} + +/* post_jail_init - initialization after privilege drop */ + +static void post_jail_init(char *service_name, char **unused_argv) +{ + char *saved_filter; + + /* + * Are we proxy writer? + */ + if (strcmp(service_name, MAIL_SERVICE_PROXYWRITE) == 0) + proxy_writer = 1; + else if (strcmp(service_name, MAIL_SERVICE_PROXYMAP) != 0) + msg_fatal("service name must be one of %s or %s", + MAIL_SERVICE_PROXYMAP, MAIL_SERVICE_PROXYMAP); + + /* + * Pre-allocate buffers. + */ + request = vstring_alloc(10); + request_map = vstring_alloc(10); + request_key = vstring_alloc(10); + request_value = vstring_alloc(10); + map_type_name_flags = vstring_alloc(10); + + /* + * Prepare the pre-approved list of proxied tables. + */ + saved_filter = mystrdup(proxy_writer ? var_proxy_write_maps : + var_proxy_read_maps); + proxy_auth_maps = htable_create(13); + authorize_proxied_maps(saved_filter); + myfree(saved_filter); + + /* + * Never, ever, get killed by a master signal, as that could corrupt a + * persistent database when we're in the middle of an update. + */ + if (proxy_writer != 0) + setsid(); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if (proxy_writer == 0 && (table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* post_accept - announce our protocol name */ + +static void post_accept(VSTREAM *stream, char *unused_name, char **unused_argv, + HTABLE *unused_attr) +{ + + /* + * Announce the protocol. + */ + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_PROXYMAP), + ATTR_TYPE_END); + (void) vstream_fflush(stream); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the multi-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_STR_TABLE str_table[] = { + VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0, + VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0, + VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0, + VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0, + VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0, + VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0, + VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0, + VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0, + VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0, + VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0, + VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0, + VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0, + VAR_VERIFY_MAP, DEF_VERIFY_MAP, &var_verify_map, 0, 0, + VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0, + VAR_PSC_CACHE_MAP, DEF_PSC_CACHE_MAP, &var_psc_cache_map, 0, 0, + /* The following two must be last for $mapname to work as expected. */ + VAR_PROXY_READ_MAPS, DEF_PROXY_READ_MAPS, &var_proxy_read_maps, 0, 0, + VAR_PROXY_WRITE_MAPS, DEF_PROXY_WRITE_MAPS, &var_proxy_write_maps, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + multi_server_main(argc, argv, proxymap_service, + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_POST_ACCEPT(post_accept), + /* XXX CA_MAIL_SERVER_SOLITARY if proxywrite */ + 0); +} diff --git a/src/qmgr/.indent.pro b/src/qmgr/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/qmgr/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/qmgr/.printfck b/src/qmgr/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/qmgr/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/qmgr/Makefile.in b/src/qmgr/Makefile.in new file mode 100644 index 0000000..79d92b2 --- /dev/null +++ b/src/qmgr/Makefile.in @@ -0,0 +1,390 @@ +SHELL = /bin/sh +SRCS = qmgr.c qmgr_active.c qmgr_transport.c qmgr_queue.c qmgr_entry.c \ + qmgr_message.c qmgr_deliver.c qmgr_move.c \ + qmgr_job.c qmgr_peer.c \ + qmgr_defer.c qmgr_enable.c qmgr_scan.c qmgr_bounce.c qmgr_error.c \ + qmgr_feedback.c +OBJS = qmgr.o qmgr_active.o qmgr_transport.o qmgr_queue.o qmgr_entry.o \ + qmgr_message.o qmgr_deliver.o qmgr_move.o \ + qmgr_job.o qmgr_peer.o \ + qmgr_defer.o qmgr_enable.o qmgr_scan.o qmgr_bounce.o qmgr_error.o \ + qmgr_feedback.o +HDRS = qmgr.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = qmgr +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec/$(PROG) + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +qmgr.o: ../../include/argv.h +qmgr.o: ../../include/attr.h +qmgr.o: ../../include/check_arg.h +qmgr.o: ../../include/dict.h +qmgr.o: ../../include/dsn.h +qmgr.o: ../../include/events.h +qmgr.o: ../../include/flush_clnt.h +qmgr.o: ../../include/htable.h +qmgr.o: ../../include/iostuff.h +qmgr.o: ../../include/mail_conf.h +qmgr.o: ../../include/mail_flow.h +qmgr.o: ../../include/mail_params.h +qmgr.o: ../../include/mail_proto.h +qmgr.o: ../../include/mail_queue.h +qmgr.o: ../../include/mail_server.h +qmgr.o: ../../include/mail_version.h +qmgr.o: ../../include/master_proto.h +qmgr.o: ../../include/msg.h +qmgr.o: ../../include/myflock.h +qmgr.o: ../../include/mymalloc.h +qmgr.o: ../../include/nvtable.h +qmgr.o: ../../include/recipient_list.h +qmgr.o: ../../include/scan_dir.h +qmgr.o: ../../include/sys_defs.h +qmgr.o: ../../include/vbuf.h +qmgr.o: ../../include/vstream.h +qmgr.o: ../../include/vstring.h +qmgr.o: qmgr.c +qmgr.o: qmgr.h +qmgr_active.o: ../../include/abounce.h +qmgr_active.o: ../../include/attr.h +qmgr_active.o: ../../include/bounce.h +qmgr_active.o: ../../include/check_arg.h +qmgr_active.o: ../../include/defer.h +qmgr_active.o: ../../include/deliver_request.h +qmgr_active.o: ../../include/dsn.h +qmgr_active.o: ../../include/dsn_buf.h +qmgr_active.o: ../../include/dsn_mask.h +qmgr_active.o: ../../include/events.h +qmgr_active.o: ../../include/htable.h +qmgr_active.o: ../../include/info_log_addr_form.h +qmgr_active.o: ../../include/mail_open_ok.h +qmgr_active.o: ../../include/mail_params.h +qmgr_active.o: ../../include/mail_queue.h +qmgr_active.o: ../../include/msg.h +qmgr_active.o: ../../include/msg_stats.h +qmgr_active.o: ../../include/mymalloc.h +qmgr_active.o: ../../include/nvtable.h +qmgr_active.o: ../../include/qmgr_user.h +qmgr_active.o: ../../include/rec_type.h +qmgr_active.o: ../../include/recipient_list.h +qmgr_active.o: ../../include/scan_dir.h +qmgr_active.o: ../../include/sys_defs.h +qmgr_active.o: ../../include/trace.h +qmgr_active.o: ../../include/vbuf.h +qmgr_active.o: ../../include/vstream.h +qmgr_active.o: ../../include/vstring.h +qmgr_active.o: ../../include/warn_stat.h +qmgr_active.o: qmgr.h +qmgr_active.o: qmgr_active.c +qmgr_bounce.o: ../../include/attr.h +qmgr_bounce.o: ../../include/bounce.h +qmgr_bounce.o: ../../include/check_arg.h +qmgr_bounce.o: ../../include/deliver_completed.h +qmgr_bounce.o: ../../include/deliver_request.h +qmgr_bounce.o: ../../include/dsn.h +qmgr_bounce.o: ../../include/dsn_buf.h +qmgr_bounce.o: ../../include/htable.h +qmgr_bounce.o: ../../include/msg_stats.h +qmgr_bounce.o: ../../include/mymalloc.h +qmgr_bounce.o: ../../include/nvtable.h +qmgr_bounce.o: ../../include/recipient_list.h +qmgr_bounce.o: ../../include/scan_dir.h +qmgr_bounce.o: ../../include/sys_defs.h +qmgr_bounce.o: ../../include/vbuf.h +qmgr_bounce.o: ../../include/vstream.h +qmgr_bounce.o: ../../include/vstring.h +qmgr_bounce.o: qmgr.h +qmgr_bounce.o: qmgr_bounce.c +qmgr_defer.o: ../../include/attr.h +qmgr_defer.o: ../../include/bounce.h +qmgr_defer.o: ../../include/check_arg.h +qmgr_defer.o: ../../include/defer.h +qmgr_defer.o: ../../include/deliver_request.h +qmgr_defer.o: ../../include/dsn.h +qmgr_defer.o: ../../include/dsn_buf.h +qmgr_defer.o: ../../include/htable.h +qmgr_defer.o: ../../include/iostuff.h +qmgr_defer.o: ../../include/mail_proto.h +qmgr_defer.o: ../../include/msg.h +qmgr_defer.o: ../../include/msg_stats.h +qmgr_defer.o: ../../include/mymalloc.h +qmgr_defer.o: ../../include/nvtable.h +qmgr_defer.o: ../../include/recipient_list.h +qmgr_defer.o: ../../include/scan_dir.h +qmgr_defer.o: ../../include/sys_defs.h +qmgr_defer.o: ../../include/vbuf.h +qmgr_defer.o: ../../include/vstream.h +qmgr_defer.o: ../../include/vstring.h +qmgr_defer.o: qmgr.h +qmgr_defer.o: qmgr_defer.c +qmgr_deliver.o: ../../include/attr.h +qmgr_deliver.o: ../../include/check_arg.h +qmgr_deliver.o: ../../include/deliver_request.h +qmgr_deliver.o: ../../include/dsb_scan.h +qmgr_deliver.o: ../../include/dsn.h +qmgr_deliver.o: ../../include/dsn_buf.h +qmgr_deliver.o: ../../include/dsn_util.h +qmgr_deliver.o: ../../include/events.h +qmgr_deliver.o: ../../include/htable.h +qmgr_deliver.o: ../../include/iostuff.h +qmgr_deliver.o: ../../include/mail_params.h +qmgr_deliver.o: ../../include/mail_proto.h +qmgr_deliver.o: ../../include/mail_queue.h +qmgr_deliver.o: ../../include/msg.h +qmgr_deliver.o: ../../include/msg_stats.h +qmgr_deliver.o: ../../include/mymalloc.h +qmgr_deliver.o: ../../include/nvtable.h +qmgr_deliver.o: ../../include/rcpt_print.h +qmgr_deliver.o: ../../include/recipient_list.h +qmgr_deliver.o: ../../include/scan_dir.h +qmgr_deliver.o: ../../include/smtputf8.h +qmgr_deliver.o: ../../include/stringops.h +qmgr_deliver.o: ../../include/sys_defs.h +qmgr_deliver.o: ../../include/vbuf.h +qmgr_deliver.o: ../../include/verp_sender.h +qmgr_deliver.o: ../../include/vstream.h +qmgr_deliver.o: ../../include/vstring.h +qmgr_deliver.o: ../../include/vstring_vstream.h +qmgr_deliver.o: qmgr.h +qmgr_deliver.o: qmgr_deliver.c +qmgr_enable.o: ../../include/check_arg.h +qmgr_enable.o: ../../include/dsn.h +qmgr_enable.o: ../../include/msg.h +qmgr_enable.o: ../../include/recipient_list.h +qmgr_enable.o: ../../include/scan_dir.h +qmgr_enable.o: ../../include/sys_defs.h +qmgr_enable.o: ../../include/vbuf.h +qmgr_enable.o: ../../include/vstream.h +qmgr_enable.o: qmgr.h +qmgr_enable.o: qmgr_enable.c +qmgr_entry.o: ../../include/attr.h +qmgr_entry.o: ../../include/check_arg.h +qmgr_entry.o: ../../include/deliver_request.h +qmgr_entry.o: ../../include/dsn.h +qmgr_entry.o: ../../include/events.h +qmgr_entry.o: ../../include/htable.h +qmgr_entry.o: ../../include/mail_params.h +qmgr_entry.o: ../../include/msg.h +qmgr_entry.o: ../../include/msg_stats.h +qmgr_entry.o: ../../include/mymalloc.h +qmgr_entry.o: ../../include/nvtable.h +qmgr_entry.o: ../../include/recipient_list.h +qmgr_entry.o: ../../include/scan_dir.h +qmgr_entry.o: ../../include/sys_defs.h +qmgr_entry.o: ../../include/vbuf.h +qmgr_entry.o: ../../include/vstream.h +qmgr_entry.o: ../../include/vstring.h +qmgr_entry.o: qmgr.h +qmgr_entry.o: qmgr_entry.c +qmgr_error.o: ../../include/check_arg.h +qmgr_error.o: ../../include/dsn.h +qmgr_error.o: ../../include/mymalloc.h +qmgr_error.o: ../../include/recipient_list.h +qmgr_error.o: ../../include/scan_dir.h +qmgr_error.o: ../../include/stringops.h +qmgr_error.o: ../../include/sys_defs.h +qmgr_error.o: ../../include/vbuf.h +qmgr_error.o: ../../include/vstream.h +qmgr_error.o: ../../include/vstring.h +qmgr_error.o: qmgr.h +qmgr_error.o: qmgr_error.c +qmgr_feedback.o: ../../include/check_arg.h +qmgr_feedback.o: ../../include/dsn.h +qmgr_feedback.o: ../../include/mail_conf.h +qmgr_feedback.o: ../../include/mail_params.h +qmgr_feedback.o: ../../include/msg.h +qmgr_feedback.o: ../../include/mymalloc.h +qmgr_feedback.o: ../../include/name_code.h +qmgr_feedback.o: ../../include/recipient_list.h +qmgr_feedback.o: ../../include/scan_dir.h +qmgr_feedback.o: ../../include/stringops.h +qmgr_feedback.o: ../../include/sys_defs.h +qmgr_feedback.o: ../../include/vbuf.h +qmgr_feedback.o: ../../include/vstream.h +qmgr_feedback.o: ../../include/vstring.h +qmgr_feedback.o: qmgr.h +qmgr_feedback.o: qmgr_feedback.c +qmgr_job.o: ../../include/check_arg.h +qmgr_job.o: ../../include/dsn.h +qmgr_job.o: ../../include/htable.h +qmgr_job.o: ../../include/msg.h +qmgr_job.o: ../../include/mymalloc.h +qmgr_job.o: ../../include/recipient_list.h +qmgr_job.o: ../../include/sane_time.h +qmgr_job.o: ../../include/scan_dir.h +qmgr_job.o: ../../include/sys_defs.h +qmgr_job.o: ../../include/vbuf.h +qmgr_job.o: ../../include/vstream.h +qmgr_job.o: qmgr.h +qmgr_job.o: qmgr_job.c +qmgr_message.o: ../../include/argv.h +qmgr_message.o: ../../include/attr.h +qmgr_message.o: ../../include/bounce.h +qmgr_message.o: ../../include/canon_addr.h +qmgr_message.o: ../../include/check_arg.h +qmgr_message.o: ../../include/deliver_completed.h +qmgr_message.o: ../../include/deliver_request.h +qmgr_message.o: ../../include/dict.h +qmgr_message.o: ../../include/dsn.h +qmgr_message.o: ../../include/dsn_buf.h +qmgr_message.o: ../../include/dsn_mask.h +qmgr_message.o: ../../include/htable.h +qmgr_message.o: ../../include/iostuff.h +qmgr_message.o: ../../include/mail_params.h +qmgr_message.o: ../../include/mail_proto.h +qmgr_message.o: ../../include/mail_queue.h +qmgr_message.o: ../../include/msg.h +qmgr_message.o: ../../include/msg_stats.h +qmgr_message.o: ../../include/myflock.h +qmgr_message.o: ../../include/mymalloc.h +qmgr_message.o: ../../include/nvtable.h +qmgr_message.o: ../../include/opened.h +qmgr_message.o: ../../include/qmgr_user.h +qmgr_message.o: ../../include/rec_attr_map.h +qmgr_message.o: ../../include/rec_type.h +qmgr_message.o: ../../include/recipient_list.h +qmgr_message.o: ../../include/record.h +qmgr_message.o: ../../include/resolve_clnt.h +qmgr_message.o: ../../include/rewrite_clnt.h +qmgr_message.o: ../../include/sane_time.h +qmgr_message.o: ../../include/scan_dir.h +qmgr_message.o: ../../include/sent.h +qmgr_message.o: ../../include/split_addr.h +qmgr_message.o: ../../include/split_at.h +qmgr_message.o: ../../include/stringops.h +qmgr_message.o: ../../include/sys_defs.h +qmgr_message.o: ../../include/valid_hostname.h +qmgr_message.o: ../../include/vbuf.h +qmgr_message.o: ../../include/verp_sender.h +qmgr_message.o: ../../include/vstream.h +qmgr_message.o: ../../include/vstring.h +qmgr_message.o: qmgr.h +qmgr_message.o: qmgr_message.c +qmgr_move.o: ../../include/check_arg.h +qmgr_move.o: ../../include/dsn.h +qmgr_move.o: ../../include/mail_queue.h +qmgr_move.o: ../../include/mail_scan_dir.h +qmgr_move.o: ../../include/msg.h +qmgr_move.o: ../../include/recipient_list.h +qmgr_move.o: ../../include/scan_dir.h +qmgr_move.o: ../../include/sys_defs.h +qmgr_move.o: ../../include/vbuf.h +qmgr_move.o: ../../include/vstream.h +qmgr_move.o: ../../include/vstring.h +qmgr_move.o: qmgr.h +qmgr_move.o: qmgr_move.c +qmgr_peer.o: ../../include/check_arg.h +qmgr_peer.o: ../../include/dsn.h +qmgr_peer.o: ../../include/htable.h +qmgr_peer.o: ../../include/msg.h +qmgr_peer.o: ../../include/mymalloc.h +qmgr_peer.o: ../../include/recipient_list.h +qmgr_peer.o: ../../include/scan_dir.h +qmgr_peer.o: ../../include/sys_defs.h +qmgr_peer.o: ../../include/vbuf.h +qmgr_peer.o: ../../include/vstream.h +qmgr_peer.o: qmgr.h +qmgr_peer.o: qmgr_peer.c +qmgr_queue.o: ../../include/attr.h +qmgr_queue.o: ../../include/check_arg.h +qmgr_queue.o: ../../include/dsn.h +qmgr_queue.o: ../../include/events.h +qmgr_queue.o: ../../include/htable.h +qmgr_queue.o: ../../include/iostuff.h +qmgr_queue.o: ../../include/mail_params.h +qmgr_queue.o: ../../include/mail_proto.h +qmgr_queue.o: ../../include/msg.h +qmgr_queue.o: ../../include/mymalloc.h +qmgr_queue.o: ../../include/nvtable.h +qmgr_queue.o: ../../include/recipient_list.h +qmgr_queue.o: ../../include/scan_dir.h +qmgr_queue.o: ../../include/sys_defs.h +qmgr_queue.o: ../../include/vbuf.h +qmgr_queue.o: ../../include/vstream.h +qmgr_queue.o: ../../include/vstring.h +qmgr_queue.o: qmgr.h +qmgr_queue.o: qmgr_queue.c +qmgr_scan.o: ../../include/check_arg.h +qmgr_scan.o: ../../include/dsn.h +qmgr_scan.o: ../../include/mail_scan_dir.h +qmgr_scan.o: ../../include/msg.h +qmgr_scan.o: ../../include/mymalloc.h +qmgr_scan.o: ../../include/recipient_list.h +qmgr_scan.o: ../../include/scan_dir.h +qmgr_scan.o: ../../include/sys_defs.h +qmgr_scan.o: ../../include/vbuf.h +qmgr_scan.o: ../../include/vstream.h +qmgr_scan.o: qmgr.h +qmgr_scan.o: qmgr_scan.c +qmgr_transport.o: ../../include/attr.h +qmgr_transport.o: ../../include/check_arg.h +qmgr_transport.o: ../../include/dsn.h +qmgr_transport.o: ../../include/events.h +qmgr_transport.o: ../../include/htable.h +qmgr_transport.o: ../../include/iostuff.h +qmgr_transport.o: ../../include/mail_conf.h +qmgr_transport.o: ../../include/mail_params.h +qmgr_transport.o: ../../include/mail_proto.h +qmgr_transport.o: ../../include/msg.h +qmgr_transport.o: ../../include/mymalloc.h +qmgr_transport.o: ../../include/nvtable.h +qmgr_transport.o: ../../include/recipient_list.h +qmgr_transport.o: ../../include/scan_dir.h +qmgr_transport.o: ../../include/sys_defs.h +qmgr_transport.o: ../../include/vbuf.h +qmgr_transport.o: ../../include/vstream.h +qmgr_transport.o: ../../include/vstring.h +qmgr_transport.o: qmgr.h +qmgr_transport.o: qmgr_transport.c diff --git a/src/qmgr/qmgr.c b/src/qmgr/qmgr.c new file mode 100644 index 0000000..9d90a6e --- /dev/null +++ b/src/qmgr/qmgr.c @@ -0,0 +1,827 @@ +/*++ +/* NAME +/* qmgr 8 +/* SUMMARY +/* Postfix queue manager +/* SYNOPSIS +/* \fBqmgr\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBqmgr\fR(8) daemon awaits the arrival of incoming mail +/* and arranges for its delivery via Postfix delivery processes. +/* The actual mail routing strategy is delegated to the +/* \fBtrivial-rewrite\fR(8) daemon. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* Mail addressed to the local \fBdouble-bounce\fR address is +/* logged and discarded. This stops potential loops caused by +/* undeliverable bounce notifications. +/* MAIL QUEUES +/* .ad +/* .fi +/* The \fBqmgr\fR(8) daemon maintains the following queues: +/* .IP \fBincoming\fR +/* Inbound mail from the network, or mail picked up by the +/* local \fBpickup\fR(8) daemon from the \fBmaildrop\fR directory. +/* .IP \fBactive\fR +/* Messages that the queue manager has opened for delivery. Only +/* a limited number of messages is allowed to enter the \fBactive\fR +/* queue (leaky bucket strategy, for a fixed delivery rate). +/* .IP \fBdeferred\fR +/* Mail that could not be delivered upon the first attempt. The queue +/* manager implements exponential backoff by doubling the time between +/* delivery attempts. +/* .IP \fBcorrupt\fR +/* Unreadable or damaged queue files are moved here for inspection. +/* .IP \fBhold\fR +/* Messages that are kept "on hold" are kept here until someone +/* sets them free. +/* DELIVERY STATUS REPORTS +/* .ad +/* .fi +/* The \fBqmgr\fR(8) daemon keeps an eye on per-message delivery status +/* reports in the following directories. Each status report file has +/* the same name as the corresponding message file: +/* .IP \fBbounce\fR +/* Per-recipient status information about why mail is bounced. +/* These files are maintained by the \fBbounce\fR(8) daemon. +/* .IP \fBdefer\fR +/* Per-recipient status information about why mail is delayed. +/* These files are maintained by the \fBdefer\fR(8) daemon. +/* .IP \fBtrace\fR +/* Per-recipient status information as requested with the +/* Postfix "\fBsendmail -v\fR" or "\fBsendmail -bv\fR" command. +/* These files are maintained by the \fBtrace\fR(8) daemon. +/* .PP +/* The \fBqmgr\fR(8) daemon is responsible for asking the +/* \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemons to +/* send delivery reports. +/* STRATEGIES +/* .ad +/* .fi +/* The queue manager implements a variety of strategies for +/* either opening queue files (input) or for message delivery (output). +/* .IP "\fBleaky bucket\fR" +/* This strategy limits the number of messages in the \fBactive\fR queue +/* and prevents the queue manager from running out of memory under +/* heavy load. +/* .IP \fBfairness\fR +/* When the \fBactive\fR queue has room, the queue manager takes one +/* message from the \fBincoming\fR queue and one from the \fBdeferred\fR +/* queue. This prevents a large mail backlog from blocking the delivery +/* of new mail. +/* .IP "\fBslow start\fR" +/* This strategy eliminates "thundering herd" problems by slowly +/* adjusting the number of parallel deliveries to the same destination. +/* .IP "\fBround robin\fR" +/* The queue manager sorts delivery requests by destination. +/* Round-robin selection prevents one destination from dominating +/* deliveries to other destinations. +/* .IP "\fBexponential backoff\fR" +/* Mail that cannot be delivered upon the first attempt is deferred. +/* The time interval between delivery attempts is doubled after each +/* attempt. +/* .IP "\fBdestination status cache\fR" +/* The queue manager avoids unnecessary delivery attempts by +/* maintaining a short-term, in-memory list of unreachable destinations. +/* .IP "\fBpreemptive message scheduling\fR" +/* The queue manager attempts to minimize the average per-recipient delay +/* while still preserving the correct per-message delays, using +/* a sophisticated preemptive message scheduling. +/* TRIGGERS +/* .ad +/* .fi +/* On an idle system, the queue manager waits for the arrival of +/* trigger events, or it waits for a timer to go off. A trigger +/* is a one-byte message. +/* Depending on the message received, the queue manager performs +/* one of the following actions (the message is followed by the +/* symbolic constant used internally by the software): +/* .IP "\fBD (QMGR_REQ_SCAN_DEFERRED)\fR" +/* Start a deferred queue scan. If a deferred queue scan is already +/* in progress, that scan will be restarted as soon as it finishes. +/* .IP "\fBI (QMGR_REQ_SCAN_INCOMING)\fR" +/* Start an incoming queue scan. If an incoming queue scan is already +/* in progress, that scan will be restarted as soon as it finishes. +/* .IP "\fBA (QMGR_REQ_SCAN_ALL)\fR" +/* Ignore deferred queue file time stamps. The request affects +/* the next deferred queue scan. +/* .IP "\fBF (QMGR_REQ_FLUSH_DEAD)\fR" +/* Purge all information about dead transports and destinations. +/* .IP "\fBW (TRIGGER_REQ_WAKEUP)\fR" +/* Wakeup call, This is used by the master server to instantiate +/* servers that should not go away forever. The action is to start +/* an incoming queue scan. +/* .PP +/* The \fBqmgr\fR(8) daemon reads an entire buffer worth of triggers. +/* Multiple identical trigger requests are collapsed into one, and +/* trigger requests are sorted so that \fBA\fR and \fBF\fR precede +/* \fBD\fR and \fBI\fR. Thus, in order to force a deferred queue run, +/* one would request \fBA F D\fR; in order to notify the queue manager +/* of the arrival of new mail one would request \fBI\fR. +/* STANDARDS +/* RFC 3463 (Enhanced status codes) +/* RFC 3464 (Delivery status notifications) +/* SECURITY +/* .ad +/* .fi +/* The \fBqmgr\fR(8) daemon is not security sensitive. It reads +/* single-character messages from untrusted local users, and thus may +/* be susceptible to denial of service attacks. The \fBqmgr\fR(8) daemon +/* does not talk to the outside world, and it can be run at fixed low +/* privilege in a chrooted environment. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* Corrupted message files are saved to the \fBcorrupt\fR queue +/* for further inspection. +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces and of other trouble. +/* BUGS +/* A single queue manager process has to compete for disk access with +/* multiple front-end processes such as \fBcleanup\fR(8). A sudden burst of +/* inbound mail can negatively impact outbound delivery rates. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are not picked up automatically +/* as \fBqmgr\fR(8) +/* is a persistent process. Use the "\fBpostfix reload\fR" command after +/* a configuration change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* +/* In the text below, \fItransport\fR is the first field in a +/* \fBmaster.cf\fR entry. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* Available before Postfix version 2.5: +/* .IP "\fBallow_min_user (no)\fR" +/* Allow a sender or recipient address to have `-' as the first +/* character. +/* .PP +/* Available with Postfix version 2.7 and later: +/* .IP "\fBdefault_filter_nexthop (empty)\fR" +/* When a content_filter or FILTER request specifies no explicit +/* next-hop destination, use $default_filter_nexthop instead; when +/* that value is empty, use the domain in the recipient address. +/* ACTIVE QUEUE CONTROLS +/* .ad +/* .fi +/* .IP "\fBqmgr_clog_warn_time (300s)\fR" +/* The minimal delay between warnings that a specific destination is +/* clogging up the Postfix active queue. +/* .IP "\fBqmgr_message_active_limit (20000)\fR" +/* The maximal number of messages in the active queue. +/* .IP "\fBqmgr_message_recipient_limit (20000)\fR" +/* The maximal number of recipients held in memory by the Postfix +/* queue manager, and the maximal size of the short-term, +/* in-memory "dead" destination status cache. +/* .IP "\fBqmgr_message_recipient_minimum (10)\fR" +/* The minimal number of in-memory recipients for any message. +/* .IP "\fBdefault_recipient_limit (20000)\fR" +/* The default per-transport upper limit on the number of in-memory +/* recipients. +/* .IP "\fBtransport_recipient_limit ($default_recipient_limit)\fR" +/* A transport-specific override for the default_recipient_limit +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* .IP "\fBdefault_extra_recipient_limit (1000)\fR" +/* The default value for the extra per-transport limit imposed on the +/* number of in-memory recipients. +/* .IP "\fBtransport_extra_recipient_limit ($default_extra_recipient_limit)\fR" +/* A transport-specific override for the default_extra_recipient_limit +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* .PP +/* Available in Postfix version 2.4 and later: +/* .IP "\fBdefault_recipient_refill_limit (100)\fR" +/* The default per-transport limit on the number of recipients refilled at +/* once. +/* .IP "\fBtransport_recipient_refill_limit ($default_recipient_refill_limit)\fR" +/* A transport-specific override for the default_recipient_refill_limit +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* .IP "\fBdefault_recipient_refill_delay (5s)\fR" +/* The default per-transport maximum delay between recipients refills. +/* .IP "\fBtransport_recipient_refill_delay ($default_recipient_refill_delay)\fR" +/* A transport-specific override for the default_recipient_refill_delay +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* DELIVERY CONCURRENCY CONTROLS +/* .ad +/* .fi +/* .IP "\fBinitial_destination_concurrency (5)\fR" +/* The initial per-destination concurrency level for parallel delivery +/* to the same destination. +/* .IP "\fBdefault_destination_concurrency_limit (20)\fR" +/* The default maximal number of parallel deliveries to the same +/* destination. +/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBtransport_initial_destination_concurrency ($initial_destination_concurrency)\fR" +/* A transport-specific override for the initial_destination_concurrency +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* .IP "\fBdefault_destination_concurrency_failed_cohort_limit (1)\fR" +/* How many pseudo-cohorts must suffer connection or handshake +/* failure before a specific destination is considered unavailable +/* (and further delivery is suspended). +/* .IP "\fBtransport_destination_concurrency_failed_cohort_limit ($default_destination_concurrency_failed_cohort_limit)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_failed_cohort_limit parameter value, +/* where \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .IP "\fBdefault_destination_concurrency_negative_feedback (1)\fR" +/* The per-destination amount of delivery concurrency negative +/* feedback, after a delivery completes with a connection or handshake +/* failure. +/* .IP "\fBtransport_destination_concurrency_negative_feedback ($default_destination_concurrency_negative_feedback)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_negative_feedback parameter value, +/* where \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .IP "\fBdefault_destination_concurrency_positive_feedback (1)\fR" +/* The per-destination amount of delivery concurrency positive +/* feedback, after a delivery completes without connection or handshake +/* failure. +/* .IP "\fBtransport_destination_concurrency_positive_feedback ($default_destination_concurrency_positive_feedback)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_positive_feedback parameter value, +/* where \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .IP "\fBdestination_concurrency_feedback_debug (no)\fR" +/* Make the queue manager's feedback algorithm verbose for performance +/* analysis purposes. +/* RECIPIENT SCHEDULING CONTROLS +/* .ad +/* .fi +/* .IP "\fBdefault_destination_recipient_limit (50)\fR" +/* The default maximal number of recipients per message delivery. +/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR" +/* A transport-specific override for the +/* default_destination_recipient_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* MESSAGE SCHEDULING CONTROLS +/* .ad +/* .fi +/* .IP "\fBdefault_delivery_slot_cost (5)\fR" +/* How often the Postfix queue manager's scheduler is allowed to +/* preempt delivery of one message with another. +/* .IP "\fBtransport_delivery_slot_cost ($default_delivery_slot_cost)\fR" +/* A transport-specific override for the default_delivery_slot_cost +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* .IP "\fBdefault_minimum_delivery_slots (3)\fR" +/* How many recipients a message must have in order to invoke the +/* Postfix queue manager's scheduling algorithm at all. +/* .IP "\fBtransport_minimum_delivery_slots ($default_minimum_delivery_slots)\fR" +/* A transport-specific override for the default_minimum_delivery_slots +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* .IP "\fBdefault_delivery_slot_discount (50)\fR" +/* The default value for transport-specific _delivery_slot_discount +/* settings. +/* .IP "\fBtransport_delivery_slot_discount ($default_delivery_slot_discount)\fR" +/* A transport-specific override for the default_delivery_slot_discount +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* .IP "\fBdefault_delivery_slot_loan (3)\fR" +/* The default value for transport-specific _delivery_slot_loan +/* settings. +/* .IP "\fBtransport_delivery_slot_loan ($default_delivery_slot_loan)\fR" +/* A transport-specific override for the default_delivery_slot_loan +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* OTHER RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBminimal_backoff_time (300s)\fR" +/* The minimal time between attempts to deliver a deferred message; +/* prior to Postfix 2.4 the default value was 1000s. +/* .IP "\fBmaximal_backoff_time (4000s)\fR" +/* The maximal time between attempts to deliver a deferred message. +/* .IP "\fBmaximal_queue_lifetime (5d)\fR" +/* Consider a message as undeliverable, when delivery fails with a +/* temporary error, and the time in the queue has reached the +/* maximal_queue_lifetime limit. +/* .IP "\fBqueue_run_delay (300s)\fR" +/* The time between deferred queue scans by the queue manager; +/* prior to Postfix 2.4 the default value was 1000s. +/* .IP "\fBtransport_retry_time (60s)\fR" +/* The time between attempts by the Postfix queue manager to contact +/* a malfunctioning message delivery transport. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBbounce_queue_lifetime (5d)\fR" +/* Consider a bounce message as undeliverable, when delivery fails +/* with a temporary error, and the time in the queue has reached the +/* bounce_queue_lifetime limit. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBdefault_destination_rate_delay (0s)\fR" +/* The default amount of delay that is inserted between individual +/* message deliveries to the same destination and over the same message +/* delivery transport. +/* .IP "\fBtransport_destination_rate_delay ($default_destination_rate_delay)\fR" +/* A transport-specific override for the default_destination_rate_delay +/* parameter value, where \fItransport\fR is the master.cf name of +/* the message delivery transport. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBdefault_transport_rate_delay (0s)\fR" +/* The default amount of delay that is inserted between individual +/* message deliveries over the same message delivery transport, +/* regardless of destination. +/* .IP "\fBtransport_transport_rate_delay ($default_transport_rate_delay)\fR" +/* A transport-specific override for the default_transport_rate_delay +/* parameter value, where the initial \fItransport\fR in the parameter +/* name is the master.cf name of the message delivery transport. +/* SAFETY CONTROLS +/* .ad +/* .fi +/* .IP "\fBqmgr_daemon_timeout (1000s)\fR" +/* How much time a Postfix queue manager process may take to handle +/* a request before it is terminated by a built-in watchdog timer. +/* .IP "\fBqmgr_ipc_timeout (60s)\fR" +/* The time limit for the queue manager to send or receive information +/* over an internal communication channel. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBaddress_verify_pending_request_limit (see 'postconf -d' output)\fR" +/* A safety limit that prevents address verification requests from +/* overwhelming the Postfix queue. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdefer_transports (empty)\fR" +/* The names of message delivery transports that should not deliver mail +/* unless someone issues "\fBsendmail -q\fR" or equivalent. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBhelpful_warnings (yes)\fR" +/* Log warnings about problematic configuration settings, and provide +/* helpful suggestions. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBconfirm_delay_cleared (no)\fR" +/* After sending a "your message is delayed" notification, inform +/* the sender when the delay clears up. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.5 and later: +/* .IP "\fBinfo_log_address_format (external)\fR" +/* The email address form that will be used in non-debug logging +/* (info, warning, etc.). +/* FILES +/* /var/spool/postfix/incoming, incoming queue +/* /var/spool/postfix/active, active queue +/* /var/spool/postfix/deferred, deferred queue +/* /var/spool/postfix/bounce, non-delivery status +/* /var/spool/postfix/defer, non-delivery status +/* /var/spool/postfix/trace, delivery status +/* SEE ALSO +/* trivial-rewrite(8), address routing +/* bounce(8), delivery status reports +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* SCHEDULER_README, scheduling algorithm +/* QSHAPE_README, Postfix queue analysis +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Preemptive scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include /* QMGR_SCAN constants */ +#include +#include + +/* Master process interface */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + + /* + * Tunables. + */ +int var_queue_run_delay; +int var_min_backoff_time; +int var_max_backoff_time; +int var_max_queue_time; +int var_dsn_queue_time; +int var_qmgr_active_limit; +int var_qmgr_rcpt_limit; +int var_qmgr_msg_rcpt_limit; +int var_xport_rcpt_limit; +int var_stack_rcpt_limit; +int var_xport_refill_limit; +int var_xport_refill_delay; +int var_delivery_slot_cost; +int var_delivery_slot_loan; +int var_delivery_slot_discount; +int var_min_delivery_slots; +int var_init_dest_concurrency; +int var_transport_retry_time; +int var_dest_con_limit; +int var_dest_rcpt_limit; +char *var_defer_xports; +int var_local_con_lim; +int var_local_rcpt_lim; +bool var_verp_bounce_off; +int var_qmgr_clog_warn_time; +char *var_conc_pos_feedback; +char *var_conc_neg_feedback; +int var_conc_cohort_limit; +int var_conc_feedback_debug; +int var_xport_rate_delay; +int var_dest_rate_delay; +char *var_def_filter_nexthop; +int var_qmgr_daemon_timeout; +int var_qmgr_ipc_timeout; +int var_dsn_delay_cleared; +int var_vrfy_pend_limit; + +static QMGR_SCAN *qmgr_scans[2]; + +#define QMGR_SCAN_IDX_INCOMING 0 +#define QMGR_SCAN_IDX_DEFERRED 1 +#define QMGR_SCAN_IDX_COUNT (sizeof(qmgr_scans) / sizeof(qmgr_scans[0])) + +/* qmgr_deferred_run_event - queue manager heartbeat */ + +static void qmgr_deferred_run_event(int unused_event, void *dummy) +{ + + /* + * This routine runs when it is time for another deferred queue scan. + * Make sure this routine gets called again in the future. + */ + qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_DEFERRED], QMGR_SCAN_START); + event_request_timer(qmgr_deferred_run_event, dummy, var_queue_run_delay); +} + +/* qmgr_trigger_event - respond to external trigger(s) */ + +static void qmgr_trigger_event(char *buf, ssize_t len, + char *unused_service, char **argv) +{ + int incoming_flag = 0; + int deferred_flag = 0; + int i; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Collapse identical requests that have arrived since we looked last + * time. There is no client feedback so there is no need to process each + * request in order. And as long as we don't have conflicting requests we + * are free to sort them into the most suitable order. + */ +#define QMGR_FLUSH_BEFORE (QMGR_FLUSH_ONCE | QMGR_FLUSH_DFXP) + + for (i = 0; i < len; i++) { + if (msg_verbose) + msg_info("request: %d (%c)", + buf[i], ISALNUM(buf[i]) ? buf[i] : '?'); + switch (buf[i]) { + case TRIGGER_REQ_WAKEUP: + case QMGR_REQ_SCAN_INCOMING: + incoming_flag |= QMGR_SCAN_START; + break; + case QMGR_REQ_SCAN_DEFERRED: + deferred_flag |= QMGR_SCAN_START; + break; + case QMGR_REQ_FLUSH_DEAD: + deferred_flag |= QMGR_FLUSH_BEFORE; + incoming_flag |= QMGR_FLUSH_BEFORE; + break; + case QMGR_REQ_SCAN_ALL: + deferred_flag |= QMGR_SCAN_ALL; + incoming_flag |= QMGR_SCAN_ALL; + break; + default: + if (msg_verbose) + msg_info("request ignored"); + break; + } + } + + /* + * Process each request type at most once. Modifiers take effect upon the + * next queue run. If no queue run is in progress, and a queue scan is + * requested, the request takes effect immediately. + */ + if (incoming_flag != 0) + qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_INCOMING], incoming_flag); + if (deferred_flag != 0) + qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_DEFERRED], deferred_flag); +} + +/* qmgr_loop - queue manager main loop */ + +static int qmgr_loop(char *unused_name, char **unused_argv) +{ + char *path; + ssize_t token_count; + int feed = 0; + int scan_idx; /* Priority order scan index */ + static int first_scan_idx = QMGR_SCAN_IDX_INCOMING; + int last_scan_idx = QMGR_SCAN_IDX_COUNT - 1; + int delay; + + /* + * This routine runs as part of the event handling loop, after the event + * manager has delivered a timer or I/O event (including the completion + * of a connection to a delivery process), or after it has waited for a + * specified amount of time. The result value of qmgr_loop() specifies + * how long the event manager should wait for the next event. + */ +#define DONT_WAIT 0 +#define WAIT_FOR_EVENT (-1) + + /* + * Attempt to drain the active queue by allocating a suitable delivery + * process and by delivering mail via it. Delivery process allocation and + * mail delivery are asynchronous. + */ + qmgr_active_drain(); + + /* + * Let some new blood into the active queue when the queue size is + * smaller than some configurable limit. + * + * We import one message per interrupt, to optimally tune the input count + * for the number of delivery agent protocol wait states, as explained in + * qmgr_transport.c. + */ + delay = WAIT_FOR_EVENT; + for (scan_idx = 0; qmgr_message_count < var_qmgr_active_limit + && scan_idx < QMGR_SCAN_IDX_COUNT; ++scan_idx) { + last_scan_idx = (scan_idx + first_scan_idx) % QMGR_SCAN_IDX_COUNT; + if ((path = qmgr_scan_next(qmgr_scans[last_scan_idx])) != 0) { + delay = DONT_WAIT; + if ((feed = qmgr_active_feed(qmgr_scans[last_scan_idx], path)) != 0) + break; + } + } + + /* + * Round-robin the queue scans. When the active queue becomes full, + * prefer new mail over deferred mail. + */ + if (qmgr_message_count < var_qmgr_active_limit) { + first_scan_idx = (last_scan_idx + 1) % QMGR_SCAN_IDX_COUNT; + } else if (first_scan_idx != QMGR_SCAN_IDX_INCOMING) { + first_scan_idx = QMGR_SCAN_IDX_INCOMING; + } + + /* + * Global flow control. If enabled, slow down receiving processes that + * get ahead of the queue manager, but don't block them completely. + */ + if (var_in_flow_delay > 0) { + token_count = mail_flow_count(); + if (token_count < var_proc_limit) { + if (feed != 0 && last_scan_idx == QMGR_SCAN_IDX_INCOMING) + mail_flow_put(1); + else if (qmgr_scans[QMGR_SCAN_IDX_INCOMING]->handle == 0) + mail_flow_put(var_proc_limit - token_count); + } else if (token_count > var_proc_limit) { + mail_flow_get(token_count - var_proc_limit); + } + } + return (delay); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* qmgr_pre_init - pre-jail initialization */ + +static void qmgr_pre_init(char *unused_name, char **unused_argv) +{ + flush_init(); +} + +/* qmgr_post_init - post-jail initialization */ + +static void qmgr_post_init(char *name, char **unused_argv) +{ + + /* + * Backwards compatibility. + */ + if (strcmp(var_procname, "nqmgr") == 0) { + msg_warn("please update the %s/%s file; the new queue manager", + var_config_dir, MASTER_CONF_FILE); + msg_warn("(old name: nqmgr) has become the standard queue manager (new name: qmgr)"); + msg_warn("support for the name old name (nqmgr) will be removed from Postfix"); + } + + /* + * Sanity check. + */ + if (var_qmgr_rcpt_limit < var_qmgr_active_limit) { + msg_warn("%s is smaller than %s - adjusting %s", + VAR_QMGR_RCPT_LIMIT, VAR_QMGR_ACT_LIMIT, VAR_QMGR_RCPT_LIMIT); + var_qmgr_rcpt_limit = var_qmgr_active_limit; + } + if (var_dsn_queue_time > var_max_queue_time) { + msg_warn("%s is larger than %s - adjusting %s", + VAR_DSN_QUEUE_TIME, VAR_MAX_QUEUE_TIME, VAR_DSN_QUEUE_TIME); + var_dsn_queue_time = var_max_queue_time; + } + + /* + * This routine runs after the skeleton code has entered the chroot jail. + * Prevent automatic process suicide after a limited number of client + * requests or after a limited amount of idle time. Move any left-over + * entries from the active queue to the incoming queue, and give them a + * time stamp into the future, in order to allow ongoing deliveries to + * finish first. Start scanning the incoming and deferred queues. + * Left-over active queue entries are moved to the incoming queue because + * the incoming queue has priority; moving left-overs to the deferred + * queue could cause anomalous delays when "postfix reload/start" are + * issued often. Override the IPC timeout (default 3600s) so that the + * queue manager can reset a broken IPC channel before the watchdog timer + * goes off. + */ + var_ipc_timeout = var_qmgr_ipc_timeout; + var_use_limit = 0; + var_idle_limit = 0; + qmgr_move(MAIL_QUEUE_ACTIVE, MAIL_QUEUE_INCOMING, event_time()); + qmgr_scans[QMGR_SCAN_IDX_INCOMING] = qmgr_scan_create(MAIL_QUEUE_INCOMING); + qmgr_scans[QMGR_SCAN_IDX_DEFERRED] = qmgr_scan_create(MAIL_QUEUE_DEFERRED); + qmgr_scan_request(qmgr_scans[QMGR_SCAN_IDX_INCOMING], QMGR_SCAN_START); + qmgr_deferred_run_event(0, (void *) 0); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static const CONFIG_STR_TABLE str_table[] = { + VAR_DEFER_XPORTS, DEF_DEFER_XPORTS, &var_defer_xports, 0, 0, + VAR_CONC_POS_FDBACK, DEF_CONC_POS_FDBACK, &var_conc_pos_feedback, 1, 0, + VAR_CONC_NEG_FDBACK, DEF_CONC_NEG_FDBACK, &var_conc_neg_feedback, 1, 0, + VAR_DEF_FILTER_NEXTHOP, DEF_DEF_FILTER_NEXTHOP, &var_def_filter_nexthop, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0, + VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0, + VAR_MAX_BACKOFF_TIME, DEF_MAX_BACKOFF_TIME, &var_max_backoff_time, 1, 0, + VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 0, 8640000, + VAR_DSN_QUEUE_TIME, DEF_DSN_QUEUE_TIME, &var_dsn_queue_time, 0, 8640000, + VAR_XPORT_RETRY_TIME, DEF_XPORT_RETRY_TIME, &var_transport_retry_time, 1, 0, + VAR_QMGR_CLOG_WARN_TIME, DEF_QMGR_CLOG_WARN_TIME, &var_qmgr_clog_warn_time, 0, 0, + VAR_XPORT_REFILL_DELAY, DEF_XPORT_REFILL_DELAY, &var_xport_refill_delay, 1, 0, + VAR_XPORT_RATE_DELAY, DEF_XPORT_RATE_DELAY, &var_xport_rate_delay, 0, 0, + VAR_DEST_RATE_DELAY, DEF_DEST_RATE_DELAY, &var_dest_rate_delay, 0, 0, + VAR_QMGR_DAEMON_TIMEOUT, DEF_QMGR_DAEMON_TIMEOUT, &var_qmgr_daemon_timeout, 1, 0, + VAR_QMGR_IPC_TIMEOUT, DEF_QMGR_IPC_TIMEOUT, &var_qmgr_ipc_timeout, 1, 0, + 0, + }; + static const CONFIG_INT_TABLE int_table[] = { + VAR_QMGR_ACT_LIMIT, DEF_QMGR_ACT_LIMIT, &var_qmgr_active_limit, 1, 0, + VAR_QMGR_RCPT_LIMIT, DEF_QMGR_RCPT_LIMIT, &var_qmgr_rcpt_limit, 1, 0, + VAR_QMGR_MSG_RCPT_LIMIT, DEF_QMGR_MSG_RCPT_LIMIT, &var_qmgr_msg_rcpt_limit, 1, 0, + VAR_XPORT_RCPT_LIMIT, DEF_XPORT_RCPT_LIMIT, &var_xport_rcpt_limit, 0, 0, + VAR_STACK_RCPT_LIMIT, DEF_STACK_RCPT_LIMIT, &var_stack_rcpt_limit, 0, 0, + VAR_XPORT_REFILL_LIMIT, DEF_XPORT_REFILL_LIMIT, &var_xport_refill_limit, 1, 0, + VAR_DELIVERY_SLOT_COST, DEF_DELIVERY_SLOT_COST, &var_delivery_slot_cost, 0, 0, + VAR_DELIVERY_SLOT_LOAN, DEF_DELIVERY_SLOT_LOAN, &var_delivery_slot_loan, 0, 0, + VAR_DELIVERY_SLOT_DISCOUNT, DEF_DELIVERY_SLOT_DISCOUNT, &var_delivery_slot_discount, 0, 100, + VAR_MIN_DELIVERY_SLOTS, DEF_MIN_DELIVERY_SLOTS, &var_min_delivery_slots, 0, 0, + VAR_INIT_DEST_CON, DEF_INIT_DEST_CON, &var_init_dest_concurrency, 1, 0, + VAR_DEST_CON_LIMIT, DEF_DEST_CON_LIMIT, &var_dest_con_limit, 0, 0, + VAR_DEST_RCPT_LIMIT, DEF_DEST_RCPT_LIMIT, &var_dest_rcpt_limit, 0, 0, + VAR_LOCAL_RCPT_LIMIT, DEF_LOCAL_RCPT_LIMIT, &var_local_rcpt_lim, 0, 0, + VAR_LOCAL_CON_LIMIT, DEF_LOCAL_CON_LIMIT, &var_local_con_lim, 0, 0, + VAR_CONC_COHORT_LIM, DEF_CONC_COHORT_LIM, &var_conc_cohort_limit, 0, 0, + VAR_VRFY_PEND_LIMIT, DEF_VRFY_PEND_LIMIT, &var_vrfy_pend_limit, 1, 0, + 0, + }; + static const CONFIG_BOOL_TABLE bool_table[] = { + VAR_VERP_BOUNCE_OFF, DEF_VERP_BOUNCE_OFF, &var_verp_bounce_off, + VAR_CONC_FDBACK_DEBUG, DEF_CONC_FDBACK_DEBUG, &var_conc_feedback_debug, + VAR_DSN_DELAY_CLEARED, DEF_DSN_DELAY_CLEARED, &var_dsn_delay_cleared, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Use the trigger service skeleton, because no-one else should be + * monitoring our service port while this process runs, and because we do + * not talk back to the client. + */ + trigger_server_main(argc, argv, qmgr_trigger_event, + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_BOOL_TABLE(bool_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(qmgr_pre_init), + CA_MAIL_SERVER_POST_INIT(qmgr_post_init), + CA_MAIL_SERVER_LOOP(qmgr_loop), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_SOLITARY, + CA_MAIL_SERVER_WATCHDOG(&var_qmgr_daemon_timeout), + 0); +} diff --git a/src/qmgr/qmgr.h b/src/qmgr/qmgr.h new file mode 100644 index 0000000..4a205b4 --- /dev/null +++ b/src/qmgr/qmgr.h @@ -0,0 +1,546 @@ +/*++ +/* NAME +/* qmgr 3h +/* SUMMARY +/* queue manager data structures +/* SYNOPSIS +/* #include "qmgr.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include + + /* + * The queue manager is built around lots of mutually-referring structures. + * These typedefs save some typing. + */ +typedef struct QMGR_TRANSPORT QMGR_TRANSPORT; +typedef struct QMGR_QUEUE QMGR_QUEUE; +typedef struct QMGR_ENTRY QMGR_ENTRY; +typedef struct QMGR_MESSAGE QMGR_MESSAGE; +typedef struct QMGR_JOB QMGR_JOB; +typedef struct QMGR_PEER QMGR_PEER; +typedef struct QMGR_TRANSPORT_LIST QMGR_TRANSPORT_LIST; +typedef struct QMGR_QUEUE_LIST QMGR_QUEUE_LIST; +typedef struct QMGR_ENTRY_LIST QMGR_ENTRY_LIST; +typedef struct QMGR_JOB_LIST QMGR_JOB_LIST; +typedef struct QMGR_PEER_LIST QMGR_PEER_LIST; +typedef struct QMGR_SCAN QMGR_SCAN; +typedef struct QMGR_FEEDBACK QMGR_FEEDBACK; + + /* + * Hairy macros to update doubly-linked lists. + */ +#define QMGR_LIST_ROTATE(head, object, peers) { \ + head.next->peers.prev = head.prev; \ + head.prev->peers.next = head.next; \ + head.next = object->peers.next; \ + head.next->peers.prev = 0; \ + head.prev = object; \ + object->peers.next = 0; \ +} + +#define QMGR_LIST_UNLINK(head, type, object, peers) { \ + type _next = object->peers.next; \ + type _prev = object->peers.prev; \ + if (_prev) _prev->peers.next = _next; \ + else head.next = _next; \ + if (_next) _next->peers.prev = _prev; \ + else head.prev = _prev; \ + object->peers.next = object->peers.prev = 0; \ +} + +#define QMGR_LIST_LINK(head, pred, object, succ, peers) { \ + object->peers.prev = pred; \ + object->peers.next = succ; \ + if (pred) pred->peers.next = object; \ + else head.next = object; \ + if (succ) succ->peers.prev = object; \ + else head.prev = object; \ +} + +#define QMGR_LIST_PREPEND(head, object, peers) { \ + object->peers.next = head.next; \ + object->peers.prev = 0; \ + if (head.next) { \ + head.next->peers.prev = object; \ + } else { \ + head.prev = object; \ + } \ + head.next = object; \ +} + +#define QMGR_LIST_APPEND(head, object, peers) { \ + object->peers.prev = head.prev; \ + object->peers.next = 0; \ + if (head.prev) { \ + head.prev->peers.next = object; \ + } else { \ + head.next = object; \ + } \ + head.prev = object; \ +} + +#define QMGR_LIST_INIT(head) { \ + head.prev = 0; \ + head.next = 0; \ +} + + /* + * Transports are looked up by name (when we have resolved a message), or + * round-robin wise (when we want to distribute resources fairly). + */ +struct QMGR_TRANSPORT_LIST { + QMGR_TRANSPORT *next; + QMGR_TRANSPORT *prev; +}; + +extern struct HTABLE *qmgr_transport_byname; /* transport by name */ +extern QMGR_TRANSPORT_LIST qmgr_transport_list; /* transports, round robin */ + + /* + * Delivery agents provide feedback, as hints that Postfix should expend + * more or fewer resources on a specific destination domain. The main.cf + * file specifies how feedback affects delivery concurrency: add/subtract a + * constant, a ratio of constants, or a constant divided by the delivery + * concurrency; and it specifies how much feedback must accumulate between + * concurrency updates. + */ +struct QMGR_FEEDBACK { + int hysteresis; /* to pass, need to be this tall */ + double base; /* pre-computed from main.cf */ + int index; /* none, window, sqrt(window) */ +}; + +#define QMGR_FEEDBACK_IDX_NONE 0 /* no window dependence */ +#define QMGR_FEEDBACK_IDX_WIN 1 /* 1/window dependence */ +#if 0 +#define QMGR_FEEDBACK_IDX_SQRT_WIN 2 /* 1/sqrt(window) dependence */ +#endif + +#ifdef QMGR_FEEDBACK_IDX_SQRT_WIN +#include +#endif + +extern void qmgr_feedback_init(QMGR_FEEDBACK *, const char *, const char *, const char *, const char *); + +#ifndef QMGR_FEEDBACK_IDX_SQRT_WIN +#define QMGR_FEEDBACK_VAL(fb, win) \ + ((fb).index == QMGR_FEEDBACK_IDX_NONE ? (fb).base : (fb).base / (win)) +#else +#define QMGR_FEEDBACK_VAL(fb, win) \ + ((fb).index == QMGR_FEEDBACK_IDX_NONE ? (fb).base : \ + (fb).index == QMGR_FEEDBACK_IDX_WIN ? (fb).base / (win) : \ + (fb).base / sqrt(win)) +#endif + + /* + * Each transport (local, smtp-out, bounce) can have one queue per next hop + * name. Queues are looked up by next hop name (when we have resolved a + * message destination), or round-robin wise (when we want to deliver + * messages fairly). + */ +struct QMGR_QUEUE_LIST { + QMGR_QUEUE *next; + QMGR_QUEUE *prev; +}; + +struct QMGR_JOB_LIST { + QMGR_JOB *next; + QMGR_JOB *prev; +}; + +struct QMGR_TRANSPORT { + int flags; /* blocked, etc. */ + int pending; /* incomplete DA connections */ + char *name; /* transport name */ + int dest_concurrency_limit; /* concurrency per domain */ + int init_dest_concurrency; /* init. per-domain concurrency */ + int recipient_limit; /* recipients per transaction */ + int rcpt_per_stack; /* extra slots reserved for jobs put + * on the job stack */ + int rcpt_unused; /* available in-core recipient slots */ + int refill_limit; /* recipient batch size for message + * refill */ + int refill_delay; /* delay before message refill */ + int slot_cost; /* cost of new preemption slot (# of + * selected entries) */ + int slot_loan; /* preemption boost offset and */ + int slot_loan_factor; /* factor, see qmgr_job_preempt() */ + int min_slots; /* when preemption can take effect at + * all */ + struct HTABLE *queue_byname; /* queues indexed by domain */ + QMGR_QUEUE_LIST queue_list; /* queues, round robin order */ + struct HTABLE *job_byname; /* jobs indexed by queue id */ + QMGR_JOB_LIST job_list; /* list of message jobs (1 per + * message) ordered by scheduler */ + QMGR_JOB_LIST job_bytime; /* jobs ordered by time since queued */ + QMGR_JOB *job_current; /* keeps track of the current job */ + QMGR_JOB *job_next_unread; /* next job with unread recipients */ + QMGR_JOB *candidate_cache; /* cached result from + * qmgr_job_candidate() */ + QMGR_JOB *candidate_cache_current; /* current job tied to the candidate */ + time_t candidate_cache_time; /* when candidate_cache was last + * updated */ + int blocker_tag; /* for marking blocker jobs */ + QMGR_TRANSPORT_LIST peers; /* linkage */ + DSN *dsn; /* why unavailable */ + QMGR_FEEDBACK pos_feedback; /* positive feedback control */ + QMGR_FEEDBACK neg_feedback; /* negative feedback control */ + int fail_cohort_limit; /* flow shutdown control */ + int xport_rate_delay; /* suspend per delivery */ + int rate_delay; /* suspend per delivery */ +}; + +#define QMGR_TRANSPORT_STAT_DEAD (1<<1) +#define QMGR_TRANSPORT_STAT_RATE_LOCK (1<<2) + +typedef void (*QMGR_TRANSPORT_ALLOC_NOTIFY) (QMGR_TRANSPORT *, VSTREAM *); +extern QMGR_TRANSPORT *qmgr_transport_select(void); +extern void qmgr_transport_alloc(QMGR_TRANSPORT *, QMGR_TRANSPORT_ALLOC_NOTIFY); +extern void qmgr_transport_throttle(QMGR_TRANSPORT *, DSN *); +extern void qmgr_transport_unthrottle(QMGR_TRANSPORT *); +extern QMGR_TRANSPORT *qmgr_transport_create(const char *); +extern QMGR_TRANSPORT *qmgr_transport_find(const char *); + +#define QMGR_TRANSPORT_THROTTLED(t) ((t)->flags & QMGR_TRANSPORT_STAT_DEAD) + + /* + * Each next hop (e.g., a domain name) has its own queue of pending message + * transactions. The "todo" queue contains messages that are to be delivered + * to this next hop. When a message is elected for transmission, it is moved + * from the "todo" queue to the "busy" queue. Messages are taken from the + * "todo" queue in round-robin order. + */ +struct QMGR_ENTRY_LIST { + QMGR_ENTRY *next; + QMGR_ENTRY *prev; +}; + +struct QMGR_QUEUE { + int dflags; /* delivery request options */ + time_t last_done; /* last delivery completion */ + char *name; /* domain name or address */ + char *nexthop; /* domain name */ + int todo_refcount; /* queue entries (todo list) */ + int busy_refcount; /* queue entries (busy list) */ + int window; /* slow open algorithm */ + double success; /* accumulated positive feedback */ + double failure; /* accumulated negative feedback */ + double fail_cohorts; /* pseudo-cohort failure count */ + QMGR_TRANSPORT *transport; /* transport linkage */ + QMGR_ENTRY_LIST todo; /* todo queue entries */ + QMGR_ENTRY_LIST busy; /* messages on the wire */ + QMGR_QUEUE_LIST peers; /* neighbor queues */ + DSN *dsn; /* why unavailable */ + time_t clog_time_to_warn; /* time of last warning */ + int blocker_tag; /* tagged if blocks job list */ +}; + +#define QMGR_QUEUE_TODO 1 /* waiting for service */ +#define QMGR_QUEUE_BUSY 2 /* recipients on the wire */ + +extern int qmgr_queue_count; + +extern QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *, const char *, const char *); +extern void qmgr_queue_done(QMGR_QUEUE *); +extern void qmgr_queue_throttle(QMGR_QUEUE *, DSN *); +extern void qmgr_queue_unthrottle(QMGR_QUEUE *); +extern QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *, const char *); +extern void qmgr_queue_suspend(QMGR_QUEUE *, int); + + /* + * Exclusive queue states. Originally there were only two: "throttled" and + * "not throttled". It was natural to encode these in the queue window size. + * After 10 years it's not practical to rip out all the working code and + * change representations, so we just clean up the names a little. + * + * Note: only the "ready" state can reach every state (including itself); + * non-ready states can reach only the "ready" state. Other transitions are + * forbidden, because they would result in dangling event handlers. + */ +#define QMGR_QUEUE_STAT_THROTTLED 0 /* back-off timer */ +#define QMGR_QUEUE_STAT_SUSPENDED -1 /* voluntary delay timer */ +#define QMGR_QUEUE_STAT_SAVED -2 /* delayed cleanup timer */ +#define QMGR_QUEUE_STAT_BAD -3 /* can't happen */ + +#define QMGR_QUEUE_READY(q) ((q)->window > 0) +#define QMGR_QUEUE_THROTTLED(q) ((q)->window == QMGR_QUEUE_STAT_THROTTLED) +#define QMGR_QUEUE_SUSPENDED(q) ((q)->window == QMGR_QUEUE_STAT_SUSPENDED) +#define QMGR_QUEUE_SAVED(q) ((q)->window == QMGR_QUEUE_STAT_SAVED) +#define QMGR_QUEUE_BAD(q) ((q)->window <= QMGR_QUEUE_STAT_BAD) + +#define QMGR_QUEUE_STATUS(q) ( \ + QMGR_QUEUE_READY(q) ? "ready" : \ + QMGR_QUEUE_THROTTLED(q) ? "throttled" : \ + QMGR_QUEUE_SUSPENDED(q) ? "suspended" : \ + QMGR_QUEUE_SAVED(q) ? "saved" : \ + "invalid queue status" \ + ) + + /* + * Structure of one next-hop queue entry. In order to save some copying + * effort we allow multiple recipients per transaction. + */ +struct QMGR_ENTRY { + VSTREAM *stream; /* delivery process */ + QMGR_MESSAGE *message; /* message info */ + RECIPIENT_LIST rcpt_list; /* as many as it takes */ + QMGR_QUEUE *queue; /* parent linkage */ + QMGR_PEER *peer; /* parent linkage */ + QMGR_ENTRY_LIST queue_peers; /* per queue neighbor entries */ + QMGR_ENTRY_LIST peer_peers; /* per peer neighbor entries */ +}; + +extern QMGR_ENTRY *qmgr_entry_select(QMGR_PEER *); +extern void qmgr_entry_unselect(QMGR_ENTRY *); +extern void qmgr_entry_move_todo(QMGR_QUEUE *, QMGR_ENTRY *); +extern void qmgr_entry_done(QMGR_ENTRY *, int); +extern QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *, QMGR_MESSAGE *); + + /* + * All common in-core information about a message is kept here. When all + * recipients have been tried the message file is linked to the "deferred" + * queue (some hosts not reachable), to the "bounce" queue (some recipients + * were rejected), and is then removed from the "active" queue. + */ +struct QMGR_MESSAGE { + int flags; /* delivery problems */ + int qflags; /* queuing flags */ + int tflags; /* tracing flags */ + long tflags_offset; /* offset for killing */ + int rflags; /* queue file read flags */ + VSTREAM *fp; /* open queue file or null */ + int refcount; /* queue entries */ + int single_rcpt; /* send one rcpt at a time */ + struct timeval arrival_time; /* start of receive transaction */ + time_t create_time; /* queue file create time */ + struct timeval active_time; /* time of entry into active queue */ + time_t queued_time; /* sanitized time when moved to the + * active queue */ + time_t refill_time; /* sanitized time of last message + * refill */ + long warn_offset; /* warning bounce flag offset */ + time_t warn_time; /* time next warning to be sent */ + long data_offset; /* data seek offset */ + char *queue_name; /* queue name */ + char *queue_id; /* queue file */ + char *encoding; /* content encoding */ + char *sender; /* complete address */ + char *dsn_envid; /* DSN envelope ID */ + int dsn_ret; /* DSN headers/full */ + int smtputf8; /* requires unicode */ + char *verp_delims; /* VERP delimiters */ + char *filter_xport; /* filtering transport */ + char *inspect_xport; /* inspecting transport */ + char *redirect_addr; /* info@spammer.tld */ + long data_size; /* data segment size */ + long cont_length; /* message content length */ + long rcpt_offset; /* more recipients here */ + char *client_name; /* client hostname */ + char *client_addr; /* client address */ + char *client_port; /* client port */ + char *client_proto; /* client protocol */ + char *client_helo; /* helo parameter */ + char *sasl_method; /* SASL method */ + char *sasl_username; /* SASL user name */ + char *sasl_sender; /* SASL sender */ + char *log_ident; /* up-stream queue ID */ + char *rewrite_context; /* address qualification */ + RECIPIENT_LIST rcpt_list; /* complete addresses */ + int rcpt_count; /* used recipient slots */ + int rcpt_limit; /* maximum read in-core */ + int rcpt_unread; /* # of recipients left in queue file */ + QMGR_JOB_LIST job_list; /* jobs delivering this message (1 + * per transport) */ +}; + + /* + * Flags 0-15 are reserved for qmgr_user.h. + */ +#define QMGR_READ_FLAG_SEEN_ALL_NON_RCPT (1<<16) + +#define QMGR_MESSAGE_LOCKED ((QMGR_MESSAGE *) 1) + +extern int qmgr_message_count; +extern int qmgr_recipient_count; +extern int qmgr_vrfy_pend_count; + +extern void qmgr_message_free(QMGR_MESSAGE *); +extern void qmgr_message_update_warn(QMGR_MESSAGE *); +extern void qmgr_message_kill_record(QMGR_MESSAGE *, long); +extern QMGR_MESSAGE *qmgr_message_alloc(const char *, const char *, int, mode_t); +extern QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *); + +#define QMGR_MSG_STATS(stats, message) \ + MSG_STATS_INIT2(stats, \ + incoming_arrival, message->arrival_time, \ + active_arrival, message->active_time) + + /* + * Sometimes it's required to access the transport queues and entries on per + * message basis. That's what the QMGR_JOB structure is for - it groups all + * per message information within each transport using a list of QMGR_PEER + * structures. These structures in turn correspond with per message + * QMGR_QUEUE structure and list all per message QMGR_ENTRY structures. + */ +struct QMGR_PEER_LIST { + QMGR_PEER *next; + QMGR_PEER *prev; +}; + +struct QMGR_JOB { + QMGR_MESSAGE *message; /* message delivered by this job */ + QMGR_TRANSPORT *transport; /* transport this job belongs to */ + QMGR_JOB_LIST message_peers; /* per message neighbor linkage */ + QMGR_JOB_LIST transport_peers; /* per transport neighbor linkage */ + QMGR_JOB_LIST time_peers; /* by time neighbor linkage */ + QMGR_JOB *stack_parent; /* stack parent */ + QMGR_JOB_LIST stack_children; /* all stack children */ + QMGR_JOB_LIST stack_siblings; /* stack children linkage */ + int stack_level; /* job stack nesting level (-1 means + * it's not on the lists at all) */ + int blocker_tag; /* tagged if blocks the job list */ + struct HTABLE *peer_byname; /* message job peers, indexed by + * domain */ + QMGR_PEER_LIST peer_list; /* list of message job peers */ + int slots_used; /* slots used during preemption */ + int slots_available; /* slots available for preemption (in + * multiples of slot_cost) */ + int selected_entries; /* # of entries selected for delivery + * so far */ + int read_entries; /* # of entries read in-core so far */ + int rcpt_count; /* used recipient slots */ + int rcpt_limit; /* available recipient slots */ +}; + +struct QMGR_PEER { + QMGR_JOB *job; /* job handling this peer */ + QMGR_QUEUE *queue; /* queue corresponding with this peer */ + int refcount; /* peer entries */ + QMGR_ENTRY_LIST entry_list; /* todo message entries queued for + * this peer */ + QMGR_PEER_LIST peers; /* neighbor linkage */ +}; + +extern QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *); +extern QMGR_PEER *qmgr_peer_select(QMGR_JOB *); +extern void qmgr_job_blocker_update(QMGR_QUEUE *); + +extern QMGR_JOB *qmgr_job_obtain(QMGR_MESSAGE *, QMGR_TRANSPORT *); +extern void qmgr_job_free(QMGR_JOB *); +extern void qmgr_job_move_limits(QMGR_JOB *); + +extern QMGR_PEER *qmgr_peer_create(QMGR_JOB *, QMGR_QUEUE *); +extern QMGR_PEER *qmgr_peer_find(QMGR_JOB *, QMGR_QUEUE *); +extern QMGR_PEER *qmgr_peer_obtain(QMGR_JOB *, QMGR_QUEUE *); +extern void qmgr_peer_free(QMGR_PEER *); + + /* + * qmgr_defer.c + */ +extern void qmgr_defer_transport(QMGR_TRANSPORT *, DSN *); +extern void qmgr_defer_todo(QMGR_QUEUE *, DSN *); +extern void qmgr_defer_recipient(QMGR_MESSAGE *, RECIPIENT *, DSN *); + + /* + * qmgr_bounce.c + */ +extern void qmgr_bounce_recipient(QMGR_MESSAGE *, RECIPIENT *, DSN *); + + /* + * qmgr_deliver.c + */ +extern int qmgr_deliver_concurrency; +extern void qmgr_deliver(QMGR_TRANSPORT *, VSTREAM *); + + /* + * qmgr_active.c + */ +extern int qmgr_active_feed(QMGR_SCAN *, const char *); +extern void qmgr_active_drain(void); +extern void qmgr_active_done(QMGR_MESSAGE *); + + /* + * qmgr_move.c + */ +extern void qmgr_move(const char *, const char *, time_t); + + /* + * qmgr_enable.c + */ +extern void qmgr_enable_all(void); +extern void qmgr_enable_transport(QMGR_TRANSPORT *); +extern void qmgr_enable_queue(QMGR_QUEUE *); + + /* + * Queue scan context. + */ +struct QMGR_SCAN { + char *queue; /* queue name */ + int flags; /* private, this run */ + int nflags; /* private, next run */ + struct SCAN_DIR *handle; /* scan */ +}; + + /* + * Flags that control queue scans or destination selection. These are + * similar to the QMGR_REQ_XXX request codes. + */ +#define QMGR_SCAN_START (1<<0) /* start now/restart when done */ +#define QMGR_SCAN_ALL (1<<1) /* all queue file time stamps */ +#define QMGR_FLUSH_ONCE (1<<2) /* unthrottle once */ +#define QMGR_FLUSH_DFXP (1<<3) /* override defer_transports */ +#define QMGR_FLUSH_EACH (1<<4) /* unthrottle per message */ +#define QMGR_FORCE_EXPIRE (1<<5) /* force-defer and force-expire */ + + /* + * qmgr_scan.c + */ +extern QMGR_SCAN *qmgr_scan_create(const char *); +extern void qmgr_scan_request(QMGR_SCAN *, int); +extern char *qmgr_scan_next(QMGR_SCAN *); + + /* + * qmgr_error.c + */ +extern QMGR_TRANSPORT *qmgr_error_transport(const char *); +extern QMGR_QUEUE *qmgr_error_queue(const char *, DSN *); +extern char *qmgr_error_nexthop(DSN *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Preemptive scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ diff --git a/src/qmgr/qmgr_active.c b/src/qmgr/qmgr_active.c new file mode 100644 index 0000000..b1c1a4a --- /dev/null +++ b/src/qmgr/qmgr_active.c @@ -0,0 +1,607 @@ +/*++ +/* NAME +/* qmgr_active 3 +/* SUMMARY +/* active queue management +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_active_feed(scan_info, queue_id) +/* QMGR_SCAN *scan_info; +/* const char *queue_id; +/* +/* void qmgr_active_drain() +/* +/* int qmgr_active_done(message) +/* QMGR_MESSAGE *message; +/* DESCRIPTION +/* These functions maintain the active message queue: the set +/* of messages that the queue manager is actually working on. +/* The active queue is limited in size. Messages are drained +/* from the active queue by allocating a delivery process and +/* by delivering mail via that process. Messages leak into the +/* active queue only when the active queue is small enough. +/* Damaged message files are saved to the "corrupt" directory. +/* +/* qmgr_active_feed() inserts the named message file into +/* the active queue. Message files with the wrong name or +/* with other wrong properties are skipped but not removed. +/* The following queue flags are recognized, other flags being +/* ignored: +/* .IP QMGR_SCAN_ALL +/* Examine all queue files. Normally, deferred queue files with +/* future time stamps are ignored, and incoming queue files with +/* future time stamps are frowned upon. +/* .PP +/* qmgr_active_drain() allocates one delivery process. +/* Process allocation is asynchronous. Once the delivery +/* process is available, an attempt is made to deliver +/* a message via it. Message delivery is asynchronous, too. +/* +/* qmgr_active_done() deals with a message after delivery +/* has been tried for all in-core recipients. If the message +/* was bounced, a bounce message is sent to the sender, or +/* to the Errors-To: address if one was specified. +/* If there are more on-file recipients, a new batch of +/* in-core recipients is read from the queue file. Otherwise, +/* if a delivery agent marked the queue file as corrupt, +/* the queue file is moved to the "corrupt" queue (surprise); +/* if at least one delivery failed, the message is moved +/* to the deferred queue. The time stamps of a deferred queue +/* file are set to the nearest wakeup time of its recipient +/* sites (if delivery failed due to a problem with a next-hop +/* host), are set into the future by the amount of time the +/* message was queued (per-message exponential backoff), or are set +/* into the future by a minimal backoff time, whichever is more. +/* The minimal_backoff_time parameter specifies the minimal +/* amount of time between delivery attempts; maximal_backoff_time +/* specifies an upper limit. +/* DIAGNOSTICS +/* Fatal: queue file access failures, out of memory. +/* Panic: interface violations, internal consistency errors. +/* Warnings: corrupt message file. A corrupt message is saved +/* to the "corrupt" queue for further inspection. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef S_IRWXU /* What? no POSIX system? */ +#define S_IRWXU 0700 +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + + /* + * A bunch of call-back routines. + */ +static void qmgr_active_done_2_bounce_flush(int, void *); +static void qmgr_active_done_2_generic(QMGR_MESSAGE *); +static void qmgr_active_done_25_trace_flush(int, void *); +static void qmgr_active_done_25_generic(QMGR_MESSAGE *); +static void qmgr_active_done_3_defer_flush(int, void *); +static void qmgr_active_done_3_defer_warn(int, void *); +static void qmgr_active_done_3_generic(QMGR_MESSAGE *); + +/* qmgr_active_corrupt - move corrupted file out of the way */ + +static void qmgr_active_corrupt(const char *queue_id) +{ + const char *myname = "qmgr_active_corrupt"; + + if (mail_queue_rename(queue_id, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_CORRUPT)) { + if (errno != ENOENT) + msg_fatal("%s: save corrupt file queue %s id %s: %m", + myname, MAIL_QUEUE_ACTIVE, queue_id); + } else { + msg_warn("saving corrupt file \"%s\" from queue \"%s\" to queue \"%s\"", + queue_id, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_CORRUPT); + } +} + +/* qmgr_active_defer - defer queue file */ + +static void qmgr_active_defer(const char *queue_name, const char *queue_id, + const char *dest_queue, int delay) +{ + const char *myname = "qmgr_active_defer"; + const char *path; + struct utimbuf tbuf; + + if (msg_verbose) + msg_info("wakeup %s after %ld secs", queue_id, (long) delay); + + tbuf.actime = tbuf.modtime = event_time() + delay; + path = mail_queue_path((VSTRING *) 0, queue_name, queue_id); + if (utime(path, &tbuf) < 0 && errno != ENOENT) + msg_fatal("%s: update %s time stamps: %m", myname, path); + if (mail_queue_rename(queue_id, queue_name, dest_queue)) { + if (errno != ENOENT) + msg_fatal("%s: rename %s from %s to %s: %m", myname, + queue_id, queue_name, dest_queue); + msg_warn("%s: rename %s from %s to %s: %m", myname, + queue_id, queue_name, dest_queue); + } else if (msg_verbose) { + msg_info("%s: defer %s", myname, queue_id); + } +} + +/* qmgr_active_feed - feed one message into active queue */ + +int qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) +{ + const char *myname = "qmgr_active_feed"; + QMGR_MESSAGE *message; + struct stat st; + const char *path; + + if (strcmp(scan_info->queue, MAIL_QUEUE_ACTIVE) == 0) + msg_panic("%s: bad queue %s", myname, scan_info->queue); + if (msg_verbose) + msg_info("%s: queue %s", myname, scan_info->queue); + + /* + * Make sure this is something we are willing to open. + */ + if (mail_open_ok(scan_info->queue, queue_id, &st, &path) == MAIL_OPEN_NO) + return (0); + + if (msg_verbose) + msg_info("%s: %s", myname, path); + + /* + * Skip files that have time stamps into the future. They need to cool + * down. Incoming and deferred files can have future time stamps. + */ + if ((scan_info->flags & QMGR_SCAN_ALL) == 0 + && st.st_mtime > time((time_t *) 0) + 1) { + if (msg_verbose) + msg_info("%s: skip %s (%ld seconds)", myname, queue_id, + (long) (st.st_mtime - event_time())); + return (0); + } + + /* + * Move the message to the active queue. File access errors are fatal. + */ + if (mail_queue_rename(queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE)) { + if (errno != ENOENT) + msg_fatal("%s: %s: rename from %s to %s: %m", myname, + queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE); + msg_warn("%s: %s: rename from %s to %s: %m", myname, + queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE); + return (0); + } + + /* + * Extract envelope information: sender and recipients. At this point, + * mail addresses have been processed by the cleanup service so they + * should be in canonical form. Generate requests to deliver this + * message. + * + * Throwing away queue files seems bad, especially when they made it this + * far into the mail system. Therefore we save bad files to a separate + * directory for further inspection. + * + * After queue manager restart it is possible that a queue file is still + * being delivered. In that case (the file is locked), defer delivery by + * a minimal amount of time. + */ +#define QMGR_FLUSH_AFTER (QMGR_FLUSH_EACH | QMGR_FLUSH_DFXP) +#define MAYBE_FLUSH_AFTER(mode) \ + (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? QMGR_FLUSH_AFTER : 0) +#define MAYBE_FORCE_EXPIRE(mode) \ + (((mode) & MAIL_QUEUE_STAT_EXPIRE) ? QMGR_FORCE_EXPIRE : 0) +#define MAYBE_UPDATE_MODE(mode) \ + (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? \ + (mode) & ~MAIL_QUEUE_STAT_UNTHROTTLE : 0) + + if ((message = qmgr_message_alloc(MAIL_QUEUE_ACTIVE, queue_id, + scan_info->flags + | MAYBE_FLUSH_AFTER(st.st_mode) + | MAYBE_FORCE_EXPIRE(st.st_mode), + MAYBE_UPDATE_MODE(st.st_mode))) == 0) { + qmgr_active_corrupt(queue_id); + return (0); + } else if (message == QMGR_MESSAGE_LOCKED) { + qmgr_active_defer(MAIL_QUEUE_ACTIVE, queue_id, MAIL_QUEUE_INCOMING, 60); + return (0); + } else { + + /* + * Special case if all recipients were already delivered. Send any + * bounces and clean up. + */ + if (message->refcount == 0) + qmgr_active_done(message); + return (1); + } +} + +/* qmgr_active_done - dispose of message after recipients have been tried */ + +void qmgr_active_done(QMGR_MESSAGE *message) +{ + const char *myname = "qmgr_active_done"; + struct stat st; + + if (msg_verbose) + msg_info("%s: %s", myname, message->queue_id); + + /* + * During a previous iteration, an attempt to bounce this message may + * have failed, so there may still be a bounce log lying around. XXX By + * groping around in the bounce queue, we're trespassing on the bounce + * service's territory. But doing so is more robust than depending on the + * bounce daemon to do the lookup for us, and for us to do the deleting + * after we have received a successful status from the bounce service. + * The bounce queue directory blocks are most likely in memory anyway. If + * these lookups become a performance problem we will have to build an + * in-core cache into the bounce daemon. + * + * Don't bounce when the bounce log is empty. The bounce process obviously + * failed, and the delivery agent will have requested that the message be + * deferred. + * + * Bounces are sent asynchronously to avoid stalling while the cleanup + * daemon waits for the qmgr to accept the "new mail" trigger. + * + * See also code in cleanup_bounce.c. + */ + if (stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_BOUNCE, message->queue_id), &st) == 0) { + if (st.st_size == 0) { + if (mail_queue_remove(MAIL_QUEUE_BOUNCE, message->queue_id)) + msg_fatal("remove %s %s: %m", + MAIL_QUEUE_BOUNCE, message->queue_id); + } else { + if (msg_verbose) + msg_info("%s: bounce %s", myname, message->queue_id); + if (message->verp_delims == 0 || var_verp_bounce_off) + abounce_flush(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + qmgr_active_done_2_bounce_flush, + (void *) message); + else + abounce_flush_verp(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + message->verp_delims, + qmgr_active_done_2_bounce_flush, + (void *) message); + return; + } + } + + /* + * Asynchronous processing does not reach this point. + */ + qmgr_active_done_2_generic(message); +} + +/* qmgr_active_done_2_bounce_flush - process abounce_flush() status */ + +static void qmgr_active_done_2_bounce_flush(int status, void *context) +{ + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process abounce_flush() status and continue processing. + */ + message->flags |= status; + qmgr_active_done_2_generic(message); +} + +/* qmgr_active_done_2_generic - continue processing */ + +static void qmgr_active_done_2_generic(QMGR_MESSAGE *message) +{ + const char *path; + struct stat st; + + /* + * A delivery agent marks a queue file as corrupt by changing its + * attributes, and by pretending that delivery was deferred. + */ + if (message->flags + && mail_open_ok(MAIL_QUEUE_ACTIVE, message->queue_id, &st, &path) == MAIL_OPEN_NO) { + qmgr_active_corrupt(message->queue_id); + qmgr_message_free(message); + return; + } + + /* + * If we did not read all recipients from this file, go read some more, + * but remember whether some recipients have to be tried again. + * + * Throwing away queue files seems bad, especially when they made it this + * far into the mail system. Therefore we save bad files to a separate + * directory for further inspection by a human being. + */ + if (message->rcpt_offset > 0) { + if (qmgr_message_realloc(message) == 0) { + qmgr_active_corrupt(message->queue_id); + qmgr_message_free(message); + } else { + if (message->refcount == 0) + qmgr_active_done(message); /* recurse for consistency */ + } + return; + } + + /* + * XXX With multi-recipient mail, some recipients may have NOTIFY=SUCCESS + * and others not. Depending on what subset of recipients are delivered, + * a trace file may or may not be created. Even when the last partial + * delivery attempt had no NOTIFY=SUCCESS recipients, a trace file may + * still exist from a previous partial delivery attempt. So as long as + * any recipient has NOTIFY=SUCCESS we have to always look for the trace + * file and be prepared for the file not to exist. + * + * See also comments in bounce/bounce_notify_util.c. + */ + if ((message->tflags & (DEL_REQ_FLAG_USR_VRFY | DEL_REQ_FLAG_RECORD + | DEL_REQ_FLAG_REC_DLY_SENT)) + || (message->rflags & QMGR_READ_FLAG_NOTIFY_SUCCESS)) { + atrace_flush(message->tflags, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + qmgr_active_done_25_trace_flush, + (void *) message); + return; + } + + /* + * Asynchronous processing does not reach this point. + */ + qmgr_active_done_25_generic(message); +} + +/* qmgr_active_done_25_trace_flush - continue after atrace_flush() completion */ + +static void qmgr_active_done_25_trace_flush(int status, void *context) +{ + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process atrace_flush() status and continue processing. + */ + if (status == 0 && message->tflags_offset) + qmgr_message_kill_record(message, message->tflags_offset); + message->flags |= status; + qmgr_active_done_25_generic(message); +} + +/* qmgr_active_done_25_generic - continue processing */ + +static void qmgr_active_done_25_generic(QMGR_MESSAGE *message) +{ + const char *myname = "qmgr_active_done_25_generic"; + const char *expire_status = 0; + + /* + * If we get to this point we have tried all recipients for this message. + * If the message is too old, try to bounce it. + * + * Bounces are sent asynchronously to avoid stalling while the cleanup + * daemon waits for the qmgr to accept the "new mail" trigger. + */ + if (message->flags) { + if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) { + expire_status = "force-expired"; + } else if (event_time() >= message->create_time + + (*message->sender ? var_max_queue_time : var_dsn_queue_time)) { + expire_status = "expired"; + } else { + expire_status = 0; + } + if (expire_status != 0) { + msg_info("%s: from=<%s>, status=%s, returned to sender", + message->queue_id, info_log_addr_form_sender(message->sender), + expire_status); + if (message->verp_delims == 0 || var_verp_bounce_off) + adefer_flush(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + qmgr_active_done_3_defer_flush, + (void *) message); + else + adefer_flush_verp(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + message->verp_delims, + qmgr_active_done_3_defer_flush, + (void *) message); + return; + } else if (message->warn_time > 0 + && event_time() >= message->warn_time - 1) { + if (msg_verbose) + msg_info("%s: sending defer warning for %s", myname, message->queue_id); + adefer_warn(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + message->encoding, + message->smtputf8, + message->sender, + message->dsn_envid, + message->dsn_ret, + qmgr_active_done_3_defer_warn, + (void *) message); + return; + } + } + + /* + * Asynchronous processing does not reach this point. + */ + qmgr_active_done_3_generic(message); +} + +/* qmgr_active_done_3_defer_warn - continue after adefer_warn() completion */ + +static void qmgr_active_done_3_defer_warn(int status, void *context) +{ + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process adefer_warn() completion status and continue processing. + */ + if (status == 0) + qmgr_message_update_warn(message); + qmgr_active_done_3_generic(message); +} + +/* qmgr_active_done_3_defer_flush - continue after adefer_flush() completion */ + +static void qmgr_active_done_3_defer_flush(int status, void *context) +{ + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process adefer_flush() status and continue processing. + */ + message->flags = status; + qmgr_active_done_3_generic(message); +} + +/* qmgr_active_done_3_generic - continue processing */ + +static void qmgr_active_done_3_generic(QMGR_MESSAGE *message) +{ + const char *myname = "qmgr_active_done_3_generic"; + int delay; + + /* + * Some recipients need to be tried again. Move the queue file time + * stamps into the future by the amount of time that the message is + * delayed, and move the message to the deferred queue. Impose minimal + * and maximal backoff times. + * + * Since we look at actual time in queue, not time since last delivery + * attempt, backoff times will be distributed. However, we can still see + * spikes in delivery activity because the interval between deferred + * queue scans is finite. + */ + if (message->flags) { + if (message->create_time > 0) { + delay = event_time() - message->create_time; + if (delay > var_max_backoff_time) + delay = var_max_backoff_time; + if (delay < var_min_backoff_time) + delay = var_min_backoff_time; + } else { + delay = var_min_backoff_time; + } + qmgr_active_defer(message->queue_name, message->queue_id, + MAIL_QUEUE_DEFERRED, delay); + } + + /* + * All recipients done. Remove the queue file. + */ + else { + if (mail_queue_remove(message->queue_name, message->queue_id)) { + if (errno != ENOENT) + msg_fatal("%s: remove %s from %s: %m", myname, + message->queue_id, message->queue_name); + msg_warn("%s: remove %s from %s: %m", myname, + message->queue_id, message->queue_name); + } else { + /* Same format as logged by postsuper. */ + msg_info("%s: removed", message->queue_id); + } + } + + /* + * Finally, delete the in-core message structure. + */ + qmgr_message_free(message); +} + +/* qmgr_active_drain - drain active queue by allocating a delivery process */ + +void qmgr_active_drain(void) +{ + QMGR_TRANSPORT *transport; + + /* + * Allocate one delivery process for every transport with pending mail. + * The process allocation completes asynchronously. + */ + while ((transport = qmgr_transport_select()) != 0) { + if (msg_verbose) + msg_info("qmgr_active_drain: allocate %s", transport->name); + qmgr_transport_alloc(transport, qmgr_deliver); + } +} diff --git a/src/qmgr/qmgr_bounce.c b/src/qmgr/qmgr_bounce.c new file mode 100644 index 0000000..00ba885 --- /dev/null +++ b/src/qmgr/qmgr_bounce.c @@ -0,0 +1,71 @@ +/*++ +/* NAME +/* qmgr_bounce +/* SUMMARY +/* deal with mail that will not be delivered +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_QUEUE *qmgr_bounce_recipient(message, recipient, dsn) +/* QMGR_MESSAGE *message; +/* RECIPIENT *recipient; +/* DSN *dsn; +/* DESCRIPTION +/* qmgr_bounce_recipient() produces a bounce log record. +/* Once the bounce record is written successfully, the recipient +/* is marked as done. When the bounce record cannot be written, +/* the message structure is updated to reflect that the mail is +/* deferred. +/* +/* Arguments: +/* .IP message +/* Open queue file with the message being bounced. +/* .IP recipient +/* The recipient that will not be delivered. +/* .IP dsn +/* Delivery status information. See dsn(3). +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_bounce_recipient - bounce one message recipient */ + +void qmgr_bounce_recipient(QMGR_MESSAGE *message, RECIPIENT *recipient, + DSN *dsn) +{ + MSG_STATS stats; + int status; + + status = bounce_append(message->tflags, message->queue_id, + QMGR_MSG_STATS(&stats, message), recipient, + "none", dsn); + + if (status == 0) + deliver_completed(message->fp, recipient->offset); + else + message->flags |= status; +} diff --git a/src/qmgr/qmgr_defer.c b/src/qmgr/qmgr_defer.c new file mode 100644 index 0000000..79615cc --- /dev/null +++ b/src/qmgr/qmgr_defer.c @@ -0,0 +1,163 @@ +/*++ +/* NAME +/* qmgr_defer +/* SUMMARY +/* deal with mail that must be delivered later +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_defer_recipient(message, recipient, dsn) +/* QMGR_MESSAGE *message; +/* RECIPIENT *recipient; +/* DSN *dsn; +/* +/* void qmgr_defer_todo(queue, dsn) +/* QMGR_QUEUE *queue; +/* DSN *dsn; +/* +/* void qmgr_defer_transport(transport, dsn) +/* QMGR_TRANSPORT *transport; +/* DSN *dsn; +/* DESCRIPTION +/* qmgr_defer_recipient() defers delivery of the named message to +/* the named recipient. It updates the message structure and writes +/* a log entry. +/* +/* qmgr_defer_todo() iterates over all "todo" deliveries queued for +/* the named site, and calls qmgr_defer_recipient() for each recipient +/* found. Side effects caused by qmgr_entry_done(), qmgr_queue_done(), +/* and by qmgr_active_done(): in-core queue entries will disappear, +/* in-core queues may disappear, in-core and on-disk messages may +/* disappear, bounces may be sent, new in-core queues, queue entries +/* and recipients may appear. +/* +/* qmgr_defer_transport() calls qmgr_defer_todo() for each queue +/* that depends on the named transport. See there for side effects. +/* +/* Arguments: +/* .IP recipient +/* A recipient address; used for logging purposes, and for updating +/* the message-specific \fIdefer\fR log. +/* .IP queue +/* Specifies a queue with delivery requests for a specific next-hop +/* host (or local user). +/* .IP transport +/* Specifies a message delivery transport. +/* .IP dsn +/* See dsn(3). +/* BUGS +/* The side effects of calling this routine are quite dramatic. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Preemptive scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_defer_transport - defer todo entries for named transport */ + +void qmgr_defer_transport(QMGR_TRANSPORT *transport, DSN *dsn) +{ + QMGR_QUEUE *queue; + QMGR_QUEUE *next; + + if (msg_verbose) + msg_info("defer transport %s: %s %s", + transport->name, dsn->status, dsn->reason); + + /* + * Proceed carefully. Queues may disappear as a side effect. + */ + for (queue = transport->queue_list.next; queue; queue = next) { + next = queue->peers.next; + qmgr_defer_todo(queue, dsn); + } +} + +/* qmgr_defer_todo - defer all todo queue entries for specific site */ + +void qmgr_defer_todo(QMGR_QUEUE *queue, DSN *dsn) +{ + QMGR_ENTRY *entry; + QMGR_ENTRY *next; + QMGR_MESSAGE *message; + RECIPIENT *recipient; + int nrcpt; + QMGR_QUEUE *retry_queue; + + /* + * Sanity checks. + */ + if (msg_verbose) + msg_info("defer site %s: %s %s", + queue->name, dsn->status, dsn->reason); + + /* + * See if we can redirect the deliveries to the retry(8) delivery agent, + * so that they can be handled asynchronously. If the retry(8) service is + * unavailable, use the synchronous defer(8) server. With a large todo + * queue, this blocks the queue manager for a significant time. + */ + retry_queue = qmgr_error_queue(MAIL_SERVICE_RETRY, dsn); + + /* + * Proceed carefully. Queue entries may disappear as a side effect. + */ + for (entry = queue->todo.next; entry != 0; entry = next) { + next = entry->queue_peers.next; + if (retry_queue != 0) { + qmgr_entry_move_todo(retry_queue, entry); + continue; + } + message = entry->message; + for (nrcpt = 0; nrcpt < entry->rcpt_list.len; nrcpt++) { + recipient = entry->rcpt_list.info + nrcpt; + qmgr_defer_recipient(message, recipient, dsn); + } + qmgr_entry_done(entry, QMGR_QUEUE_TODO); + } +} + +/* qmgr_defer_recipient - defer delivery of specific recipient */ + +void qmgr_defer_recipient(QMGR_MESSAGE *message, RECIPIENT *recipient, + DSN *dsn) +{ + MSG_STATS stats; + + /* + * Update the message structure and log the message disposition. + */ + message->flags |= defer_append(message->tflags, message->queue_id, + QMGR_MSG_STATS(&stats, message), recipient, + "none", dsn); +} diff --git a/src/qmgr/qmgr_deliver.c b/src/qmgr/qmgr_deliver.c new file mode 100644 index 0000000..07e89d4 --- /dev/null +++ b/src/qmgr/qmgr_deliver.c @@ -0,0 +1,456 @@ +/*++ +/* NAME +/* qmgr_deliver 3 +/* SUMMARY +/* deliver one per-site queue entry to that site +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* int qmgr_deliver_concurrency; +/* +/* int qmgr_deliver(transport, fp) +/* QMGR_TRANSPORT *transport; +/* VSTREAM *fp; +/* DESCRIPTION +/* This module implements the client side of the `queue manager +/* to delivery agent' protocol. The queue manager uses +/* asynchronous I/O so that it can drive multiple delivery +/* agents in parallel. Depending on the outcome of a delivery +/* attempt, the status of messages, queues and transports is +/* updated. +/* +/* qmgr_deliver_concurrency is a global counter that says how +/* many delivery processes are in use. This can be used, for +/* example, to control the size of the `active' message queue. +/* +/* qmgr_deliver() executes when a delivery process announces its +/* availability for the named transport. It arranges for delivery +/* of a suitable queue entry. The \fIfp\fR argument specifies a +/* stream that is connected to a delivery process, or a null +/* pointer if the transport accepts no connection. Upon completion +/* of delivery (successful or not), the stream is closed, so that the +/* delivery process is released. +/* DIAGNOSTICS +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Preemptive scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + + /* + * Important note on the _transport_rate_delay implementation: after + * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all + * code paths must directly or indirectly invoke qmgr_transport_unthrottle() + * or qmgr_transport_throttle(). Otherwise, transports with non-zero + * _transport_rate_delay will become stuck. + */ + +int qmgr_deliver_concurrency; + + /* + * Message delivery status codes. + */ +#define DELIVER_STAT_OK 0 /* all recipients delivered */ +#define DELIVER_STAT_DEFER 1 /* try some recipients later */ +#define DELIVER_STAT_CRASH 2 /* mailer internal problem */ + +/* qmgr_deliver_initial_reply - retrieve initial delivery process response */ + +static int qmgr_deliver_initial_reply(VSTREAM *stream) +{ + if (peekfd(vstream_fileno(stream)) < 0) { + msg_warn("%s: premature disconnect", VSTREAM_PATH(stream)); + return (DELIVER_STAT_CRASH); + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_DELIVER), + ATTR_TYPE_END) != 0) { + msg_warn("%s: malformed response", VSTREAM_PATH(stream)); + return (DELIVER_STAT_DEFER); + } else { + return (0); + } +} + +/* qmgr_deliver_final_reply - retrieve final delivery process response */ + +static int qmgr_deliver_final_reply(VSTREAM *stream, DSN_BUF *dsb) +{ + int stat; + + if (peekfd(vstream_fileno(stream)) < 0) { + msg_warn("%s: premature disconnect", VSTREAM_PATH(stream)); + return (DELIVER_STAT_CRASH); + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_FUNC(dsb_scan, (void *) dsb), + RECV_ATTR_INT(MAIL_ATTR_STATUS, &stat), + ATTR_TYPE_END) != 2) { + msg_warn("%s: malformed response", VSTREAM_PATH(stream)); + return (DELIVER_STAT_CRASH); + } else { + return (stat ? DELIVER_STAT_DEFER : 0); + } +} + +/* qmgr_deliver_send_request - send delivery request to delivery process */ + +static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) +{ + RECIPIENT_LIST list = entry->rcpt_list; + RECIPIENT *recipient; + QMGR_MESSAGE *message = entry->message; + VSTRING *sender_buf = 0; + MSG_STATS stats; + char *sender; + int flags; + int smtputf8 = message->smtputf8; + const char *addr; + + /* + * Todo: integrate with code up-stream that builds the delivery request. + */ + for (recipient = list.info; recipient < list.info + list.len; recipient++) + if (var_smtputf8_enable && (addr = recipient->address)[0] + && !allascii(addr) && valid_utf8_string(addr, strlen(addr))) { + smtputf8 |= SMTPUTF8_FLAG_RECIPIENT; + if (message->verp_delims) + smtputf8 |= SMTPUTF8_FLAG_SENDER; + } + + /* + * If variable envelope return path is requested, change prefix+@origin + * into prefix+user=domain@origin. Note that with VERP there is only one + * recipient per delivery. + */ + if (message->verp_delims == 0) { + sender = message->sender; + } else { + sender_buf = vstring_alloc(100); + verp_sender(sender_buf, message->verp_delims, + message->sender, list.info); + sender = vstring_str(sender_buf); + } + + flags = message->tflags + | entry->queue->dflags + | (message->inspect_xport ? DEL_REQ_FLAG_BOUNCE : DEL_REQ_FLAG_DEFLT); + (void) QMGR_MSG_STATS(&stats, message); + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags), + SEND_ATTR_STR(MAIL_ATTR_QUEUE, message->queue_name), + SEND_ATTR_STR(MAIL_ATTR_QUEUEID, message->queue_id), + SEND_ATTR_LONG(MAIL_ATTR_OFFSET, message->data_offset), + SEND_ATTR_LONG(MAIL_ATTR_SIZE, message->cont_length), + SEND_ATTR_STR(MAIL_ATTR_NEXTHOP, entry->queue->nexthop), + SEND_ATTR_STR(MAIL_ATTR_ENCODING, message->encoding), + SEND_ATTR_INT(MAIL_ATTR_SMTPUTF8, smtputf8), + SEND_ATTR_STR(MAIL_ATTR_SENDER, sender), + SEND_ATTR_STR(MAIL_ATTR_DSN_ENVID, message->dsn_envid), + SEND_ATTR_INT(MAIL_ATTR_DSN_RET, message->dsn_ret), + SEND_ATTR_FUNC(msg_stats_print, (const void *) &stats), + /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */ + SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_NAME, message->client_name), + SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_ADDR, message->client_addr), + SEND_ATTR_STR(MAIL_ATTR_LOG_CLIENT_PORT, message->client_port), + SEND_ATTR_STR(MAIL_ATTR_LOG_PROTO_NAME, message->client_proto), + SEND_ATTR_STR(MAIL_ATTR_LOG_HELO_NAME, message->client_helo), + /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */ + SEND_ATTR_STR(MAIL_ATTR_SASL_METHOD, message->sasl_method), + SEND_ATTR_STR(MAIL_ATTR_SASL_USERNAME, message->sasl_username), + SEND_ATTR_STR(MAIL_ATTR_SASL_SENDER, message->sasl_sender), + /* XXX Ditto if we want to pass TLS certificate info. */ + SEND_ATTR_STR(MAIL_ATTR_LOG_IDENT, message->log_ident), + SEND_ATTR_STR(MAIL_ATTR_RWR_CONTEXT, message->rewrite_context), + SEND_ATTR_INT(MAIL_ATTR_RCPT_COUNT, list.len), + ATTR_TYPE_END); + if (sender_buf != 0) + vstring_free(sender_buf); + for (recipient = list.info; recipient < list.info + list.len; recipient++) + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(rcpt_print, (const void *) recipient), + ATTR_TYPE_END); + if (vstream_fflush(stream) != 0) { + msg_warn("write to process (%s): %m", entry->queue->transport->name); + return (-1); + } else { + if (msg_verbose) + msg_info("qmgr_deliver: site `%s'", entry->queue->name); + return (0); + } +} + +/* qmgr_deliver_abort - transport response watchdog */ + +static void qmgr_deliver_abort(int unused_event, void *context) +{ + QMGR_ENTRY *entry = (QMGR_ENTRY *) context; + QMGR_QUEUE *queue = entry->queue; + QMGR_TRANSPORT *transport = queue->transport; + QMGR_MESSAGE *message = entry->message; + + msg_fatal("%s: timeout receiving delivery status from transport: %s", + message->queue_id, transport->name); +} + +/* qmgr_deliver_update - process delivery status report */ + +static void qmgr_deliver_update(int unused_event, void *context) +{ + QMGR_ENTRY *entry = (QMGR_ENTRY *) context; + QMGR_QUEUE *queue = entry->queue; + QMGR_TRANSPORT *transport = queue->transport; + QMGR_MESSAGE *message = entry->message; + static DSN_BUF *dsb; + int status; + + /* + * Release the delivery agent from a "hot" queue entry. + */ +#define QMGR_DELIVER_RELEASE_AGENT(entry) do { \ + event_disable_readwrite(vstream_fileno(entry->stream)); \ + (void) vstream_fclose(entry->stream); \ + entry->stream = 0; \ + qmgr_deliver_concurrency--; \ + } while (0) + + if (dsb == 0) + dsb = dsb_create(); + + /* + * The message transport has responded. Stop the watchdog timer. + */ + event_cancel_timer(qmgr_deliver_abort, context); + + /* + * Retrieve the delivery agent status report. The numerical status code + * indicates if delivery should be tried again. The reason text is sent + * only when a site should be avoided for a while, so that the queue + * manager can log why it does not even try to schedule delivery to the + * affected recipients. + */ + status = qmgr_deliver_final_reply(entry->stream, dsb); + + /* + * The mail delivery process failed for some reason (although delivery + * may have been successful). Back off with this transport type for a + * while. Dispose of queue entries for this transport that await + * selection (the todo lists). Stay away from queue entries that have + * been selected (the busy lists), or we would have dangling pointers. + * The queue itself won't go away before we dispose of the current queue + * entry. + */ + if (status == DELIVER_STAT_CRASH) { + message->flags |= DELIVER_STAT_DEFER; +#if 0 + whatsup = concatenate("unknown ", transport->name, + " mail transport error", (char *) 0); + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsb->dsn, "4.3.0", whatsup)); + myfree(whatsup); +#else + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsb->dsn, "4.3.0", + "unknown mail transport error")); +#endif + msg_warn("transport %s failure -- see a previous warning/fatal/panic logfile record for the problem description", + transport->name); + + /* + * Assume the worst and write a defer logfile record for each + * recipient. This omission was already present in the first queue + * manager implementation of 199703, and was fixed 200511. + * + * To avoid the synchronous qmgr_defer_recipient() operation for each + * recipient of this queue entry, release the delivery process and + * move the entry back to the todo queue. Let qmgr_defer_transport() + * log the recipient asynchronously if possible, and get out of here. + * Note: if asynchronous logging is not possible, + * qmgr_defer_transport() eventually invokes qmgr_entry_done() and + * the entry becomes a dangling pointer. + */ + QMGR_DELIVER_RELEASE_AGENT(entry); + qmgr_entry_unselect(entry); + qmgr_defer_transport(transport, &dsb->dsn); + return; + } + + /* + * This message must be tried again. + * + * If we have a problem talking to this site, back off with this site for a + * while; dispose of queue entries for this site that await selection + * (the todo list); stay away from queue entries that have been selected + * (the busy list), or we would have dangling pointers. The queue itself + * won't go away before we dispose of the current queue entry. + * + * XXX Caution: DSN_COPY() will panic on empty status or reason. + */ +#define SUSPENDED "delivery temporarily suspended: " + + if (status == DELIVER_STAT_DEFER) { + message->flags |= DELIVER_STAT_DEFER; + if (VSTRING_LEN(dsb->status)) { + /* Sanitize the DSN status/reason from the delivery agent. */ + if (!dsn_valid(vstring_str(dsb->status))) + vstring_strcpy(dsb->status, "4.0.0"); + if (VSTRING_LEN(dsb->reason) == 0) + vstring_strcpy(dsb->reason, "unknown error"); + vstring_prepend(dsb->reason, SUSPENDED, sizeof(SUSPENDED) - 1); + if (QMGR_QUEUE_READY(queue)) { + qmgr_queue_throttle(queue, DSN_FROM_DSN_BUF(dsb)); + if (QMGR_QUEUE_THROTTLED(queue)) + qmgr_defer_todo(queue, &dsb->dsn); + } + } + } + + /* + * No problems detected. Mark the transport and queue as alive. The queue + * itself won't go away before we dispose of the current queue entry. + */ + if (status != DELIVER_STAT_CRASH) { + qmgr_transport_unthrottle(transport); + if (VSTRING_LEN(dsb->reason) == 0) + qmgr_queue_unthrottle(queue); + } + + /* + * Release the delivery process, and give some other queue entry a chance + * to be delivered. When all recipients for a message have been tried, + * decide what to do next with this message: defer, bounce, delete. + */ + QMGR_DELIVER_RELEASE_AGENT(entry); + qmgr_entry_done(entry, QMGR_QUEUE_BUSY); +} + +/* qmgr_deliver - deliver one per-site queue entry */ + +void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream) +{ + QMGR_ENTRY *entry; + DSN dsn; + + /* + * Find out if this delivery process is really available. Once elected, + * the delivery process is supposed to express its happiness. If there is + * a problem, wipe the pending deliveries for this transport. This + * routine runs in response to an external event, so it does not run + * while some other queue manipulation is happening. + */ + if (stream == 0 || qmgr_deliver_initial_reply(stream) != 0) { +#if 0 + whatsup = concatenate(transport->name, + " mail transport unavailable", (char *) 0); + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsn, "4.3.0", whatsup)); + myfree(whatsup); +#else + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsn, "4.3.0", + "mail transport unavailable")); +#endif + qmgr_defer_transport(transport, &dsn); + if (stream) + (void) vstream_fclose(stream); + return; + } + + /* + * Find a suitable queue entry. Things may have changed since this + * transport was allocated. If no suitable entry is found, + * unceremoniously disconnect from the delivery process. The delivery + * agent request reading routine is prepared for the queue manager to + * change its mind for no apparent reason. + */ + if ((entry = qmgr_job_entry_select(transport)) == 0) { + (void) vstream_fclose(stream); + return; + } + + /* + * Send the queue file info and recipient info to the delivery process. + * If there is a problem, wipe the pending deliveries for this transport. + * This routine runs in response to an external event, so it does not run + * while some other queue manipulation is happening. + */ + if (qmgr_deliver_send_request(entry, stream) < 0) { + qmgr_entry_unselect(entry); +#if 0 + whatsup = concatenate(transport->name, + " mail transport unavailable", (char *) 0); + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsn, "4.3.0", whatsup)); + myfree(whatsup); +#else + qmgr_transport_throttle(transport, + DSN_SIMPLE(&dsn, "4.3.0", + "mail transport unavailable")); +#endif + qmgr_defer_transport(transport, &dsn); + /* warning: entry may be a dangling pointer here */ + (void) vstream_fclose(stream); + return; + } + + /* + * If we get this far, go wait for the delivery status report. + */ + qmgr_deliver_concurrency++; + entry->stream = stream; + event_enable_read(vstream_fileno(stream), + qmgr_deliver_update, (void *) entry); + + /* + * Guard against broken systems. + */ + event_request_timer(qmgr_deliver_abort, (void *) entry, var_daemon_timeout); +} diff --git a/src/qmgr/qmgr_enable.c b/src/qmgr/qmgr_enable.c new file mode 100644 index 0000000..a35e46e --- /dev/null +++ b/src/qmgr/qmgr_enable.c @@ -0,0 +1,107 @@ +/*++ +/* NAME +/* qmgr_enable +/* SUMMARY +/* enable dead transports or sites +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_enable_queue(queue) +/* QMGR_QUEUE *queue; +/* +/* QMGR_QUEUE *qmgr_enable_transport(transport) +/* QMGR_TRANSPORT *transport; +/* +/* void qmgr_enable_all(void) +/* DESCRIPTION +/* This module purges dead in-core state information, effectively +/* re-enabling delivery. +/* +/* qmgr_enable_queue() enables deliveries to the named dead site. +/* Empty queues are destroyed. The existed solely to indicate that +/* a site is dead. +/* +/* qmgr_enable_transport() enables deliveries via the specified +/* transport, and calls qmgr_enable_queue() for each destination +/* on that transport. Empty queues are destroyed. +/* +/* qmgr_enable_all() enables all transports and queues. +/* See above for the side effects caused by doing this. +/* BUGS +/* The side effects of calling this module can be quite dramatic. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_enable_all - enable transports and queues */ + +void qmgr_enable_all(void) +{ + QMGR_TRANSPORT *xport; + + if (msg_verbose) + msg_info("qmgr_enable_all"); + + /* + * The number of transports does not change as a side effect, so this can + * be a straightforward loop. + */ + for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next) + qmgr_enable_transport(xport); +} + +/* qmgr_enable_transport - defer todo entries for named transport */ + +void qmgr_enable_transport(QMGR_TRANSPORT *transport) +{ + QMGR_QUEUE *queue; + QMGR_QUEUE *next; + + /* + * Proceed carefully. Queues may disappear as a side effect. + */ + if (transport->flags & QMGR_TRANSPORT_STAT_DEAD) { + if (msg_verbose) + msg_info("enable transport %s", transport->name); + qmgr_transport_unthrottle(transport); + } + for (queue = transport->queue_list.next; queue; queue = next) { + next = queue->peers.next; + qmgr_enable_queue(queue); + } +} + +/* qmgr_enable_queue - enable and possibly delete queue */ + +void qmgr_enable_queue(QMGR_QUEUE *queue) +{ + if (QMGR_QUEUE_THROTTLED(queue)) { + if (msg_verbose) + msg_info("enable site %s/%s", queue->transport->name, queue->name); + qmgr_queue_unthrottle(queue); + } + if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0) + qmgr_queue_done(queue); +} diff --git a/src/qmgr/qmgr_entry.c b/src/qmgr/qmgr_entry.c new file mode 100644 index 0000000..e0673a9 --- /dev/null +++ b/src/qmgr/qmgr_entry.c @@ -0,0 +1,452 @@ +/*++ +/* NAME +/* qmgr_entry 3 +/* SUMMARY +/* per-site queue entries +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_ENTRY *qmgr_entry_create(peer, message) +/* QMGR_PEER *peer; +/* QMGR_MESSAGE *message; +/* +/* void qmgr_entry_done(entry, which) +/* QMGR_ENTRY *entry; +/* int which; +/* +/* QMGR_ENTRY *qmgr_entry_select(queue) +/* QMGR_QUEUE *queue; +/* +/* void qmgr_entry_unselect(queue, entry) +/* QMGR_QUEUE *queue; +/* QMGR_ENTRY *entry; +/* +/* void qmgr_entry_move_todo(dst, entry) +/* QMGR_QUEUE *dst; +/* QMGR_ENTRY *entry; +/* DESCRIPTION +/* These routines add/delete/manipulate per-site message +/* delivery requests. +/* +/* qmgr_entry_create() creates an entry for the named peer and message, +/* and appends the entry to the peer's list and its queue's todo list. +/* Filling in and cleaning up the recipients is the responsibility +/* of the caller. +/* +/* qmgr_entry_done() discards a per-site queue entry. The +/* \fIwhich\fR argument is either QMGR_QUEUE_BUSY for an entry +/* of the site's `busy' list (i.e. queue entries that have been +/* selected for actual delivery), or QMGR_QUEUE_TODO for an entry +/* of the site's `todo' list (i.e. queue entries awaiting selection +/* for actual delivery). +/* +/* qmgr_entry_done() discards its peer structure when the peer +/* is not referenced anymore. +/* +/* qmgr_entry_done() triggers cleanup of the per-site queue when +/* the site has no pending deliveries, and the site is either +/* alive, or the site is dead and the number of in-core queues +/* exceeds a configurable limit (see qmgr_queue_done()). +/* +/* qmgr_entry_done() triggers special action when the last in-core +/* queue entry for a message is done with: either read more +/* recipients from the queue file, delete the queue file, or move +/* the queue file to the deferred queue; send bounce reports to the +/* message originator (see qmgr_active_done()). +/* +/* qmgr_entry_select() selects first entry from the named +/* per-site queue's `todo' list for actual delivery. The entry is +/* moved to the queue's `busy' list: the list of messages being +/* delivered. The entry is also removed from its peer list. +/* +/* qmgr_entry_unselect() takes the named entry off the named +/* per-site queue's `busy' list and moves it to the queue's +/* `todo' list. The entry is also prepended to its peer list again. +/* +/* qmgr_entry_move_todo() moves the specified "todo" queue entry +/* to the specified "todo" queue. +/* DIAGNOSTICS +/* Panic: interface violations, internal inconsistencies. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Preemptive scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include /* opportunistic session caching */ + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_entry_select - select queue entry for delivery */ + +QMGR_ENTRY *qmgr_entry_select(QMGR_PEER *peer) +{ + const char *myname = "qmgr_entry_select"; + QMGR_ENTRY *entry; + QMGR_QUEUE *queue; + + if ((entry = peer->entry_list.next) != 0) { + queue = entry->queue; + QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers); + queue->todo_refcount--; + QMGR_LIST_APPEND(queue->busy, entry, queue_peers); + queue->busy_refcount++; + QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers); + peer->job->selected_entries++; + + /* + * With opportunistic session caching, the delivery agent must not + * only 1) save a session upon completion, but also 2) reuse a cached + * session upon the next delivery request. In order to not miss out + * on 2), we have to make caching sticky or else we get silly + * behavior when the in-memory queue drains. Specifically, new + * connections must not be made as long as cached connections exist. + * + * Safety: don't enable opportunistic session caching unless the queue + * manager is able to schedule concurrent or back-to-back deliveries + * (we need to recognize back-to-back deliveries for transports with + * concurrency 1). + * + * If caching has previously been enabled, but is not now, fetch any + * existing entries from the cache, but don't add new ones. + */ +#define CONCURRENT_OR_BACK_TO_BACK_DELIVERY() \ + (queue->busy_refcount > 1 || BACK_TO_BACK_DELIVERY()) + +#define BACK_TO_BACK_DELIVERY() \ + (queue->last_done + 1 >= event_time()) + + /* + * Turn on session caching after we get up to speed. Don't enable + * session caching just because we have concurrent deliveries. This + * prevents unnecessary session caching when we have a burst of mail + * <= the initial concurrency limit. + */ + if ((queue->dflags & DEL_REQ_FLAG_CONN_STORE) == 0) { + if (BACK_TO_BACK_DELIVERY()) { + if (msg_verbose) + msg_info("%s: allowing on-demand session caching for %s", + myname, queue->name); + queue->dflags |= DEL_REQ_FLAG_CONN_MASK; + } + } + + /* + * Turn off session caching when concurrency drops and we're running + * out of steam. This is what prevents from turning off session + * caching too early, and from making new connections while old ones + * are still cached. + */ + else { + if (!CONCURRENT_OR_BACK_TO_BACK_DELIVERY()) { + if (msg_verbose) + msg_info("%s: disallowing on-demand session caching for %s", + myname, queue->name); + queue->dflags &= ~DEL_REQ_FLAG_CONN_STORE; + } + } + } + return (entry); +} + +/* qmgr_entry_unselect - unselect queue entry for delivery */ + +void qmgr_entry_unselect(QMGR_ENTRY *entry) +{ + QMGR_PEER *peer = entry->peer; + QMGR_QUEUE *queue = entry->queue; + + /* + * Move the entry back to the todo lists. In case of the peer list, put + * it back to the beginning, so the select()/unselect() does not reorder + * entries. We use this in qmgr_message_assign() to put recipients into + * existing entries when possible. + */ + QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers); + queue->busy_refcount--; + QMGR_LIST_APPEND(queue->todo, entry, queue_peers); + queue->todo_refcount++; + QMGR_LIST_PREPEND(peer->entry_list, entry, peer_peers); + peer->job->selected_entries--; +} + +/* qmgr_entry_move_todo - move entry between todo queues */ + +void qmgr_entry_move_todo(QMGR_QUEUE *dst_queue, QMGR_ENTRY *entry) +{ + const char *myname = "qmgr_entry_move_todo"; + QMGR_TRANSPORT *dst_transport = dst_queue->transport; + QMGR_MESSAGE *message = entry->message; + QMGR_QUEUE *src_queue = entry->queue; + QMGR_PEER *dst_peer, *src_peer = entry->peer; + QMGR_JOB *dst_job, *src_job = src_peer->job; + QMGR_ENTRY *new_entry; + int rcpt_count = entry->rcpt_list.len; + + if (entry->stream != 0) + msg_panic("%s: queue %s entry is busy", myname, src_queue->name); + if (QMGR_QUEUE_THROTTLED(dst_queue)) + msg_panic("%s: destination queue %s is throttled", myname, dst_queue->name); + if (QMGR_TRANSPORT_THROTTLED(dst_transport)) + msg_panic("%s: destination transport %s is throttled", + myname, dst_transport->name); + + /* + * Create new entry, swap the recipients between the two entries, + * adjusting the job counters accordingly, then dispose of the old entry. + * + * Note that qmgr_entry_done() will also take care of adjusting the + * recipient limits of all the message jobs, so we do not have to do that + * explicitly for the new job here. + * + * XXX This does not enforce the per-entry recipient limit, but that is not + * a problem as long as qmgr_entry_move_todo() is called only to bounce + * or defer mail. + */ + dst_job = qmgr_job_obtain(message, dst_transport); + dst_peer = qmgr_peer_obtain(dst_job, dst_queue); + + new_entry = qmgr_entry_create(dst_peer, message); + + recipient_list_swap(&entry->rcpt_list, &new_entry->rcpt_list); + + src_job->rcpt_count -= rcpt_count; + dst_job->rcpt_count += rcpt_count; + + qmgr_entry_done(entry, QMGR_QUEUE_TODO); +} + +/* qmgr_entry_done - dispose of queue entry */ + +void qmgr_entry_done(QMGR_ENTRY *entry, int which) +{ + const char *myname = "qmgr_entry_done"; + QMGR_QUEUE *queue = entry->queue; + QMGR_MESSAGE *message = entry->message; + QMGR_PEER *peer = entry->peer; + QMGR_JOB *sponsor, *job = peer->job; + QMGR_TRANSPORT *transport = job->transport; + + /* + * Take this entry off the in-core queue. + */ + if (entry->stream != 0) + msg_panic("%s: file is open", myname); + if (which == QMGR_QUEUE_BUSY) { + QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers); + queue->busy_refcount--; + } else if (which == QMGR_QUEUE_TODO) { + QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers); + job->selected_entries++; + QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers); + queue->todo_refcount--; + } else { + msg_panic("%s: bad queue spec: %d", myname, which); + } + + /* + * Decrease the in-core recipient counts and free the recipient list and + * the structure itself. + */ + job->rcpt_count -= entry->rcpt_list.len; + message->rcpt_count -= entry->rcpt_list.len; + qmgr_recipient_count -= entry->rcpt_list.len; + recipient_list_free(&entry->rcpt_list); + myfree((void *) entry); + + /* + * Make sure that the transport of any retired or finishing job that + * donated recipient slots to this message gets them back first. Then, if + * possible, pass the remaining unused recipient slots to the next job on + * the job list. + */ + for (sponsor = message->job_list.next; sponsor; sponsor = sponsor->message_peers.next) { + if (sponsor->rcpt_count >= sponsor->rcpt_limit || sponsor == job) + continue; + if (sponsor->stack_level < 0 || message->rcpt_offset == 0) + qmgr_job_move_limits(sponsor); + } + if (message->rcpt_offset == 0) { + qmgr_job_move_limits(job); + } + + /* + * We implement a rate-limited queue by emulating a slow delivery + * channel. We insert the artificial delays with qmgr_queue_suspend(). + * + * When a queue is suspended, we must postpone any job scheduling decisions + * until the queue is resumed. Otherwise, we make those decisions now. + * The job scheduling decisions are made by qmgr_job_blocker_update(). + */ + if (which == QMGR_QUEUE_BUSY && transport->rate_delay > 0) { + if (queue->window > 1) + msg_panic("%s: queue %s/%s: window %d > 1 on rate-limited service", + myname, transport->name, queue->name, queue->window); + if (QMGR_QUEUE_THROTTLED(queue)) /* XXX */ + qmgr_queue_unthrottle(queue); + if (QMGR_QUEUE_READY(queue)) + qmgr_queue_suspend(queue, transport->rate_delay); + } + if (!QMGR_QUEUE_SUSPENDED(queue) + && queue->blocker_tag == transport->blocker_tag) + qmgr_job_blocker_update(queue); + + /* + * When there are no more entries for this peer, discard the peer + * structure. + */ + peer->refcount--; + if (peer->refcount == 0) + qmgr_peer_free(peer); + + /* + * Maintain back-to-back delivery status. + */ + if (which == QMGR_QUEUE_BUSY) + queue->last_done = event_time(); + + /* + * When the in-core queue for this site is empty and when this site is + * not dead or suspended, discard the in-core queue. When this site is + * dead, but the number of in-core queues exceeds some threshold, get rid + * of this in-core queue anyway, in order to avoid running out of memory. + */ + if (queue->todo.next == 0 && queue->busy.next == 0) { + if (QMGR_QUEUE_THROTTLED(queue) && qmgr_queue_count > 2 * var_qmgr_rcpt_limit) + qmgr_queue_unthrottle(queue); + if (QMGR_QUEUE_READY(queue)) + qmgr_queue_done(queue); + } + + /* + * Update the in-core message reference count. When the in-core message + * structure has no more references, dispose of the message. + */ + message->refcount--; + if (message->refcount == 0) + qmgr_active_done(message); +} + +/* qmgr_entry_create - create queue todo entry */ + +QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *peer, QMGR_MESSAGE *message) +{ + QMGR_ENTRY *entry; + QMGR_QUEUE *queue = peer->queue; + + /* + * Sanity check. + */ + if (QMGR_QUEUE_THROTTLED(queue)) + msg_panic("qmgr_entry_create: dead queue: %s", queue->name); + + /* + * Create the delivery request. + */ + entry = (QMGR_ENTRY *) mymalloc(sizeof(QMGR_ENTRY)); + entry->stream = 0; + entry->message = message; + recipient_list_init(&entry->rcpt_list, RCPT_LIST_INIT_QUEUE); + message->refcount++; + entry->peer = peer; + QMGR_LIST_APPEND(peer->entry_list, entry, peer_peers); + peer->refcount++; + entry->queue = queue; + QMGR_LIST_APPEND(queue->todo, entry, queue_peers); + queue->todo_refcount++; + peer->job->read_entries++; + + /* + * Warn if a destination is falling behind while the active queue + * contains a non-trivial amount of single-recipient email. When a + * destination takes up more and more space in the active queue, then + * other mail will not get through and delivery performance will suffer. + * + * XXX At this point in the code, the busy reference count is still less + * than the concurrency limit (otherwise this code would not be invoked + * in the first place) so we have to make some awkward adjustments + * below. + * + * XXX The queue length test below looks at the active queue share of an + * individual destination. This catches the case where mail for one + * destination is falling behind because it has to round-robin compete + * with many other destinations. However, Postfix will also perform + * poorly when most of the active queue is tied up by a small number of + * concurrency limited destinations. The queue length test below detects + * such conditions only indirectly. + * + * XXX This code does not detect the case that the active queue is being + * starved because incoming mail is pounding the disk. + */ + if (var_helpful_warnings && var_qmgr_clog_warn_time > 0) { + int queue_length = queue->todo_refcount + queue->busy_refcount; + time_t now; + QMGR_TRANSPORT *transport; + double active_share; + + if (queue_length > var_qmgr_active_limit / 5 + && (now = event_time()) >= queue->clog_time_to_warn) { + active_share = queue_length / (double) qmgr_message_count; + msg_warn("mail for %s is using up %d of %d active queue entries", + queue->nexthop, queue_length, qmgr_message_count); + if (active_share < 0.9) + msg_warn("this may slow down other mail deliveries"); + transport = queue->transport; + if (transport->dest_concurrency_limit > 0 + && transport->dest_concurrency_limit <= queue->busy_refcount + 1) + msg_warn("you may need to increase the main.cf %s%s from %d", + transport->name, _DEST_CON_LIMIT, + transport->dest_concurrency_limit); + else if (queue->window > var_qmgr_active_limit * active_share) + msg_warn("you may need to increase the main.cf %s from %d", + VAR_QMGR_ACT_LIMIT, var_qmgr_active_limit); + else if (queue->peers.next != queue->peers.prev) + msg_warn("you may need a separate master.cf transport for %s", + queue->nexthop); + else { + msg_warn("you may need to reduce %s connect and helo timeouts", + transport->name); + msg_warn("so that Postfix quickly skips unavailable hosts"); + msg_warn("you may need to increase the main.cf %s and %s", + VAR_MIN_BACKOFF_TIME, VAR_MAX_BACKOFF_TIME); + msg_warn("so that Postfix wastes less time on undeliverable mail"); + msg_warn("you may need to increase the master.cf %s process limit", + transport->name); + } + msg_warn("please avoid flushing the whole queue when you have"); + msg_warn("lots of deferred mail, that is bad for performance"); + msg_warn("to turn off these warnings specify: %s = 0", + VAR_QMGR_CLOG_WARN_TIME); + queue->clog_time_to_warn = now + var_qmgr_clog_warn_time; + } + } + return (entry); +} diff --git a/src/qmgr/qmgr_error.c b/src/qmgr/qmgr_error.c new file mode 100644 index 0000000..6541c35 --- /dev/null +++ b/src/qmgr/qmgr_error.c @@ -0,0 +1,121 @@ +/*++ +/* NAME +/* qmgr_error 3 +/* SUMMARY +/* look up/create error/retry queue +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_TRANSPORT *qmgr_error_transport(service) +/* const char *service; +/* +/* QMGR_QUEUE *qmgr_error_queue(service, dsn) +/* const char *service; +/* DSN *dsn; +/* +/* char *qmgr_error_nexthop(dsn) +/* DSN *dsn; +/* DESCRIPTION +/* qmgr_error_transport() looks up the error transport for the +/* specified service. The result is null if the transport is +/* not available. +/* +/* qmgr_error_queue() looks up an error queue for the specified +/* service and problem. The result is null if the queue is not +/* available. +/* +/* qmgr_error_nexthop() computes the next-hop information for +/* the specified problem. The result must be passed to myfree(). +/* +/* Arguments: +/* .IP dsn +/* See dsn(3). +/* .IP service +/* One of MAIL_SERVICE_ERROR or MAIL_SERVICE_RETRY. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_error_transport - look up error transport for specified service */ + +QMGR_TRANSPORT *qmgr_error_transport(const char *service) +{ + QMGR_TRANSPORT *transport; + + /* + * Find or create retry transport. + */ + if ((transport = qmgr_transport_find(service)) == 0) + transport = qmgr_transport_create(service); + if (QMGR_TRANSPORT_THROTTLED(transport)) + return (0); + + /* + * Done. + */ + return (transport); +} + +/* qmgr_error_queue - look up error queue for specified service and problem */ + +QMGR_QUEUE *qmgr_error_queue(const char *service, DSN *dsn) +{ + QMGR_TRANSPORT *transport; + QMGR_QUEUE *queue; + char *nexthop; + + /* + * Find or create transport. + */ + if ((transport = qmgr_error_transport(service)) == 0) + return (0); + + /* + * Find or create queue. + */ + nexthop = qmgr_error_nexthop(dsn); + if ((queue = qmgr_queue_find(transport, nexthop)) == 0) + queue = qmgr_queue_create(transport, nexthop, nexthop); + myfree(nexthop); + if (QMGR_QUEUE_THROTTLED(queue)) + return (0); + + /* + * Done. + */ + return (queue); +} + +/* qmgr_error_nexthop - compute next-hop information from problem description */ + +char *qmgr_error_nexthop(DSN *dsn) +{ + char *nexthop; + + nexthop = concatenate(dsn->status, " ", dsn->reason, (char *) 0); + return (nexthop); +} diff --git a/src/qmgr/qmgr_feedback.c b/src/qmgr/qmgr_feedback.c new file mode 100644 index 0000000..f8019f8 --- /dev/null +++ b/src/qmgr/qmgr_feedback.c @@ -0,0 +1,182 @@ +/*++ +/* NAME +/* qmgr_feedback 3 +/* SUMMARY +/* delivery agent feedback management +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_feedback_init(fbck_ctl, name_prefix, name_tail, +/* def_name, def_value) +/* QMGR_FEEDBACK *fbck_ctl; +/* const char *name_prefix; +/* const char *name_tail; +/* const char *def_name; +/* const char *def_value; +/* +/* double QMGR_FEEDBACK_VAL(fbck_ctl, concurrency) +/* QMGR_FEEDBACK *fbck_ctl; +/* const int concurrency; +/* DESCRIPTION +/* Upon completion of a delivery request, a delivery agent +/* provides a hint that the scheduler should dedicate fewer or +/* more resources to a specific destination. +/* +/* qmgr_feedback_init() looks up transport-dependent positive +/* or negative concurrency feedback control information from +/* main.cf, and converts it to internal form. +/* +/* QMGR_FEEDBACK_VAL() computes a concurrency adjustment based +/* on a preprocessed feedback control information and the +/* current concurrency window. This is an "unsafe" macro that +/* evaluates some arguments multiple times. +/* +/* Arguments: +/* .IP fbck_ctl +/* Pointer to QMGR_FEEDBACK structure where the result will +/* be stored. +/* .IP name_prefix +/* Mail delivery transport name, used as the initial portion +/* of a transport-dependent concurrency feedback parameter +/* name. +/* .IP name_tail +/* The second, and fixed, portion of a transport-dependent +/* concurrency feedback parameter. +/* .IP def_name +/* The name of a default feedback parameter. +/* .IP def_val +/* The value of the default feedback parameter. +/* .IP concurrency +/* Delivery concurrency for concurrency-dependent feedback calculation. +/* DIAGNOSTICS +/* Warning: configuration error or unreasonable input. The program +/* uses name_tail feedback instead. +/* Panic: consistency check failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include /* INT_MAX */ +#include /* sscanf() */ +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + + /* + * Lookup tables for main.cf feedback method names. + */ +const NAME_CODE qmgr_feedback_map[] = { + CONC_FDBACK_NAME_WIN, QMGR_FEEDBACK_IDX_WIN, +#ifdef QMGR_FEEDBACK_IDX_SQRT_WIN + CONC_FDBACK_NAME_SQRT_WIN, QMGR_FEEDBACK_IDX_SQRT_WIN, +#endif + 0, QMGR_FEEDBACK_IDX_NONE, +}; + +/* qmgr_feedback_init - initialize feedback control */ + +void qmgr_feedback_init(QMGR_FEEDBACK *fb, + const char *name_prefix, + const char *name_tail, + const char *def_name, + const char *def_val) +{ + double enum_val; + char denom_str[30 + 1]; + double denom_val; + char slash[1 + 1]; + char junk; + char *fbck_name; + char *fbck_val; + + /* + * Look up the transport-dependent feedback value. + */ + fbck_name = concatenate(name_prefix, name_tail, (char *) 0); + fbck_val = get_mail_conf_str(fbck_name, def_val, 1, 0); + + /* + * We allow users to express feedback as 1/8, as a more user-friendly + * alternative to 0.125 (or worse, having users specify the number of + * events in a feedback hysteresis cycle). + * + * We use some sscanf() fu to parse the value into numerator and optional + * "/" followed by denominator. We're doing this only a few times during + * the process life time, so we strive for convenience instead of speed. + */ +#define INCLUSIVE_BOUNDS(val, low, high) ((val) >= (low) && (val) <= (high)) + + fb->hysteresis = 1; /* legacy */ + fb->base = -1; /* assume error */ + + switch (sscanf(fbck_val, "%lf %1[/] %30s%c", + &enum_val, slash, denom_str, &junk)) { + case 1: + fb->index = QMGR_FEEDBACK_IDX_NONE; + fb->base = enum_val; + break; + case 3: + if ((fb->index = name_code(qmgr_feedback_map, NAME_CODE_FLAG_NONE, + denom_str)) != QMGR_FEEDBACK_IDX_NONE) { + fb->base = enum_val; + } else if (INCLUSIVE_BOUNDS(enum_val, 0, INT_MAX) + && sscanf(denom_str, "%lf%c", &denom_val, &junk) == 1 + && INCLUSIVE_BOUNDS(denom_val, 1.0 / INT_MAX, INT_MAX)) { + fb->base = enum_val / denom_val; + } + break; + } + + /* + * Sanity check. If input is bad, we just warn and use a reasonable + * default. + */ + if (!INCLUSIVE_BOUNDS(fb->base, 0, 1)) { + msg_warn("%s: ignoring malformed or unreasonable feedback: %s", + strcmp(fbck_val, def_val) ? fbck_name : def_name, fbck_val); + fb->index = QMGR_FEEDBACK_IDX_NONE; + fb->base = 1; + } + + /* + * Performance debugging/analysis. + */ + if (var_conc_feedback_debug) + msg_info("%s: %s feedback type %d value at %d: %g", + name_prefix, strcmp(fbck_val, def_val) ? + fbck_name : def_name, fb->index, var_init_dest_concurrency, + QMGR_FEEDBACK_VAL(*fb, var_init_dest_concurrency)); + + myfree(fbck_name); + myfree(fbck_val); +} diff --git a/src/qmgr/qmgr_job.c b/src/qmgr/qmgr_job.c new file mode 100644 index 0000000..24065f3 --- /dev/null +++ b/src/qmgr/qmgr_job.c @@ -0,0 +1,978 @@ +/*++ +/* NAME +/* qmgr_job 3 +/* SUMMARY +/* per-transport jobs +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_JOB *qmgr_job_obtain(message, transport) +/* QMGR_MESSAGE *message; +/* QMGR_TRANSPORT *transport; +/* +/* void qmgr_job_free(job) +/* QMGR_JOB *job; +/* +/* void qmgr_job_move_limits(job) +/* QMGR_JOB *job; +/* +/* QMGR_ENTRY *qmgr_job_entry_select(transport) +/* QMGR_TRANSPORT *transport; +/* +/* void qmgr_job_blocker_update(queue) +/* QMGR_QUEUE *queue; +/* DESCRIPTION +/* These routines add/delete/manipulate per-transport jobs. +/* Each job corresponds to a specific transport and message. +/* Each job has a peer list containing all pending delivery +/* requests for that message. +/* +/* qmgr_job_obtain() finds an existing job for named message and +/* transport combination. New empty job is created if no existing can +/* be found. In either case, the job is prepared for assignment of +/* (more) message recipients. +/* +/* qmgr_job_free() disposes of a per-transport job after all +/* its entries have been taken care of. It is an error to dispose +/* of a job that is still in use. +/* +/* qmgr_job_entry_select() attempts to find the next entry suitable +/* for delivery. The job preempting algorithm is also exercised. +/* If necessary, an attempt to read more recipients into core is made. +/* This can result in creation of more job, queue and entry structures. +/* +/* qmgr_job_blocker_update() updates the status of blocked +/* jobs after a decrease in the queue's concurrency level, +/* after the queue is throttled, or after the queue is resumed +/* from suspension. +/* +/* qmgr_job_move_limits() takes care of proper distribution of the +/* per-transport recipients limit among the per-transport jobs. +/* Should be called whenever a job's recipient slot becomes available. +/* DIAGNOSTICS +/* Panic: consistency check failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Patrik Rak +/* patrik@raxoft.cz +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* Forward declarations */ + +static void qmgr_job_pop(QMGR_JOB *); + +/* Helper macros */ + +#define HAS_ENTRIES(job) ((job)->selected_entries < (job)->read_entries) + +/* + * The MIN_ENTRIES macro may underestimate a lot but we can't use message->rcpt_unread + * because we don't know if all those unread recipients go to our transport yet. + */ + +#define MIN_ENTRIES(job) ((job)->read_entries) +#define MAX_ENTRIES(job) ((job)->read_entries + (job)->message->rcpt_unread) + +#define RESET_CANDIDATE_CACHE(transport) ((transport)->candidate_cache_current = 0) + +#define IS_BLOCKER(job,transport) ((job)->blocker_tag == (transport)->blocker_tag) + +/* qmgr_job_create - create and initialize message job structure */ + +static QMGR_JOB *qmgr_job_create(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport) +{ + QMGR_JOB *job; + + job = (QMGR_JOB *) mymalloc(sizeof(QMGR_JOB)); + job->message = message; + QMGR_LIST_APPEND(message->job_list, job, message_peers); + htable_enter(transport->job_byname, message->queue_id, (void *) job); + job->transport = transport; + QMGR_LIST_INIT(job->transport_peers); + QMGR_LIST_INIT(job->time_peers); + job->stack_parent = 0; + QMGR_LIST_INIT(job->stack_children); + QMGR_LIST_INIT(job->stack_siblings); + job->stack_level = -1; + job->blocker_tag = 0; + job->peer_byname = htable_create(0); + QMGR_LIST_INIT(job->peer_list); + job->slots_used = 0; + job->slots_available = 0; + job->selected_entries = 0; + job->read_entries = 0; + job->rcpt_count = 0; + job->rcpt_limit = 0; + return (job); +} + +/* qmgr_job_link - append the job to the job lists based on the time it was queued */ + +static void qmgr_job_link(QMGR_JOB *job) +{ + QMGR_TRANSPORT *transport = job->transport; + QMGR_MESSAGE *message = job->message; + QMGR_JOB *prev, *next, *list_prev, *list_next, *unread, *current; + int delay; + + /* + * Sanity checks. + */ + if (job->stack_level >= 0) + msg_panic("qmgr_job_link: already on the job lists (%d)", job->stack_level); + + /* + * Traverse the time list and the scheduler list from the end and stop + * when we found job older than the one being linked. + * + * During the traversals keep track if we have come across either the + * current job or the first unread job on the job list. If this is the + * case, these pointers will be adjusted below as required. + * + * Although both lists are exactly the same when only jobs on the stack + * level zero are considered, it's easier to traverse them separately. + * Otherwise it's impossible to keep track of the current job pointer + * effectively. + * + * This may look inefficient but under normal operation it is expected that + * the loops will stop right away, resulting in normal list appends + * below. However, this code is necessary for reviving retired jobs and + * for jobs which are created long after the first chunk of recipients + * was read in-core (either of these can happen only for multi-transport + * messages). + * + * XXX Note that we test stack_parent rather than stack_level below. This + * subtle difference allows us to enqueue the job in correct time order + * with respect to orphaned children even after their original parent on + * level zero is gone. Consequently, the early loop stop in candidate + * selection works reliably, too. These are the reasons why we care to + * bother with children adoption at all. + */ + current = transport->job_current; + for (next = 0, prev = transport->job_list.prev; prev; + next = prev, prev = prev->transport_peers.prev) { + if (prev->stack_parent == 0) { + delay = message->queued_time - prev->message->queued_time; + if (delay >= 0) + break; + } + if (current == prev) + current = 0; + } + list_prev = prev; + list_next = next; + + unread = transport->job_next_unread; + for (next = 0, prev = transport->job_bytime.prev; prev; + next = prev, prev = prev->time_peers.prev) { + delay = message->queued_time - prev->message->queued_time; + if (delay >= 0) + break; + if (unread == prev) + unread = 0; + } + + /* + * Link the job into the proper place on the job lists and mark it so we + * know it has been linked. + */ + job->stack_level = 0; + QMGR_LIST_LINK(transport->job_list, list_prev, job, list_next, transport_peers); + QMGR_LIST_LINK(transport->job_bytime, prev, job, next, time_peers); + + /* + * Update the current job pointer if necessary. + */ + if (current == 0) + transport->job_current = job; + + /* + * Update the pointer to the first unread job on the job list and steal + * the unused recipient slots from the old one. + */ + if (unread == 0) { + unread = transport->job_next_unread; + transport->job_next_unread = job; + if (unread != 0) + qmgr_job_move_limits(unread); + } + + /* + * Get as much recipient slots as possible. The excess will be returned + * to the transport pool as soon as the exact amount required is known + * (which is usually after all recipients have been read in core). + */ + if (transport->rcpt_unused > 0) { + job->rcpt_limit += transport->rcpt_unused; + message->rcpt_limit += transport->rcpt_unused; + transport->rcpt_unused = 0; + } +} + +/* qmgr_job_find - lookup job associated with named message and transport */ + +static QMGR_JOB *qmgr_job_find(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport) +{ + + /* + * Instead of traversing the message job list, we use single per + * transport hash table. This is better (at least with respect to memory + * usage) than having single hash table (usually almost empty) for each + * message. + */ + return ((QMGR_JOB *) htable_find(transport->job_byname, message->queue_id)); +} + +/* qmgr_job_obtain - find/create the appropriate job and make it ready for new recipients */ + +QMGR_JOB *qmgr_job_obtain(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport) +{ + QMGR_JOB *job; + + /* + * Try finding an existing job, reviving it if it was already retired. + * Create a new job for this transport/message combination otherwise. In + * either case, the job ends linked on the job lists. + */ + if ((job = qmgr_job_find(message, transport)) == 0) + job = qmgr_job_create(message, transport); + if (job->stack_level < 0) + qmgr_job_link(job); + + /* + * Reset the candidate cache because of the new expected recipients. Make + * sure the job is not marked as a blocker for the same reason. Note that + * this can result in having a non-blocker followed by more blockers. + * Consequently, we can't just update the current job pointer, we have to + * reset it. Fortunately qmgr_job_entry_select() will easily deal with + * this and will lookup the real current job for us. + */ + RESET_CANDIDATE_CACHE(transport); + if (IS_BLOCKER(job, transport)) { + job->blocker_tag = 0; + transport->job_current = transport->job_list.next; + } + return (job); +} + +/* qmgr_job_move_limits - move unused recipient slots to the next unread job */ + +void qmgr_job_move_limits(QMGR_JOB *job) +{ + QMGR_TRANSPORT *transport = job->transport; + QMGR_MESSAGE *message = job->message; + QMGR_JOB *next = transport->job_next_unread; + int rcpt_unused, msg_rcpt_unused; + + /* + * Find next unread job on the job list if necessary. Cache it for later. + * This makes the amortized efficiency of this routine O(1) per job. Note + * that we use the time list whose ordering doesn't change over time. + */ + if (job == next) { + for (next = next->time_peers.next; next; next = next->time_peers.next) + if (next->message->rcpt_offset != 0) + break; + transport->job_next_unread = next; + } + + /* + * Calculate the number of available unused slots. + */ + rcpt_unused = job->rcpt_limit - job->rcpt_count; + msg_rcpt_unused = message->rcpt_limit - message->rcpt_count; + if (msg_rcpt_unused < rcpt_unused) + rcpt_unused = msg_rcpt_unused; + + /* + * Transfer the unused recipient slots back to the transport pool and to + * the next not-fully-read job. Job's message limits are adjusted + * accordingly. Note that the transport pool can be negative if we used + * some of the rcpt_per_stack slots. + */ + if (rcpt_unused > 0) { + job->rcpt_limit -= rcpt_unused; + message->rcpt_limit -= rcpt_unused; + transport->rcpt_unused += rcpt_unused; + if (next != 0 && (rcpt_unused = transport->rcpt_unused) > 0) { + next->rcpt_limit += rcpt_unused; + next->message->rcpt_limit += rcpt_unused; + transport->rcpt_unused = 0; + } + } +} + +/* qmgr_job_parent_gone - take care of orphaned stack children */ + +static void qmgr_job_parent_gone(QMGR_JOB *job, QMGR_JOB *parent) +{ + QMGR_JOB *child; + + while ((child = job->stack_children.next) != 0) { + QMGR_LIST_UNLINK(job->stack_children, QMGR_JOB *, child, stack_siblings); + if (parent != 0) + QMGR_LIST_APPEND(parent->stack_children, child, stack_siblings); + child->stack_parent = parent; + } +} + +/* qmgr_job_unlink - unlink the job from the job lists */ + +static void qmgr_job_unlink(QMGR_JOB *job) +{ + const char *myname = "qmgr_job_unlink"; + QMGR_TRANSPORT *transport = job->transport; + + /* + * Sanity checks. + */ + if (job->stack_level != 0) + msg_panic("%s: non-zero stack level (%d)", myname, job->stack_level); + if (job->stack_parent != 0) + msg_panic("%s: parent present", myname); + if (job->stack_siblings.next != 0) + msg_panic("%s: siblings present", myname); + + /* + * Make sure that children of job on zero stack level are informed that + * their parent is gone too. + */ + qmgr_job_parent_gone(job, 0); + + /* + * Update the current job pointer if necessary. + */ + if (transport->job_current == job) + transport->job_current = job->transport_peers.next; + + /* + * Invalidate the candidate selection cache if necessary. + */ + if (job == transport->candidate_cache + || job == transport->candidate_cache_current) + RESET_CANDIDATE_CACHE(transport); + + /* + * Remove the job from the job lists and mark it as unlinked. + */ + QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers); + QMGR_LIST_UNLINK(transport->job_bytime, QMGR_JOB *, job, time_peers); + job->stack_level = -1; +} + +/* qmgr_job_retire - remove the job from the job lists while waiting for recipients to deliver */ + +static void qmgr_job_retire(QMGR_JOB *job) +{ + if (msg_verbose) + msg_info("qmgr_job_retire: %s", job->message->queue_id); + + /* + * Pop the job from the job stack if necessary. + */ + if (job->stack_level > 0) + qmgr_job_pop(job); + + /* + * Make sure this job is not cached as the next unread job for this + * transport. The qmgr_entry_done() will make sure that the slots donated + * by this job are moved back to the transport pool as soon as possible. + */ + qmgr_job_move_limits(job); + + /* + * Remove the job from the job lists. Note that it remains on the message + * job list, though, and that it can be revived by using + * qmgr_job_obtain(). Also note that the available slot counter is left + * intact. + */ + qmgr_job_unlink(job); +} + +/* qmgr_job_free - release the job structure */ + +void qmgr_job_free(QMGR_JOB *job) +{ + const char *myname = "qmgr_job_free"; + QMGR_MESSAGE *message = job->message; + QMGR_TRANSPORT *transport = job->transport; + + if (msg_verbose) + msg_info("%s: %s %s", myname, message->queue_id, transport->name); + + /* + * Sanity checks. + */ + if (job->rcpt_count) + msg_panic("%s: non-zero recipient count (%d)", myname, job->rcpt_count); + + /* + * Pop the job from the job stack if necessary. + */ + if (job->stack_level > 0) + qmgr_job_pop(job); + + /* + * Return any remaining recipient slots back to the recipient slots pool. + */ + qmgr_job_move_limits(job); + if (job->rcpt_limit) + msg_panic("%s: recipient slots leak (%d)", myname, job->rcpt_limit); + + /* + * Unlink and discard the structure. Check if the job is still linked on + * the job lists or if it was already retired before unlinking it. + */ + if (job->stack_level >= 0) + qmgr_job_unlink(job); + QMGR_LIST_UNLINK(message->job_list, QMGR_JOB *, job, message_peers); + htable_delete(transport->job_byname, message->queue_id, (void (*) (void *)) 0); + htable_free(job->peer_byname, (void (*) (void *)) 0); + myfree((void *) job); +} + +/* qmgr_job_count_slots - maintain the delivery slot counters */ + +static void qmgr_job_count_slots(QMGR_JOB *job) +{ + + /* + * Count the number of delivery slots used during the delivery of the + * selected job. Also count the number of delivery slots available for + * its preemption. + * + * Despite its trivial look, this is one of the key parts of the theory + * behind this preempting scheduler. + */ + job->slots_available++; + job->slots_used++; + + /* + * If the selected job is not the original current job, reset the + * candidate cache because the change above have slightly increased the + * chance of this job becoming a candidate next time. + * + * Don't expect that the change of the current jobs this turn will render + * the candidate cache invalid the next turn - it can happen that the + * next turn the original current job will be selected again and the + * cache would be considered valid in such case. + */ + if (job != job->transport->candidate_cache_current) + RESET_CANDIDATE_CACHE(job->transport); +} + +/* qmgr_job_candidate - find best job candidate for preempting given job */ + +static QMGR_JOB *qmgr_job_candidate(QMGR_JOB *current) +{ + QMGR_TRANSPORT *transport = current->transport; + QMGR_JOB *job, *best_job = 0; + double score, best_score = 0.0; + int max_slots, max_needed_entries, max_total_entries; + int delay; + time_t now = sane_time(); + + /* + * Fetch the result directly from the cache if the cache is still valid. + * + * Note that we cache negative results too, so the cache must be invalidated + * by resetting the cached current job pointer, not the candidate pointer + * itself. + * + * In case the cache is valid and contains no candidate, we can ignore the + * time change, as it affects only which candidate is the best, not if + * one exists. However, this feature requires that we no longer relax the + * cache resetting rules, depending on the automatic cache timeout. + */ + if (transport->candidate_cache_current == current + && (transport->candidate_cache_time == now + || transport->candidate_cache == 0)) + return (transport->candidate_cache); + + /* + * Estimate the minimum amount of delivery slots that can ever be + * accumulated for the given job. All jobs that won't fit into these + * slots are excluded from the candidate selection. + */ + max_slots = (MIN_ENTRIES(current) - current->selected_entries + + current->slots_available) / transport->slot_cost; + + /* + * Select the candidate with best time_since_queued/total_recipients + * score. In addition to jobs which don't meet the max_slots limit, skip + * also jobs which don't have any selectable entries at the moment. + * + * Instead of traversing the whole job list we traverse it just from the + * current job forward. This has several advantages. First, we skip some + * of the blocker jobs and the current job itself right away. But the + * really important advantage is that we are sure that we don't consider + * any jobs that are already stack children of the current job. Thanks to + * this we can easily include all encountered jobs which are leaf + * children of some of the preempting stacks as valid candidates. All we + * need to do is to make sure we do not include any of the stack parents. + * And, because the leaf children are not ordered by the time since + * queued, we have to exclude them from the early loop end test. + * + * However, don't bother searching if we can't find anything suitable + * anyway. + */ + if (max_slots > 0) { + for (job = current->transport_peers.next; job; job = job->transport_peers.next) { + if (job->stack_children.next != 0 || IS_BLOCKER(job, transport)) + continue; + max_total_entries = MAX_ENTRIES(job); + max_needed_entries = max_total_entries - job->selected_entries; + delay = now - job->message->queued_time + 1; + if (max_needed_entries > 0 && max_needed_entries <= max_slots) { + score = (double) delay / max_total_entries; + if (score > best_score) { + best_score = score; + best_job = job; + } + } + + /* + * Stop early if the best score is as good as it can get. + */ + if (delay <= best_score && job->stack_level == 0) + break; + } + } + + /* + * Cache the result for later use. + */ + transport->candidate_cache = best_job; + transport->candidate_cache_current = current; + transport->candidate_cache_time = now; + + return (best_job); +} + +/* qmgr_job_preempt - preempt large message with smaller one */ + +static QMGR_JOB *qmgr_job_preempt(QMGR_JOB *current) +{ + const char *myname = "qmgr_job_preempt"; + QMGR_TRANSPORT *transport = current->transport; + QMGR_JOB *job, *prev; + int expected_slots; + int rcpt_slots; + + /* + * Suppress preempting completely if the current job is not big enough to + * accumulate even the minimal number of slots required. + * + * Also, don't look for better job candidate if there are no available slots + * yet (the count can get negative due to the slot loans below). + */ + if (current->slots_available <= 0 + || MAX_ENTRIES(current) < transport->min_slots * transport->slot_cost) + return (current); + + /* + * Find best candidate for preempting the current job. + * + * Note that the function also takes care that the candidate fits within the + * number of delivery slots which the current job is still able to + * accumulate. + */ + if ((job = qmgr_job_candidate(current)) == 0) + return (current); + + /* + * Sanity checks. + */ + if (job == current) + msg_panic("%s: attempt to preempt itself", myname); + if (job->stack_children.next != 0) + msg_panic("%s: already on the job stack (%d)", myname, job->stack_level); + if (job->stack_level < 0) + msg_panic("%s: not on the job list (%d)", myname, job->stack_level); + + /* + * Check if there is enough available delivery slots accumulated to + * preempt the current job. + * + * The slot loaning scheme improves the average message response time. Note + * that the loan only allows the preemption happen earlier, though. It + * doesn't affect how many slots have to be "paid" - in either case the + * full number of slots required has to be accumulated later before the + * current job can be preempted again. + */ + expected_slots = MAX_ENTRIES(job) - job->selected_entries; + if (current->slots_available / transport->slot_cost + transport->slot_loan + < expected_slots * transport->slot_loan_factor / 100.0) + return (current); + + /* + * Preempt the current job. + * + * This involves placing the selected candidate in front of the current job + * on the job list and updating the stack parent/child/sibling pointers + * appropriately. But first we need to make sure that the candidate is + * taken from its previous job stack which it might be top of. + */ + if (job->stack_level > 0) + qmgr_job_pop(job); + QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers); + prev = current->transport_peers.prev; + QMGR_LIST_LINK(transport->job_list, prev, job, current, transport_peers); + job->stack_parent = current; + QMGR_LIST_APPEND(current->stack_children, job, stack_siblings); + job->stack_level = current->stack_level + 1; + + /* + * Update the current job pointer and explicitly reset the candidate + * cache. + */ + transport->job_current = job; + RESET_CANDIDATE_CACHE(transport); + + /* + * Since the single job can be preempted by several jobs at the same + * time, we have to adjust the available slot count now to prevent using + * the same slots multiple times. To do that we subtract the number of + * slots the preempting job will supposedly use. This number will be + * corrected later when that job is popped from the stack to reflect the + * number of slots really used. + * + * As long as we don't need to keep track of how many slots were really + * used, we can (ab)use the slots_used counter for counting the + * difference between the real and expected amounts instead of the + * absolute amount. + */ + current->slots_available -= expected_slots * transport->slot_cost; + job->slots_used = -expected_slots; + + /* + * Add part of extra recipient slots reserved for preempting jobs to the + * new current job if necessary. + * + * Note that transport->rcpt_unused is within <-rcpt_per_stack,0> in such + * case. + */ + if (job->message->rcpt_offset != 0) { + rcpt_slots = (transport->rcpt_per_stack + transport->rcpt_unused + 1) / 2; + job->rcpt_limit += rcpt_slots; + job->message->rcpt_limit += rcpt_slots; + transport->rcpt_unused -= rcpt_slots; + } + if (msg_verbose) + msg_info("%s: %s by %s, level %d", myname, current->message->queue_id, + job->message->queue_id, job->stack_level); + + return (job); +} + +/* qmgr_job_pop - remove the job from its job preemption stack */ + +static void qmgr_job_pop(QMGR_JOB *job) +{ + const char *myname = "qmgr_job_pop"; + QMGR_TRANSPORT *transport = job->transport; + QMGR_JOB *parent; + + if (msg_verbose) + msg_info("%s: %s", myname, job->message->queue_id); + + /* + * Sanity checks. + */ + if (job->stack_level <= 0) + msg_panic("%s: not on the job stack (%d)", myname, job->stack_level); + + /* + * Adjust the number of delivery slots available to preempt job's parent. + * Note that the -= actually adds back any unused slots, as we have + * already subtracted the expected amount of slots from both counters + * when we did the preemption. + * + * Note that we intentionally do not adjust slots_used of the parent. Doing + * so would decrease the maximum per message inflation factor if the + * preemption appeared near the end of parent delivery. + * + * For the same reason we do not adjust parent's slots_available if the + * parent is not the original parent that was preempted by this job + * (i.e., the original parent job has already completed). + * + * This is another key part of the theory behind this preempting scheduler. + */ + if ((parent = job->stack_parent) != 0 + && job->stack_level == parent->stack_level + 1) + parent->slots_available -= job->slots_used * transport->slot_cost; + + /* + * Remove the job from its parent's children list. + */ + if (parent != 0) { + QMGR_LIST_UNLINK(parent->stack_children, QMGR_JOB *, job, stack_siblings); + job->stack_parent = 0; + } + + /* + * If there is a parent, let it adopt all those orphaned children. + * Otherwise at least notify the children that their parent is gone. + */ + qmgr_job_parent_gone(job, parent); + + /* + * Put the job back to stack level zero. + */ + job->stack_level = 0; + + /* + * Explicitly reset the candidate cache. It's not worth trying to skip + * this under some complicated conditions - in most cases the popped job + * is the current job so we would have to reset it anyway. + */ + RESET_CANDIDATE_CACHE(transport); + + /* + * Here we leave the remaining work involving the proper placement on the + * job list to the caller. The most important reason for this is that it + * allows us not to look up where exactly to place the job. + * + * The caller is also made responsible for invalidating the current job + * cache if necessary. + */ +#if 0 + QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers); + QMGR_LIST_LINK(transport->job_list, some_prev, job, some_next, transport_peers); + + if (transport->job_current == job) + transport->job_current = job->transport_peers.next; +#endif +} + +/* qmgr_job_peer_select - select next peer suitable for delivery */ + +static QMGR_PEER *qmgr_job_peer_select(QMGR_JOB *job) +{ + QMGR_PEER *peer; + QMGR_MESSAGE *message = job->message; + + /* + * Try reading in more recipients. We do that as soon as possible + * (almost, see below), to make sure there is enough new blood pouring + * in. Otherwise single recipient for slow destination might starve the + * entire message delivery, leaving lot of fast destination recipients + * sitting idle in the queue file. + * + * Ideally we would like to read in recipients whenever there is a space, + * but to prevent excessive I/O, we read them only when enough time has + * passed or we can read enough of them at once. + * + * Note that even if we read the recipients few at a time, the message + * loading code tries to put them to existing recipient entries whenever + * possible, so the per-destination recipient grouping is not grossly + * affected. + * + * XXX Workaround for logic mismatch. The message->refcount test needs + * explanation. If the refcount is zero, it means that qmgr_active_done() + * is being completed asynchronously. In such case, we can't read in + * more recipients as bad things would happen after qmgr_active_done() + * continues processing. Note that this results in the given job being + * stalled for some time, but fortunately this particular situation is so + * rare that it is not critical. Still we seek for better solution. + */ + if (message->rcpt_offset != 0 + && message->refcount > 0 + && (message->rcpt_limit - message->rcpt_count >= job->transport->refill_limit + || (message->rcpt_limit > message->rcpt_count + && sane_time() - message->refill_time >= job->transport->refill_delay))) + qmgr_message_realloc(message); + + /* + * Get the next suitable peer, if there is any. + */ + if (HAS_ENTRIES(job) && (peer = qmgr_peer_select(job)) != 0) + return (peer); + + /* + * There is no suitable peer in-core, so try reading in more recipients + * if possible. This is our last chance to get suitable peer before + * giving up on this job for now. + * + * XXX For message->refcount, see above. + */ + if (message->rcpt_offset != 0 + && message->refcount > 0 + && message->rcpt_limit > message->rcpt_count) { + qmgr_message_realloc(message); + if (HAS_ENTRIES(job)) + return (qmgr_peer_select(job)); + } + return (0); +} + +/* qmgr_job_entry_select - select next entry suitable for delivery */ + +QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *transport) +{ + QMGR_JOB *job, *next; + QMGR_PEER *peer; + QMGR_ENTRY *entry; + + /* + * Get the current job if there is one. + */ + if ((job = transport->job_current) == 0) + return (0); + + /* + * Exercise the preempting algorithm if enabled. + * + * The slot_cost equal to 1 causes the algorithm to degenerate and is + * therefore disabled too. + */ + if (transport->slot_cost >= 2) + job = qmgr_job_preempt(job); + + /* + * Select next entry suitable for delivery. In case the current job can't + * provide one because of the per-destination concurrency limits, we mark + * it as a "blocker" job and continue with the next job on the job list. + * + * Note that the loop also takes care of getting the "stall" jobs (job with + * no entries currently available) out of the way if necessary. Stall + * jobs can appear in case of multi-transport messages whose recipients + * don't fit in-core at once. Some jobs created by such message may have + * only few recipients and would stay on the job list until all other + * jobs of that message are delivered, blocking precious recipient slots + * available to this transport. Or it can happen that the job has some + * more entries but suddenly they all get deferred. Whatever the reason, + * we retire such jobs below if we happen to come across some. + */ + for ( /* empty */ ; job; job = next) { + next = job->transport_peers.next; + + /* + * Don't bother if the job is known to have no available entries + * because of the per-destination concurrency limits. + */ + if (IS_BLOCKER(job, transport)) + continue; + + if ((peer = qmgr_job_peer_select(job)) != 0) { + + /* + * We have found a suitable peer. Select one of its entries and + * adjust the delivery slot counters. + */ + entry = qmgr_entry_select(peer); + qmgr_job_count_slots(job); + + /* + * Remember the current job for the next time so we don't have to + * crawl over all those blockers again. They will be reconsidered + * when the concurrency limit permits. + */ + transport->job_current = job; + + /* + * In case we selected the very last job entry, remove the job + * from the job lists right now. + * + * This action uses the assumption that once the job entry has been + * selected, it can be unselected only before the message ifself + * is deferred. Thus the job with all entries selected can't + * re-appear with more entries available for selection again + * (without reading in more entries from the queue file, which in + * turn invokes qmgr_job_obtain() which re-links the job back on + * the lists if necessary). + * + * Note that qmgr_job_move_limits() transfers the recipients slots + * correctly even if the job is unlinked from the job list thanks + * to the job_next_unread caching. + */ + if (!HAS_ENTRIES(job) && job->message->rcpt_offset == 0) + qmgr_job_retire(job); + + /* + * Finally. Hand back the fruit of our tedious effort. + */ + return (entry); + } else if (HAS_ENTRIES(job)) { + + /* + * The job can't be selected due the concurrency limits. Mark it + * together with its queues so we know they are blocking the job + * list and they get the appropriate treatment. In particular, + * all blockers will be reconsidered when one of the problematic + * queues will accept more deliveries. And the job itself will be + * reconsidered if it is assigned some more entries. + */ + job->blocker_tag = transport->blocker_tag; + for (peer = job->peer_list.next; peer; peer = peer->peers.next) + if (peer->entry_list.next != 0) + peer->queue->blocker_tag = transport->blocker_tag; + } else { + + /* + * The job is "stalled". Retire it until it either gets freed or + * gets more entries later. + */ + qmgr_job_retire(job); + } + } + + /* + * We have not found any entry we could use for delivery. Well, things + * must have changed since this transport was selected for asynchronous + * allocation. Never mind. Clear the current job pointer and reluctantly + * report back that we have failed in our task. + */ + transport->job_current = 0; + return (0); +} + +/* qmgr_job_blocker_update - update "blocked job" status */ + +void qmgr_job_blocker_update(QMGR_QUEUE *queue) +{ + QMGR_TRANSPORT *transport = queue->transport; + + /* + * If the queue was blocking some of the jobs on the job list, check if + * the concurrency limit has lifted. If there are still some pending + * deliveries, give it a try and unmark all transport blockers at once. + * The qmgr_job_entry_select() will do the rest. In either case make sure + * the queue is not marked as a blocker anymore, with extra handling of + * queues which were declared dead. + * + * Note that changing the blocker status also affects the candidate cache. + * Most of the cases would be automatically recognized by the current job + * change, but we play safe and reset the cache explicitly below. + * + * Keeping the transport blocker tag odd is an easy way to make sure the tag + * never matches jobs that are not explicitly marked as blockers. + */ + if (queue->blocker_tag == transport->blocker_tag) { + if (queue->window > queue->busy_refcount && queue->todo.next != 0) { + transport->blocker_tag += 2; + transport->job_current = transport->job_list.next; + transport->candidate_cache_current = 0; + } + if (queue->window > queue->busy_refcount || QMGR_QUEUE_THROTTLED(queue)) + queue->blocker_tag = 0; + } +} diff --git a/src/qmgr/qmgr_message.c b/src/qmgr/qmgr_message.c new file mode 100644 index 0000000..79143f3 --- /dev/null +++ b/src/qmgr/qmgr_message.c @@ -0,0 +1,1622 @@ +/*++ +/* NAME +/* qmgr_message 3 +/* SUMMARY +/* in-core message structures +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* int qmgr_message_count; +/* int qmgr_recipient_count; +/* int qmgr_vrfy_pend_count; +/* +/* QMGR_MESSAGE *qmgr_message_alloc(class, name, qflags, mode) +/* const char *class; +/* const char *name; +/* int qflags; +/* mode_t mode; +/* +/* QMGR_MESSAGE *qmgr_message_realloc(message) +/* QMGR_MESSAGE *message; +/* +/* void qmgr_message_free(message) +/* QMGR_MESSAGE *message; +/* +/* void qmgr_message_update_warn(message) +/* QMGR_MESSAGE *message; +/* +/* void qmgr_message_kill_record(message, offset) +/* QMGR_MESSAGE *message; +/* long offset; +/* DESCRIPTION +/* This module performs en-gross operations on queue messages. +/* +/* qmgr_message_count is a global counter for the total number +/* of in-core message structures (i.e. the total size of the +/* `active' message queue). +/* +/* qmgr_recipient_count is a global counter for the total number +/* of in-core recipient structures (i.e. the sum of all recipients +/* in all in-core message structures). +/* +/* qmgr_vrfy_pend_count is a global counter for the total +/* number of in-core message structures that are associated +/* with an address verification request. Requests that exceed +/* the address_verify_pending_limit are deferred immediately. +/* This is a backup mechanism for a more refined enforcement +/* mechanism in the verify(8) daemon. +/* +/* qmgr_message_alloc() creates an in-core message structure +/* with sender and recipient information taken from the named queue +/* file. A null result means the queue file could not be read or +/* that the queue file contained incorrect information. A result +/* QMGR_MESSAGE_LOCKED means delivery must be deferred. The number +/* of recipients read from a queue file is limited by the global +/* var_qmgr_rcpt_limit configuration parameter. When the limit +/* is reached, the \fIrcpt_offset\fR structure member is set to +/* the position where the read was terminated. Recipients are +/* run through the resolver, and are assigned to destination +/* queues. Recipients that cannot be assigned are deferred or +/* bounced. Mail that has bounced twice is silently absorbed. +/* A non-zero mode means change the queue file permissions. +/* +/* qmgr_message_realloc() resumes reading recipients from the queue +/* file, and updates the recipient list and \fIrcpt_offset\fR message +/* structure members. A null result means that the file could not be +/* read or that the file contained incorrect information. Recipient +/* limit imposed this time is based on the position of the message +/* job(s) on corresponding transport job list(s). It's considered +/* an error to call this when the recipient slots can't be allocated. +/* +/* qmgr_message_free() destroys an in-core message structure and makes +/* the resources available for reuse. It is an error to destroy +/* a message structure that is still referenced by queue entry structures. +/* +/* qmgr_message_update_warn() takes a closed message, opens it, updates +/* the warning field, and closes it again. +/* +/* qmgr_message_kill_record() takes a closed message, opens it, updates +/* the record type at the given offset to "killed", and closes the file. +/* A killed envelope record is ignored. Killed records are not allowed +/* inside the message content. +/* DIAGNOSTICS +/* Warnings: malformed message file. Fatal errors: out of memory. +/* SEE ALSO +/* envelope(3) message envelope parser +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Preemptive scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include +#include +#include +#include /* sscanf() */ +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Client stubs. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +int qmgr_message_count; +int qmgr_recipient_count; +int qmgr_vrfy_pend_count; + +/* qmgr_message_create - create in-core message structure */ + +static QMGR_MESSAGE *qmgr_message_create(const char *queue_name, + const char *queue_id, int qflags) +{ + QMGR_MESSAGE *message; + + message = (QMGR_MESSAGE *) mymalloc(sizeof(QMGR_MESSAGE)); + qmgr_message_count++; + message->flags = 0; + message->qflags = qflags; + message->tflags = 0; + message->tflags_offset = 0; + message->rflags = QMGR_READ_FLAG_DEFAULT; + message->fp = 0; + message->refcount = 0; + message->single_rcpt = 0; + message->arrival_time.tv_sec = message->arrival_time.tv_usec = 0; + message->create_time = 0; + GETTIMEOFDAY(&message->active_time); + message->queued_time = sane_time(); + message->refill_time = 0; + message->data_offset = 0; + message->queue_id = mystrdup(queue_id); + message->queue_name = mystrdup(queue_name); + message->encoding = 0; + message->sender = 0; + message->dsn_envid = 0; + message->dsn_ret = 0; + message->smtputf8 = 0; + message->filter_xport = 0; + message->inspect_xport = 0; + message->redirect_addr = 0; + message->data_size = 0; + message->cont_length = 0; + message->warn_offset = 0; + message->warn_time = 0; + message->rcpt_offset = 0; + message->verp_delims = 0; + message->client_name = 0; + message->client_addr = 0; + message->client_port = 0; + message->client_proto = 0; + message->client_helo = 0; + message->sasl_method = 0; + message->sasl_username = 0; + message->sasl_sender = 0; + message->log_ident = 0; + message->rewrite_context = 0; + recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE); + message->rcpt_count = 0; + message->rcpt_limit = var_qmgr_msg_rcpt_limit; + message->rcpt_unread = 0; + QMGR_LIST_INIT(message->job_list); + return (message); +} + +/* qmgr_message_close - close queue file */ + +static void qmgr_message_close(QMGR_MESSAGE *message) +{ + vstream_fclose(message->fp); + message->fp = 0; +} + +/* qmgr_message_open - open queue file */ + +static int qmgr_message_open(QMGR_MESSAGE *message) +{ + + /* + * Sanity check. + */ + if (message->fp) + msg_panic("%s: queue file is open", message->queue_id); + + /* + * Open this queue file. Skip files that we cannot open. Back off when + * the system appears to be running out of resources. + */ + if ((message->fp = mail_queue_open(message->queue_name, + message->queue_id, + O_RDWR, 0)) == 0) { + if (errno != ENOENT) + msg_fatal("open %s %s: %m", message->queue_name, message->queue_id); + msg_warn("open %s %s: %m", message->queue_name, message->queue_id); + return (-1); + } + return (0); +} + +/* qmgr_message_oldstyle_scan - support for Postfix < 1.0 queue files */ + +static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message) +{ + VSTRING *buf; + long orig_offset, extra_offset; + int rec_type; + char *start; + + /* + * Initialize. No early returns or we have a memory leak. + */ + buf = vstring_alloc(100); + if ((orig_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); + + /* + * Rewind to the very beginning to make sure we see all records. + */ + if (vstream_fseek(message->fp, 0, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + + /* + * Scan through the old style queue file. Count the total number of + * recipients and find the data/extra sections offsets. Note that the new + * queue files require that data_size equals extra_offset - data_offset, + * so we set data_size to this as well and ignore the size record itself + * completely. + */ + message->rcpt_unread = 0; + for (;;) { + rec_type = rec_get(message->fp, buf, 0); + if (rec_type <= 0) + /* Report missing end record later. */ + break; + start = vstring_str(buf); + if (msg_verbose > 1) + msg_info("old-style scan record %c %s", rec_type, start); + if (rec_type == REC_TYPE_END) + break; + if (rec_type == REC_TYPE_DONE + || rec_type == REC_TYPE_RCPT + || rec_type == REC_TYPE_DRCP) { + message->rcpt_unread++; + continue; + } + if (rec_type == REC_TYPE_MESG) { + if (message->data_offset == 0) { + if ((message->data_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); + if ((extra_offset = atol(start)) <= message->data_offset) + msg_fatal("bad extra offset %s file %s", + start, VSTREAM_PATH(message->fp)); + if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + message->data_size = extra_offset - message->data_offset; + } + continue; + } + } + + /* + * Clean up. + */ + if (vstream_fseek(message->fp, orig_offset, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + vstring_free(buf); + + /* + * Sanity checks. Verify that all required information was found, + * including the queue file end marker. + */ + if (message->data_offset == 0 || rec_type != REC_TYPE_END) + msg_fatal("%s: envelope records out of order", message->queue_id); +} + +/* qmgr_message_read - read envelope records */ + +static int qmgr_message_read(QMGR_MESSAGE *message) +{ + VSTRING *buf; + int rec_type; + long curr_offset; + long save_offset = message->rcpt_offset; /* save a flag */ + int save_unread = message->rcpt_unread; /* save a count */ + char *start; + int recipient_limit; + const char *error_text; + char *name; + char *value; + char *orig_rcpt = 0; + int count; + int dsn_notify = 0; + char *dsn_orcpt = 0; + int n; + int have_log_client_attr = 0; + static const char env_rec_types[] = REC_TYPE_ENVELOPE REC_TYPE_EXTRACT; + static const char extra_rec_type[] = {REC_TYPE_XTRA, 0}; + const char *expected_rec_types; + + /* + * Initialize. No early returns or we have a memory leak. + */ + buf = vstring_alloc(100); + + /* + * If we re-open this file, skip over on-file recipient records that we + * already looked at, and refill the in-core recipient address list. + * + * For the first time, the message recipient limit is calculated from the + * global recipient limit. This is to avoid reading little recipients + * when the active queue is near empty. When the queue becomes full, only + * the necessary amount is read in core. Such priming is necessary + * because there are no message jobs yet. + * + * For the next time, the recipient limit is based solely on the message + * jobs' positions in the job lists and/or job stacks. + */ + if (message->rcpt_offset) { + if (message->rcpt_list.len) + msg_panic("%s: recipient list not empty on recipient reload", + message->queue_id); + if (vstream_fseek(message->fp, message->rcpt_offset, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + message->rcpt_offset = 0; + recipient_limit = message->rcpt_limit - message->rcpt_count; + } else { + recipient_limit = var_qmgr_rcpt_limit - qmgr_recipient_count; + if (recipient_limit < message->rcpt_limit) + recipient_limit = message->rcpt_limit; + } + /* Keep interrupt latency in check. */ + if (recipient_limit > 5000) + recipient_limit = 5000; + if (recipient_limit <= 0) + msg_panic("%s: no recipient slots available", message->queue_id); + if (msg_verbose) + msg_info("%s: recipient limit %d", message->queue_id, recipient_limit); + + /* + * Read envelope records. XXX Rely on the front-end programs to enforce + * record size limits. Read up to recipient_limit recipients from the + * queue file, to protect against memory exhaustion. Recipient records + * may appear before or after the message content, so we keep reading + * from the queue file until we have enough recipients (rcpt_offset != 0) + * and until we know all the non-recipient information. + * + * Note that the total recipient count record is accurate only for fresh + * queue files. After some of the recipients are marked as done and the + * queue file is deferred, it can be used as upper bound estimate only. + * Fortunately, this poses no major problem on the scheduling algorithm, + * as the only impact is that the already deferred messages are not + * chosen by qmgr_job_candidate() as often as they could. + * + * On the first open, we must examine all non-recipient records. + * + * Optimization: when we know that recipient records are not mixed with + * non-recipient records, as is typical with mailing list mail, then we + * can avoid having to examine all the queue file records before we can + * start deliveries. This avoids some file system thrashing with huge + * mailing lists. + */ + for (;;) { + expected_rec_types = env_rec_types; + if ((curr_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); + if (curr_offset == message->data_offset && curr_offset > 0) { + if (vstream_fseek(message->fp, message->data_size, SEEK_CUR) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + curr_offset += message->data_size; + expected_rec_types = extra_rec_type; + } + rec_type = rec_get_raw(message->fp, buf, 0, REC_FLAG_NONE); + start = vstring_str(buf); + if (msg_verbose > 1) + msg_info("record %c %s", rec_type, start); + if (rec_type == REC_TYPE_PTR) { + if ((rec_type = rec_goto(message->fp, start)) == REC_TYPE_ERROR) + break; + /* Need to update curr_offset after pointer jump. */ + continue; + } + if (rec_type <= 0) { + msg_warn("%s: message rejected: missing end record", + message->queue_id); + break; + } + if (strchr(expected_rec_types, rec_type) == 0) { + msg_warn("Unexpected record type '%c' at offset %ld", + rec_type, (long) curr_offset); + rec_type = REC_TYPE_ERROR; + break; + } + if (rec_type == REC_TYPE_END) { + message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT; + break; + } + + /* + * Map named attributes to pseudo record types, so that we don't have + * to pollute the queue file with records that are incompatible with + * past Postfix versions. Preferably, people should be able to back + * out from an upgrade without losing mail. + */ + if (rec_type == REC_TYPE_ATTR) { + if ((error_text = split_nameval(start, &name, &value)) != 0) { + msg_warn("%s: bad attribute record: %s: %.200s", + message->queue_id, error_text, start); + rec_type = REC_TYPE_ERROR; + break; + } + if ((n = rec_attr_map(name)) != 0) { + start = value; + rec_type = n; + } + } + + /* + * Process recipient records. + */ + if (rec_type == REC_TYPE_RCPT) { + /* See also below for code setting orig_rcpt etc. */ + if (message->rcpt_offset == 0) { + message->rcpt_unread--; + recipient_list_add(&message->rcpt_list, curr_offset, + dsn_orcpt ? dsn_orcpt : "", + dsn_notify ? dsn_notify : 0, + orig_rcpt ? orig_rcpt : "", start); + if (dsn_orcpt) { + myfree(dsn_orcpt); + dsn_orcpt = 0; + } + if (orig_rcpt) { + myfree(orig_rcpt); + orig_rcpt = 0; + } + if (dsn_notify) + dsn_notify = 0; + if (message->rcpt_list.len >= recipient_limit) { + if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", + VSTREAM_PATH(message->fp)); + if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT) + /* We already examined all non-recipient records. */ + break; + if (message->rflags & QMGR_READ_FLAG_MIXED_RCPT_OTHER) + /* Examine all remaining non-recipient records. */ + continue; + /* Optimizations for "pure recipient" record sections. */ + if (curr_offset > message->data_offset) { + /* We already examined all non-recipient records. */ + message->rflags |= QMGR_READ_FLAG_SEEN_ALL_NON_RCPT; + break; + } + + /* + * Examine non-recipient records in the extracted + * segment. Note that this skips to the message start + * record, because the handler for that record changes + * the expectations for allowed record types. + */ + if (vstream_fseek(message->fp, message->data_offset, + SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + continue; + } + } + continue; + } + if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_DRCP) { + if (message->rcpt_offset == 0) { + message->rcpt_unread--; + if (dsn_orcpt) { + myfree(dsn_orcpt); + dsn_orcpt = 0; + } + if (orig_rcpt) { + myfree(orig_rcpt); + orig_rcpt = 0; + } + if (dsn_notify) + dsn_notify = 0; + } + continue; + } + if (rec_type == REC_TYPE_DSN_ORCPT) { + /* See also above for code clearing dsn_orcpt. */ + if (dsn_orcpt != 0) { + msg_warn("%s: ignoring out-of-order DSN original recipient address <%.200s>", + message->queue_id, dsn_orcpt); + myfree(dsn_orcpt); + dsn_orcpt = 0; + } + if (message->rcpt_offset == 0) + dsn_orcpt = mystrdup(start); + continue; + } + if (rec_type == REC_TYPE_DSN_NOTIFY) { + /* See also above for code clearing dsn_notify. */ + if (dsn_notify != 0) { + msg_warn("%s: ignoring out-of-order DSN notify flags <%d>", + message->queue_id, dsn_notify); + dsn_notify = 0; + } + if (message->rcpt_offset == 0) { + if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_NOTIFY_OK(n)) + msg_warn("%s: ignoring malformed DSN notify flags <%.200s>", + message->queue_id, start); + else + dsn_notify = n; + continue; + } + } + if (rec_type == REC_TYPE_ORCP) { + /* See also above for code clearing orig_rcpt. */ + if (orig_rcpt != 0) { + msg_warn("%s: ignoring out-of-order original recipient <%.200s>", + message->queue_id, orig_rcpt); + myfree(orig_rcpt); + orig_rcpt = 0; + } + if (message->rcpt_offset == 0) + orig_rcpt = mystrdup(start); + continue; + } + + /* + * Process non-recipient records. + */ + if (message->rflags & QMGR_READ_FLAG_SEEN_ALL_NON_RCPT) + /* We already examined all non-recipient records. */ + continue; + if (rec_type == REC_TYPE_SIZE) { + if (message->data_offset == 0) { + if ((count = sscanf(start, "%ld %ld %d %d %ld %d", + &message->data_size, &message->data_offset, + &message->rcpt_unread, &message->rflags, + &message->cont_length, + &message->smtputf8)) >= 3) { + /* Postfix >= 1.0 (a.k.a. 20010228). */ + if (message->data_offset <= 0 || message->data_size <= 0) { + msg_warn("%s: invalid size record: %.100s", + message->queue_id, start); + rec_type = REC_TYPE_ERROR; + break; + } + if (message->rflags & ~QMGR_READ_FLAG_USER) { + msg_warn("%s: invalid flags in size record: %.100s", + message->queue_id, start); + rec_type = REC_TYPE_ERROR; + break; + } + } else if (count == 1) { + /* Postfix < 1.0 (a.k.a. 20010228). */ + qmgr_message_oldstyle_scan(message); + } else { + /* Can't happen. */ + msg_warn("%s: message rejected: weird size record", + message->queue_id); + rec_type = REC_TYPE_ERROR; + break; + } + } + /* Postfix < 2.4 compatibility. */ + if (message->cont_length == 0) { + message->cont_length = message->data_size; + } else if (message->cont_length < 0) { + msg_warn("%s: invalid size record: %.100s", + message->queue_id, start); + rec_type = REC_TYPE_ERROR; + break; + } + continue; + } + if (rec_type == REC_TYPE_TIME) { + if (message->arrival_time.tv_sec == 0) + REC_TYPE_TIME_SCAN(start, message->arrival_time); + continue; + } + if (rec_type == REC_TYPE_CTIME) { + if (message->create_time == 0) + message->create_time = atol(start); + continue; + } + if (rec_type == REC_TYPE_FILT) { + if (message->filter_xport != 0) + myfree(message->filter_xport); + message->filter_xport = mystrdup(start); + continue; + } + if (rec_type == REC_TYPE_INSP) { + if (message->inspect_xport != 0) + myfree(message->inspect_xport); + message->inspect_xport = mystrdup(start); + continue; + } + if (rec_type == REC_TYPE_RDR) { + if (message->redirect_addr != 0) + myfree(message->redirect_addr); + message->redirect_addr = mystrdup(start); + continue; + } + if (rec_type == REC_TYPE_FROM) { + if (message->sender == 0) { + message->sender = mystrdup(start); + opened(message->queue_id, message->sender, + message->cont_length, message->rcpt_unread, + "queue %s", message->queue_name); + } + continue; + } + if (rec_type == REC_TYPE_DSN_ENVID) { + /* Allow Milter override. */ + if (message->dsn_envid != 0) + myfree(message->dsn_envid); + message->dsn_envid = mystrdup(start); + } + if (rec_type == REC_TYPE_DSN_RET) { + /* Allow Milter override. */ + if (!alldig(start) || (n = atoi(start)) == 0 || !DSN_RET_OK(n)) + msg_warn("%s: ignoring malformed DSN RET flags in queue file record:%.100s", + message->queue_id, start); + else + message->dsn_ret = n; + } + if (rec_type == REC_TYPE_ATTR) { + /* Allow extra segment to override envelope segment info. */ + if (strcmp(name, MAIL_ATTR_ENCODING) == 0) { + if (message->encoding != 0) + myfree(message->encoding); + message->encoding = mystrdup(value); + } + + /* + * Backwards compatibility. Before Postfix 2.3, the logging + * attributes were called client_name, etc. Now they are called + * log_client_name. etc., and client_name is used for the actual + * client information. To support old queue files we accept both + * names for the purpose of logging; the new name overrides the + * old one. + * + * XXX Do not use the "legacy" client_name etc. attribute values for + * initializing the logging attributes, when this file already + * contains the "modern" log_client_name etc. logging attributes. + * Otherwise, logging attributes that are not present in the + * queue file would be set with information from the real client. + */ + else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_NAME) == 0) { + if (have_log_client_attr == 0 && message->client_name == 0) + message->client_name = mystrdup(value); + } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_ADDR) == 0) { + if (have_log_client_attr == 0 && message->client_addr == 0) + message->client_addr = mystrdup(value); + } else if (strcmp(name, MAIL_ATTR_ACT_CLIENT_PORT) == 0) { + if (have_log_client_attr == 0 && message->client_port == 0) + message->client_port = mystrdup(value); + } else if (strcmp(name, MAIL_ATTR_ACT_PROTO_NAME) == 0) { + if (have_log_client_attr == 0 && message->client_proto == 0) + message->client_proto = mystrdup(value); + } else if (strcmp(name, MAIL_ATTR_ACT_HELO_NAME) == 0) { + if (have_log_client_attr == 0 && message->client_helo == 0) + message->client_helo = mystrdup(value); + } + /* Original client attributes. */ + else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_NAME) == 0) { + if (message->client_name != 0) + myfree(message->client_name); + message->client_name = mystrdup(value); + have_log_client_attr = 1; + } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_ADDR) == 0) { + if (message->client_addr != 0) + myfree(message->client_addr); + message->client_addr = mystrdup(value); + have_log_client_attr = 1; + } else if (strcmp(name, MAIL_ATTR_LOG_CLIENT_PORT) == 0) { + if (message->client_port != 0) + myfree(message->client_port); + message->client_port = mystrdup(value); + have_log_client_attr = 1; + } else if (strcmp(name, MAIL_ATTR_LOG_PROTO_NAME) == 0) { + if (message->client_proto != 0) + myfree(message->client_proto); + message->client_proto = mystrdup(value); + have_log_client_attr = 1; + } else if (strcmp(name, MAIL_ATTR_LOG_HELO_NAME) == 0) { + if (message->client_helo != 0) + myfree(message->client_helo); + message->client_helo = mystrdup(value); + have_log_client_attr = 1; + } else if (strcmp(name, MAIL_ATTR_SASL_METHOD) == 0) { + if (message->sasl_method == 0) + message->sasl_method = mystrdup(value); + else + msg_warn("%s: ignoring multiple %s attribute: %s", + message->queue_id, MAIL_ATTR_SASL_METHOD, value); + } else if (strcmp(name, MAIL_ATTR_SASL_USERNAME) == 0) { + if (message->sasl_username == 0) + message->sasl_username = mystrdup(value); + else + msg_warn("%s: ignoring multiple %s attribute: %s", + message->queue_id, MAIL_ATTR_SASL_USERNAME, value); + } else if (strcmp(name, MAIL_ATTR_SASL_SENDER) == 0) { + if (message->sasl_sender == 0) + message->sasl_sender = mystrdup(value); + else + msg_warn("%s: ignoring multiple %s attribute: %s", + message->queue_id, MAIL_ATTR_SASL_SENDER, value); + } else if (strcmp(name, MAIL_ATTR_LOG_IDENT) == 0) { + if (message->log_ident == 0) + message->log_ident = mystrdup(value); + else + msg_warn("%s: ignoring multiple %s attribute: %s", + message->queue_id, MAIL_ATTR_LOG_IDENT, value); + } else if (strcmp(name, MAIL_ATTR_RWR_CONTEXT) == 0) { + if (message->rewrite_context == 0) + message->rewrite_context = mystrdup(value); + else + msg_warn("%s: ignoring multiple %s attribute: %s", + message->queue_id, MAIL_ATTR_RWR_CONTEXT, value); + } + + /* + * Optional tracing flags (verify, sendmail -v, sendmail -bv). + * This record is killed after a trace logfile report is sent and + * after the logfile is deleted. + */ + else if (strcmp(name, MAIL_ATTR_TRACE_FLAGS) == 0) { + if (message->tflags == 0) { + message->tflags = DEL_REQ_TRACE_FLAGS(atoi(value)); + if (message->tflags == DEL_REQ_FLAG_RECORD) + message->tflags_offset = curr_offset; + else + message->tflags_offset = 0; + if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0) + qmgr_vrfy_pend_count++; + } + } + continue; + } + if (rec_type == REC_TYPE_WARN) { + if (message->warn_offset == 0) { + message->warn_offset = curr_offset; + REC_TYPE_WARN_SCAN(start, message->warn_time); + } + continue; + } + if (rec_type == REC_TYPE_VERP) { + if (message->verp_delims == 0) { + if (message->sender == 0 || message->sender[0] == 0) { + msg_warn("%s: ignoring VERP request for null sender", + message->queue_id); + } else if (verp_delims_verify(start) != 0) { + msg_warn("%s: ignoring bad VERP request: \"%.100s\"", + message->queue_id, start); + } else { + if (msg_verbose) + msg_info("%s: enabling VERP for sender \"%.100s\"", + message->queue_id, message->sender); + message->single_rcpt = 1; + message->verp_delims = mystrdup(start); + } + } + continue; + } + } + + /* + * Grr. + */ + if (dsn_orcpt != 0) { + if (rec_type > 0) + msg_warn("%s: ignoring out-of-order DSN original recipient <%.200s>", + message->queue_id, dsn_orcpt); + myfree(dsn_orcpt); + } + if (orig_rcpt != 0) { + if (rec_type > 0) + msg_warn("%s: ignoring out-of-order original recipient <%.200s>", + message->queue_id, orig_rcpt); + myfree(orig_rcpt); + } + + /* + * After sending a "delayed" warning, request sender notification when + * message delivery is completed. While "mail delayed" notifications are + * bad enough because they multiply the amount of email traffic, "delay + * cleared" notifications are even worse because they come in a sudden + * burst when the queue drains after a network outage. + */ + if (var_dsn_delay_cleared && message->warn_time < 0) + message->tflags |= DEL_REQ_FLAG_REC_DLY_SENT; + + /* + * Remember when we have read the last recipient batch. Note that we do + * it here after reading as reading might have used considerable amount + * of time. + */ + message->refill_time = sane_time(); + + /* + * Avoid clumsiness elsewhere in the program. When sending data across an + * IPC channel, sending an empty string is more convenient than sending a + * null pointer. + */ + if (message->dsn_envid == 0) + message->dsn_envid = mystrdup(""); + if (message->encoding == 0) + message->encoding = mystrdup(MAIL_ATTR_ENC_NONE); + if (message->client_name == 0) + message->client_name = mystrdup(""); + if (message->client_addr == 0) + message->client_addr = mystrdup(""); + if (message->client_port == 0) + message->client_port = mystrdup(""); + if (message->client_proto == 0) + message->client_proto = mystrdup(""); + if (message->client_helo == 0) + message->client_helo = mystrdup(""); + if (message->sasl_method == 0) + message->sasl_method = mystrdup(""); + if (message->sasl_username == 0) + message->sasl_username = mystrdup(""); + if (message->sasl_sender == 0) + message->sasl_sender = mystrdup(""); + if (message->log_ident == 0) + message->log_ident = mystrdup(""); + if (message->rewrite_context == 0) + message->rewrite_context = mystrdup(MAIL_ATTR_RWR_LOCAL); + /* Postfix < 2.3 compatibility. */ + if (message->create_time == 0) + message->create_time = message->arrival_time.tv_sec; + + /* + * Clean up. + */ + vstring_free(buf); + + /* + * Sanity checks. Verify that all required information was found, + * including the queue file end marker. + */ + if (message->rcpt_unread < 0 + || (message->rcpt_offset == 0 && message->rcpt_unread != 0)) { + msg_warn("%s: rcpt count mismatch (%d)", + message->queue_id, message->rcpt_unread); + message->rcpt_unread = 0; + } + if (rec_type <= 0) { + /* Already logged warning. */ + } else if (message->arrival_time.tv_sec == 0) { + msg_warn("%s: message rejected: missing arrival time record", + message->queue_id); + } else if (message->sender == 0) { + msg_warn("%s: message rejected: missing sender record", + message->queue_id); + } else if (message->data_offset == 0) { + msg_warn("%s: message rejected: missing size record", + message->queue_id); + } else { + return (0); + } + message->rcpt_offset = save_offset; /* restore flag */ + message->rcpt_unread = save_unread; /* restore count */ + recipient_list_free(&message->rcpt_list); + recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE); + return (-1); +} + +/* qmgr_message_update_warn - update the time of next delay warning */ + +void qmgr_message_update_warn(QMGR_MESSAGE *message) +{ + + /* + * After the "mail delayed" warning, optionally send a "delay cleared" + * notification. + */ + if (qmgr_message_open(message) + || vstream_fseek(message->fp, message->warn_offset, SEEK_SET) < 0 + || rec_fprintf(message->fp, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT, + REC_TYPE_WARN_ARG(-1)) < 0 + || vstream_fflush(message->fp)) + msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp)); + qmgr_message_close(message); +} + +/* qmgr_message_kill_record - mark one message record as killed */ + +void qmgr_message_kill_record(QMGR_MESSAGE *message, long offset) +{ + if (offset <= 0) + msg_panic("qmgr_message_kill_record: bad offset 0x%lx", offset); + if (qmgr_message_open(message) + || rec_put_type(message->fp, REC_TYPE_KILL, offset) < 0 + || vstream_fflush(message->fp)) + msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp)); + qmgr_message_close(message); +} + +/* qmgr_message_sort_compare - compare recipient information */ + +static int qmgr_message_sort_compare(const void *p1, const void *p2) +{ + RECIPIENT *rcpt1 = (RECIPIENT *) p1; + RECIPIENT *rcpt2 = (RECIPIENT *) p2; + QMGR_QUEUE *queue1; + QMGR_QUEUE *queue2; + char *at1; + char *at2; + int result; + + /* + * Compare most significant to least significant recipient attributes. + * The comparison function must be transitive, so NULL values need to be + * assigned an ordinal (we set NULL last). + */ + + queue1 = rcpt1->u.queue; + queue2 = rcpt2->u.queue; + if (queue1 != 0 && queue2 == 0) + return (-1); + if (queue1 == 0 && queue2 != 0) + return (1); + if (queue1 != 0 && queue2 != 0) { + + /* + * Compare message transport. + */ + if ((result = strcmp(queue1->transport->name, + queue2->transport->name)) != 0) + return (result); + + /* + * Compare queue name (nexthop or recipient@nexthop). + */ + if ((result = strcmp(queue1->name, queue2->name)) != 0) + return (result); + } + + /* + * Compare recipient domain. + */ + at1 = strrchr(rcpt1->address, '@'); + at2 = strrchr(rcpt2->address, '@'); + if (at1 == 0 && at2 != 0) + return (1); + if (at1 != 0 && at2 == 0) + return (-1); + if (at1 != 0 && at2 != 0 + && (result = strcasecmp_utf8(at1, at2)) != 0) + return (result); + + /* + * Compare recipient address. + */ + return (strcmp(rcpt1->address, rcpt2->address)); +} + +/* qmgr_message_sort - sort message recipient addresses by domain */ + +static void qmgr_message_sort(QMGR_MESSAGE *message) +{ + qsort((void *) message->rcpt_list.info, message->rcpt_list.len, + sizeof(message->rcpt_list.info[0]), qmgr_message_sort_compare); + if (msg_verbose) { + RECIPIENT_LIST list = message->rcpt_list; + RECIPIENT *rcpt; + + msg_info("start sorted recipient list"); + for (rcpt = list.info; rcpt < list.info + list.len; rcpt++) + msg_info("qmgr_message_sort: %s", rcpt->address); + msg_info("end sorted recipient list"); + } +} + +/* qmgr_resolve_one - resolve or skip one recipient */ + +static int qmgr_resolve_one(QMGR_MESSAGE *message, RECIPIENT *recipient, + const char *addr, RESOLVE_REPLY *reply) +{ +#define QMGR_REDIRECT(rp, tp, np) do { \ + (rp)->flags = 0; \ + vstring_strcpy((rp)->transport, (tp)); \ + vstring_strcpy((rp)->nexthop, (np)); \ + } while (0) + + if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) == 0) + resolve_clnt_query_from(message->sender, addr, reply); + else + resolve_clnt_verify_from(message->sender, addr, reply); + if (reply->flags & RESOLVE_FLAG_FAIL) { + QMGR_REDIRECT(reply, MAIL_SERVICE_RETRY, + "4.3.0 address resolver failure"); + return (0); + } else if (reply->flags & RESOLVE_FLAG_ERROR) { + QMGR_REDIRECT(reply, MAIL_SERVICE_ERROR, + "5.1.3 bad address syntax"); + return (0); + } else { + return (0); + } +} + +/* qmgr_message_resolve - resolve recipients */ + +static void qmgr_message_resolve(QMGR_MESSAGE *message) +{ + static ARGV *defer_xport_argv; + RECIPIENT_LIST list = message->rcpt_list; + RECIPIENT *recipient; + QMGR_TRANSPORT *transport = 0; + QMGR_QUEUE *queue = 0; + RESOLVE_REPLY reply; + VSTRING *queue_name; + char *at; + char **cpp; + char *nexthop; + ssize_t len; + int status; + DSN dsn; + MSG_STATS stats; + DSN *saved_dsn; + +#define STREQ(x,y) (strcmp(x,y) == 0) +#define STR vstring_str +#define LEN VSTRING_LEN + + resolve_clnt_init(&reply); + queue_name = vstring_alloc(1); + for (recipient = list.info; recipient < list.info + list.len; recipient++) { + + /* + * Redirect overrides all else. But only once (per entire message). + * For consistency with the remainder of Postfix, rewrite the address + * to canonical form before resolving it. + */ + if (message->redirect_addr) { + if (recipient > list.info) { + recipient->u.queue = 0; + continue; + } + message->rcpt_offset = 0; + message->rcpt_unread = 0; + + rewrite_clnt_internal(REWRITE_CANON, message->redirect_addr, + reply.recipient); + RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); + if (qmgr_resolve_one(message, recipient, + recipient->address, &reply) < 0) + continue; + if (!STREQ(recipient->address, STR(reply.recipient))) + RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); + } + + /* + * Content filtering overrides the address resolver. + * + * XXX Bypass content_filter inspection for user-generated probes + * (sendmail -bv). MTA-generated probes never have the "please filter + * me" bits turned on, but we handle them here anyway for the sake of + * future proofing. + */ +#define FILTER_WITHOUT_NEXTHOP(filter, next) \ + (((next) = split_at((filter), ':')) == 0 || *(next) == 0) + +#define RCPT_WITHOUT_DOMAIN(rcpt, next) \ + ((next = strrchr(rcpt, '@')) == 0 || *++(next) == 0) + + else if (message->filter_xport + && (message->tflags & DEL_REQ_TRACE_ONLY_MASK) == 0) { + reply.flags = 0; + vstring_strcpy(reply.transport, message->filter_xport); + if (FILTER_WITHOUT_NEXTHOP(STR(reply.transport), nexthop) + && *(nexthop = var_def_filter_nexthop) == 0 + && RCPT_WITHOUT_DOMAIN(recipient->address, nexthop)) + nexthop = var_myhostname; + vstring_strcpy(reply.nexthop, nexthop); + vstring_strcpy(reply.recipient, recipient->address); + } + + /* + * Resolve the destination to (transport, nexthop, address). The + * result address may differ from the one specified by the sender. + */ + else { + if (qmgr_resolve_one(message, recipient, + recipient->address, &reply) < 0) + continue; + if (!STREQ(recipient->address, STR(reply.recipient))) + RECIPIENT_UPDATE(recipient->address, STR(reply.recipient)); + } + + /* + * Bounce null recipients. This should never happen, but is most + * likely the result of a fault in a different program, so aborting + * the queue manager process does not help. + */ + if (recipient->address[0] == 0) { + QMGR_REDIRECT(&reply, MAIL_SERVICE_ERROR, + "5.1.3 null recipient address"); + } + + /* + * Redirect a forced-to-expire message without defer log to the retry + * service, so that its defer log will contain an appropriate reason. + * Do not redirect such a message to the error service, because if + * that request fails, a defer log would be created with reason + * "bounce or trace service failure" which would make no sense. Note + * that if the bounce service fails to create a defer log, the + * message will be returned as undeliverable anyway, because it is + * expired. + */ + if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) { + QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY, + "4.7.0 message is administratively expired"); + } + + /* + * Discard mail to the local double bounce address here, so this + * system can run without a local delivery agent. They'd still have + * to configure something for mail directed to the local postmaster, + * though, but that is an RFC requirement anyway. + * + * XXX This lookup should be done in the resolver, and the mail should + * be directed to a general-purpose null delivery agent. + */ + if (reply.flags & RESOLVE_CLASS_LOCAL) { + at = strrchr(STR(reply.recipient), '@'); + len = (at ? (at - STR(reply.recipient)) + : strlen(STR(reply.recipient))); + if (strncasecmp_utf8(STR(reply.recipient), + var_double_bounce_sender, len) == 0 + && !var_double_bounce_sender[len]) { + status = sent(message->tflags, message->queue_id, + QMGR_MSG_STATS(&stats, message), recipient, + "none", DSN_SIMPLE(&dsn, "2.0.0", + "undeliverable postmaster notification discarded")); + if (status == 0) { + deliver_completed(message->fp, recipient->offset); +#if 0 + /* It's the default verification probe sender address. */ + msg_warn("%s: undeliverable postmaster notification discarded", + message->queue_id); +#endif + } else + message->flags |= status; + continue; + } + } + + /* + * Optionally defer deliveries over specific transports, unless the + * restriction is lifted temporarily. + */ + if (*var_defer_xports && (message->qflags & QMGR_FLUSH_DFXP) == 0) { + if (defer_xport_argv == 0) + defer_xport_argv = argv_split(var_defer_xports, CHARS_COMMA_SP); + for (cpp = defer_xport_argv->argv; *cpp; cpp++) + if (strcmp(*cpp, STR(reply.transport)) == 0) + break; + if (*cpp) { + QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY, + "4.3.2 deferred transport"); + } + } + + /* + * Safety: defer excess address verification requests. + */ + if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0 + && qmgr_vrfy_pend_count > var_vrfy_pend_limit) + QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY, + "4.3.2 Too many address verification requests"); + + /* + * Look up or instantiate the proper transport. + */ + if (transport == 0 || !STREQ(transport->name, STR(reply.transport))) { + if ((transport = qmgr_transport_find(STR(reply.transport))) == 0) + transport = qmgr_transport_create(STR(reply.transport)); + queue = 0; + } + + /* + * This message is being flushed. If need-be unthrottle the + * transport. + */ + if ((message->qflags & QMGR_FLUSH_EACH) != 0 + && QMGR_TRANSPORT_THROTTLED(transport)) + qmgr_transport_unthrottle(transport); + + /* + * This transport is dead. Defer delivery to this recipient. + */ + if (QMGR_TRANSPORT_THROTTLED(transport)) { + saved_dsn = transport->dsn; + if ((transport = qmgr_error_transport(MAIL_SERVICE_RETRY)) != 0) { + nexthop = qmgr_error_nexthop(saved_dsn); + vstring_strcpy(reply.nexthop, nexthop); + myfree(nexthop); + queue = 0; + } else { + qmgr_defer_recipient(message, recipient, saved_dsn); + continue; + } + } + + /* + * The nexthop destination provides the default name for the + * per-destination queue. When the delivery agent accepts only one + * recipient per delivery, give each recipient its own queue, so that + * deliveries to different recipients of the same message can happen + * in parallel, and so that we can enforce per-recipient concurrency + * limits and prevent one recipient from tying up all the delivery + * agent resources. We use recipient@nexthop as queue name rather + * than the actual recipient domain name, so that one recipient in + * multiple equivalent domains cannot evade the per-recipient + * concurrency limit. Split the address on the recipient delimiter if + * one is defined, so that extended addresses don't get extra + * delivery slots. + * + * Fold the result to lower case so that we don't have multiple queues + * for the same name. + * + * Important! All recipients in a queue must have the same nexthop + * value. It is OK to have multiple queues with the same nexthop + * value, but only when those queues are named after recipients. + * + * The single-recipient code below was written for local(8) like + * delivery agents, and assumes that all domains that deliver to the + * same (transport + nexthop) are aliases for $nexthop. Delivery + * concurrency is changed from per-domain into per-recipient, by + * changing the queue name from nexthop into localpart@nexthop. + * + * XXX This assumption is incorrect when different destinations share + * the same (transport + nexthop). In reality, such transports are + * rarely configured to use single-recipient deliveries. The fix is + * to decouple the per-destination recipient limit from the + * per-destination concurrency. + */ + vstring_strcpy(queue_name, STR(reply.nexthop)); + if (strcmp(transport->name, MAIL_SERVICE_ERROR) != 0 + && strcmp(transport->name, MAIL_SERVICE_RETRY) != 0 + && transport->recipient_limit == 1) { + /* Copy the recipient localpart. */ + at = strrchr(STR(reply.recipient), '@'); + len = (at ? (at - STR(reply.recipient)) + : strlen(STR(reply.recipient))); + vstring_strncpy(queue_name, STR(reply.recipient), len); + /* Remove the address extension from the recipient localpart. */ + if (*var_rcpt_delim && split_addr(STR(queue_name), var_rcpt_delim)) + vstring_truncate(queue_name, strlen(STR(queue_name))); + /* Assume the recipient domain is equivalent to nexthop. */ + vstring_sprintf_append(queue_name, "@%s", STR(reply.nexthop)); + } + lowercase(STR(queue_name)); + + /* + * This transport is alive. Find or instantiate a queue for this + * recipient. + */ + if (queue == 0 || !STREQ(queue->name, STR(queue_name))) { + if ((queue = qmgr_queue_find(transport, STR(queue_name))) == 0) + queue = qmgr_queue_create(transport, STR(queue_name), + STR(reply.nexthop)); + } + + /* + * This message is being flushed. If need-be unthrottle the queue. + */ + if ((message->qflags & QMGR_FLUSH_EACH) != 0 + && QMGR_QUEUE_THROTTLED(queue)) + qmgr_queue_unthrottle(queue); + + /* + * This queue is dead. Defer delivery to this recipient. + */ + if (QMGR_QUEUE_THROTTLED(queue)) { + saved_dsn = queue->dsn; + if ((queue = qmgr_error_queue(MAIL_SERVICE_RETRY, saved_dsn)) == 0) { + qmgr_defer_recipient(message, recipient, saved_dsn); + continue; + } + } + + /* + * This queue is alive. Bind this recipient to this queue instance. + */ + recipient->u.queue = queue; + } + resolve_clnt_free(&reply); + vstring_free(queue_name); +} + +/* qmgr_message_assign - assign recipients to specific delivery requests */ + +static void qmgr_message_assign(QMGR_MESSAGE *message) +{ + RECIPIENT_LIST list = message->rcpt_list; + RECIPIENT *recipient; + QMGR_ENTRY *entry = 0; + QMGR_QUEUE *queue; + QMGR_JOB *job = 0; + QMGR_PEER *peer = 0; + + /* + * Try to bundle as many recipients in a delivery request as we can. When + * the recipient resolves to the same site and transport as an existing + * recipient, do not create a new queue entry, just move that recipient + * to the recipient list of the existing queue entry. All this provided + * that we do not exceed the transport-specific limit on the number of + * recipients per transaction. + */ +#define LIMIT_OK(limit, count) ((limit) == 0 || ((count) < (limit))) + + for (recipient = list.info; recipient < list.info + list.len; recipient++) { + + /* + * Skip recipients with a dead transport or destination. + */ + if ((queue = recipient->u.queue) == 0) + continue; + + /* + * Lookup or instantiate the message job if necessary. + */ + if (job == 0 || queue->transport != job->transport) { + job = qmgr_job_obtain(message, queue->transport); + peer = 0; + } + + /* + * Lookup or instantiate job peer if necessary. + */ + if (peer == 0 || queue != peer->queue) + peer = qmgr_peer_obtain(job, queue); + + /* + * Lookup old or instantiate new recipient entry. We try to reuse the + * last existing entry whenever the recipient limit permits. + */ + entry = peer->entry_list.prev; + if (message->single_rcpt || entry == 0 + || !LIMIT_OK(queue->transport->recipient_limit, entry->rcpt_list.len)) + entry = qmgr_entry_create(peer, message); + + /* + * Add the recipient to the current entry and increase all those + * recipient counters accordingly. + */ + recipient_list_add(&entry->rcpt_list, recipient->offset, + recipient->dsn_orcpt, recipient->dsn_notify, + recipient->orig_addr, recipient->address); + job->rcpt_count++; + message->rcpt_count++; + qmgr_recipient_count++; + } + + /* + * Release the message recipient list and reinitialize it for the next + * time. + */ + recipient_list_free(&message->rcpt_list); + recipient_list_init(&message->rcpt_list, RCPT_LIST_INIT_QUEUE); + + /* + * Note that even if qmgr_job_obtain() reset the job candidate cache of + * all transports to which we assigned new recipients, this message may + * have other jobs which we didn't touch at all this time. But the number + * of unread recipients affecting the candidate selection might have + * changed considerably, so we must invalidate the caches if it might be + * of some use. + */ + for (job = message->job_list.next; job; job = job->message_peers.next) + if (job->selected_entries < job->read_entries + && job->blocker_tag != job->transport->blocker_tag) + job->transport->candidate_cache_current = 0; +} + +/* qmgr_message_move_limits - recycle unused recipient slots */ + +static void qmgr_message_move_limits(QMGR_MESSAGE *message) +{ + QMGR_JOB *job; + + for (job = message->job_list.next; job; job = job->message_peers.next) + qmgr_job_move_limits(job); +} + +/* qmgr_message_free - release memory for in-core message structure */ + +void qmgr_message_free(QMGR_MESSAGE *message) +{ + QMGR_JOB *job; + + if (message->refcount != 0) + msg_panic("qmgr_message_free: reference len: %d", message->refcount); + if (message->fp) + msg_panic("qmgr_message_free: queue file is open"); + while ((job = message->job_list.next) != 0) + qmgr_job_free(job); + myfree(message->queue_id); + myfree(message->queue_name); + if (message->dsn_envid) + myfree(message->dsn_envid); + if (message->encoding) + myfree(message->encoding); + if (message->sender) + myfree(message->sender); + if (message->verp_delims) + myfree(message->verp_delims); + if (message->filter_xport) + myfree(message->filter_xport); + if (message->inspect_xport) + myfree(message->inspect_xport); + if (message->redirect_addr) + myfree(message->redirect_addr); + if (message->client_name) + myfree(message->client_name); + if (message->client_addr) + myfree(message->client_addr); + if (message->client_port) + myfree(message->client_port); + if (message->client_proto) + myfree(message->client_proto); + if (message->client_helo) + myfree(message->client_helo); + if (message->sasl_method) + myfree(message->sasl_method); + if (message->sasl_username) + myfree(message->sasl_username); + if (message->sasl_sender) + myfree(message->sasl_sender); + if (message->log_ident) + myfree(message->log_ident); + if (message->rewrite_context) + myfree(message->rewrite_context); + recipient_list_free(&message->rcpt_list); + qmgr_message_count--; + if ((message->tflags & DEL_REQ_FLAG_MTA_VRFY) != 0) + qmgr_vrfy_pend_count--; + myfree((void *) message); +} + +/* qmgr_message_alloc - create in-core message structure */ + +QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *queue_id, + int qflags, mode_t mode) +{ + const char *myname = "qmgr_message_alloc"; + QMGR_MESSAGE *message; + struct stat st; + + if (msg_verbose) + msg_info("%s: %s %s", myname, queue_name, queue_id); + + /* + * Create an in-core message structure. + */ + message = qmgr_message_create(queue_name, queue_id, qflags); + + /* + * Extract message envelope information: time of arrival, sender address, + * recipient addresses. Skip files with malformed envelope information. + */ +#define QMGR_LOCK_MODE (MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) + + if (qmgr_message_open(message) < 0) { + qmgr_message_free(message); + return (0); + } + if (myflock(vstream_fileno(message->fp), INTERNAL_LOCK, QMGR_LOCK_MODE) < 0) { + msg_info("%s: skipped, still being delivered", queue_id); + qmgr_message_close(message); + qmgr_message_free(message); + return (QMGR_MESSAGE_LOCKED); + } + if (qmgr_message_read(message) < 0) { + qmgr_message_close(message); + qmgr_message_free(message); + return (0); + } else { + + /* + * We have validated the queue file content, so it is safe to modify + * the file properties now. + */ + if (mode != 0 && fchmod(vstream_fileno(message->fp), mode) < 0) + msg_fatal("fchmod %s: %m", VSTREAM_PATH(message->fp)); + + /* + * If this message is forced to expire, use the existing defer + * logfile records and do not assign any deliveries, leaving the + * refcount at zero. If this message is forced to expire, but no + * defer logfile records are available, assign deliveries to the + * retry transport so that the sender will still find out what + * recipients are affected and why. Either way, do not assign normal + * deliveries because that would be undesirable especially with mail + * that was expired in the 'hold' queue. + */ + if ((message->qflags & QMGR_FORCE_EXPIRE) != 0 + && stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_DEFER, + queue_id), &st) == 0 && st.st_size > 0) { + /* Use this defer log; don't assign deliveries (refcount == 0). */ + message->flags = 1; /* simplify downstream code */ + qmgr_message_close(message); + return (message); + } + + /* + * Reset the defer log. This code should not be here, but we must + * reset the defer log *after* acquiring the exclusive lock on the + * queue file and *before* resolving new recipients. Since all those + * operations are encapsulated so nicely by this routine, the defer + * log reset has to be done here as well. + * + * Note: it is safe to remove the defer logfile from a previous queue + * run of this queue file, because the defer log contains information + * about recipients that still exist in this queue file. + */ + if (mail_queue_remove(MAIL_QUEUE_DEFER, queue_id) && errno != ENOENT) + msg_fatal("%s: %s: remove %s %s: %m", myname, + queue_id, MAIL_QUEUE_DEFER, queue_id); + qmgr_message_sort(message); + qmgr_message_resolve(message); + qmgr_message_sort(message); + qmgr_message_assign(message); + qmgr_message_close(message); + if (message->rcpt_offset == 0) + qmgr_message_move_limits(message); + return (message); + } +} + +/* qmgr_message_realloc - refresh in-core message structure */ + +QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *message) +{ + const char *myname = "qmgr_message_realloc"; + + /* + * Sanity checks. + */ + if (message->rcpt_offset <= 0) + msg_panic("%s: invalid offset: %ld", myname, message->rcpt_offset); + if (msg_verbose) + msg_info("%s: %s %s offset %ld", myname, message->queue_name, + message->queue_id, message->rcpt_offset); + + /* + * Extract recipient addresses. Skip files with malformed envelope + * information. + */ + if (qmgr_message_open(message) < 0) + return (0); + if (qmgr_message_read(message) < 0) { + qmgr_message_close(message); + return (0); + } else { + qmgr_message_sort(message); + qmgr_message_resolve(message); + qmgr_message_sort(message); + qmgr_message_assign(message); + qmgr_message_close(message); + if (message->rcpt_offset == 0) + qmgr_message_move_limits(message); + return (message); + } +} diff --git a/src/qmgr/qmgr_move.c b/src/qmgr/qmgr_move.c new file mode 100644 index 0000000..e68f803 --- /dev/null +++ b/src/qmgr/qmgr_move.c @@ -0,0 +1,104 @@ +/*++ +/* NAME +/* qmgr_move 3 +/* SUMMARY +/* move queue entries to another queue +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_move(from, to, time_stamp) +/* const char *from; +/* const char *to; +/* time_t time_stamp; +/* DESCRIPTION +/* The \fBqmgr_move\fR routine scans the \fIfrom\fR queue for entries +/* with valid queue names and moves them to the \fIto\fR queue. +/* If \fItime_stamp\fR is non-zero, the queue file time stamps are +/* set to the specified value. +/* Entries with invalid names are left alone. No attempt is made to +/* look for other badness such as multiple links or weird file types. +/* These issues are dealt with when a queue file is actually opened. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_move - move queue entries to another queue, leave bad files alone */ + +void qmgr_move(const char *src_queue, const char *dst_queue, + time_t time_stamp) +{ + const char *myname = "qmgr_move"; + SCAN_DIR *queue_dir; + char *queue_id; + struct utimbuf tbuf; + const char *path; + + if (strcmp(src_queue, dst_queue) == 0) + msg_panic("%s: source queue %s is destination", myname, src_queue); + if (msg_verbose) + msg_info("start move queue %s -> %s", src_queue, dst_queue); + + queue_dir = scan_dir_open(src_queue); + while ((queue_id = mail_scan_dir_next(queue_dir)) != 0) { + if (mail_queue_id_ok(queue_id)) { + if (time_stamp > 0) { + tbuf.actime = tbuf.modtime = time_stamp; + path = mail_queue_path((VSTRING *) 0, src_queue, queue_id); + if (utime(path, &tbuf) < 0) { + if (errno != ENOENT) + msg_fatal("%s: update %s time stamps: %m", myname, path); + msg_warn("%s: update %s time stamps: %m", myname, path); + continue; + } + } + if (mail_queue_rename(queue_id, src_queue, dst_queue)) { + if (errno != ENOENT) + msg_fatal("%s: rename %s from %s to %s: %m", + myname, queue_id, src_queue, dst_queue); + msg_warn("%s: rename %s from %s to %s: %m", + myname, queue_id, src_queue, dst_queue); + continue; + } + if (msg_verbose) + msg_info("%s: moved %s from %s to %s", + myname, queue_id, src_queue, dst_queue); + } else { + msg_warn("%s: ignored: queue %s id %s", + myname, src_queue, queue_id); + } + } + scan_dir_close(queue_dir); + + if (msg_verbose) + msg_info("end move queue %s -> %s", src_queue, dst_queue); +} diff --git a/src/qmgr/qmgr_peer.c b/src/qmgr/qmgr_peer.c new file mode 100644 index 0000000..ebf9632 --- /dev/null +++ b/src/qmgr/qmgr_peer.c @@ -0,0 +1,154 @@ +/*++ +/* NAME +/* qmgr_peer 3 +/* SUMMARY +/* per-job peers +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_PEER *qmgr_peer_create(job, queue) +/* QMGR_JOB *job; +/* QMGR_QUEUE *queue; +/* +/* QMGR_PEER *qmgr_peer_find(job, queue) +/* QMGR_JOB *job; +/* QMGR_QUEUE *queue; +/* +/* QMGR_PEER *qmgr_peer_obtain(job, queue) +/* QMGR_JOB *job; +/* QMGR_QUEUE *queue; +/* +/* void qmgr_peer_free(peer) +/* QMGR_PEER *peer; +/* +/* QMGR_PEER *qmgr_peer_select(job) +/* QMGR_JOB *job; +/* +/* DESCRIPTION +/* These routines add/delete/manipulate per-job peers. +/* Each peer corresponds to a specific job and destination. +/* It is similar to per-transport queue structure, but groups +/* only the entries of the given job. +/* +/* qmgr_peer_create() creates an empty peer structure for the named +/* job and destination. It is an error to call this function +/* if a peer for given combination already exists. +/* +/* qmgr_peer_find() looks up the peer for the named destination +/* for the named job. A null result means that the peer +/* was not found. +/* +/* qmgr_peer_obtain() looks up the peer for the named destination +/* for the named job. If it doesn't exist yet, it creates it. +/* +/* qmgr_peer_free() disposes of a per-job peer after all +/* its entries have been taken care of. It is an error to dispose +/* of a peer still in use. +/* +/* qmgr_peer_select() attempts to find a peer of named job that +/* has messages pending delivery. This routine implements +/* round-robin search among job's peers. +/* DIAGNOSTICS +/* Panic: consistency check failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Patrik Rak +/* patrik@raxoft.cz +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_peer_create - create and initialize message peer structure */ + +QMGR_PEER *qmgr_peer_create(QMGR_JOB *job, QMGR_QUEUE *queue) +{ + QMGR_PEER *peer; + + peer = (QMGR_PEER *) mymalloc(sizeof(QMGR_PEER)); + peer->queue = queue; + peer->job = job; + QMGR_LIST_APPEND(job->peer_list, peer, peers); + htable_enter(job->peer_byname, queue->name, (void *) peer); + peer->refcount = 0; + QMGR_LIST_INIT(peer->entry_list); + return (peer); +} + +/* qmgr_peer_free - release peer structure */ + +void qmgr_peer_free(QMGR_PEER *peer) +{ + const char *myname = "qmgr_peer_free"; + QMGR_JOB *job = peer->job; + QMGR_QUEUE *queue = peer->queue; + + /* + * Sanity checks. It is an error to delete a referenced peer structure. + */ + if (peer->refcount != 0) + msg_panic("%s: refcount: %d", myname, peer->refcount); + if (peer->entry_list.next != 0) + msg_panic("%s: entry list not empty: %s", myname, queue->name); + + QMGR_LIST_UNLINK(job->peer_list, QMGR_PEER *, peer, peers); + htable_delete(job->peer_byname, queue->name, (void (*) (void *)) 0); + myfree((void *) peer); +} + +/* qmgr_peer_find - lookup peer associated with given job and queue */ + +QMGR_PEER *qmgr_peer_find(QMGR_JOB *job, QMGR_QUEUE *queue) +{ + return ((QMGR_PEER *) htable_find(job->peer_byname, queue->name)); +} + +/* qmgr_peer_obtain - find/create peer associated with given job and queue */ + +QMGR_PEER *qmgr_peer_obtain(QMGR_JOB *job, QMGR_QUEUE *queue) +{ + QMGR_PEER *peer; + + if ((peer = qmgr_peer_find(job, queue)) == 0) + peer = qmgr_peer_create(job, queue); + return (peer); +} + +/* qmgr_peer_select - select next peer suitable for delivery within given job */ + +QMGR_PEER *qmgr_peer_select(QMGR_JOB *job) +{ + QMGR_PEER *peer; + QMGR_QUEUE *queue; + + /* + * If we find a suitable site, rotate the list to enforce round-robin + * selection. See similar selection code in qmgr_transport_select(). + */ + for (peer = job->peer_list.next; peer; peer = peer->peers.next) { + queue = peer->queue; + if (queue->window > queue->busy_refcount && peer->entry_list.next != 0) { + QMGR_LIST_ROTATE(job->peer_list, peer, peers); + if (msg_verbose) + msg_info("qmgr_peer_select: %s %s %s (%d of %d)", + job->message->queue_id, queue->transport->name, queue->name, + queue->busy_refcount + 1, queue->window); + return (peer); + } + } + return (0); +} diff --git a/src/qmgr/qmgr_queue.c b/src/qmgr/qmgr_queue.c new file mode 100644 index 0000000..d2ffe09 --- /dev/null +++ b/src/qmgr/qmgr_queue.c @@ -0,0 +1,439 @@ +/*++ +/* NAME +/* qmgr_queue 3 +/* SUMMARY +/* per-destination queues +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* int qmgr_queue_count; +/* +/* QMGR_QUEUE *qmgr_queue_create(transport, name, nexthop) +/* QMGR_TRANSPORT *transport; +/* const char *name; +/* const char *nexthop; +/* +/* void qmgr_queue_done(queue) +/* QMGR_QUEUE *queue; +/* +/* QMGR_QUEUE *qmgr_queue_find(transport, name) +/* QMGR_TRANSPORT *transport; +/* const char *name; +/* +/* void qmgr_queue_throttle(queue, dsn) +/* QMGR_QUEUE *queue; +/* DSN *dsn; +/* +/* void qmgr_queue_unthrottle(queue) +/* QMGR_QUEUE *queue; +/* +/* void qmgr_queue_suspend(queue, delay) +/* QMGR_QUEUE *queue; +/* int delay; +/* DESCRIPTION +/* These routines add/delete/manipulate per-destination queues. +/* Each queue corresponds to a specific transport and destination. +/* Each queue has a `todo' list of delivery requests for that +/* destination, and a `busy' list of delivery requests in progress. +/* +/* qmgr_queue_count is a global counter for the total number +/* of in-core queue structures. +/* +/* qmgr_queue_create() creates an empty named queue for the named +/* transport and destination. The queue is given an initial +/* concurrency limit as specified with the +/* \fIinitial_destination_concurrency\fR configuration parameter, +/* provided that it does not exceed the transport-specific +/* concurrency limit. +/* +/* qmgr_queue_done() disposes of a per-destination queue after all +/* its entries have been taken care of. It is an error to dispose +/* of a dead queue. +/* +/* qmgr_queue_find() looks up the named queue for the named +/* transport. A null result means that the queue was not found. +/* +/* qmgr_queue_throttle() handles a delivery error, and decrements the +/* concurrency limit for the destination, with a lower bound of 1. +/* When the cohort failure bound is reached, qmgr_queue_throttle() +/* sets the concurrency limit to zero and starts a timer +/* to re-enable delivery to the destination after a configurable delay. +/* +/* qmgr_queue_unthrottle() undoes qmgr_queue_throttle()'s effects. +/* The concurrency limit for the destination is incremented, +/* provided that it does not exceed the destination concurrency +/* limit specified for the transport. This routine implements +/* "slow open" mode, and eliminates the "thundering herd" problem. +/* +/* qmgr_queue_suspend() suspends delivery for this destination +/* briefly. This function invalidates any scheduling decisions +/* that are based on the present queue's concurrency window. +/* To compensate for work skipped by qmgr_entry_done(), the +/* status of blocker jobs is re-evaluated after the queue is +/* resumed. +/* DIAGNOSTICS +/* Panic: consistency check failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Pre-emptive scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/* +/* Concurrency scheduler enhancements with: +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include /* QMGR_LOG_WINDOW */ + +/* Application-specific. */ + +#include "qmgr.h" + +int qmgr_queue_count; + +#define QMGR_ERROR_OR_RETRY_QUEUE(queue) \ + (strcmp(queue->transport->name, MAIL_SERVICE_RETRY) == 0 \ + || strcmp(queue->transport->name, MAIL_SERVICE_ERROR) == 0) + +#define QMGR_LOG_FEEDBACK(feedback) \ + if (var_conc_feedback_debug && !QMGR_ERROR_OR_RETRY_QUEUE(queue)) \ + msg_info("%s: feedback %g", myname, feedback); + +#define QMGR_LOG_WINDOW(queue) \ + if (var_conc_feedback_debug && !QMGR_ERROR_OR_RETRY_QUEUE(queue)) \ + msg_info("%s: queue %s: limit %d window %d success %g failure %g fail_cohorts %g", \ + myname, queue->name, queue->transport->dest_concurrency_limit, \ + queue->window, queue->success, queue->failure, queue->fail_cohorts); + +/* qmgr_queue_resume - resume delivery to destination */ + +static void qmgr_queue_resume(int event, void *context) +{ + QMGR_QUEUE *queue = (QMGR_QUEUE *) context; + const char *myname = "qmgr_queue_resume"; + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_SUSPENDED(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + + /* + * We can't simply force delivery on this queue: the transport's pending + * count may already be maxed out, and there may be other constraints + * that definitely should be none of our business. The best we can do is + * to play by the same rules as everyone else: let qmgr_active_drain() + * and round-robin selection take care of message selection. + */ + queue->window = 1; + + /* + * Every event handler that leaves a queue in the "ready" state should + * remove the queue when it is empty. + * + * XXX Do not omit the redundant test below. It is here to simplify code + * consistency checks. The check is trivially eliminated by the compiler + * optimizer. There is no need to sacrifice code clarity for the sake of + * performance. + * + * XXX Do not expose the blocker job logic here. Rate-limited queues are not + * a performance-critical feature. Here, too, there is no need to + * sacrifice code clarity for the sake of performance. + */ + if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0) + qmgr_queue_done(queue); + else + qmgr_job_blocker_update(queue); +} + +/* qmgr_queue_suspend - briefly suspend a destination */ + +void qmgr_queue_suspend(QMGR_QUEUE *queue, int delay) +{ + const char *myname = "qmgr_queue_suspend"; + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + if (queue->busy_refcount > 0) + msg_panic("%s: queue is busy", myname); + + /* + * Set the queue status to "suspended". No-one is supposed to remove a + * queue in suspended state. + */ + queue->window = QMGR_QUEUE_STAT_SUSPENDED; + event_request_timer(qmgr_queue_resume, (void *) queue, delay); +} + +/* qmgr_queue_unthrottle_wrapper - in case (char *) != (struct *) */ + +static void qmgr_queue_unthrottle_wrapper(int unused_event, void *context) +{ + QMGR_QUEUE *queue = (QMGR_QUEUE *) context; + + /* + * This routine runs when a wakeup timer goes off; it does not run in the + * context of some queue manipulation. Therefore, it is safe to discard + * this in-core queue when it is empty and when this site is not dead. + */ + qmgr_queue_unthrottle(queue); + if (QMGR_QUEUE_READY(queue) && queue->todo.next == 0 && queue->busy.next == 0) + qmgr_queue_done(queue); +} + +/* qmgr_queue_unthrottle - give this destination another chance */ + +void qmgr_queue_unthrottle(QMGR_QUEUE *queue) +{ + const char *myname = "qmgr_queue_unthrottle"; + QMGR_TRANSPORT *transport = queue->transport; + double feedback; + + if (msg_verbose) + msg_info("%s: queue %s", myname, queue->name); + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_READY(queue) && !QMGR_QUEUE_THROTTLED(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + + /* + * Don't restart the negative feedback hysteresis cycle with every + * positive feedback. Restart it only when we make a positive concurrency + * adjustment (i.e. at the end of a positive feedback hysteresis cycle). + * Otherwise negative feedback would be too aggressive: negative feedback + * takes effect immediately at the start of its hysteresis cycle. + */ + queue->fail_cohorts = 0; + + /* + * Special case when this site was dead. + */ + if (QMGR_QUEUE_THROTTLED(queue)) { + event_cancel_timer(qmgr_queue_unthrottle_wrapper, (void *) queue); + if (queue->dsn == 0) + msg_panic("%s: queue %s: window 0 status 0", myname, queue->name); + dsn_free(queue->dsn); + queue->dsn = 0; + /* Back from the almost grave, best concurrency is anyone's guess. */ + if (queue->busy_refcount > 0) + queue->window = queue->busy_refcount; + else + queue->window = transport->init_dest_concurrency; + queue->success = queue->failure = 0; + QMGR_LOG_WINDOW(queue); + return; + } + + /* + * Increase the destination's concurrency limit until we reach the + * transport's concurrency limit. Allow for a margin the size of the + * initial destination concurrency, so that we're not too gentle. + * + * Why is the concurrency increment based on preferred concurrency and not + * on the number of outstanding delivery requests? The latter fluctuates + * wildly when deliveries complete in bursts (artificial benchmark + * measurements), and does not account for cached connections. + * + * Keep the window within reasonable distance from actual concurrency + * otherwise negative feedback will be ineffective. This expression + * assumes that busy_refcount changes gradually. This is invalid when + * deliveries complete in bursts (artificial benchmark measurements). + */ + if (transport->dest_concurrency_limit == 0 + || transport->dest_concurrency_limit > queue->window) + if (queue->window < queue->busy_refcount + transport->init_dest_concurrency) { + feedback = QMGR_FEEDBACK_VAL(transport->pos_feedback, queue->window); + QMGR_LOG_FEEDBACK(feedback); + queue->success += feedback; + /* Prepare for overshoot (feedback > hysteresis, rounding error). */ + while (queue->success + feedback / 2 >= transport->pos_feedback.hysteresis) { + queue->window += transport->pos_feedback.hysteresis; + queue->success -= transport->pos_feedback.hysteresis; + queue->failure = 0; + } + /* Prepare for overshoot. */ + if (transport->dest_concurrency_limit > 0 + && queue->window > transport->dest_concurrency_limit) + queue->window = transport->dest_concurrency_limit; + } + QMGR_LOG_WINDOW(queue); +} + +/* qmgr_queue_throttle - handle destination delivery failure */ + +void qmgr_queue_throttle(QMGR_QUEUE *queue, DSN *dsn) +{ + const char *myname = "qmgr_queue_throttle"; + QMGR_TRANSPORT *transport = queue->transport; + double feedback; + + /* + * Sanity checks. + */ + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + if (queue->dsn) + msg_panic("%s: queue %s: spurious reason %s", + myname, queue->name, queue->dsn->reason); + if (msg_verbose) + msg_info("%s: queue %s: %s %s", + myname, queue->name, dsn->status, dsn->reason); + + /* + * Don't restart the positive feedback hysteresis cycle with every + * negative feedback. Restart it only when we make a negative concurrency + * adjustment (i.e. at the start of a negative feedback hysteresis + * cycle). Otherwise positive feedback would be too weak (positive + * feedback does not take effect until the end of its hysteresis cycle). + */ + + /* + * This queue is declared dead after a configurable number of + * pseudo-cohort failures. + */ + if (QMGR_QUEUE_READY(queue)) { + queue->fail_cohorts += 1.0 / queue->window; + if (transport->fail_cohort_limit > 0 + && queue->fail_cohorts >= transport->fail_cohort_limit) + queue->window = QMGR_QUEUE_STAT_THROTTLED; + } + + /* + * Decrease the destination's concurrency limit until we reach 1. Base + * adjustments on the concurrency limit itself, instead of using the + * actual concurrency. The latter fluctuates wildly when deliveries + * complete in bursts (artificial benchmark measurements). + * + * Even after reaching 1, we maintain the negative hysteresis cycle so that + * negative feedback can cancel out positive feedback. + */ + if (QMGR_QUEUE_READY(queue)) { + feedback = QMGR_FEEDBACK_VAL(transport->neg_feedback, queue->window); + QMGR_LOG_FEEDBACK(feedback); + queue->failure -= feedback; + /* Prepare for overshoot (feedback > hysteresis, rounding error). */ + while (queue->failure - feedback / 2 < 0) { + queue->window -= transport->neg_feedback.hysteresis; + queue->success = 0; + queue->failure += transport->neg_feedback.hysteresis; + } + /* Prepare for overshoot. */ + if (queue->window < 1) + queue->window = 1; + } + + /* + * Special case for a site that just was declared dead. + */ + if (QMGR_QUEUE_THROTTLED(queue)) { + queue->dsn = DSN_COPY(dsn); + event_request_timer(qmgr_queue_unthrottle_wrapper, + (void *) queue, var_min_backoff_time); + queue->dflags = 0; + } + QMGR_LOG_WINDOW(queue); +} + +/* qmgr_queue_done - delete in-core queue for site */ + +void qmgr_queue_done(QMGR_QUEUE *queue) +{ + const char *myname = "qmgr_queue_done"; + QMGR_TRANSPORT *transport = queue->transport; + + /* + * Sanity checks. It is an error to delete an in-core queue with pending + * messages or timers. + */ + if (queue->busy_refcount != 0 || queue->todo_refcount != 0) + msg_panic("%s: refcount: %d", myname, + queue->busy_refcount + queue->todo_refcount); + if (queue->todo.next || queue->busy.next) + msg_panic("%s: queue not empty: %s", myname, queue->name); + if (!QMGR_QUEUE_READY(queue)) + msg_panic("%s: bad queue status: %s", myname, QMGR_QUEUE_STATUS(queue)); + if (queue->dsn) + msg_panic("%s: queue %s: spurious reason %s", + myname, queue->name, queue->dsn->reason); + + /* + * Clean up this in-core queue. + */ + QMGR_LIST_UNLINK(transport->queue_list, QMGR_QUEUE *, queue, peers); + htable_delete(transport->queue_byname, queue->name, (void (*) (void *)) 0); + myfree(queue->name); + myfree(queue->nexthop); + qmgr_queue_count--; + myfree((void *) queue); +} + +/* qmgr_queue_create - create in-core queue for site */ + +QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *name, + const char *nexthop) +{ + QMGR_QUEUE *queue; + + /* + * If possible, choose an initial concurrency of > 1 so that one bad + * message or one bad network won't slow us down unnecessarily. + */ + + queue = (QMGR_QUEUE *) mymalloc(sizeof(QMGR_QUEUE)); + qmgr_queue_count++; + queue->dflags = 0; + queue->last_done = 0; + queue->name = mystrdup(name); + queue->nexthop = mystrdup(nexthop); + queue->todo_refcount = 0; + queue->busy_refcount = 0; + queue->transport = transport; + queue->window = transport->init_dest_concurrency; + queue->success = queue->failure = queue->fail_cohorts = 0; + QMGR_LIST_INIT(queue->todo); + QMGR_LIST_INIT(queue->busy); + queue->dsn = 0; + queue->clog_time_to_warn = 0; + queue->blocker_tag = 0; + QMGR_LIST_APPEND(transport->queue_list, queue, peers); + htable_enter(transport->queue_byname, name, (void *) queue); + return (queue); +} + +/* qmgr_queue_find - find in-core named queue */ + +QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *transport, const char *name) +{ + return ((QMGR_QUEUE *) htable_find(transport->queue_byname, name)); +} diff --git a/src/qmgr/qmgr_scan.c b/src/qmgr/qmgr_scan.c new file mode 100644 index 0000000..0665a23 --- /dev/null +++ b/src/qmgr/qmgr_scan.c @@ -0,0 +1,185 @@ +/*++ +/* NAME +/* qmgr_scan 3 +/* SUMMARY +/* queue scanning +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_SCAN *qmgr_scan_create(queue_name) +/* const char *queue_name; +/* +/* char *qmgr_scan_next(scan_info) +/* QMGR_SCAN *scan_info; +/* +/* void qmgr_scan_request(scan_info, flags) +/* QMGR_SCAN *scan_info; +/* int flags; +/* DESCRIPTION +/* This module implements queue scans. A queue scan always runs +/* to completion, so that all files get a fair chance. The caller +/* can request that a queue scan be restarted once it completes. +/* +/* qmgr_scan_create() creates a context for scanning the named queue, +/* but does not start a queue scan. +/* +/* qmgr_scan_next() returns the base name of the next queue file. +/* A null pointer means that no file was found. qmgr_scan_next() +/* automagically restarts a queue scan when a scan request had +/* arrived while the scan was in progress. +/* +/* qmgr_scan_request() records a request for the next queue scan. The +/* flags argument is the bit-wise OR of zero or more of the following, +/* unrecognized flags being ignored: +/* .IP QMGR_FLUSH_ONCE +/* Forget state information about dead hosts or transports. +/* This request takes effect immediately. +/* .IP QMGR_FLUSH_DFXP +/* Override the defer_transports setting. This takes effect +/* immediately when a queue scan is in progress, and affects +/* the next queue scan. +/* .IP QMGR_SCAN_ALL +/* Ignore queue file time stamps. This takes effect immediately +/* when a queue scan is in progress, and affects the next queue +/* scan. +/* .IP QMGR_SCAN_START +/* Start a queue scan when none is in progress, or restart the +/* current scan upon completion. +/* DIAGNOSTICS +/* Fatal: out of memory. +/* Panic: interface violations, internal consistency errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_scan_start - start queue scan */ + +static void qmgr_scan_start(QMGR_SCAN *scan_info) +{ + const char *myname = "qmgr_scan_start"; + + /* + * Sanity check. + */ + if (scan_info->handle) + msg_panic("%s: %s queue scan in progress", + myname, scan_info->queue); + + /* + * Give the poor tester a clue. + */ + if (msg_verbose) + msg_info("%s: %sstart %s queue scan", + myname, + scan_info->nflags & QMGR_SCAN_START ? "re" : "", + scan_info->queue); + + /* + * Start or restart the scan. + */ + scan_info->flags = scan_info->nflags; + scan_info->nflags = 0; + scan_info->handle = scan_dir_open(scan_info->queue); +} + +/* qmgr_scan_request - request for future scan */ + +void qmgr_scan_request(QMGR_SCAN *scan_info, int flags) +{ + + /* + * Apply "forget all dead destinations" requests immediately. Throttle + * dead transports and queues at the earliest opportunity: preferably + * during an already ongoing queue scan, otherwise the throttling will + * have to wait until a "start scan" trigger arrives. + * + * The QMGR_FLUSH_ONCE request always comes with QMGR_FLUSH_DFXP, and + * sometimes it also comes with QMGR_SCAN_ALL. It becomes a completely + * different story when a flush request is encoded in file permissions. + */ + if (flags & QMGR_FLUSH_ONCE) + qmgr_enable_all(); + + /* + * Apply "ignore time stamp" requests also towards the scan that is + * already in progress. + */ + if (scan_info->handle != 0 && (flags & QMGR_SCAN_ALL)) + scan_info->flags |= QMGR_SCAN_ALL; + + /* + * Apply "override defer_transports" requests also towards the scan that + * is already in progress. + */ + if (scan_info->handle != 0 && (flags & QMGR_FLUSH_DFXP)) + scan_info->flags |= QMGR_FLUSH_DFXP; + + /* + * If a scan is in progress, just record the request. + */ + scan_info->nflags |= flags; + if (scan_info->handle == 0 && (flags & QMGR_SCAN_START) != 0) { + scan_info->nflags &= ~QMGR_SCAN_START; + qmgr_scan_start(scan_info); + } +} + +/* qmgr_scan_next - look for next queue file */ + +char *qmgr_scan_next(QMGR_SCAN *scan_info) +{ + char *path = 0; + + /* + * Restart the scan if we reach the end and a queue scan request has + * arrived in the mean time. + */ + if (scan_info->handle && (path = mail_scan_dir_next(scan_info->handle)) == 0) { + scan_info->handle = scan_dir_close(scan_info->handle); + if (msg_verbose && (scan_info->nflags & QMGR_SCAN_START) == 0) + msg_info("done %s queue scan", scan_info->queue); + } + if (!scan_info->handle && (scan_info->nflags & QMGR_SCAN_START)) { + qmgr_scan_start(scan_info); + path = mail_scan_dir_next(scan_info->handle); + } + return (path); +} + +/* qmgr_scan_create - create queue scan context */ + +QMGR_SCAN *qmgr_scan_create(const char *queue) +{ + QMGR_SCAN *scan_info; + + scan_info = (QMGR_SCAN *) mymalloc(sizeof(*scan_info)); + scan_info->queue = mystrdup(queue); + scan_info->flags = scan_info->nflags = 0; + scan_info->handle = 0; + return (scan_info); +} diff --git a/src/qmgr/qmgr_transport.c b/src/qmgr/qmgr_transport.c new file mode 100644 index 0000000..fe99806 --- /dev/null +++ b/src/qmgr/qmgr_transport.c @@ -0,0 +1,504 @@ +/*++ +/* NAME +/* qmgr_transport 3 +/* SUMMARY +/* per-transport data structures +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_TRANSPORT *qmgr_transport_create(name) +/* const char *name; +/* +/* QMGR_TRANSPORT *qmgr_transport_find(name) +/* const char *name; +/* +/* QMGR_TRANSPORT *qmgr_transport_select() +/* +/* void qmgr_transport_alloc(transport, notify) +/* QMGR_TRANSPORT *transport; +/* void (*notify)(QMGR_TRANSPORT *transport, VSTREAM *fp); +/* +/* void qmgr_transport_throttle(transport, dsn) +/* QMGR_TRANSPORT *transport; +/* DSN *dsn; +/* +/* void qmgr_transport_unthrottle(transport) +/* QMGR_TRANSPORT *transport; +/* DESCRIPTION +/* This module organizes the world by message transport type. +/* Each transport can have zero or more destination queues +/* associated with it. +/* +/* qmgr_transport_create() instantiates a data structure for the +/* named transport type. +/* +/* qmgr_transport_find() looks up an existing message transport +/* data structure. +/* +/* qmgr_transport_select() attempts to find a transport that +/* has messages pending delivery. This routine implements +/* round-robin search among transports. +/* +/* qmgr_transport_alloc() allocates a delivery process for the +/* specified transport type. Allocation is performed asynchronously. +/* When a process becomes available, the application callback routine +/* is invoked with as arguments the transport and a stream that +/* is connected to a delivery process. It is an error to call +/* qmgr_transport_alloc() while delivery process allocation for +/* the same transport is in progress. +/* +/* qmgr_transport_throttle blocks further allocation of delivery +/* processes for the named transport. Attempts to throttle a +/* throttled transport are ignored. +/* +/* qmgr_transport_unthrottle() undoes qmgr_transport_throttle(). +/* Attempts to unthrottle a non-throttled transport are ignored. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Preemptive scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +#include /* FD_SETSIZE */ +#include /* FD_SETSIZE */ +#include /* FD_SETSIZE */ + +#ifdef USE_SYS_SELECT_H +#include /* FD_SETSIZE */ +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include "qmgr.h" + +HTABLE *qmgr_transport_byname; /* transport by name */ +QMGR_TRANSPORT_LIST qmgr_transport_list;/* transports, round robin */ + + /* + * A local structure to remember a delivery process allocation request. + */ +typedef struct QMGR_TRANSPORT_ALLOC QMGR_TRANSPORT_ALLOC; + +struct QMGR_TRANSPORT_ALLOC { + QMGR_TRANSPORT *transport; /* transport context */ + VSTREAM *stream; /* delivery service stream */ + QMGR_TRANSPORT_ALLOC_NOTIFY notify; /* application call-back routine */ +}; + + /* + * Connections to delivery agents are managed asynchronously. Each delivery + * agent connection goes through multiple wait states: + * + * - With Linux/Solaris and old queue manager implementations only, wait for + * the server to invoke accept(). + * + * - Wait for the delivery agent's announcement that it is ready to receive a + * delivery request. + * + * - Wait for the delivery request completion status. + * + * Older queue manager implementations had only one pending delivery agent + * connection per transport. With low-latency destinations, the output rates + * were reduced on Linux/Solaris systems that had the extra wait state. + * + * To maximize delivery agent output rates with low-latency destinations, the + * following changes were made to the queue manager by the end of the 2.4 + * development cycle: + * + * - The Linux/Solaris accept() wait state was eliminated. + * + * - A pipeline was implemented for pending delivery agent connections. The + * number of pending delivery agent connections was increased from one to + * two: the number of before-delivery wait states, plus one extra pipeline + * slot to prevent the pipeline from stalling easily. Increasing the + * pipeline much further actually hurt performance. + * + * - To reduce queue manager disk competition with delivery agents, the queue + * scanning algorithm was modified to import only one message per interrupt. + * The incoming and deferred queue scans now happen on alternate interrupts. + * + * Simplistically reasoned, a non-zero (incoming + active) queue length is + * equivalent to a time shift for mail deliveries; this is undesirable when + * delivery agents are not fully utilized. + * + * On the other hand a non-empty active queue is what allows us to do clever + * things such as queue file prefetch, concurrency windows, and connection + * caching; the idea is that such "thinking time" is affordable only after + * the output channels are maxed out. + */ +#ifndef QMGR_TRANSPORT_MAX_PEND +#define QMGR_TRANSPORT_MAX_PEND 2 +#endif + + /* + * Important note on the _transport_rate_delay implementation: after + * qmgr_transport_alloc() sets the QMGR_TRANSPORT_STAT_RATE_LOCK flag, all + * code paths must directly or indirectly invoke qmgr_transport_unthrottle() + * or qmgr_transport_throttle(). Otherwise, transports with non-zero + * _transport_rate_delay will become stuck. + */ + +/* qmgr_transport_unthrottle_wrapper - in case (char *) != (struct *) */ + +static void qmgr_transport_unthrottle_wrapper(int unused_event, void *context) +{ + qmgr_transport_unthrottle((QMGR_TRANSPORT *) context); +} + +/* qmgr_transport_unthrottle - open the throttle */ + +void qmgr_transport_unthrottle(QMGR_TRANSPORT *transport) +{ + const char *myname = "qmgr_transport_unthrottle"; + + /* + * This routine runs after expiration of the timer set by + * qmgr_transport_throttle(), or whenever a delivery transport has been + * used without malfunction. In either case, we enable delivery again if + * the transport was throttled. We always reset the transport rate lock. + */ + if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0) { + if (msg_verbose) + msg_info("%s: transport %s", myname, transport->name); + transport->flags &= ~QMGR_TRANSPORT_STAT_DEAD; + if (transport->dsn == 0) + msg_panic("%s: transport %s: null reason", + myname, transport->name); + dsn_free(transport->dsn); + transport->dsn = 0; + event_cancel_timer(qmgr_transport_unthrottle_wrapper, + (void *) transport); + } + if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) + transport->flags &= ~QMGR_TRANSPORT_STAT_RATE_LOCK; +} + +/* qmgr_transport_throttle - disable delivery process allocation */ + +void qmgr_transport_throttle(QMGR_TRANSPORT *transport, DSN *dsn) +{ + const char *myname = "qmgr_transport_throttle"; + + /* + * We are unable to connect to a deliver process for this type of message + * transport. Instead of hosing the system by retrying in a tight loop, + * back off and disable this transport type for a while. + */ + if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) == 0) { + if (msg_verbose) + msg_info("%s: transport %s: status: %s reason: %s", + myname, transport->name, dsn->status, dsn->reason); + transport->flags |= QMGR_TRANSPORT_STAT_DEAD; + if (transport->dsn) + msg_panic("%s: transport %s: spurious reason: %s", + myname, transport->name, transport->dsn->reason); + transport->dsn = DSN_COPY(dsn); + event_request_timer(qmgr_transport_unthrottle_wrapper, + (void *) transport, var_transport_retry_time); + } +} + +/* qmgr_transport_abort - transport connect watchdog */ + +static void qmgr_transport_abort(int unused_event, void *context) +{ + QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context; + + msg_fatal("timeout connecting to transport: %s", alloc->transport->name); +} + +/* qmgr_transport_rate_event - delivery process availability notice */ + +static void qmgr_transport_rate_event(int unused_event, void *context) +{ + QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context; + + alloc->notify(alloc->transport, alloc->stream); + myfree((void *) alloc); +} + +/* qmgr_transport_event - delivery process availability notice */ + +static void qmgr_transport_event(int unused_event, void *context) +{ + QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context; + + /* + * This routine notifies the application when the request given to + * qmgr_transport_alloc() completes. + */ + if (msg_verbose) + msg_info("transport_event: %s", alloc->transport->name); + + /* + * Connection request completed. Stop the watchdog timer. + */ + event_cancel_timer(qmgr_transport_abort, context); + + /* + * Disable further read events that end up calling this function, and + * free up this pending connection pipeline slot. + */ + if (alloc->stream) { + event_disable_readwrite(vstream_fileno(alloc->stream)); + non_blocking(vstream_fileno(alloc->stream), BLOCKING); + } + alloc->transport->pending -= 1; + + /* + * Notify the requestor. + */ + if (alloc->transport->xport_rate_delay > 0) { + if ((alloc->transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) == 0) + msg_panic("transport_event: missing rate lock for transport %s", + alloc->transport->name); + event_request_timer(qmgr_transport_rate_event, (void *) alloc, + alloc->transport->xport_rate_delay); + } else { + alloc->notify(alloc->transport, alloc->stream); + myfree((void *) alloc); + } +} + +/* qmgr_transport_select - select transport for allocation */ + +QMGR_TRANSPORT *qmgr_transport_select(void) +{ + QMGR_TRANSPORT *xport; + QMGR_QUEUE *queue; + int need; + + /* + * If we find a suitable transport, rotate the list of transports to + * effectuate round-robin selection. See similar selection code in + * qmgr_peer_select(). + * + * This function is called repeatedly until all transports have maxed out + * the number of pending delivery agent connections, until all delivery + * agent concurrency windows are maxed out, or until we run out of "todo" + * queue entries. + */ +#define MIN5af51743e4eef(x, y) ((x) < (y) ? (x) : (y)) + + for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next) { + if ((xport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0 + || (xport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) != 0 + || xport->pending >= QMGR_TRANSPORT_MAX_PEND) + continue; + need = xport->pending + 1; + for (queue = xport->queue_list.next; queue; queue = queue->peers.next) { + if (QMGR_QUEUE_READY(queue) == 0) + continue; + if ((need -= MIN5af51743e4eef(queue->window - queue->busy_refcount, + queue->todo_refcount)) <= 0) { + QMGR_LIST_ROTATE(qmgr_transport_list, xport, peers); + if (msg_verbose) + msg_info("qmgr_transport_select: %s", xport->name); + return (xport); + } + } + } + return (0); +} + +/* qmgr_transport_alloc - allocate delivery process */ + +void qmgr_transport_alloc(QMGR_TRANSPORT *transport, QMGR_TRANSPORT_ALLOC_NOTIFY notify) +{ + QMGR_TRANSPORT_ALLOC *alloc; + + /* + * Sanity checks. + */ + if (transport->flags & QMGR_TRANSPORT_STAT_DEAD) + msg_panic("qmgr_transport: dead transport: %s", transport->name); + if (transport->flags & QMGR_TRANSPORT_STAT_RATE_LOCK) + msg_panic("qmgr_transport: rate-locked transport: %s", transport->name); + if (transport->pending >= QMGR_TRANSPORT_MAX_PEND) + msg_panic("qmgr_transport: excess allocation: %s", transport->name); + + /* + * When this message delivery transport is rate-limited, do not select it + * again before the end of a message delivery transaction. + */ + if (transport->xport_rate_delay > 0) + transport->flags |= QMGR_TRANSPORT_STAT_RATE_LOCK; + + /* + * Connect to the well-known port for this delivery service, and wake up + * when a process announces its availability. Allow only a limited number + * of delivery process allocation attempts for this transport. In case of + * problems, back off. Do not hose the system when it is in trouble + * already. + * + * Use non-blocking connect(), so that Linux won't block the queue manager + * until the delivery agent calls accept(). + * + * When the connection to delivery agent cannot be completed, notify the + * event handler so that it can throttle the transport and defer the todo + * queues, just like it does when communication fails *after* connection + * completion. + * + * Before Postfix 2.4, the event handler was not invoked after connect() + * error, and mail was not deferred. Because of this, mail would be stuck + * in the active queue after triggering a "connection refused" condition. + */ + alloc = (QMGR_TRANSPORT_ALLOC *) mymalloc(sizeof(*alloc)); + alloc->transport = transport; + alloc->notify = notify; + transport->pending += 1; + if ((alloc->stream = mail_connect(MAIL_CLASS_PRIVATE, transport->name, + NON_BLOCKING)) == 0) { + msg_warn("connect to transport %s/%s: %m", + MAIL_CLASS_PRIVATE, transport->name); + event_request_timer(qmgr_transport_event, (void *) alloc, 0); + return; + } +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) && defined(CA_VSTREAM_CTL_DUPFD) +#ifndef THRESHOLD_FD_WORKAROUND +#define THRESHOLD_FD_WORKAROUND 128 +#endif + vstream_control(alloc->stream, + CA_VSTREAM_CTL_DUPFD(THRESHOLD_FD_WORKAROUND), + CA_VSTREAM_CTL_END); +#endif + event_enable_read(vstream_fileno(alloc->stream), qmgr_transport_event, + (void *) alloc); + + /* + * Guard against broken systems. + */ + event_request_timer(qmgr_transport_abort, (void *) alloc, + var_daemon_timeout); +} + +/* qmgr_transport_create - create transport instance */ + +QMGR_TRANSPORT *qmgr_transport_create(const char *name) +{ + QMGR_TRANSPORT *transport; + + if (htable_find(qmgr_transport_byname, name) != 0) + msg_panic("qmgr_transport_create: transport exists: %s", name); + transport = (QMGR_TRANSPORT *) mymalloc(sizeof(QMGR_TRANSPORT)); + transport->flags = 0; + transport->pending = 0; + transport->name = mystrdup(name); + + /* + * Use global configuration settings or transport-specific settings. + */ + transport->dest_concurrency_limit = + get_mail_conf_int2(name, _DEST_CON_LIMIT, + var_dest_con_limit, 0, 0); + transport->recipient_limit = + get_mail_conf_int2(name, _DEST_RCPT_LIMIT, + var_dest_rcpt_limit, 0, 0); + transport->init_dest_concurrency = + get_mail_conf_int2(name, _INIT_DEST_CON, + var_init_dest_concurrency, 1, 0); + transport->xport_rate_delay = get_mail_conf_time2(name, _XPORT_RATE_DELAY, + var_xport_rate_delay, + 's', 0, 0); + transport->rate_delay = get_mail_conf_time2(name, _DEST_RATE_DELAY, + var_dest_rate_delay, + 's', 0, 0); + + if (transport->rate_delay > 0) + transport->dest_concurrency_limit = 1; + if (transport->dest_concurrency_limit != 0 + && transport->dest_concurrency_limit < transport->init_dest_concurrency) + transport->init_dest_concurrency = transport->dest_concurrency_limit; + + transport->slot_cost = get_mail_conf_int2(name, _DELIVERY_SLOT_COST, + var_delivery_slot_cost, 0, 0); + transport->slot_loan = get_mail_conf_int2(name, _DELIVERY_SLOT_LOAN, + var_delivery_slot_loan, 0, 0); + transport->slot_loan_factor = + 100 - get_mail_conf_int2(name, _DELIVERY_SLOT_DISCOUNT, + var_delivery_slot_discount, 0, 100); + transport->min_slots = get_mail_conf_int2(name, _MIN_DELIVERY_SLOTS, + var_min_delivery_slots, 0, 0); + transport->rcpt_unused = get_mail_conf_int2(name, _XPORT_RCPT_LIMIT, + var_xport_rcpt_limit, 0, 0); + transport->rcpt_per_stack = get_mail_conf_int2(name, _STACK_RCPT_LIMIT, + var_stack_rcpt_limit, 0, 0); + transport->refill_limit = get_mail_conf_int2(name, _XPORT_REFILL_LIMIT, + var_xport_refill_limit, 1, 0); + transport->refill_delay = get_mail_conf_time2(name, _XPORT_REFILL_DELAY, + var_xport_refill_delay, 's', 1, 0); + + transport->queue_byname = htable_create(0); + QMGR_LIST_INIT(transport->queue_list); + transport->job_byname = htable_create(0); + QMGR_LIST_INIT(transport->job_list); + QMGR_LIST_INIT(transport->job_bytime); + transport->job_current = 0; + transport->job_next_unread = 0; + transport->candidate_cache = 0; + transport->candidate_cache_current = 0; + transport->candidate_cache_time = (time_t) 0; + transport->blocker_tag = 1; + transport->dsn = 0; + qmgr_feedback_init(&transport->pos_feedback, name, _CONC_POS_FDBACK, + VAR_CONC_POS_FDBACK, var_conc_pos_feedback); + qmgr_feedback_init(&transport->neg_feedback, name, _CONC_NEG_FDBACK, + VAR_CONC_NEG_FDBACK, var_conc_neg_feedback); + transport->fail_cohort_limit = + get_mail_conf_int2(name, _CONC_COHORT_LIM, + var_conc_cohort_limit, 0, 0); + if (qmgr_transport_byname == 0) + qmgr_transport_byname = htable_create(10); + htable_enter(qmgr_transport_byname, name, (void *) transport); + QMGR_LIST_PREPEND(qmgr_transport_list, transport, peers); + if (msg_verbose) + msg_info("qmgr_transport_create: %s concurrency %d recipients %d", + transport->name, transport->dest_concurrency_limit, + transport->recipient_limit); + return (transport); +} + +/* qmgr_transport_find - find transport instance */ + +QMGR_TRANSPORT *qmgr_transport_find(const char *name) +{ + return ((QMGR_TRANSPORT *) htable_find(qmgr_transport_byname, name)); +} diff --git a/src/qmqpd/.indent.pro b/src/qmqpd/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/qmqpd/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/qmqpd/.printfck b/src/qmqpd/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/qmqpd/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/qmqpd/Makefile.in b/src/qmqpd/Makefile.in new file mode 100644 index 0000000..d4cdf33 --- /dev/null +++ b/src/qmqpd/Makefile.in @@ -0,0 +1,139 @@ +SHELL = /bin/sh +SRCS = qmqpd.c qmqpd_state.c qmqpd_peer.c +OBJS = qmqpd.o qmqpd_state.o qmqpd_peer.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = qmqpd +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk *.db *.out *.tmp + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +qmqpd.o: ../../include/argv.h +qmqpd.o: ../../include/attr.h +qmqpd.o: ../../include/check_arg.h +qmqpd.o: ../../include/cleanup_user.h +qmqpd.o: ../../include/debug_peer.h +qmqpd.o: ../../include/dict.h +qmqpd.o: ../../include/htable.h +qmqpd.o: ../../include/inet_proto.h +qmqpd.o: ../../include/input_transp.h +qmqpd.o: ../../include/iostuff.h +qmqpd.o: ../../include/lex_822.h +qmqpd.o: ../../include/mail_conf.h +qmqpd.o: ../../include/mail_date.h +qmqpd.o: ../../include/mail_params.h +qmqpd.o: ../../include/mail_proto.h +qmqpd.o: ../../include/mail_server.h +qmqpd.o: ../../include/mail_stream.h +qmqpd.o: ../../include/mail_version.h +qmqpd.o: ../../include/match_list.h +qmqpd.o: ../../include/match_parent_style.h +qmqpd.o: ../../include/msg.h +qmqpd.o: ../../include/myflock.h +qmqpd.o: ../../include/mymalloc.h +qmqpd.o: ../../include/namadr_list.h +qmqpd.o: ../../include/netstring.h +qmqpd.o: ../../include/nvtable.h +qmqpd.o: ../../include/quote_822_local.h +qmqpd.o: ../../include/quote_flags.h +qmqpd.o: ../../include/rec_type.h +qmqpd.o: ../../include/recipient_list.h +qmqpd.o: ../../include/record.h +qmqpd.o: ../../include/smtputf8.h +qmqpd.o: ../../include/sys_defs.h +qmqpd.o: ../../include/vbuf.h +qmqpd.o: ../../include/verp_sender.h +qmqpd.o: ../../include/vstream.h +qmqpd.o: ../../include/vstring.h +qmqpd.o: qmqpd.c +qmqpd.o: qmqpd.h +qmqpd_peer.o: ../../include/attr.h +qmqpd_peer.o: ../../include/check_arg.h +qmqpd_peer.o: ../../include/htable.h +qmqpd_peer.o: ../../include/inet_proto.h +qmqpd_peer.o: ../../include/iostuff.h +qmqpd_peer.o: ../../include/mail_params.h +qmqpd_peer.o: ../../include/mail_proto.h +qmqpd_peer.o: ../../include/mail_stream.h +qmqpd_peer.o: ../../include/msg.h +qmqpd_peer.o: ../../include/myaddrinfo.h +qmqpd_peer.o: ../../include/mymalloc.h +qmqpd_peer.o: ../../include/nvtable.h +qmqpd_peer.o: ../../include/sock_addr.h +qmqpd_peer.o: ../../include/split_at.h +qmqpd_peer.o: ../../include/stringops.h +qmqpd_peer.o: ../../include/sys_defs.h +qmqpd_peer.o: ../../include/valid_hostname.h +qmqpd_peer.o: ../../include/valid_mailhost_addr.h +qmqpd_peer.o: ../../include/vbuf.h +qmqpd_peer.o: ../../include/vstream.h +qmqpd_peer.o: ../../include/vstring.h +qmqpd_peer.o: qmqpd.h +qmqpd_peer.o: qmqpd_peer.c +qmqpd_state.o: ../../include/attr.h +qmqpd_state.o: ../../include/check_arg.h +qmqpd_state.o: ../../include/cleanup_user.h +qmqpd_state.o: ../../include/htable.h +qmqpd_state.o: ../../include/iostuff.h +qmqpd_state.o: ../../include/mail_proto.h +qmqpd_state.o: ../../include/mail_stream.h +qmqpd_state.o: ../../include/mymalloc.h +qmqpd_state.o: ../../include/nvtable.h +qmqpd_state.o: ../../include/sys_defs.h +qmqpd_state.o: ../../include/vbuf.h +qmqpd_state.o: ../../include/vstream.h +qmqpd_state.o: ../../include/vstring.h +qmqpd_state.o: qmqpd.h +qmqpd_state.o: qmqpd_state.c diff --git a/src/qmqpd/qmqpd.c b/src/qmqpd/qmqpd.c new file mode 100644 index 0000000..d94d33d --- /dev/null +++ b/src/qmqpd/qmqpd.c @@ -0,0 +1,865 @@ +/*++ +/* NAME +/* qmqpd 8 +/* SUMMARY +/* Postfix QMQP server +/* SYNOPSIS +/* \fBqmqpd\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The Postfix QMQP server receives one message per connection. +/* Each message is piped through the \fBcleanup\fR(8) +/* daemon, and is placed into the \fBincoming\fR queue as one +/* single queue file. The program expects to be run from the +/* \fBmaster\fR(8) process manager. +/* +/* The QMQP server implements one access policy: only explicitly +/* authorized client hosts are allowed to use the service. +/* SECURITY +/* .ad +/* .fi +/* The QMQP server is moderately security-sensitive. It talks to QMQP +/* clients and to DNS servers on the network. The QMQP server can be +/* run chrooted at fixed low privilege. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* The QMQP protocol provides only one server reply per message +/* delivery. It is therefore not possible to reject individual +/* recipients. +/* +/* The QMQP protocol requires the server to receive the entire +/* message before replying. If a message is malformed, or if any +/* netstring component is longer than acceptable, Postfix replies +/* immediately and closes the connection. It is left up to the +/* client to handle the situation. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically, as \fBqmqpd\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* .IP "\fBcontent_filter (empty)\fR" +/* After the message is queued, send the entire message to the +/* specified \fItransport:destination\fR. +/* .IP "\fBreceive_override_options (empty)\fR" +/* Enable or disable recipient validation, built-in content +/* filtering, or address mapping. +/* SMTPUTF8 CONTROLS +/* .ad +/* .fi +/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable preliminary SMTPUTF8 support for the protocols described +/* in RFC 6531..6533. +/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR" +/* Detect that a message requires SMTPUTF8 support for the specified +/* mail origin classes. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBenable_idna2003_compatibility (no)\fR" +/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008, +/* when converting UTF-8 domain names to/from the ASCII form that is +/* used for DNS lookups. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBline_length_limit (2048)\fR" +/* Upon input, long lines are chopped up into pieces of at most +/* this length; upon delivery, long lines are reconstructed. +/* .IP "\fBhopcount_limit (50)\fR" +/* The maximal number of Received: message headers that is allowed +/* in the primary message headers. +/* .IP "\fBmessage_size_limit (10240000)\fR" +/* The maximal size in bytes of a message, including envelope information. +/* .IP "\fBqmqpd_timeout (300s)\fR" +/* The time limit for sending or receiving information over the network. +/* TROUBLE SHOOTING CONTROLS +/* .ad +/* .fi +/* .IP "\fBdebug_peer_level (2)\fR" +/* The increment in verbose logging level when a nexthop destination, +/* remote client or server name or network address matches a pattern +/* given with the debug_peer_list parameter. +/* .IP "\fBdebug_peer_list (empty)\fR" +/* Optional list of nexthop destination, remote client or server +/* name or network address patterns that, if matched, cause the verbose +/* logging level to increase by the amount specified in $debug_peer_level. +/* .IP "\fBsoft_bounce (no)\fR" +/* Safety net to keep mail queued that would otherwise be returned to +/* the sender. +/* TARPIT CONTROLS +/* .ad +/* .fi +/* .IP "\fBqmqpd_error_delay (1s)\fR" +/* How long the Postfix QMQP server will pause before sending a negative +/* reply to the remote QMQP client. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqmqpd_authorized_clients (empty)\fR" +/* What remote QMQP clients are allowed to connect to the Postfix QMQP +/* server port. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .IP "\fBverp_delimiter_filter (-=+)\fR" +/* The characters Postfix accepts as VERP delimiter characters on the +/* Postfix \fBsendmail\fR(1) command line and in SMTP commands. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBqmqpd_client_port_logging (no)\fR" +/* Enable logging of the remote QMQP client port in addition to +/* the hostname and IP address. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* http://cr.yp.to/proto/qmqp.html, QMQP protocol +/* cleanup(8), message canonicalization +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* QMQP_README, Postfix ezmlm-idx howto. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* The qmqpd service was introduced with Postfix version 1.1. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single-threaded server skeleton. */ + +#include + +/* Application-specific */ + +#include + + /* + * Tunable parameters. Make sure that there is some bound on the length of a + * netstring, so that the mail system stays in control even when a malicious + * client sends netstrings of unreasonable length. The recipient count limit + * is enforced by the message size limit. + */ +int var_qmqpd_timeout; +int var_qmqpd_err_sleep; +char *var_filter_xport; +char *var_qmqpd_clients; +char *var_input_transp; +bool var_qmqpd_client_port_log; + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +#define DO_LOG 1 +#define DONT_LOG 0 + + /* + * Access control. This service should be exposed only to explicitly + * authorized clients. There is no default authorization. + */ +static NAMADR_LIST *qmqpd_clients; + + /* + * Transparency: before mail is queued, do we allow address mapping, + * automatic bcc, header/body checks? + */ +int qmqpd_input_transp_mask; + +/* qmqpd_open_file - open a queue file */ + +static void qmqpd_open_file(QMQPD_STATE *state) +{ + int cleanup_flags; + + /* + * Connect to the cleanup server. Log client name/address with queue ID. + */ + cleanup_flags = input_transp_cleanup(CLEANUP_FLAG_MASK_EXTERNAL, + qmqpd_input_transp_mask); + cleanup_flags |= smtputf8_autodetect(MAIL_SRC_MASK_QMQPD); + state->dest = mail_stream_service(MAIL_CLASS_PUBLIC, var_cleanup_service); + if (state->dest == 0 + || attr_print(state->dest->stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags), + ATTR_TYPE_END) != 0) + msg_fatal("unable to connect to the %s %s service", + MAIL_CLASS_PUBLIC, var_cleanup_service); + state->cleanup = state->dest->stream; + state->queue_id = mystrdup(state->dest->id); + msg_info("%s: client=%s", state->queue_id, state->namaddr); + + /* + * Record the time of arrival. Optionally, enable content filtering (not + * bloody likely, but present for the sake of consistency with all other + * Postfix points of entrance). + */ + rec_fprintf(state->cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, + REC_TYPE_TIME_ARG(state->arrival_time)); + if (*var_filter_xport) + rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport); +} + +/* qmqpd_read_content - receive message content */ + +static void qmqpd_read_content(QMQPD_STATE *state) +{ + state->where = "receiving message content"; + netstring_get(state->client, state->message, var_message_limit); +} + +/* qmqpd_copy_sender - copy envelope sender */ + +static void qmqpd_copy_sender(QMQPD_STATE *state) +{ + char *end_prefix; + char *end_origin; + int verp_requested; + static char verp_delims[] = "-="; + + /* + * If the sender address looks like prefix@origin-@[], then request + * variable envelope return path delivery, with an envelope sender + * address of prefi@origin, and with VERP delimiters of x and =. This + * way, the recipients will see envelope sender addresses that look like: + * prefixuser=domain@origin. + */ + state->where = "receiving sender address"; + netstring_get(state->client, state->buf, var_line_limit); + VSTRING_TERMINATE(state->buf); + verp_requested = + ((end_origin = vstring_end(state->buf) - 4) > STR(state->buf) + && strcmp(end_origin, "-@[]") == 0 + && (end_prefix = strchr(STR(state->buf), '@')) != 0 /* XXX */ + && --end_prefix < end_origin - 2 /* non-null origin */ + && end_prefix > STR(state->buf)); /* non-null prefix */ + if (verp_requested) { + verp_delims[0] = end_prefix[0]; + if (verp_delims_verify(verp_delims) != 0) { + state->err |= CLEANUP_STAT_CONT; /* XXX */ + vstring_sprintf(state->why_rejected, "Invalid VERP delimiters: \"%s\". Need two characters from \"%s\"", + verp_delims, var_verp_filter); + } + memmove(end_prefix, end_prefix + 1, end_origin - end_prefix - 1); + vstring_truncate(state->buf, end_origin - STR(state->buf) - 1); + } + if (state->err == CLEANUP_STAT_OK + && REC_PUT_BUF(state->cleanup, REC_TYPE_FROM, state->buf) < 0) + state->err = CLEANUP_STAT_WRITE; + if (verp_requested) + if (state->err == CLEANUP_STAT_OK + && rec_put(state->cleanup, REC_TYPE_VERP, verp_delims, 2) < 0) + state->err = CLEANUP_STAT_WRITE; + state->sender = mystrndup(STR(state->buf), LEN(state->buf)); +} + +/* qmqpd_write_attributes - save session attributes */ + +static void qmqpd_write_attributes(QMQPD_STATE *state) +{ + + /* + * Logging attributes, also used for XFORWARD. + */ + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_CLIENT_NAME, state->name); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_CLIENT_ADDR, state->rfc_addr); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_CLIENT_PORT, state->port); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_ORIGIN, state->namaddr); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_PROTO_NAME, state->protocol); + + /* + * For consistency with the smtpd Milter client, we need to provide the + * real client attributes to the cleanup Milter client. This does not + * matter much with qmqpd which speaks to trusted clients only, but we + * want to be sure that the cleanup input protocol is ready when a new + * type of network daemon is added to receive mail from the Internet. + * + * See also the comments in smtpd.c. + */ + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_CLIENT_NAME, state->name); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_CLIENT_ADDR, state->addr); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_CLIENT_PORT, state->port); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%u", + MAIL_ATTR_ACT_CLIENT_AF, state->addr_family); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_PROTO_NAME, state->protocol); + + /* XXX What about the address rewriting context? */ +} + +/* qmqpd_copy_recipients - copy message recipients */ + +static void qmqpd_copy_recipients(QMQPD_STATE *state) +{ + int ch; + + /* + * Remember the first recipient. We are done when we read the over-all + * netstring terminator. + * + * XXX This approach violates abstractions, but it is a heck of a lot more + * convenient than counting the over-all byte count down to zero, like + * qmail does. + */ + state->where = "receiving recipient address"; + while ((ch = VSTREAM_GETC(state->client)) != ',') { + vstream_ungetc(state->client, ch); + netstring_get(state->client, state->buf, var_line_limit); + if (state->err == CLEANUP_STAT_OK + && REC_PUT_BUF(state->cleanup, REC_TYPE_RCPT, state->buf) < 0) + state->err = CLEANUP_STAT_WRITE; + state->rcpt_count++; + if (state->recipient == 0) + state->recipient = mystrndup(STR(state->buf), LEN(state->buf)); + } +} + +/* qmqpd_next_line - get line from buffer, return last char, newline, or -1 */ + +static int qmqpd_next_line(VSTRING *message, char **start, int *len, + char **next) +{ + char *beyond = STR(message) + LEN(message); + char *enough = *next + var_line_limit; + char *cp; + + /* + * Stop at newline or at some limit. Don't look beyond the end of the + * buffer. + */ +#define UCHARPTR(x) ((unsigned char *) (x)) + + for (cp = *start = *next; /* void */ ; cp++) { + if (cp >= beyond) + return ((*len = (*next = cp) - *start) > 0 ? UCHARPTR(cp)[-1] : -1); + if (*cp == '\n') + return ((*len = cp - *start), (*next = cp + 1), '\n'); + if (cp >= enough) + return ((*len = cp - *start), (*next = cp), UCHARPTR(cp)[-1]); + } +} + +/* qmqpd_write_content - write the message content segment */ + +static void qmqpd_write_content(QMQPD_STATE *state) +{ + char *start; + char *next; + int len; + int rec_type; + int first = 1; + int ch; + + /* + * Start the message content segment. Prepend our own Received: header to + * the message content. List the recipient only when a message has one + * recipient. Otherwise, don't list the recipient to avoid revealing Bcc: + * recipients that are supposed to be invisible. + */ + rec_fputs(state->cleanup, REC_TYPE_MESG, ""); + rec_fprintf(state->cleanup, REC_TYPE_NORM, "Received: from %s (%s [%s])", + state->name, state->name, state->rfc_addr); + if (state->rcpt_count == 1 && state->recipient) { + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\tby %s (%s) with %s id %s", + var_myhostname, var_mail_name, + state->protocol, state->queue_id); + quote_822_local(state->buf, state->recipient); + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\tfor <%s>; %s", STR(state->buf), + mail_date(state->arrival_time.tv_sec)); + } else { + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\tby %s (%s) with %s", + var_myhostname, var_mail_name, state->protocol); + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\tid %s; %s", state->queue_id, + mail_date(state->arrival_time.tv_sec)); + } +#ifdef RECEIVED_ENVELOPE_FROM + quote_822_local(state->buf, state->sender); + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(envelope-from <%s>)", STR(state->buf)); +#endif + + /* + * Write the message content. + * + * XXX Force an empty record when the queue file content begins with + * whitespace, so that it won't be considered as being part of our own + * Received: header. What an ugly Kluge. + * + * XXX Deal with UNIX-style From_ lines at the start of message content just + * in case. + */ + for (next = STR(state->message); /* void */ ; /* void */ ) { + if ((ch = qmqpd_next_line(state->message, &start, &len, &next)) < 0) + break; + if (ch == '\n') + rec_type = REC_TYPE_NORM; + else + rec_type = REC_TYPE_CONT; + if (first) { + if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) { + rec_fprintf(state->cleanup, rec_type, + "X-Mailbox-Line: %.*s", len, start); + continue; + } + first = 0; + if (len > 0 && IS_SPACE_TAB(start[0])) + rec_put(state->cleanup, REC_TYPE_NORM, "", 0); + } + if (rec_put(state->cleanup, rec_type, start, len) < 0) { + state->err = CLEANUP_STAT_WRITE; + return; + } + } +} + +/* qmqpd_close_file - close queue file */ + +static void qmqpd_close_file(QMQPD_STATE *state) +{ + + /* + * Send the end-of-segment markers. + */ + if (state->err == CLEANUP_STAT_OK) + if (rec_fputs(state->cleanup, REC_TYPE_XTRA, "") < 0 + || rec_fputs(state->cleanup, REC_TYPE_END, "") < 0 + || vstream_fflush(state->cleanup)) + state->err = CLEANUP_STAT_WRITE; + + /* + * Finish the queue file or finish the cleanup conversation. + */ + if (state->err == 0) + state->err = mail_stream_finish(state->dest, state->why_rejected); + else + mail_stream_cleanup(state->dest); + state->dest = 0; +} + +/* qmqpd_reply - send status to client and optionally log message */ + +static void qmqpd_reply(QMQPD_STATE *state, int log_message, + int status_code, const char *fmt,...) +{ + va_list ap; + + /* + * Optionally change hard errors into retryable ones. Send the reply and + * optionally log it. Always insert a delay before reporting a problem. + * This slows down software run-away conditions. + */ + if (status_code == QMQPD_STAT_HARD && var_soft_bounce) + status_code = QMQPD_STAT_RETRY; + VSTRING_RESET(state->buf); + VSTRING_ADDCH(state->buf, status_code); + va_start(ap, fmt); + vstring_vsprintf_append(state->buf, fmt, ap); + va_end(ap); + NETSTRING_PUT_BUF(state->client, state->buf); + if (log_message) + (status_code == QMQPD_STAT_OK ? msg_info : msg_warn) ("%s: %s: %s", + state->queue_id, state->namaddr, STR(state->buf) + 1); + if (status_code != QMQPD_STAT_OK) + sleep(var_qmqpd_err_sleep); + netstring_fflush(state->client); +} + +/* qmqpd_send_status - send mail transaction completion status */ + +static void qmqpd_send_status(QMQPD_STATE *state) +{ + + /* + * One message may suffer from multiple errors, so complain only about + * the most severe error. + * + * See also: smtpd.c + */ + state->where = "sending completion status"; + + if (state->err == CLEANUP_STAT_OK) { + qmqpd_reply(state, DONT_LOG, QMQPD_STAT_OK, + "Ok: queued as %s", state->queue_id); + } else if ((state->err & CLEANUP_STAT_DEFER) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY, + "Error: %s", STR(state->why_rejected)); + } else if ((state->err & CLEANUP_STAT_BAD) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY, + "Error: internal error %d", state->err); + } else if ((state->err & CLEANUP_STAT_SIZE) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD, + "Error: message too large"); + } else if ((state->err & CLEANUP_STAT_HOPS) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD, + "Error: too many hops"); + } else if ((state->err & CLEANUP_STAT_CONT) != 0) { + qmqpd_reply(state, DO_LOG, STR(state->why_rejected)[0] == '4' ? + QMQPD_STAT_RETRY : QMQPD_STAT_HARD, + "Error: %s", STR(state->why_rejected)); + } else if ((state->err & CLEANUP_STAT_WRITE) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY, + "Error: queue file write error"); + } else if ((state->err & CLEANUP_STAT_RCPT) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD, + "Error: no recipients specified"); + } else { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY, + "Error: internal error %d", state->err); + } +} + +/* qmqpd_receive - receive QMQP message+sender+recipients */ + +static void qmqpd_receive(QMQPD_STATE *state) +{ + + /* + * Open a queue file. This must be first so that we can simplify the + * error logging and always include the queue ID information. + */ + qmqpd_open_file(state); + + /* + * Read and ignore the over-all netstring length indicator. + */ + state->where = "receiving QMQP packet header"; + (void) netstring_get_length(state->client); + + /* + * XXX Read the message content into memory, because Postfix expects to + * store the sender before storing the message content. Fixing that + * requires changes to pickup, cleanup, qmgr, and perhaps elsewhere, so + * that will have to happen later when I have more time. However, QMQP is + * used for mailing list distribution, so the bulk of the volume is + * expected to be not message content but recipients, and recipients are + * not accumulated in memory. + */ + qmqpd_read_content(state); + + /* + * Read and write the envelope sender. + */ + qmqpd_copy_sender(state); + + /* + * Record some session attributes. + */ + qmqpd_write_attributes(state); + + /* + * Read and write the envelope recipients, including the optional big + * brother recipient. + */ + qmqpd_copy_recipients(state); + + /* + * Start the message content segment, prepend our own Received: header, + * and write the message content. + */ + if (state->err == 0) + qmqpd_write_content(state); + + /* + * Close the queue file. + */ + qmqpd_close_file(state); + + /* + * Report the completion status to the client. + */ + qmqpd_send_status(state); +} + +/* qmqpd_proto - speak the QMQP "protocol" */ + +static void qmqpd_proto(QMQPD_STATE *state) +{ + int status; + + netstring_setup(state->client, var_qmqpd_timeout); + + switch (status = vstream_setjmp(state->client)) { + + default: + msg_panic("qmqpd_proto: unknown status %d", status); + + case NETSTRING_ERR_EOF: + state->reason = "lost connection"; + break; + + case NETSTRING_ERR_TIME: + state->reason = "read/write timeout"; + break; + + case NETSTRING_ERR_FORMAT: + state->reason = "netstring format error"; + if (vstream_setjmp(state->client) == 0) + if (state->reason && state->where) + qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s", + state->reason, state->where); + break; + + case NETSTRING_ERR_SIZE: + state->reason = "netstring length exceeds storage limit"; + if (vstream_setjmp(state->client) == 0) + if (state->reason && state->where) + qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s", + state->reason, state->where); + break; + + case 0: + + /* + * See if we want to talk to this client at all. + */ + if (namadr_list_match(qmqpd_clients, state->name, state->addr) != 0) { + qmqpd_receive(state); + } else if (qmqpd_clients->error == 0) { + qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, + "Error: %s is not authorized to use this service", + state->namaddr); + } else { + qmqpd_reply(state, DONT_LOG, QMQPD_STAT_RETRY, + "Error: server configuration error"); + } + break; + } + + /* + * Log abnormal session termination. Indicate the last recognized state + * before things went wrong. + */ + if (state->reason && state->where) + msg_info("%s: %s: %s while %s", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr, state->reason, state->where); +} + +/* qmqpd_service - service one client */ + +static void qmqpd_service(VSTREAM *stream, char *unused_service, char **argv) +{ + QMQPD_STATE *state; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * For sanity, require that at least one of INET or INET6 is enabled. + * Otherwise, we can't look up interface information, and we can't + * convert names or addresses. + */ + if (inet_proto_info()->ai_family_list[0] == 0) + msg_fatal("all network protocols are disabled (%s = %s)", + VAR_INET_PROTOCOLS, var_inet_protocols); + + /* + * This routine runs when a client has connected to our network port. + * Look up and sanitize the peer name and initialize some connection- + * specific state. + */ + state = qmqpd_state_alloc(stream); + + /* + * See if we need to turn on verbose logging for this client. + */ + debug_peer_check(state->name, state->addr); + + /* + * Provide the QMQP service. + */ + msg_info("connect from %s", state->namaddr); + qmqpd_proto(state); + msg_info("disconnect from %s", state->namaddr); + + /* + * After the client has gone away, clean up whatever we have set up at + * connection time. + */ + debug_peer_restore(); + qmqpd_state_free(state); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + debug_peer_init(); + qmqpd_clients = + namadr_list_init(VAR_QMQPD_CLIENTS, MATCH_FLAG_RETURN + | match_parent_style(VAR_QMQPD_CLIENTS), + var_qmqpd_clients); +} + +/* post_jail_init - post-jail initialization */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Initialize the receive transparency options: do we want unknown + * recipient checks, do we want address mapping. + */ + qmqpd_input_transp_mask = + input_transp_mask(VAR_INPUT_TRANSP, var_input_transp); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_QMTPD_TMOUT, DEF_QMTPD_TMOUT, &var_qmqpd_timeout, 1, 0, + VAR_QMTPD_ERR_SLEEP, DEF_QMTPD_ERR_SLEEP, &var_qmqpd_err_sleep, 0, 0, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0, + VAR_QMQPD_CLIENTS, DEF_QMQPD_CLIENTS, &var_qmqpd_clients, 0, 0, + VAR_INPUT_TRANSP, DEF_INPUT_TRANSP, &var_input_transp, 0, 0, + 0, + }; + static const CONFIG_BOOL_TABLE bool_table[] = { + VAR_QMQPD_CLIENT_PORT_LOG, DEF_QMQPD_CLIENT_PORT_LOG, &var_qmqpd_client_port_log, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Pass control to the single-threaded service skeleton. + */ + single_server_main(argc, argv, qmqpd_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_BOOL_TABLE(bool_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + 0); +} diff --git a/src/qmqpd/qmqpd.h b/src/qmqpd/qmqpd.h new file mode 100644 index 0000000..aad185b --- /dev/null +++ b/src/qmqpd/qmqpd.h @@ -0,0 +1,95 @@ +/*++ +/* NAME +/* qmqpd 3h +/* SUMMARY +/* Postfix QMQP server +/* SYNOPSIS +/* include "qmqpd.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include + + /* + * Per-session state. + */ +typedef struct { + int err; /* error flags */ + VSTREAM *client; /* client connection */ + VSTRING *message; /* message buffer */ + VSTRING *buf; /* line buffer */ + struct timeval arrival_time; /* start of session */ + char *name; /* client name */ + char *addr; /* client IP address */ + char *port; /* client TCP port */ + char *namaddr; /* name[addr]:port */ + char *rfc_addr; /* RFC 2821 client IP address */ + int addr_family; /* address family */ + char *queue_id; /* queue file ID */ + VSTREAM *cleanup; /* cleanup server */ + MAIL_STREAM *dest; /* cleanup server */ + int rcpt_count; /* recipient count */ + char *reason; /* exception name */ + char *sender; /* sender address */ + char *recipient; /* recipient address */ + char *protocol; /* protocol name */ + char *where; /* protocol state */ + VSTRING *why_rejected; /* REJECT reason */ +} QMQPD_STATE; + + /* + * Representation of unknown upstream client or message information within + * qmqpd processes. This is not the representation that Postfix uses in + * queue files, in queue manager delivery requests, or in XCLIENT/XFORWARD + * commands! + */ +#define CLIENT_ATTR_UNKNOWN "unknown" + +#define CLIENT_NAME_UNKNOWN CLIENT_ATTR_UNKNOWN +#define CLIENT_ADDR_UNKNOWN CLIENT_ATTR_UNKNOWN +#define CLIENT_PORT_UNKNOWN CLIENT_ATTR_UNKNOWN +#define CLIENT_NAMADDR_UNKNOWN CLIENT_ATTR_UNKNOWN + + /* + * QMQP protocol status codes. + */ +#define QMQPD_STAT_OK 'K' +#define QMQPD_STAT_RETRY 'Z' +#define QMQPD_STAT_HARD 'D' + + /* + * qmqpd_state.c + */ +QMQPD_STATE *qmqpd_state_alloc(VSTREAM *); +void qmqpd_state_free(QMQPD_STATE *); + + /* + * qmqpd_peer.c + */ +void qmqpd_peer_init(QMQPD_STATE *); +void qmqpd_peer_reset(QMQPD_STATE *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/qmqpd/qmqpd_peer.c b/src/qmqpd/qmqpd_peer.c new file mode 100644 index 0000000..41cd009 --- /dev/null +++ b/src/qmqpd/qmqpd_peer.c @@ -0,0 +1,309 @@ +/*++ +/* NAME +/* qmqpd_peer 3 +/* SUMMARY +/* look up peer name/address information +/* SYNOPSIS +/* #include "qmqpd.h" +/* +/* void qmqpd_peer_init(state) +/* QMQPD_STATE *state; +/* +/* void qmqpd_peer_reset(state) +/* QMQPD_STATE *state; +/* DESCRIPTION +/* The qmqpd_peer_init() routine attempts to produce a printable +/* version of the peer name and address of the specified socket. +/* Where information is unavailable, the name and/or address +/* are set to "unknown". +/* +/* qmqpd_peer_init() updates the following fields: +/* .IP name +/* The client hostname. An unknown name is represented by the +/* string "unknown". +/* .IP addr +/* Printable representation of the client address. +/* .IP namaddr +/* String of the form: "name[addr]:port". +/* .PP +/* qmqpd_peer_reset() releases memory allocated by qmqpd_peer_init(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include "qmqpd.h" + +/* qmqpd_peer_init - initialize peer information */ + +void qmqpd_peer_init(QMQPD_STATE *state) +{ + const char *myname = "qmqpd_peer_init"; + struct sockaddr_storage ss; + struct sockaddr *sa; + SOCKADDR_SIZE sa_length; + const INET_PROTO_INFO *proto_info = inet_proto_info(); + + sa = (struct sockaddr *) &ss; + sa_length = sizeof(ss); + + /* + * Look up the peer address information. + */ + if (getpeername(vstream_fileno(state->client), sa, &sa_length) >= 0) { + errno = 0; + } + + /* + * If peer went away, give up. + */ + if (errno != 0 && errno != ENOTSOCK) { + state->name = mystrdup(CLIENT_NAME_UNKNOWN); + state->addr = mystrdup(CLIENT_ADDR_UNKNOWN); + state->rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN); + state->addr_family = AF_UNSPEC; + state->port = mystrdup(CLIENT_PORT_UNKNOWN); + } + + /* + * Convert the client address to printable address and hostname. + * + * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd, while + * Postfix IPv6 (or IPv4) support is turned off, don't (skip to the final + * else clause, pretend the origin is localhost[127.0.0.1], and become an + * open relay). + */ + else if (errno == 0 + && (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + )) { + MAI_HOSTNAME_STR client_name; + MAI_HOSTADDR_STR client_addr; + MAI_SERVPORT_STR client_port; + int aierr; + char *colonp; + + /* + * Sanity check: we can't use sockets that we're not configured for. + */ + if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0) + msg_fatal("cannot handle socket type %s with \"%s = %s\"", +#ifdef AF_INET6 + sa->sa_family == AF_INET6 ? "AF_INET6" : +#endif + sa->sa_family == AF_INET ? "AF_INET" : + "other", VAR_INET_PROTOCOLS, var_inet_protocols); + + /* + * Sorry, but there are some things that we just cannot do while + * connected to the network. + */ + if (geteuid() != var_owner_uid || getuid() != var_owner_uid) { + msg_error("incorrect QMQP server privileges: uid=%lu euid=%lu", + (unsigned long) getuid(), (unsigned long) geteuid()); + msg_fatal("the Postfix QMQP server must run with $%s privileges", + VAR_MAIL_OWNER); + } + + /* + * Convert the client address to printable form. + */ + if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr, + &client_port, 0)) != 0) + msg_fatal("%s: cannot convert client address/port to string: %s", + myname, MAI_STRERROR(aierr)); + state->port = mystrdup(client_port.buf); + + /* + * XXX Require that the infrastructure strips off the IPv6 datalink + * suffix to avoid false alarms with strict address syntax checks. + */ +#ifdef HAS_IPV6 + if (strchr(client_addr.buf, '%') != 0) + msg_panic("%s: address %s has datalink suffix", + myname, client_addr.buf); +#endif + + /* + * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on, + * but only if IPv4 support is enabled (why would anyone want to turn + * it off)? With IPv4 support enabled we have no need for the IPv6 + * form in logging, hostname verification and access checks. + */ +#ifdef HAS_IPV6 + if (sa->sa_family == AF_INET6) { + if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0 + && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa)) + && (colonp = strrchr(client_addr.buf, ':')) != 0) { + struct addrinfo *res0; + + if (msg_verbose > 1) + msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"", + myname, client_addr.buf, colonp + 1); + + state->addr = mystrdup(colonp + 1); + state->rfc_addr = mystrdup(colonp + 1); + state->addr_family = AF_INET; + aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0); + if (aierr) + msg_fatal("%s: cannot convert %s from string to binary: %s", + myname, state->addr, MAI_STRERROR(aierr)); + sa_length = res0->ai_addrlen; + if (sa_length > sizeof(ss)) + sa_length = sizeof(ss); + memcpy((void *) sa, res0->ai_addr, sa_length); + freeaddrinfo(res0); + } + + /* + * Following RFC 2821 section 4.1.3, an IPv6 address literal gets + * a prefix of 'IPv6:'. We do this consistently for all IPv6 + * addresses that appear in headers or envelopes. The fact + * that valid_mailhost_addr() enforces the form helps of course. + * We use the form without IPV6: prefix when doing access + * control, or when accessing the connection cache. + */ + else { + state->addr = mystrdup(client_addr.buf); + state->rfc_addr = + concatenate(IPV6_COL, client_addr.buf, (char *) 0); + state->addr_family = sa->sa_family; + } + } + + /* + * An IPv4 address is in dotted quad decimal form. + */ + else +#endif + { + state->addr = mystrdup(client_addr.buf); + state->rfc_addr = mystrdup(client_addr.buf); + state->addr_family = sa->sa_family; + } + + /* + * Look up and sanity check the client hostname. + * + * It is unsafe to allow numeric hostnames, especially because there + * exists pressure to turn off the name->addr double check. In that + * case an attacker could trivally bypass access restrictions. + * + * sockaddr_to_hostname() already rejects malformed or numeric names. + */ +#define REJECT_PEER_NAME(state) { \ + myfree(state->name); \ + state->name = mystrdup(CLIENT_NAME_UNKNOWN); \ + } + + if ((aierr = sockaddr_to_hostname(sa, sa_length, &client_name, + (MAI_SERVNAME_STR *) 0, 0)) != 0) { + state->name = mystrdup(CLIENT_NAME_UNKNOWN); + } else { + struct addrinfo *res0; + struct addrinfo *res; + + state->name = mystrdup(client_name.buf); + + /* + * Reject the hostname if it does not list the peer address. + */ + aierr = hostname_to_sockaddr_pf(state->name, state->addr_family, + (char *) 0, 0, &res0); + if (aierr) { + msg_warn("hostname %s does not resolve to address %s: %s", + state->name, state->addr, MAI_STRERROR(aierr)); + REJECT_PEER_NAME(state); + } else { + for (res = res0; /* void */ ; res = res->ai_next) { + if (res == 0) { + msg_warn("hostname %s does not resolve to address %s", + state->addr, state->name); + REJECT_PEER_NAME(state); + break; + } + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + msg_info("skipping address family %d for host %s", + res->ai_family, state->name); + continue; + } + if (sock_addr_cmp_addr(res->ai_addr, sa) == 0) + break; /* keep peer name */ + } + freeaddrinfo(res0); + } + } + } + + /* + * If it's not Internet, assume the client is local, and avoid using the + * naming service because that can hang when the machine is disconnected. + */ + else { + state->name = mystrdup("localhost"); + state->addr = mystrdup("127.0.0.1"); /* XXX bogus. */ + state->rfc_addr = mystrdup("127.0.0.1");/* XXX bogus. */ + state->addr_family = AF_UNSPEC; + state->port = mystrdup("0"); /* XXX bogus. */ + } + + /* + * Do the name[addr]:port formatting for pretty reports. + */ + state->namaddr = + concatenate(state->name, "[", state->addr, "]", + var_qmqpd_client_port_log ? ":" : (char *) 0, + state->port, (char *) 0); +} + +/* qmqpd_peer_reset - destroy peer information */ + +void qmqpd_peer_reset(QMQPD_STATE *state) +{ + myfree(state->name); + myfree(state->addr); + myfree(state->namaddr); + myfree(state->rfc_addr); + myfree(state->port); +} diff --git a/src/qmqpd/qmqpd_state.c b/src/qmqpd/qmqpd_state.c new file mode 100644 index 0000000..9bee879 --- /dev/null +++ b/src/qmqpd/qmqpd_state.c @@ -0,0 +1,99 @@ +/*++ +/* NAME +/* qmqpd_state 3 +/* SUMMARY +/* Postfix QMQP server +/* SYNOPSIS +/* #include "qmqpd.h" +/* +/* QMQPD_STATE *qmqpd_state_alloc(stream) +/* VSTREAM *stream; +/* +/* void qmqpd_state_free(state) +/* QMQPD_STATE *state; +/* DESCRIPTION +/* qmqpd_state_alloc() creates and initializes session context. +/* +/* qmqpd_state_free() destroys session context. +/* +/* Arguments: +/* .IP stream +/* Stream connected to peer. The stream is not copied. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include + +/* qmqpd_state_alloc - allocate and initialize session state */ + +QMQPD_STATE *qmqpd_state_alloc(VSTREAM *stream) +{ + QMQPD_STATE *state; + + state = (QMQPD_STATE *) mymalloc(sizeof(*state)); + state->err = CLEANUP_STAT_OK; + state->client = stream; + state->message = vstring_alloc(1000); + state->buf = vstring_alloc(100); + GETTIMEOFDAY(&state->arrival_time); + qmqpd_peer_init(state); + state->queue_id = 0; + state->cleanup = 0; + state->dest = 0; + state->rcpt_count = 0; + state->reason = 0; + state->sender = 0; + state->recipient = 0; + state->protocol = MAIL_PROTO_QMQP; + state->where = "initializing client connection"; + state->why_rejected = vstring_alloc(10); + return (state); +} + +/* qmqpd_state_free - destroy session state */ + +void qmqpd_state_free(QMQPD_STATE *state) +{ + vstring_free(state->message); + vstring_free(state->buf); + qmqpd_peer_reset(state); + if (state->queue_id) + myfree(state->queue_id); + if (state->dest) + mail_stream_cleanup(state->dest); + if (state->sender) + myfree(state->sender); + if (state->recipient) + myfree(state->recipient); + vstring_free(state->why_rejected); + myfree((void *) state); +} diff --git a/src/scache/.indent.pro b/src/scache/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/scache/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/scache/Makefile.in b/src/scache/Makefile.in new file mode 100644 index 0000000..882f0c6 --- /dev/null +++ b/src/scache/Makefile.in @@ -0,0 +1,81 @@ +SHELL = /bin/sh +SRCS = scache.c +OBJS = scache.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = scache +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +scache.o: ../../include/attr.h +scache.o: ../../include/check_arg.h +scache.o: ../../include/events.h +scache.o: ../../include/htable.h +scache.o: ../../include/iostuff.h +scache.o: ../../include/mail_conf.h +scache.o: ../../include/mail_params.h +scache.o: ../../include/mail_proto.h +scache.o: ../../include/mail_server.h +scache.o: ../../include/mail_version.h +scache.o: ../../include/msg.h +scache.o: ../../include/mymalloc.h +scache.o: ../../include/nvtable.h +scache.o: ../../include/ring.h +scache.o: ../../include/scache.h +scache.o: ../../include/sys_defs.h +scache.o: ../../include/vbuf.h +scache.o: ../../include/vstream.h +scache.o: ../../include/vstring.h +scache.o: scache.c diff --git a/src/scache/scache.c b/src/scache/scache.c new file mode 100644 index 0000000..85f32ef --- /dev/null +++ b/src/scache/scache.c @@ -0,0 +1,586 @@ +/*++ +/* NAME +/* scache 8 +/* SUMMARY +/* Postfix shared connection cache server +/* SYNOPSIS +/* \fBscache\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBscache\fR(8) server maintains a shared multi-connection +/* cache. This information can be used by, for example, Postfix +/* SMTP clients or other Postfix delivery agents. +/* +/* The connection cache is organized into logical destination +/* names, physical endpoint names, and connections. +/* +/* As a specific example, logical SMTP destinations specify +/* (transport, domain, port), and physical SMTP endpoints +/* specify (transport, IP address, port). An SMTP connection +/* may be saved after a successful mail transaction. +/* +/* In the general case, one logical destination may refer to +/* zero or more physical endpoints, one physical endpoint may +/* be referenced by zero or more logical destinations, and +/* one endpoint may refer to zero or more connections. +/* +/* The exact syntax of a logical destination or endpoint name +/* is application dependent; the \fBscache\fR(8) server does +/* not care. A connection is stored as a file descriptor together +/* with application-dependent information that is needed to +/* re-activate a connection object. Again, the \fBscache\fR(8) +/* server is completely unaware of the details of that +/* information. +/* +/* All information is stored with a finite time to live (ttl). +/* The connection cache daemon terminates when no client is +/* connected for \fBmax_idle\fR time units. +/* +/* This server implements the following requests: +/* .IP "\fBsave_endp\fI ttl endpoint endpoint_properties file_descriptor\fR" +/* Save the specified file descriptor and connection property data +/* under the specified endpoint name. The endpoint properties +/* are used by the client to re-activate a passivated connection +/* object. +/* .IP "\fBfind_endp\fI endpoint\fR" +/* Look up cached properties and a cached file descriptor for the +/* specified endpoint. +/* .IP "\fBsave_dest\fI ttl destination destination_properties endpoint\fR" +/* Save the binding between a logical destination and an +/* endpoint under the destination name, together with destination +/* specific connection properties. The destination properties +/* are used by the client to re-activate a passivated connection +/* object. +/* .IP "\fBfind_dest\fI destination\fR" +/* Look up cached destination properties, cached endpoint properties, +/* and a cached file descriptor for the specified logical destination. +/* SECURITY +/* .ad +/* .fi +/* The \fBscache\fR(8) server is not security-sensitive. It does not +/* talk to the network, and it does not talk to local users. +/* The \fBscache\fR(8) server can run chrooted at fixed low privilege. +/* +/* The \fBscache\fR(8) server is not a trusted process. It must +/* not be used to store information that is security sensitive. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* The session cache cannot be shared among multiple machines. +/* +/* When a connection expires from the cache, it is closed without +/* the appropriate protocol specific handshake. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically as \fBscache\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* RESOURCE CONTROLS +/* .ad +/* .fi +/* .IP "\fBconnection_cache_ttl_limit (2s)\fR" +/* The maximal time-to-live value that the \fBscache\fR(8) connection +/* cache server +/* allows. +/* .IP "\fBconnection_cache_status_update_time (600s)\fR" +/* How frequently the \fBscache\fR(8) server logs usage statistics with +/* connection cache hit and miss rates for logical destinations and for +/* physical endpoints. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* smtp(8), SMTP client +/* postconf(5), configuration parameters +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* CONNECTION_CACHE_README, Postfix connection cache +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* This service was introduced with Postfix version 2.2. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Single server skeleton. */ + +#include +#include + +/* Application-specific. */ + + /* + * Tunable parameters. + */ +int var_scache_ttl_lim; +int var_scache_stat_time; + + /* + * Request parameters. + */ +static VSTRING *scache_request; +static VSTRING *scache_dest_label; +static VSTRING *scache_dest_prop; +static VSTRING *scache_endp_label; +static VSTRING *scache_endp_prop; + +#ifdef CANT_WRITE_BEFORE_SENDING_FD +static VSTRING *scache_dummy; + +#endif + + /* + * Session cache instance. + */ +static SCACHE *scache; + + /* + * Statistics. + */ +static int scache_dest_hits; +static int scache_dest_miss; +static int scache_dest_count; +static int scache_endp_hits; +static int scache_endp_miss; +static int scache_endp_count; +static int scache_sess_count; +time_t scache_start_time; + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define VSTREQ(x,y) (strcmp(STR(x),y) == 0) + +/* scache_save_endp_service - protocol to save endpoint->stream binding */ + +static void scache_save_endp_service(VSTREAM *client_stream) +{ + const char *myname = "scache_save_endp_service"; + int ttl; + int fd; + SCACHE_SIZE size; + + if (attr_scan(client_stream, + ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_TTL, &ttl), + RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label), + RECV_ATTR_STR(MAIL_ATTR_PROP, scache_endp_prop), + ATTR_TYPE_END) != 3 + || ttl <= 0) { + msg_warn("%s: bad or missing request parameter", myname); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD), + ATTR_TYPE_END); + return; + } else if ( +#ifdef CANT_WRITE_BEFORE_SENDING_FD + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""), + ATTR_TYPE_END) != 0 + || vstream_fflush(client_stream) != 0 + || read_wait(vstream_fileno(client_stream), + client_stream->timeout) < 0 /* XXX */ + || +#endif + (fd = LOCAL_RECV_FD(vstream_fileno(client_stream))) < 0) { + msg_warn("%s: unable to receive file descriptor: %m", myname); + (void) attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL), + ATTR_TYPE_END); + return; + } else { + scache_save_endp(scache, + ttl > var_scache_ttl_lim ? var_scache_ttl_lim : ttl, + STR(scache_endp_label), STR(scache_endp_prop), fd); + (void) attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK), + ATTR_TYPE_END); + scache_size(scache, &size); + if (size.endp_count > scache_endp_count) + scache_endp_count = size.endp_count; + if (size.sess_count > scache_sess_count) + scache_sess_count = size.sess_count; + return; + } +} + +/* scache_find_endp_service - protocol to find connection for endpoint */ + +static void scache_find_endp_service(VSTREAM *client_stream) +{ + const char *myname = "scache_find_endp_service"; + int fd; + + if (attr_scan(client_stream, + ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label), + ATTR_TYPE_END) != 1) { + msg_warn("%s: bad or missing request parameter", myname); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD), + SEND_ATTR_STR(MAIL_ATTR_PROP, ""), + ATTR_TYPE_END); + return; + } else if ((fd = scache_find_endp(scache, STR(scache_endp_label), + scache_endp_prop)) < 0) { + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL), + SEND_ATTR_STR(MAIL_ATTR_PROP, ""), + ATTR_TYPE_END); + scache_endp_miss++; + return; + } else { + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK), + SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_endp_prop)), + ATTR_TYPE_END); + if (vstream_fflush(client_stream) != 0 +#ifdef CANT_WRITE_BEFORE_SENDING_FD + || attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy), + ATTR_TYPE_END) != 1 +#endif + || LOCAL_SEND_FD(vstream_fileno(client_stream), fd) < 0 +#ifdef MUST_READ_AFTER_SENDING_FD + || attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy), + ATTR_TYPE_END) != 1 +#endif + ) + msg_warn("%s: cannot send file descriptor: %m", myname); + if (close(fd) < 0) + msg_warn("close(%d): %m", fd); + scache_endp_hits++; + return; + } +} + +/* scache_save_dest_service - protocol to save destination->endpoint binding */ + +static void scache_save_dest_service(VSTREAM *client_stream) +{ + const char *myname = "scache_save_dest_service"; + int ttl; + SCACHE_SIZE size; + + if (attr_scan(client_stream, + ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_TTL, &ttl), + RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_dest_label), + RECV_ATTR_STR(MAIL_ATTR_PROP, scache_dest_prop), + RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label), + ATTR_TYPE_END) != 4 + || ttl <= 0) { + msg_warn("%s: bad or missing request parameter", myname); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD), + ATTR_TYPE_END); + return; + } else { + scache_save_dest(scache, + ttl > var_scache_ttl_lim ? var_scache_ttl_lim : ttl, + STR(scache_dest_label), STR(scache_dest_prop), + STR(scache_endp_label)); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK), + ATTR_TYPE_END); + scache_size(scache, &size); + if (size.dest_count > scache_dest_count) + scache_dest_count = size.dest_count; + if (size.endp_count > scache_endp_count) + scache_endp_count = size.endp_count; + return; + } +} + +/* scache_find_dest_service - protocol to find connection for destination */ + +static void scache_find_dest_service(VSTREAM *client_stream) +{ + const char *myname = "scache_find_dest_service"; + int fd; + + if (attr_scan(client_stream, + ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_dest_label), + ATTR_TYPE_END) != 1) { + msg_warn("%s: bad or missing request parameter", myname); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD), + SEND_ATTR_STR(MAIL_ATTR_PROP, ""), + SEND_ATTR_STR(MAIL_ATTR_PROP, ""), + ATTR_TYPE_END); + return; + } else if ((fd = scache_find_dest(scache, STR(scache_dest_label), + scache_dest_prop, + scache_endp_prop)) < 0) { + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL), + SEND_ATTR_STR(MAIL_ATTR_PROP, ""), + SEND_ATTR_STR(MAIL_ATTR_PROP, ""), + ATTR_TYPE_END); + scache_dest_miss++; + return; + } else { + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK), + SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_dest_prop)), + SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_endp_prop)), + ATTR_TYPE_END); + if (vstream_fflush(client_stream) != 0 +#ifdef CANT_WRITE_BEFORE_SENDING_FD + || attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy), + ATTR_TYPE_END) != 1 +#endif + || LOCAL_SEND_FD(vstream_fileno(client_stream), fd) < 0 +#ifdef MUST_READ_AFTER_SENDING_FD + || attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy), + ATTR_TYPE_END) != 1 +#endif + ) + msg_warn("%s: cannot send file descriptor: %m", myname); + if (close(fd) < 0) + msg_warn("close(%d): %m", fd); + scache_dest_hits++; + return; + } +} + +/* scache_service - perform service for client */ + +static void scache_service(VSTREAM *client_stream, char *unused_service, + char **argv) +{ + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * dedicated to the scache service. All connection-management stuff is + * handled by the common code in multi_server.c. + * + * XXX Workaround: with some requests, the client sends a dummy message + * after the server replies (yes that's a botch). When the scache server + * is slow, this dummy message may become concatenated with the next + * request from the same client. The do-while loop below will repeat + * instead of discarding the client request. We must process it now + * because there will be no select() notification. + */ + do { + if (attr_scan(client_stream, + ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_REQ, scache_request), + ATTR_TYPE_END) == 1) { + if (VSTREQ(scache_request, SCACHE_REQ_SAVE_DEST)) { + scache_save_dest_service(client_stream); + } else if (VSTREQ(scache_request, SCACHE_REQ_FIND_DEST)) { + scache_find_dest_service(client_stream); + } else if (VSTREQ(scache_request, SCACHE_REQ_SAVE_ENDP)) { + scache_save_endp_service(client_stream); + } else if (VSTREQ(scache_request, SCACHE_REQ_FIND_ENDP)) { + scache_find_endp_service(client_stream); + } else { + msg_warn("unrecognized request: \"%s\", ignored", + STR(scache_request)); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD), + ATTR_TYPE_END); + } + } + } while (vstream_peek(client_stream) > 0); + vstream_fflush(client_stream); +} + +/* scache_status_dump - log and reset cache statistics */ + +static void scache_status_dump(char *unused_name, char **unused_argv) +{ + if (scache_dest_hits || scache_dest_miss + || scache_endp_hits || scache_endp_miss + || scache_dest_count || scache_endp_count + || scache_sess_count) + msg_info("statistics: start interval %.15s", + ctime(&scache_start_time) + 4); + + if (scache_dest_hits || scache_dest_miss) { + msg_info("statistics: domain lookup hits=%d miss=%d success=%d%%", + scache_dest_hits, scache_dest_miss, + scache_dest_hits * 100 + / (scache_dest_hits + scache_dest_miss)); + scache_dest_hits = scache_dest_miss = 0; + } + if (scache_endp_hits || scache_endp_miss) { + msg_info("statistics: address lookup hits=%d miss=%d success=%d%%", + scache_endp_hits, scache_endp_miss, + scache_endp_hits * 100 + / (scache_endp_hits + scache_endp_miss)); + scache_endp_hits = scache_endp_miss = 0; + } + if (scache_dest_count || scache_endp_count || scache_sess_count) { + msg_info("statistics: max simultaneous domains=%d addresses=%d connection=%d", + scache_dest_count, scache_endp_count, scache_sess_count); + scache_dest_count = 0; + scache_endp_count = 0; + scache_sess_count = 0; + } + scache_start_time = event_time(); +} + +/* scache_status_update - log and reset cache statistics periodically */ + +static void scache_status_update(int unused_event, void *context) +{ + scache_status_dump((char *) 0, (char **) 0); + event_request_timer(scache_status_update, context, var_scache_stat_time); +} + +/* post_jail_init - initialization after privilege drop */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Pre-allocate the cache instance. + */ + scache = scache_multi_create(); + + /* + * Pre-allocate buffers. + */ + scache_request = vstring_alloc(10); + scache_dest_label = vstring_alloc(10); + scache_dest_prop = vstring_alloc(10); + scache_endp_label = vstring_alloc(10); + scache_endp_prop = vstring_alloc(10); +#ifdef CANT_WRITE_BEFORE_SENDING_FD + scache_dummy = vstring_alloc(10); +#endif + + /* + * Disable the max_use limit. We still terminate when no client is + * connected for $idle_limit time units. + */ + var_use_limit = 0; + + /* + * Dump and reset cache statistics every so often. + */ + event_request_timer(scache_status_update, (void *) 0, var_scache_stat_time); + scache_start_time = event_time(); +} + +/* scache_post_accept - announce our protocol */ + +static void scache_post_accept(VSTREAM *stream, char *unused_name, + char **unused_argv, HTABLE *unused_table) +{ + + /* + * Announce the protocol. + */ + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SCACHE), + ATTR_TYPE_END); + (void) vstream_fflush(stream); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the multi-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_SCACHE_TTL_LIM, DEF_SCACHE_TTL_LIM, &var_scache_ttl_lim, 1, 0, + VAR_SCACHE_STAT_TIME, DEF_SCACHE_STAT_TIME, &var_scache_stat_time, 1, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + multi_server_main(argc, argv, scache_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_POST_ACCEPT(scache_post_accept), + CA_MAIL_SERVER_EXIT(scache_status_dump), + CA_MAIL_SERVER_SOLITARY, + 0); +} diff --git a/src/sendmail/.indent.pro b/src/sendmail/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/sendmail/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/sendmail/.printfck b/src/sendmail/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/sendmail/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/sendmail/Makefile.in b/src/sendmail/Makefile.in new file mode 100644 index 0000000..bb0298a --- /dev/null +++ b/src/sendmail/Makefile.in @@ -0,0 +1,113 @@ +SHELL = /bin/sh +SRCS = sendmail.c +OBJS = sendmail.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = sendmail +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +sendmail.o: ../../include/argv.h +sendmail.o: ../../include/attr.h +sendmail.o: ../../include/check_arg.h +sendmail.o: ../../include/clean_env.h +sendmail.o: ../../include/cleanup_user.h +sendmail.o: ../../include/connect.h +sendmail.o: ../../include/debug_process.h +sendmail.o: ../../include/deliver_request.h +sendmail.o: ../../include/dsn.h +sendmail.o: ../../include/dsn_mask.h +sendmail.o: ../../include/fullname.h +sendmail.o: ../../include/header_opts.h +sendmail.o: ../../include/htable.h +sendmail.o: ../../include/iostuff.h +sendmail.o: ../../include/mail_conf.h +sendmail.o: ../../include/mail_dict.h +sendmail.o: ../../include/mail_flush.h +sendmail.o: ../../include/mail_params.h +sendmail.o: ../../include/mail_parm_split.h +sendmail.o: ../../include/mail_proto.h +sendmail.o: ../../include/mail_queue.h +sendmail.o: ../../include/mail_run.h +sendmail.o: ../../include/mail_stream.h +sendmail.o: ../../include/mail_task.h +sendmail.o: ../../include/mail_version.h +sendmail.o: ../../include/maillog_client.h +sendmail.o: ../../include/mime_state.h +sendmail.o: ../../include/msg.h +sendmail.o: ../../include/msg_stats.h +sendmail.o: ../../include/msg_vstream.h +sendmail.o: ../../include/mymalloc.h +sendmail.o: ../../include/name_code.h +sendmail.o: ../../include/nvtable.h +sendmail.o: ../../include/rec_streamlf.h +sendmail.o: ../../include/rec_type.h +sendmail.o: ../../include/recipient_list.h +sendmail.o: ../../include/record.h +sendmail.o: ../../include/resolve_clnt.h +sendmail.o: ../../include/safe.h +sendmail.o: ../../include/set_ugid.h +sendmail.o: ../../include/split_at.h +sendmail.o: ../../include/stringops.h +sendmail.o: ../../include/sys_defs.h +sendmail.o: ../../include/tok822.h +sendmail.o: ../../include/user_acl.h +sendmail.o: ../../include/username.h +sendmail.o: ../../include/vbuf.h +sendmail.o: ../../include/verp_sender.h +sendmail.o: ../../include/vstream.h +sendmail.o: ../../include/vstring.h +sendmail.o: ../../include/vstring_vstream.h +sendmail.o: ../../include/warn_stat.h +sendmail.o: sendmail.c diff --git a/src/sendmail/sendmail.c b/src/sendmail/sendmail.c new file mode 100644 index 0000000..27b3543 --- /dev/null +++ b/src/sendmail/sendmail.c @@ -0,0 +1,1510 @@ +/*++ +/* NAME +/* sendmail 1 +/* SUMMARY +/* Postfix to Sendmail compatibility interface +/* SYNOPSIS +/* \fBsendmail\fR [\fIoption ...\fR] [\fIrecipient ...\fR] +/* +/* \fBmailq\fR +/* \fBsendmail -bp\fR +/* +/* \fBnewaliases\fR +/* \fBsendmail -I\fR +/* DESCRIPTION +/* The Postfix \fBsendmail\fR(1) command implements the Postfix +/* to Sendmail compatibility interface. +/* For the sake of compatibility with existing applications, some +/* Sendmail command-line options are recognized but silently ignored. +/* +/* By default, Postfix \fBsendmail\fR(1) reads a message from +/* standard input +/* until EOF or until it reads a line with only a \fB.\fR character, +/* and arranges for delivery. Postfix \fBsendmail\fR(1) relies on the +/* \fBpostdrop\fR(1) command to create a queue file in the \fBmaildrop\fR +/* directory. +/* +/* Specific command aliases are provided for other common modes of +/* operation: +/* .IP \fBmailq\fR +/* List the mail queue. Each entry shows the queue file ID, message +/* size, arrival time, sender, and the recipients that still need to +/* be delivered. If mail could not be delivered upon the last attempt, +/* the reason for failure is shown. The queue ID string is +/* followed by an optional status character: +/* .RS +/* .IP \fB*\fR +/* The message is in the \fBactive\fR queue, i.e. the message is +/* selected for delivery. +/* .IP \fB!\fR +/* The message is in the \fBhold\fR queue, i.e. no further delivery +/* attempt will be made until the mail is taken off hold. +/* .IP \fB#\fR +/* The message is forced to expire. See the \fBpostsuper\fR(1) +/* options \fB-e\fR or \fB-f\fR. +/* .RE +/* .IP +/* This mode of operation is implemented by executing the +/* \fBpostqueue\fR(1) command. +/* .IP \fBnewaliases\fR +/* Initialize the alias database. If no input file is specified (with +/* the \fB-oA\fR option, see below), the program processes the file(s) +/* specified with the \fBalias_database\fR configuration parameter. +/* If no alias database type is specified, the program uses the type +/* specified with the \fBdefault_database_type\fR configuration parameter. +/* This mode of operation is implemented by running the \fBpostalias\fR(1) +/* command. +/* .sp +/* Note: it may take a minute or so before an alias database update +/* becomes visible. Use the "\fBpostfix reload\fR" command to eliminate +/* this delay. +/* .PP +/* These and other features can be selected by specifying the +/* appropriate combination of command-line options. Some features are +/* controlled by parameters in the \fBmain.cf\fR configuration file. +/* +/* The following options are recognized: +/* .IP "\fB-Am\fR (ignored)" +/* .IP "\fB-Ac\fR (ignored)" +/* Postfix sendmail uses the same configuration file regardless of +/* whether or not a message is an initial submission. +/* .IP "\fB-B \fIbody_type\fR" +/* The message body MIME type: \fB7BIT\fR or \fB8BITMIME\fR. +/* .IP \fB-bd\fR +/* Go into daemon mode. This mode of operation is implemented by +/* executing the "\fBpostfix start\fR" command. +/* .IP "\fB-bh\fR (ignored)" +/* .IP "\fB-bH\fR (ignored)" +/* Postfix has no persistent host status database. +/* .IP \fB-bi\fR +/* Initialize alias database. See the \fBnewaliases\fR +/* command above. +/* .IP \fB-bl\fR +/* Go into daemon mode. To accept only local connections as +/* with Sendmail's \fB-bl\fR option, specify "\fBinet_interfaces +/* = loopback\fR" in the Postfix \fBmain.cf\fR configuration +/* file. +/* .IP \fB-bm\fR +/* Read mail from standard input and arrange for delivery. +/* This is the default mode of operation. +/* .IP \fB-bp\fR +/* List the mail queue. See the \fBmailq\fR command above. +/* .IP \fB-bs\fR +/* Stand-alone SMTP server mode. Read SMTP commands from +/* standard input, and write responses to standard output. +/* In stand-alone SMTP server mode, mail relaying and other +/* access controls are disabled by default. To enable them, +/* run the process as the \fBmail_owner\fR user. +/* .sp +/* This mode of operation is implemented by running the +/* \fBsmtpd\fR(8) daemon. +/* .IP \fB-bv\fR +/* Do not collect or deliver a message. Instead, send an email +/* report after verifying each recipient address. This is useful +/* for testing address rewriting and routing configurations. +/* .sp +/* This feature is available in Postfix version 2.1 and later. +/* .IP "\fB-C \fIconfig_file\fR" +/* .IP "\fB-C \fIconfig_dir\fR" +/* The path name of the Postfix \fBmain.cf\fR file, or of its +/* parent directory. This information is ignored with Postfix +/* versions before 2.3. +/* +/* With Postfix version 3.2 and later, a non-default directory +/* must be authorized in the default \fBmain.cf\fR file, through +/* the alternate_config_directories or multi_instance_directories +/* parameters. +/* +/* With all Postfix versions, you can specify a directory pathname +/* with the MAIL_CONFIG environment variable to override the +/* location of configuration files. +/* .IP "\fB-F \fIfull_name\fR" +/* Set the sender full name. This overrides the NAME environment +/* variable, and is used only with messages that +/* have no \fBFrom:\fR message header. +/* .IP "\fB-f \fIsender\fR" +/* Set the envelope sender address. This is the address where +/* delivery problems are sent to. With Postfix versions before 2.1, the +/* \fBErrors-To:\fR message header overrides the error return address. +/* .IP \fB-G\fR +/* Gateway (relay) submission, as opposed to initial user +/* submission. Either do not rewrite addresses at all, or +/* update incomplete addresses with the domain information +/* specified with \fBremote_header_rewrite_domain\fR. +/* +/* This option is ignored before Postfix version 2.3. +/* .IP "\fB-h \fIhop_count\fR (ignored)" +/* Hop count limit. Use the \fBhopcount_limit\fR configuration +/* parameter instead. +/* .IP \fB-I\fR +/* Initialize alias database. See the \fBnewaliases\fR +/* command above. +/* .IP "\fB-i\fR" +/* When reading a message from standard input, don't treat a line +/* with only a \fB.\fR character as the end of input. +/* .IP "\fB-L \fIlabel\fR (ignored)" +/* The logging label. Use the \fBsyslog_name\fR configuration +/* parameter instead. +/* .IP "\fB-m\fR (ignored)" +/* Backwards compatibility. +/* .IP "\fB-N \fIdsn\fR (default: 'delay, failure')" +/* Delivery status notification control. Specify either a +/* comma-separated list with one or more of \fBfailure\fR (send +/* notification when delivery fails), \fBdelay\fR (send +/* notification when delivery is delayed), or \fBsuccess\fR +/* (send notification when the message is delivered); or specify +/* \fBnever\fR (don't send any notifications at all). +/* +/* This feature is available in Postfix 2.3 and later. +/* .IP "\fB-n\fR (ignored)" +/* Backwards compatibility. +/* .IP "\fB-oA\fIalias_database\fR" +/* Non-default alias database. Specify \fIpathname\fR or +/* \fItype\fR:\fIpathname\fR. See \fBpostalias\fR(1) for +/* details. +/* .IP "\fB-O \fIoption=value\fR (ignored)" +/* Set the named \fIoption\fR to \fIvalue\fR. Use the equivalent +/* configuration parameter in \fBmain.cf\fR instead. +/* .IP "\fB-o7\fR (ignored)" +/* .IP "\fB-o8\fR (ignored)" +/* To send 8-bit or binary content, use an appropriate MIME encapsulation +/* and specify the appropriate \fB-B\fR command-line option. +/* .IP "\fB-oi\fR" +/* When reading a message from standard input, don't treat a line +/* with only a \fB.\fR character as the end of input. +/* .IP "\fB-om\fR (ignored)" +/* The sender is never eliminated from alias etc. expansions. +/* .IP "\fB-o \fIx value\fR (ignored)" +/* Set option \fIx\fR to \fIvalue\fR. Use the equivalent +/* configuration parameter in \fBmain.cf\fR instead. +/* .IP "\fB-r \fIsender\fR" +/* Set the envelope sender address. This is the address where +/* delivery problems are sent to. With Postfix versions before 2.1, the +/* \fBErrors-To:\fR message header overrides the error return address. +/* .IP "\fB-R \fIreturn\fR" +/* Delivery status notification control. Specify "hdrs" to +/* return only the header when a message bounces, "full" to +/* return a full copy (the default behavior). +/* +/* The \fB-R\fR option specifies an upper bound; Postfix will +/* return only the header, when a full copy would exceed the +/* bounce_size_limit setting. +/* +/* This option is ignored before Postfix version 2.10. +/* .IP \fB-q\fR +/* Attempt to deliver all queued mail. This is implemented by +/* executing the \fBpostqueue\fR(1) command. +/* +/* Warning: flushing undeliverable mail frequently will result in +/* poor delivery performance of all other mail. +/* .IP "\fB-q\fIinterval\fR (ignored)" +/* The interval between queue runs. Use the \fBqueue_run_delay\fR +/* configuration parameter instead. +/* .IP \fB-qI\fIqueueid\fR +/* Schedule immediate delivery of mail with the specified queue +/* ID. This option is implemented by executing the +/* \fBpostqueue\fR(1) command, and is available with Postfix +/* version 2.4 and later. +/* .IP \fB-qR\fIsite\fR +/* Schedule immediate delivery of all mail that is queued for the named +/* \fIsite\fR. This option accepts only \fIsite\fR names that are +/* eligible for the "fast flush" service, and is implemented by +/* executing the \fBpostqueue\fR(1) command. +/* See \fBflush\fR(8) for more information about the "fast flush" +/* service. +/* .IP \fB-qS\fIsite\fR +/* This command is not implemented. Use the slower "\fBsendmail -q\fR" +/* command instead. +/* .IP \fB-t\fR +/* Extract recipients from message headers. These are added to any +/* recipients specified on the command line. +/* +/* With Postfix versions prior to 2.1, this option requires that +/* no recipient addresses are specified on the command line. +/* .IP "\fB-U\fR (ignored)" +/* Initial user submission. +/* .IP "\fB-V \fIenvid\fR" +/* Specify the envelope ID for notification by servers that +/* support DSN. +/* +/* This feature is available in Postfix 2.3 and later. +/* .IP "\fB-XV\fR (Postfix 2.2 and earlier: \fB-V\fR)" +/* Variable Envelope Return Path. Given an envelope sender address +/* of the form \fIowner-listname\fR@\fIorigin\fR, each recipient +/* \fIuser\fR@\fIdomain\fR receives mail with a personalized envelope +/* sender address. +/* .sp +/* By default, the personalized envelope sender address is +/* \fIowner-listname\fB+\fIuser\fB=\fIdomain\fR@\fIorigin\fR. The default +/* \fB+\fR and \fB=\fR characters are configurable with the +/* \fBdefault_verp_delimiters\fR configuration parameter. +/* .IP "\fB-XV\fIxy\fR (Postfix 2.2 and earlier: \fB-V\fIxy\fR)" +/* As \fB-XV\fR, but uses \fIx\fR and \fIy\fR as the VERP delimiter +/* characters, instead of the characters specified with the +/* \fBdefault_verp_delimiters\fR configuration parameter. +/* .IP \fB-v\fR +/* Send an email report of the first delivery attempt (Postfix +/* versions 2.1 and later). Mail delivery +/* always happens in the background. When multiple \fB-v\fR +/* options are given, enable verbose logging for debugging purposes. +/* .IP "\fB-X \fIlog_file\fR (ignored)" +/* Log mailer traffic. Use the \fBdebug_peer_list\fR and +/* \fBdebug_peer_level\fR configuration parameters instead. +/* SECURITY +/* .ad +/* .fi +/* By design, this program is not set-user (or group) id. +/* It is prepared to handle message content from untrusted, +/* possibly remote, users. +/* +/* However, like most Postfix programs, this program does not +/* enforce a security policy on its command-line arguments. +/* Instead, it relies on the UNIX system to enforce access +/* policies based on the effective user and group IDs of the +/* process. Concretely, this means that running Postfix commands +/* as root (from sudo or equivalent) on behalf of a non-root +/* user is likely to create privilege escalation opportunities. +/* +/* If an application runs any Postfix programs on behalf of +/* users that do not have normal shell access to Postfix +/* commands, then that application MUST restrict user-specified +/* command-line arguments to avoid privilege escalation. +/* .IP \(bu +/* Filter all command-line arguments, for example arguments +/* that contain a pathname or that specify a database access +/* method. These pathname checks must reject user-controlled +/* symlinks or hardlinks to sensitive files, and must not be +/* vulnerable to TOCTOU race attacks. +/* .IP \(bu +/* Disable command options processing for all command arguments +/* that contain user-specified data. For example, the Postfix +/* \fBsendmail\fR(1) command line MUST be structured as follows: +/* +/* .nf +/* \fB/path/to/sendmail\fR \fIsystem-arguments\fR \fB--\fR \fIuser-arguments\fR +/* .fi +/* +/* Here, the "\fB--\fR" disables command option processing for +/* all \fIuser-arguments\fR that follow. +/* .IP +/* Without the "\fB--\fR", a malicious user could enable Postfix +/* \fBsendmail\fR(1) command options, by specifying an email +/* address that starts with "\fB-\fR". +/* DIAGNOSTICS +/* Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8), +/* and to the standard error stream. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP \fBMAIL_CONFIG\fR +/* Directory with Postfix configuration files. +/* .IP "\fBMAIL_VERBOSE\fR (value does not matter)" +/* Enable verbose logging for debugging purposes. +/* .IP "\fBMAIL_DEBUG\fR (value does not matter)" +/* Enable debugging with an external command, as specified with the +/* \fBdebugger_command\fR configuration parameter. +/* .IP \fBNAME\fR +/* The sender full name. This is used only with messages that +/* have no \fBFrom:\fR message header. See also the \fB-F\fR +/* option above. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* Available with Postfix 2.9 and later: +/* .IP "\fBsendmail_fix_line_endings (always)\fR" +/* Controls how the Postfix sendmail command converts email message +/* line endings from into UNIX format (). +/* TROUBLE SHOOTING CONTROLS +/* .ad +/* .fi +/* The DEBUG_README file gives examples of how to troubleshoot a +/* Postfix system. +/* .IP "\fBdebugger_command (empty)\fR" +/* The external command to execute when a Postfix daemon program is +/* invoked with the -D option. +/* .IP "\fBdebug_peer_level (2)\fR" +/* The increment in verbose logging level when a nexthop destination, +/* remote client or server name or network address matches a pattern +/* given with the debug_peer_list parameter. +/* .IP "\fBdebug_peer_list (empty)\fR" +/* Optional list of nexthop destination, remote client or server +/* name or network address patterns that, if matched, cause the verbose +/* logging level to increase by the amount specified in $debug_peer_level. +/* ACCESS CONTROLS +/* .ad +/* .fi +/* Available in Postfix version 2.2 and later: +/* .IP "\fBauthorized_flush_users (static:anyone)\fR" +/* List of users who are authorized to flush the queue. +/* .IP "\fBauthorized_mailq_users (static:anyone)\fR" +/* List of users who are authorized to view the queue. +/* .IP "\fBauthorized_submit_users (static:anyone)\fR" +/* List of users who are authorized to submit mail with the \fBsendmail\fR(1) +/* command (and with the privileged \fBpostdrop\fR(1) helper command). +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBbounce_size_limit (50000)\fR" +/* The maximal amount of original message text that is sent in a +/* non-delivery notification. +/* .IP "\fBfork_attempts (5)\fR" +/* The maximal number of attempts to fork() a child process. +/* .IP "\fBfork_delay (1s)\fR" +/* The delay between attempts to fork() a child process. +/* .IP "\fBhopcount_limit (50)\fR" +/* The maximal number of Received: message headers that is allowed +/* in the primary message headers. +/* .IP "\fBqueue_run_delay (300s)\fR" +/* The time between deferred queue scans by the queue manager; +/* prior to Postfix 2.4 the default value was 1000s. +/* FAST FLUSH CONTROLS +/* .ad +/* .fi +/* The ETRN_README file describes configuration and operation +/* details for the Postfix "fast flush" service. +/* .IP "\fBfast_flush_domains ($relay_domains)\fR" +/* Optional list of destinations that are eligible for per-destination +/* logfiles with mail that is queued to those destinations. +/* VERP CONTROLS +/* .ad +/* .fi +/* The VERP_README file describes configuration and operation +/* details of Postfix support for variable envelope return +/* path addresses. +/* .IP "\fBdefault_verp_delimiters (+=)\fR" +/* The two default VERP delimiter characters. +/* .IP "\fBverp_delimiter_filter (-=+)\fR" +/* The characters Postfix accepts as VERP delimiter characters on the +/* Postfix \fBsendmail\fR(1) command line and in SMTP commands. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBalias_database (see 'postconf -d' output)\fR" +/* The alias databases for \fBlocal\fR(8) delivery that are updated with +/* "\fBnewaliases\fR" or with "\fBsendmail -bi\fR". +/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR" +/* The location of all postfix administrative commands. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix support programs and daemon programs. +/* .IP "\fBdefault_database_type (see 'postconf -d' output)\fR" +/* The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1) +/* and \fBpostmap\fR(1) commands. +/* .IP "\fBdelay_warning_time (0h)\fR" +/* The time after which the sender receives a copy of the message +/* headers of mail that is still queued. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment variables that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBmail_owner (postfix)\fR" +/* The UNIX system account that owns the Postfix queue and most Postfix +/* daemon processes. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBremote_header_rewrite_domain (empty)\fR" +/* Don't rewrite message headers from remote clients at all when +/* this parameter is empty; otherwise, rewrite message headers and +/* append the specified domain name to incomplete addresses. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Postfix 3.2 and later: +/* .IP "\fBalternate_config_directories (empty)\fR" +/* A list of non-default Postfix configuration directories that may +/* be specified with "-c config_directory" on the command line (in the +/* case of \fBsendmail\fR(1), with the "-C" option), or via the MAIL_CONFIG +/* environment parameter. +/* .IP "\fBmulti_instance_directories (empty)\fR" +/* An optional list of non-default Postfix configuration directories; +/* these directories belong to additional Postfix instances that share +/* the Postfix executable files and documentation with the default +/* Postfix instance, and that are started, stopped, etc., together +/* with the default Postfix instance. +/* FILES +/* /var/spool/postfix, mail queue +/* /etc/postfix, configuration files +/* SEE ALSO +/* pickup(8), mail pickup daemon +/* qmgr(8), queue manager +/* smtpd(8), SMTP server +/* flush(8), fast flush service +/* postsuper(1), queue maintenance +/* postalias(1), create/update/query alias database +/* postdrop(1), mail posting utility +/* postfix(1), mail system control +/* postqueue(1), mail queue control +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README_FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* DEBUG_README, Postfix debugging howto +/* ETRN_README, Postfix ETRN howto +/* VERP_README, Postfix VERP howto +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include /* remove() */ +#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 +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * Modes of operation. + */ +#define SM_MODE_ENQUEUE 1 /* delivery mode */ +#define SM_MODE_NEWALIAS 2 /* initialize alias database */ +#define SM_MODE_MAILQ 3 /* list mail queue */ +#define SM_MODE_DAEMON 4 /* daemon mode */ +#define SM_MODE_USER 5 /* user (stand-alone) mode */ +#define SM_MODE_FLUSHQ 6 /* user (stand-alone) mode */ +#define SM_MODE_IGNORE 7 /* ignore this mode */ + + /* + * Flag parade. Flags 8-15 are reserved for delivery request trace flags. + */ +#define SM_FLAG_AEOF (1<<0) /* archaic EOF */ +#define SM_FLAG_XRCPT (1<<1) /* extract recipients from headers */ + +#define SM_FLAG_DEFAULT (SM_FLAG_AEOF) + + /* + * VERP support. + */ +static char *verp_delims; + + /* + * Callback context for extracting recipients. + */ +typedef struct SM_STATE { + VSTREAM *dst; /* output stream */ + ARGV *recipients; /* recipients from regular headers */ + ARGV *resent_recip; /* recipients from resent headers */ + int resent; /* resent flag */ + const char *saved_sender; /* for error messages */ + uid_t uid; /* for error messages */ + VSTRING *temp; /* scratch buffer */ +} SM_STATE; + + /* + * Mail submission ACL, line-end fixing. + */ +char *var_submit_acl; +char *var_sm_fix_eol; + +static const CONFIG_STR_TABLE str_table[] = { + VAR_SUBMIT_ACL, DEF_SUBMIT_ACL, &var_submit_acl, 0, 0, + VAR_SM_FIX_EOL, DEF_SM_FIX_EOL, &var_sm_fix_eol, 1, 0, + 0, +}; + + /* + * Silly little macros (SLMs). + */ +#define STR vstring_str + +/* output_text - output partial or complete text line */ + +static void output_text(void *context, int rec_type, const char *buf, ssize_t len, + off_t unused_offset) +{ + SM_STATE *state = (SM_STATE *) context; + + if (rec_put(state->dst, rec_type, buf, len) < 0) + msg_fatal_status(EX_TEMPFAIL, + "%s(%ld): error writing queue file: %m", + state->saved_sender, (long) state->uid); +} + +/* output_header - output one message header */ + +static void output_header(void *context, int header_class, + const HEADER_OPTS *header_info, + VSTRING *buf, off_t offset) +{ + SM_STATE *state = (SM_STATE *) context; + TOK822 *tree; + TOK822 **addr_list; + TOK822 **tpp; + ARGV *rcpt; + char *start; + char *line; + char *next_line; + ssize_t len; + + /* + * Parse the header line, and save copies of recipient addresses in the + * appropriate place. + */ + if (header_class == MIME_HDR_PRIMARY + && header_info + && (header_info->flags & HDR_OPT_RECIP) + && (header_info->flags & HDR_OPT_EXTRACT) + && (state->resent == 0 || (header_info->flags & HDR_OPT_RR))) { + if (header_info->flags & HDR_OPT_RR) { + rcpt = state->resent_recip; + if (state->resent == 0) + state->resent = 1; + } else + rcpt = state->recipients; + tree = tok822_parse(STR(buf) + strlen(header_info->name) + 1); + addr_list = tok822_grep(tree, TOK822_ADDR); + for (tpp = addr_list; *tpp; tpp++) { + tok822_internalize(state->temp, tpp[0]->head, TOK822_STR_DEFL); + argv_add(rcpt, STR(state->temp), (char *) 0); + } + myfree((void *) addr_list); + tok822_free_tree(tree); + } + + /* + * Pipe the unmodified message header through the header line folding + * routine, and ensure that long lines are chopped appropriately. + */ + for (line = start = STR(buf); line; line = next_line) { + next_line = split_at(line, '\n'); + len = next_line ? next_line - line - 1 : strlen(line); + do { + if (len > var_line_limit) { + output_text(context, REC_TYPE_CONT, line, var_line_limit, offset); + line += var_line_limit; + len -= var_line_limit; + offset += var_line_limit; + } else { + output_text(context, REC_TYPE_NORM, line, len, offset); + offset += len; + break; + } + } while (len > 0); + offset += 1; + } +} + +/* enqueue - post one message */ + +static void enqueue(const int flags, const char *encoding, + const char *dsn_envid, int dsn_ret, int dsn_notify, + const char *rewrite_context, const char *sender, + const char *full_name, char **recipients) +{ + VSTRING *buf; + VSTREAM *dst; + char *saved_sender; + char **cpp; + int type; + char *start; + int skip_from_; + TOK822 *tree; + TOK822 *tp; + int rcpt_count = 0; + enum { + STRIP_CR_DUNNO, STRIP_CR_DO, STRIP_CR_DONT, STRIP_CR_ERROR + } strip_cr; + MAIL_STREAM *handle; + VSTRING *postdrop_command; + uid_t uid = getuid(); + int status; + VSTRING *why; /* postdrop status message */ + int naddr; + int prev_type; + MIME_STATE *mime_state = 0; + SM_STATE state; + int mime_errs; + const char *errstr; + int addr_count; + int level; + static NAME_CODE sm_fix_eol_table[] = { + SM_FIX_EOL_ALWAYS, STRIP_CR_DO, + SM_FIX_EOL_STRICT, STRIP_CR_DUNNO, + SM_FIX_EOL_NEVER, STRIP_CR_DONT, + 0, STRIP_CR_ERROR, + }; + + /* + * Access control is enforced in the postdrop command. The code here + * merely produces a more user-friendly interface. + */ + if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, + var_submit_acl, uid)) != 0) + msg_fatal_status(EX_NOPERM, + "User %s(%ld) is not allowed to submit mail", errstr, (long) uid); + + /* + * Initialize. + */ + buf = vstring_alloc(100); + + /* + * Stop run-away process accidents by limiting the queue file size. This + * is not a defense against DOS attack. + */ + if (ENFORCING_SIZE_LIMIT(var_message_limit) + && get_file_limit() > var_message_limit) + set_file_limit((off_t) var_message_limit); + + /* + * The sender name is provided by the user. In principle, the mail pickup + * service could deduce the sender name from queue file ownership, but: + * pickup would not be able to run chrooted, and it may not be desirable + * to use login names at all. + */ + if (sender != 0) { + VSTRING_RESET(buf); + VSTRING_TERMINATE(buf); + tree = tok822_parse(sender); + for (naddr = 0, tp = tree; tp != 0; tp = tp->next) + if (tp->type == TOK822_ADDR && naddr++ == 0) + tok822_internalize(buf, tp->head, TOK822_STR_DEFL); + tok822_free_tree(tree); + saved_sender = mystrdup(STR(buf)); + if (naddr > 1) + msg_warn("-f option specified malformed sender: %s", sender); + } else { + if ((sender = username()) == 0) + msg_fatal_status(EX_OSERR, "no login name found for user ID %lu", + (unsigned long) uid); + saved_sender = mystrdup(sender); + } + + /* + * Let the postdrop command open the queue file for us, and sanity check + * the content. XXX Make postdrop a manifest constant. + */ + errno = 0; + postdrop_command = vstring_alloc(1000); + vstring_sprintf(postdrop_command, "%s/postdrop -r", var_command_dir); + for (level = 0; level < msg_verbose; level++) + vstring_strcat(postdrop_command, " -v"); + if ((handle = mail_stream_command(STR(postdrop_command))) == 0) + msg_fatal_status(EX_UNAVAILABLE, "%s(%ld): unable to execute %s: %m", + saved_sender, (long) uid, STR(postdrop_command)); + vstring_free(postdrop_command); + dst = handle->stream; + + /* + * First, write envelope information to the output stream. + * + * For sendmail compatibility, parse each command-line recipient as if it + * were an RFC 822 message header; some MUAs specify comma-separated + * recipient lists; and some MUAs even specify "word word
". + * + * Sort-uniq-ing the recipient list is done after address canonicalization, + * before recipients are written to queue file. That's cleaner than + * having the queue manager nuke duplicate recipient status records. + * + * XXX Should limit the size of envelope records. + * + * With "sendmail -N", instead of a per-message NOTIFY record we store one + * per recipient so that we can simplify the implementation somewhat. + */ + if (dsn_envid) + rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_DSN_ENVID, dsn_envid); + if (dsn_ret) + rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_RET, dsn_ret); + rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_RWR_CONTEXT, rewrite_context); + if (full_name || (full_name = fullname()) != 0) + rec_fputs(dst, REC_TYPE_FULL, full_name); + rec_fputs(dst, REC_TYPE_FROM, saved_sender); + if (verp_delims && *saved_sender == 0) + msg_fatal_status(EX_USAGE, + "%s(%ld): -V option requires non-null sender address", + saved_sender, (long) uid); + if (encoding) + rec_fprintf(dst, REC_TYPE_ATTR, "%s=%s", MAIL_ATTR_ENCODING, encoding); + if (DEL_REQ_TRACE_FLAGS(flags)) + rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", MAIL_ATTR_TRACE_FLAGS, + DEL_REQ_TRACE_FLAGS(flags)); + if (verp_delims) + rec_fputs(dst, REC_TYPE_VERP, verp_delims); + if (recipients) { + for (cpp = recipients; *cpp != 0; cpp++) { + tree = tok822_parse(*cpp); + for (addr_count = 0, tp = tree; tp != 0; tp = tp->next) { + if (tp->type == TOK822_ADDR) { + tok822_internalize(buf, tp->head, TOK822_STR_DEFL); + if (dsn_notify) + rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_NOTIFY, dsn_notify); + if (REC_PUT_BUF(dst, REC_TYPE_RCPT, buf) < 0) + msg_fatal_status(EX_TEMPFAIL, + "%s(%ld): error writing queue file: %m", + saved_sender, (long) uid); + ++rcpt_count; + ++addr_count; + } + } + tok822_free_tree(tree); + if (addr_count == 0) { + if (rec_put(dst, REC_TYPE_RCPT, "", 0) < 0) + msg_fatal_status(EX_TEMPFAIL, + "%s(%ld): error writing queue file: %m", + saved_sender, (long) uid); + ++rcpt_count; + } + } + } + + /* + * Append the message contents to the queue file. Write chunks of at most + * 1kbyte. Internally, we use different record types for data ending in + * LF and for data that doesn't, so we can actually be binary transparent + * for local mail. Unfortunately, SMTP has no record continuation + * convention, so there is no guarantee that arbitrary data will be + * delivered intact via SMTP. Strip leading From_ lines. For the benefit + * of UUCP environments, also get rid of leading >>>From_ lines. + */ + rec_fputs(dst, REC_TYPE_MESG, ""); + if (DEL_REQ_TRACE_ONLY(flags) != 0) { + if (flags & SM_FLAG_XRCPT) + msg_fatal_status(EX_USAGE, "%s(%ld): -t option cannot be used with -bv", + saved_sender, (long) uid); + if (*saved_sender) + rec_fprintf(dst, REC_TYPE_NORM, "From: %s", saved_sender); + rec_fprintf(dst, REC_TYPE_NORM, "Subject: probe"); + if (recipients) { + rec_fprintf(dst, REC_TYPE_CONT, "To:"); + for (cpp = recipients; *cpp != 0; cpp++) { + rec_fprintf(dst, REC_TYPE_NORM, " %s%s", + *cpp, cpp[1] ? "," : ""); + } + } + } else { + + /* + * Initialize the MIME processor and set up the callback context. + */ + if (flags & SM_FLAG_XRCPT) { + state.dst = dst; + state.recipients = argv_alloc(2); + state.resent_recip = argv_alloc(2); + state.resent = 0; + state.saved_sender = saved_sender; + state.uid = uid; + state.temp = vstring_alloc(10); + mime_state = mime_state_alloc(MIME_OPT_DISABLE_MIME + | MIME_OPT_REPORT_TRUNC_HEADER, + output_header, + (MIME_STATE_ANY_END) 0, + output_text, + (MIME_STATE_ANY_END) 0, + (MIME_STATE_ERR_PRINT) 0, + (void *) &state); + } + + /* + * Process header/body lines. + */ + skip_from_ = 1; + strip_cr = name_code(sm_fix_eol_table, NAME_CODE_FLAG_STRICT_CASE, + var_sm_fix_eol); + if (strip_cr == STRIP_CR_ERROR) + msg_fatal_status(EX_USAGE, + "invalid %s value: %s", VAR_SM_FIX_EOL, var_sm_fix_eol); + for (prev_type = 0; (type = rec_streamlf_get(VSTREAM_IN, buf, var_line_limit)) + != REC_TYPE_EOF; prev_type = type) { + if (strip_cr == STRIP_CR_DUNNO && type == REC_TYPE_NORM) { + if (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') + strip_cr = STRIP_CR_DO; + else + strip_cr = STRIP_CR_DONT; + } + if (skip_from_) { + if (type == REC_TYPE_NORM) { + start = STR(buf); + if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) + continue; + } + skip_from_ = 0; + } + if (strip_cr == STRIP_CR_DO && type == REC_TYPE_NORM) + while (VSTRING_LEN(buf) > 0 && vstring_end(buf)[-1] == '\r') + vstring_truncate(buf, VSTRING_LEN(buf) - 1); + if ((flags & SM_FLAG_AEOF) && prev_type != REC_TYPE_CONT + && VSTRING_LEN(buf) == 1 && *STR(buf) == '.') + break; + if (mime_state) { + mime_errs = mime_state_update(mime_state, type, STR(buf), + VSTRING_LEN(buf)); + if (mime_errs) + msg_fatal_status(EX_DATAERR, + "%s(%ld): unable to extract recipients: %s", + saved_sender, (long) uid, + mime_state_error(mime_errs)); + } else { + if (REC_PUT_BUF(dst, type, buf) < 0) + msg_fatal_status(EX_TEMPFAIL, + "%s(%ld): error writing queue file: %m", + saved_sender, (long) uid); + } + } + } + + /* + * Finish MIME processing. We need a final mime_state_update() call in + * order to flush text that is still buffered. That can happen when the + * last line did not end in newline. + */ + if (mime_state) { + mime_errs = mime_state_update(mime_state, REC_TYPE_EOF, "", 0); + if (mime_errs) + msg_fatal_status(EX_DATAERR, + "%s(%ld): unable to extract recipients: %s", + saved_sender, (long) uid, + mime_state_error(mime_errs)); + mime_state = mime_state_free(mime_state); + } + + /* + * Append recipient addresses that were extracted from message headers. + */ + rec_fputs(dst, REC_TYPE_XTRA, ""); + if (flags & SM_FLAG_XRCPT) { + for (cpp = state.resent ? state.resent_recip->argv : + state.recipients->argv; *cpp; cpp++) { + if (dsn_notify) + rec_fprintf(dst, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_NOTIFY, dsn_notify); + if (rec_put(dst, REC_TYPE_RCPT, *cpp, strlen(*cpp)) < 0) + msg_fatal_status(EX_TEMPFAIL, + "%s(%ld): error writing queue file: %m", + saved_sender, (long) uid); + ++rcpt_count; + } + argv_free(state.recipients); + argv_free(state.resent_recip); + vstring_free(state.temp); + } + if (rcpt_count == 0) + msg_fatal_status(EX_USAGE, (flags & SM_FLAG_XRCPT) ? + "%s(%ld): No recipient addresses found in message header" : + "%s(%ld): Recipient addresses must be specified on" + " the command line or via the -t option", + saved_sender, (long) uid); + + /* + * Identify the end of the queue file. + */ + rec_fputs(dst, REC_TYPE_END, ""); + + /* + * Make sure that the message makes it to the file system. Once we have + * terminated with successful exit status we cannot lose the message due + * to "frivolous reasons". If all goes well, prevent the run-time error + * handler from removing the file. + */ + if (vstream_ferror(VSTREAM_IN)) + msg_fatal_status(EX_DATAERR, "%s(%ld): error reading input: %m", + saved_sender, (long) uid); + why = vstring_alloc(100); + if ((status = mail_stream_finish(handle, why)) != CLEANUP_STAT_OK) + msg_fatal_status((status & CLEANUP_STAT_BAD) ? EX_SOFTWARE : + (status & CLEANUP_STAT_WRITE) ? EX_TEMPFAIL : + (status & CLEANUP_STAT_NOPERM) ? EX_NOPERM : + EX_UNAVAILABLE, "%s(%ld): %s", saved_sender, + (long) uid, VSTRING_LEN(why) ? + STR(why) : cleanup_strerror(status)); + vstring_free(why); + + /* + * Don't leave them in the dark. + */ + if (DEL_REQ_TRACE_FLAGS(flags)) { + vstream_printf("Mail Delivery Status Report will be mailed to <%s>.\n", + saved_sender); + vstream_fflush(VSTREAM_OUT); + } + + /* + * Cleanup. Not really necessary as we're about to exit, but good for + * debugging purposes. + */ + vstring_free(buf); + myfree(saved_sender); +} + +/* tempfail - sanitize exit status after library run-time error */ + +static void tempfail(void) +{ + exit(EX_TEMPFAIL); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static char *full_name = 0; /* sendmail -F */ + struct stat st; + char *slash; + char *sender = 0; /* sendmail -f */ + int c; + int fd; + int mode; + ARGV *ext_argv; + int debug_me = 0; + int err; + int n; + int flags = SM_FLAG_DEFAULT; + char *site_to_flush = 0; + char *id_to_flush = 0; + char *encoding = 0; + char *qtime = 0; + const char *errstr; + uid_t uid; + const char *rewrite_context = MAIL_ATTR_RWR_LOCAL; + int dsn_notify = 0; + int dsn_ret = 0; + const char *dsn_envid = 0; + int saved_optind; + ARGV *import_env; + char *alias_map_from_args = 0; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Be consistent with file permissions. + */ + umask(022); + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal_status(EX_OSERR, "open /dev/null: %m"); + + /* + * The CDE desktop calendar manager leaks a parent file descriptor into + * the child process. For the sake of sendmail compatibility we have to + * close the file descriptor otherwise mail notification will hang. + */ + for ( /* void */ ; fd < 100; fd++) + (void) close(fd); + + /* + * Process environment options as early as we can. We might be called + * from a set-uid (set-gid) program, so be careful with importing + * environment variables. + */ + if (safe_getenv(CONF_ENV_VERB)) + msg_verbose = 1; + if (safe_getenv(CONF_ENV_DEBUG)) + debug_me = 1; + + /* + * Initialize. Set up logging. Read the global configuration file after + * command-line processing. Set up signal handlers so that we can clean + * up incomplete output. + */ + if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) + argv[0] = slash + 1; + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_cleanup(tempfail); + maillog_client_init(mail_task("sendmail"), MAILLOG_CLIENT_FLAG_NONE); + set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Some sites mistakenly install Postfix sendmail as set-uid root. Drop + * set-uid privileges only when root, otherwise some systems will not + * reset the saved set-userid, which would be a security vulnerability. + */ + if (geteuid() == 0 && getuid() != 0) { + msg_warn("the Postfix sendmail command has set-uid root file permissions"); + msg_warn("or the command is run from a set-uid root process"); + msg_warn("the Postfix sendmail command must be installed without set-uid root file permissions"); + set_ugid(getuid(), getgid()); + } + + /* + * Further initialization. Load main.cf first, so that command-line + * options can override main.cf settings. Pre-scan the argument list so + * that we load the right main.cf file. + */ +#define GETOPT_LIST "A:B:C:F:GIL:N:O:R:UV:X:b:ce:f:h:imno:p:r:q:tvx" + + saved_optind = optind; + while (argv[OPTIND] != 0) { + if (strcmp(argv[OPTIND], "-q") == 0) { /* not getopt compatible */ + optind++; + continue; + } + if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0) + break; + if (c == 'C') { + VSTRING *buf = vstring_alloc(1); + char *dir; + + dir = strcmp(sane_basename(buf, optarg), MAIN_CONF_FILE) == 0 ? + sane_dirname(buf, optarg) : optarg; + if (strcmp(dir, DEF_CONFIG_DIR) != 0 && geteuid() != 0) + mail_conf_checkdir(dir); + if (setenv(CONF_ENV_PATH, dir, 1) < 0) + msg_fatal_status(EX_UNAVAILABLE, "out of memory"); + vstring_free(buf); + } + } + optind = saved_optind; + mail_conf_read(); + /* Enforce consistent operation of different Postfix parts. */ + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + update_env(import_env->argv); + argv_free(import_env); + /* Re-evaluate mail_task() after reading main.cf. */ + maillog_client_init(mail_task("sendmail"), MAILLOG_CLIENT_FLAG_NONE); + get_mail_conf_str_table(str_table); + + mail_dict_init(); + + if (chdir(var_queue_dir)) + msg_fatal_status(EX_UNAVAILABLE, "chdir %s: %m", var_queue_dir); + + signal(SIGPIPE, SIG_IGN); + + /* + * Optionally start the debugger on ourself. This must be done after + * reading the global configuration file, because that file specifies + * what debugger command to execute. + */ + if (debug_me) + debug_process(); + + /* + * The default mode of operation is determined by the process name. It + * can, however, be changed via command-line options (for example, + * "newaliases -bp" will show the mail queue). + */ + if (strcmp(argv[0], "mailq") == 0) { + mode = SM_MODE_MAILQ; + } else if (strcmp(argv[0], "newaliases") == 0) { + mode = SM_MODE_NEWALIAS; + } else if (strcmp(argv[0], "smtpd") == 0) { + mode = SM_MODE_DAEMON; + } else { + mode = SM_MODE_ENQUEUE; + } + + /* + * Parse JCL. Sendmail has been around for a long time, and has acquired + * a large number of options in the course of time. Some options such as + * -q are not parsable with GETOPT() and get special treatment. + */ +#define OPTIND (optind > 0 ? optind : 1) + + while (argv[OPTIND] != 0) { + if (strcmp(argv[OPTIND], "-q") == 0) { + if (mode == SM_MODE_DAEMON) + msg_warn("ignoring -q option in daemon mode"); + else + mode = SM_MODE_FLUSHQ; + optind++; + continue; + } + if (strcmp(argv[OPTIND], "-V") == 0 + && argv[OPTIND + 1] != 0 && strlen(argv[OPTIND + 1]) == 2) { + msg_warn("option -V is deprecated with Postfix 2.3; " + "specify -XV instead"); + argv[OPTIND] = "-XV"; + } + if (strncmp(argv[OPTIND], "-V", 2) == 0 && strlen(argv[OPTIND]) == 4) { + msg_warn("option %s is deprecated with Postfix 2.3; " + "specify -X%s instead", + argv[OPTIND], argv[OPTIND] + 1); + argv[OPTIND] = concatenate("-X", argv[OPTIND] + 1, (char *) 0); + } + if (strcmp(argv[OPTIND], "-XV") == 0) { + verp_delims = var_verp_delims; + optind++; + continue; + } + if ((c = GETOPT(argc, argv, GETOPT_LIST)) <= 0) + break; + switch (c) { + default: + if (msg_verbose) + msg_info("-%c option ignored", c); + break; + case 'n': + msg_fatal_status(EX_USAGE, "-%c option not supported", c); + case 'B': + if (strcmp(optarg, "8BITMIME") == 0)/* RFC 1652 */ + encoding = MAIL_ATTR_ENC_8BIT; + else if (strcmp(optarg, "7BIT") == 0) /* RFC 1652 */ + encoding = MAIL_ATTR_ENC_7BIT; + else + msg_fatal_status(EX_USAGE, "-B option needs 8BITMIME or 7BIT"); + break; + case 'F': /* full name */ + full_name = optarg; + break; + case 'G': /* gateway submission */ + rewrite_context = MAIL_ATTR_RWR_REMOTE; + break; + case 'I': /* newaliases */ + mode = SM_MODE_NEWALIAS; + break; + case 'N': + if ((dsn_notify = dsn_notify_mask(optarg)) == 0) + msg_warn("bad -N option value -- ignored"); + break; + case 'R': + if ((dsn_ret = dsn_ret_code(optarg)) == 0) + msg_warn("bad -R option value -- ignored"); + break; + case 'V': /* DSN, was: VERP */ + if (strlen(optarg) > 100) + msg_warn("too long -V option value -- ignored"); + else if (!allprint(optarg)) + msg_warn("bad syntax in -V option value -- ignored"); + else + dsn_envid = optarg; + break; + case 'X': + switch (*optarg) { + default: + msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg); + case 'V': /* VERP */ + if (verp_delims_verify(optarg + 1) != 0) + msg_fatal_status(EX_USAGE, "-V requires two characters from %s", + var_verp_filter); + verp_delims = optarg + 1; + break; + } + break; + case 'b': + switch (*optarg) { + default: + msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg); + case 'd': /* daemon mode */ + case 'l': /* daemon mode */ + if (mode == SM_MODE_FLUSHQ) + msg_warn("ignoring -q option in daemon mode"); + mode = SM_MODE_DAEMON; + break; + case 'h': /* print host status */ + case 'H': /* flush host status */ + mode = SM_MODE_IGNORE; + break; + case 'i': /* newaliases */ + mode = SM_MODE_NEWALIAS; + break; + case 'm': /* deliver mail */ + mode = SM_MODE_ENQUEUE; + break; + case 'p': /* mailq */ + mode = SM_MODE_MAILQ; + break; + case 's': /* stand-alone mode */ + mode = SM_MODE_USER; + break; + case 'v': /* expand recipients */ + flags |= DEL_REQ_FLAG_USR_VRFY; + break; + } + break; + case 'f': + sender = optarg; + break; + case 'i': + flags &= ~SM_FLAG_AEOF; + break; + case 'o': + switch (*optarg) { + default: + if (msg_verbose) + msg_info("-%c%c option ignored", c, *optarg); + break; + case 'A': + if (optarg[1] == 0) + msg_fatal_status(EX_USAGE, "-oA requires pathname"); + alias_map_from_args = optarg + 1; + break; + case '7': + case '8': + break; + case 'i': + flags &= ~SM_FLAG_AEOF; + break; + case 'm': + break; + } + break; + case 'r': /* obsoleted by -f */ + sender = optarg; + break; + case 'q': + if (ISDIGIT(optarg[0])) { + qtime = optarg; + } else if (optarg[0] == 'R') { + site_to_flush = optarg + 1; + if (*site_to_flush == 0) + msg_fatal_status(EX_USAGE, "specify: -qRsitename"); + } else if (optarg[0] == 'I') { + id_to_flush = optarg + 1; + if (*id_to_flush == 0) + msg_fatal_status(EX_USAGE, "specify: -qIqueueid"); + } else { + msg_fatal_status(EX_USAGE, "-q%c is not implemented", + optarg[0]); + } + break; + case 't': + flags |= SM_FLAG_XRCPT; + break; + case 'v': + msg_verbose++; + break; + case '?': + msg_fatal_status(EX_USAGE, "usage: %s [options]", argv[0]); + } + } + + /* + * Look for conflicting options and arguments. + */ + if ((flags & SM_FLAG_XRCPT) && mode != SM_MODE_ENQUEUE) + msg_fatal_status(EX_USAGE, "-t can be used only in delivery mode"); + + if (site_to_flush && mode != SM_MODE_ENQUEUE) + msg_fatal_status(EX_USAGE, "-qR can be used only in delivery mode"); + + if (id_to_flush && mode != SM_MODE_ENQUEUE) + msg_fatal_status(EX_USAGE, "-qI can be used only in delivery mode"); + + if (flags & DEL_REQ_FLAG_USR_VRFY) { + if (flags & SM_FLAG_XRCPT) + msg_fatal_status(EX_USAGE, "-t option cannot be used with -bv"); + if (dsn_notify) + msg_fatal_status(EX_USAGE, "-N option cannot be used with -bv"); + if (dsn_ret) + msg_fatal_status(EX_USAGE, "-R option cannot be used with -bv"); + if (msg_verbose == 1) + msg_fatal_status(EX_USAGE, "-v option cannot be used with -bv"); + } + + /* + * The -v option plays double duty. One requests verbose delivery, more + * than one requests verbose logging. + */ + if (msg_verbose == 1 && mode == SM_MODE_ENQUEUE) { + msg_verbose = 0; + flags |= DEL_REQ_FLAG_RECORD; + } + + /* + * Start processing. Everything is delegated to external commands. + */ + if (qtime && mode != SM_MODE_DAEMON) + exit(0); + switch (mode) { + default: + msg_panic("unknown operation mode: %d", mode); + /* NOTREACHED */ + case SM_MODE_ENQUEUE: + if (site_to_flush) { + if (argv[OPTIND]) + msg_fatal_status(EX_USAGE, "flush site requires no recipient"); + ext_argv = argv_alloc(2); + argv_add(ext_argv, "postqueue", "-s", site_to_flush, (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(ext_argv, "-v", (char *) 0); + argv_terminate(ext_argv); + mail_run_replace(var_command_dir, ext_argv->argv); + /* NOTREACHED */ + } else if (id_to_flush) { + if (argv[OPTIND]) + msg_fatal_status(EX_USAGE, "flush queue_id requires no recipient"); + ext_argv = argv_alloc(2); + argv_add(ext_argv, "postqueue", "-i", id_to_flush, (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(ext_argv, "-v", (char *) 0); + argv_terminate(ext_argv); + mail_run_replace(var_command_dir, ext_argv->argv); + /* NOTREACHED */ + } else { + enqueue(flags, encoding, dsn_envid, dsn_ret, dsn_notify, + rewrite_context, sender, full_name, argv + OPTIND); + exit(0); + /* NOTREACHED */ + } + break; + case SM_MODE_MAILQ: + if (argv[OPTIND]) + msg_fatal_status(EX_USAGE, + "display queue mode requires no recipient"); + ext_argv = argv_alloc(2); + argv_add(ext_argv, "postqueue", "-p", (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(ext_argv, "-v", (char *) 0); + argv_terminate(ext_argv); + mail_run_replace(var_command_dir, ext_argv->argv); + /* NOTREACHED */ + case SM_MODE_FLUSHQ: + if (argv[OPTIND]) + msg_fatal_status(EX_USAGE, + "flush queue mode requires no recipient"); + ext_argv = argv_alloc(2); + argv_add(ext_argv, "postqueue", "-f", (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(ext_argv, "-v", (char *) 0); + argv_terminate(ext_argv); + mail_run_replace(var_command_dir, ext_argv->argv); + /* NOTREACHED */ + case SM_MODE_DAEMON: + if (argv[OPTIND]) + msg_fatal_status(EX_USAGE, "daemon mode requires no recipient"); + ext_argv = argv_alloc(2); + argv_add(ext_argv, "postfix", (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(ext_argv, "-v", (char *) 0); + argv_add(ext_argv, "start", (char *) 0); + argv_terminate(ext_argv); + err = (mail_run_background(var_command_dir, ext_argv->argv) < 0); + argv_free(ext_argv); + exit(err); + break; + case SM_MODE_NEWALIAS: + if (argv[OPTIND]) + msg_fatal_status(EX_USAGE, + "alias initialization mode requires no recipient"); + if (alias_map_from_args == 0 && *var_alias_db_map == 0) + return (0); + ext_argv = argv_alloc(3); + argv_add(ext_argv, "postalias", (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(ext_argv, "-v", (char *) 0); + argv_add(ext_argv, "--", (char *) 0); + if (alias_map_from_args != 0) + argv_add(ext_argv, alias_map_from_args, (char *) 0); + else + argv_split_append(ext_argv, var_alias_db_map, CHARS_COMMA_SP); + argv_terminate(ext_argv); + mail_run_replace(var_command_dir, ext_argv->argv); + /* NOTREACHED */ + case SM_MODE_USER: + if (argv[OPTIND]) + msg_fatal_status(EX_USAGE, + "stand-alone mode requires no recipient"); + /* The actual enforcement happens in the postdrop command. */ + if ((errstr = check_user_acl_byuid(VAR_SUBMIT_ACL, var_submit_acl, + uid = getuid())) != 0) + msg_fatal_status(EX_NOPERM, + "User %s(%ld) is not allowed to submit mail", + errstr, (long) uid); + ext_argv = argv_alloc(2); + argv_add(ext_argv, "smtpd", "-S", (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(ext_argv, "-v", (char *) 0); + argv_terminate(ext_argv); + mail_run_replace(var_daemon_dir, ext_argv->argv); + /* NOTREACHED */ + case SM_MODE_IGNORE: + exit(0); + /* NOTREACHED */ + } +} diff --git a/src/showq/.indent.pro b/src/showq/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/showq/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/showq/.printfck b/src/showq/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/showq/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/showq/Makefile.in b/src/showq/Makefile.in new file mode 100644 index 0000000..47b82e7 --- /dev/null +++ b/src/showq/Makefile.in @@ -0,0 +1,95 @@ +SHELL = /bin/sh +SRCS = showq.c +OBJS = showq.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = showq +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +showq.o: ../../include/attr.h +showq.o: ../../include/bounce_log.h +showq.o: ../../include/check_arg.h +showq.o: ../../include/dsn.h +showq.o: ../../include/dsn_buf.h +showq.o: ../../include/htable.h +showq.o: ../../include/iostuff.h +showq.o: ../../include/mail_addr.h +showq.o: ../../include/mail_conf.h +showq.o: ../../include/mail_date.h +showq.o: ../../include/mail_open_ok.h +showq.o: ../../include/mail_params.h +showq.o: ../../include/mail_proto.h +showq.o: ../../include/mail_queue.h +showq.o: ../../include/mail_scan_dir.h +showq.o: ../../include/mail_server.h +showq.o: ../../include/mail_version.h +showq.o: ../../include/msg.h +showq.o: ../../include/mymalloc.h +showq.o: ../../include/nvtable.h +showq.o: ../../include/quote_822_local.h +showq.o: ../../include/quote_flags.h +showq.o: ../../include/rcpt_buf.h +showq.o: ../../include/rec_type.h +showq.o: ../../include/recipient_list.h +showq.o: ../../include/record.h +showq.o: ../../include/scan_dir.h +showq.o: ../../include/stringops.h +showq.o: ../../include/sys_defs.h +showq.o: ../../include/vbuf.h +showq.o: ../../include/vstream.h +showq.o: ../../include/vstring.h +showq.o: ../../include/vstring_vstream.h +showq.o: showq.c diff --git a/src/showq/showq.c b/src/showq/showq.c new file mode 100644 index 0000000..b8dd7e9 --- /dev/null +++ b/src/showq/showq.c @@ -0,0 +1,438 @@ +/*++ +/* NAME +/* showq 8 +/* SUMMARY +/* list the Postfix mail queue +/* SYNOPSIS +/* \fBshowq\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBshowq\fR(8) daemon reports the Postfix mail queue status. +/* The output is meant to be formatted by the postqueue(1) command, +/* as it emulates the Sendmail `mailq' command. +/* +/* The \fBshowq\fR(8) daemon can also be run in stand-alone mode +/* by the superuser. This mode of operation is used to emulate +/* the `mailq' command while the Postfix mail system is down. +/* SECURITY +/* .ad +/* .fi +/* The \fBshowq\fR(8) daemon can run in a chroot jail at fixed low +/* privilege, and takes no input from the client. Its service port +/* is accessible to local untrusted users, so the service can be +/* susceptible to denial of service attacks. +/* STANDARDS +/* .ad +/* .fi +/* None. The \fBshowq\fR(8) daemon does not interact with the +/* outside world. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically as \fBshowq\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBduplicate_filter_limit (1000)\fR" +/* The maximal number of addresses remembered by the address +/* duplicate filter for \fBaliases\fR(5) or \fBvirtual\fR(5) alias expansion, or +/* for \fBshowq\fR(8) queue displays. +/* .IP "\fBempty_address_recipient (MAILER-DAEMON)\fR" +/* The recipient of mail addressed to the null address. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBenable_long_queue_ids (no)\fR" +/* Enable long, non-repeating, queue IDs (queue file names). +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* FILES +/* /var/spool/postfix, queue directories +/* SEE ALSO +/* pickup(8), local mail pickup service +/* cleanup(8), canonicalize and enqueue mail +/* qmgr(8), queue manager +/* postconf(5), configuration parameters +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include + +/* Single-threaded server skeleton. */ + +#include + +/* Application-specific. */ + +int var_dup_filter_limit; +char *var_empty_addr; + +static void showq_reasons(VSTREAM *, BOUNCE_LOG *, RCPT_BUF *, DSN_BUF *, + HTABLE *); + +#define STR(x) vstring_str(x) + +/* showq_report - report status of sender and recipients */ + +static void showq_report(VSTREAM *client, char *queue, char *id, + VSTREAM *qfile, long size, time_t mtime, + mode_t mode) +{ + VSTRING *buf = vstring_alloc(100); + VSTRING *printable_quoted_addr = vstring_alloc(100); + int rec_type; + time_t arrival_time = 0; + char *start; + long msg_size = size; + BOUNCE_LOG *logfile; + HTABLE *dup_filter = 0; + RCPT_BUF *rcpt_buf = 0; + DSN_BUF *dsn_buf = 0; + int sender_seen = 0; + int msg_size_ok = 0; + + /* + * Let the optimizer worry about eliminating duplicate code. + */ +#define SHOWQ_CLEANUP_AND_RETURN { \ + if (sender_seen > 0) \ + attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END); \ + vstring_free(buf); \ + vstring_free(printable_quoted_addr); \ + if (rcpt_buf) \ + rcpb_free(rcpt_buf); \ + if (dsn_buf) \ + dsb_free(dsn_buf); \ + if (dup_filter) \ + htable_free(dup_filter, (void (*) (void *)) 0); \ + } + + /* + * XXX addresses in defer logfiles are in printable quoted form, while + * addresses in message envelope records are in raw unquoted form. This + * may change once we replace the present ad-hoc bounce/defer logfile + * format by one that is transparent for control etc. characters. See + * also: bounce/bounce_append_service.c. + * + * XXX With Postfix <= 2.0, "postsuper -r" results in obsolete size records + * from previous cleanup runs. Skip the obsolete size records. + */ + while (!vstream_ferror(client) && (rec_type = rec_get(qfile, buf, 0)) > 0) { + start = vstring_str(buf); + if (msg_verbose) + msg_info("record %c %s", rec_type, printable(start, '?')); + switch (rec_type) { + case REC_TYPE_TIME: + /* TODO: parse seconds and microseconds. */ + if (arrival_time == 0) + arrival_time = atol(start); + break; + case REC_TYPE_SIZE: + if (msg_size_ok == 0) { + msg_size_ok = (start[strspn(start, "0123456789 ")] == 0 + && (msg_size = atol(start)) >= 0); + if (msg_size_ok == 0) { + msg_warn("%s: malformed size record: %.100s " + "-- using file size instead", + id, printable(start, '?')); + msg_size = size; + } + } + break; + case REC_TYPE_FROM: + if (*start == 0) + start = var_empty_addr; + quote_822_local(printable_quoted_addr, start); + printable(STR(printable_quoted_addr), '?'); + if (sender_seen++ > 0) { + msg_warn("%s: duplicate sender address: %s " + "-- skipping remainder of this file", + id, STR(printable_quoted_addr)); + SHOWQ_CLEANUP_AND_RETURN; + } + attr_print(client, ATTR_FLAG_MORE, + SEND_ATTR_STR(MAIL_ATTR_QUEUE, queue), + SEND_ATTR_STR(MAIL_ATTR_QUEUEID, id), + SEND_ATTR_LONG(MAIL_ATTR_TIME, arrival_time > 0 ? + arrival_time : mtime), + SEND_ATTR_LONG(MAIL_ATTR_SIZE, msg_size), + SEND_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE, + (mode & MAIL_QUEUE_STAT_EXPIRE) != 0), + SEND_ATTR_STR(MAIL_ATTR_SENDER, + STR(printable_quoted_addr)), + ATTR_TYPE_END); + break; + case REC_TYPE_RCPT: + if (sender_seen == 0) { + msg_warn("%s: missing sender address: %s " + "-- skipping remainder of this file", + id, STR(printable_quoted_addr)); + SHOWQ_CLEANUP_AND_RETURN; + } + if (*start == 0) /* can't happen? */ + start = var_empty_addr; + quote_822_local(printable_quoted_addr, start); + printable(STR(printable_quoted_addr), '?'); + if (dup_filter == 0 + || htable_locate(dup_filter, STR(printable_quoted_addr)) == 0) + attr_print(client, ATTR_FLAG_MORE, + SEND_ATTR_STR(MAIL_ATTR_RECIP, + STR(printable_quoted_addr)), + SEND_ATTR_STR(MAIL_ATTR_WHY, ""), + ATTR_TYPE_END); + break; + case REC_TYPE_MESG: + if (msg_size_ok && vstream_fseek(qfile, msg_size, SEEK_CUR) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile)); + break; + case REC_TYPE_END: + break; + } + + /* + * Before listing any recipients from the queue file, try to list + * recipients from the corresponding defer logfile with per-recipient + * descriptions why delivery was deferred. + * + * The defer logfile is not necessarily complete: delivery may be + * interrupted (postfix stop or reload) before all recipients have + * been tried. + * + * Therefore we keep a record of recipients found in the defer logfile, + * and try to avoid listing those recipients again when processing + * recipients from the queue file. + */ + if (rec_type == REC_TYPE_FROM + && (logfile = bounce_log_open(MAIL_QUEUE_DEFER, id, O_RDONLY, 0)) != 0) { + if (dup_filter != 0) + msg_panic("showq_report: attempt to reuse duplicate filter"); + dup_filter = htable_create(var_dup_filter_limit); + if (rcpt_buf == 0) + rcpt_buf = rcpb_create(); + if (dsn_buf == 0) + dsn_buf = dsb_create(); + showq_reasons(client, logfile, rcpt_buf, dsn_buf, dup_filter); + if (bounce_log_close(logfile)) + msg_warn("close %s %s: %m", MAIL_QUEUE_DEFER, id); + } + } + SHOWQ_CLEANUP_AND_RETURN; +} + +/* showq_reasons - show deferral reasons */ + +static void showq_reasons(VSTREAM *client, BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf, + DSN_BUF *dsn_buf, HTABLE *dup_filter) +{ + RECIPIENT *rcpt = &rcpt_buf->rcpt; + DSN *dsn = &dsn_buf->dsn; + + while (bounce_log_read(bp, rcpt_buf, dsn_buf) != 0) { + + /* + * Update the duplicate filter. + */ + if (var_dup_filter_limit == 0 + || dup_filter->used < var_dup_filter_limit) + if (htable_locate(dup_filter, rcpt->address) == 0) + htable_enter(dup_filter, rcpt->address, (void *) 0); + + attr_print(client, ATTR_FLAG_MORE, + SEND_ATTR_STR(MAIL_ATTR_RECIP, rcpt->address), + SEND_ATTR_STR(MAIL_ATTR_WHY, dsn->reason), + ATTR_TYPE_END); + } +} + + +/* showq_service - service client */ + +static void showq_service(VSTREAM *client, char *unused_service, char **argv) +{ + VSTREAM *qfile; + const char *path; + int status; + char *id; + struct stat st; + struct queue_info { + char *name; /* queue name */ + char *(*scan_next) (SCAN_DIR *); /* flat or recursive */ + }; + struct queue_info *qp; + + static struct queue_info queue_info[] = { + MAIL_QUEUE_MAILDROP, scan_dir_next, + MAIL_QUEUE_ACTIVE, mail_scan_dir_next, + MAIL_QUEUE_INCOMING, mail_scan_dir_next, + MAIL_QUEUE_DEFERRED, mail_scan_dir_next, + MAIL_QUEUE_HOLD, mail_scan_dir_next, + 0, + }; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Protocol identification. + */ + (void) attr_print(client, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SHOWQ), + ATTR_TYPE_END); + + /* + * Skip any files that have the wrong permissions. If we can't open an + * existing file, assume the system is out of resources or that it is + * mis-configured, and force backoff by raising a fatal error. + */ + for (qp = queue_info; qp->name != 0; qp++) { + SCAN_DIR *scan = scan_dir_open(qp->name); + char *saved_id = 0; + + while ((id = qp->scan_next(scan)) != 0) { + + /* + * XXX I have seen showq loop on the same queue id. That would be + * an operating system bug, but who cares whose fault it is. Make + * sure this will never happen again. + */ + if (saved_id) { + if (strcmp(saved_id, id) == 0) { + msg_warn("readdir loop on queue %s id %s", qp->name, id); + break; + } + myfree(saved_id); + } + saved_id = mystrdup(id); + status = mail_open_ok(qp->name, id, &st, &path); + if (status == MAIL_OPEN_YES) { + if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) { + showq_report(client, qp->name, id, qfile, (long) st.st_size, + st.st_mtime, st.st_mode); + if (vstream_fclose(qfile)) + msg_warn("close file %s %s: %m", qp->name, id); + } else if (errno != ENOENT) { + msg_warn("open %s %s: %m", qp->name, id); + } + } + vstream_fflush(client); + } + if (saved_id) + myfree(saved_id); + scan_dir_close(scan); + } + attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded server skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_INT_TABLE int_table[] = { + VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0, + 0, + }; + CONFIG_STR_TABLE str_table[] = { + VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, showq_service, + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + 0); +} diff --git a/src/smtp/.indent.pro b/src/smtp/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/smtp/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/smtp/.printfck b/src/smtp/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/smtp/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/smtp/Makefile.in b/src/smtp/Makefile.in new file mode 100644 index 0000000..65f4198 --- /dev/null +++ b/src/smtp/Makefile.in @@ -0,0 +1,878 @@ +SHELL = /bin/sh +SRCS = smtp.c smtp_connect.c smtp_proto.c smtp_chat.c smtp_session.c \ + smtp_addr.c smtp_trouble.c smtp_state.c smtp_rcpt.c smtp_tls_policy.c \ + smtp_sasl_proto.c smtp_sasl_glue.c smtp_reuse.c smtp_map11.c \ + smtp_sasl_auth_cache.c smtp_key.c smtp_misc.c +OBJS = smtp.o smtp_connect.o smtp_proto.o smtp_chat.o smtp_session.o \ + smtp_addr.o smtp_trouble.o smtp_state.o smtp_rcpt.o smtp_tls_policy.o \ + smtp_sasl_proto.o smtp_sasl_glue.o smtp_reuse.o smtp_map11.o \ + smtp_sasl_auth_cache.o smtp_key.o smtp_misc.o +HDRS = smtp.h smtp_sasl.h smtp_addr.h smtp_reuse.h smtp_sasl_auth_cache.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= smtp_unalias smtp_map11 +PROG = smtp +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/libxsasl.a \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: smtp_map11_test + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +smtp.o: smtp.c smtp_params.c lmtp_params.c + +lmtp_params.c: smtp_params.c + egrep -v -f smtp-only smtp_params.c | \ + sed 's/SMTP/LMTP/g; s/smtp_\([a-z]*_table\)/lmtp_\1/' >$@ + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +smtp_unalias: smtp_unalias.c $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS) + +smtp_map11: smtp_map11.c $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS) + +smtp_map11_test: smtp_map11 smtp_map11.ref + $(SHLIB_ENV) $(VALGRIND) ./smtp_map11 smtp_map11.tmp 2>&1 + diff smtp_map11.ref smtp_map11.tmp + rm -f smtp_map11.tmp + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +lmtp_params.o: lmtp_params.c +smtp.o: ../../include/argv.h +smtp.o: ../../include/attr.h +smtp.o: ../../include/byte_mask.h +smtp.o: ../../include/check_arg.h +smtp.o: ../../include/debug_peer.h +smtp.o: ../../include/deliver_request.h +smtp.o: ../../include/delivered_hdr.h +smtp.o: ../../include/dict.h +smtp.o: ../../include/dns.h +smtp.o: ../../include/dsn.h +smtp.o: ../../include/dsn_buf.h +smtp.o: ../../include/ext_prop.h +smtp.o: ../../include/flush_clnt.h +smtp.o: ../../include/fold_addr.h +smtp.o: ../../include/header_body_checks.h +smtp.o: ../../include/header_opts.h +smtp.o: ../../include/hfrom_format.h +smtp.o: ../../include/htable.h +smtp.o: ../../include/iostuff.h +smtp.o: ../../include/mail_conf.h +smtp.o: ../../include/mail_params.h +smtp.o: ../../include/mail_proto.h +smtp.o: ../../include/mail_server.h +smtp.o: ../../include/mail_version.h +smtp.o: ../../include/maps.h +smtp.o: ../../include/match_list.h +smtp.o: ../../include/mime_state.h +smtp.o: ../../include/msg.h +smtp.o: ../../include/msg_stats.h +smtp.o: ../../include/myaddrinfo.h +smtp.o: ../../include/myflock.h +smtp.o: ../../include/mymalloc.h +smtp.o: ../../include/name_code.h +smtp.o: ../../include/name_mask.h +smtp.o: ../../include/nvtable.h +smtp.o: ../../include/recipient_list.h +smtp.o: ../../include/resolve_clnt.h +smtp.o: ../../include/scache.h +smtp.o: ../../include/sock_addr.h +smtp.o: ../../include/string_list.h +smtp.o: ../../include/stringops.h +smtp.o: ../../include/sys_defs.h +smtp.o: ../../include/tls.h +smtp.o: ../../include/tls_proxy.h +smtp.o: ../../include/tok822.h +smtp.o: ../../include/vbuf.h +smtp.o: ../../include/vstream.h +smtp.o: ../../include/vstring.h +smtp.o: lmtp_params.c +smtp.o: smtp.c +smtp.o: smtp.h +smtp.o: smtp_params.c +smtp.o: smtp_sasl.h +smtp_addr.o: ../../include/argv.h +smtp_addr.o: ../../include/attr.h +smtp_addr.o: ../../include/check_arg.h +smtp_addr.o: ../../include/deliver_request.h +smtp_addr.o: ../../include/dict.h +smtp_addr.o: ../../include/dns.h +smtp_addr.o: ../../include/dsn.h +smtp_addr.o: ../../include/dsn_buf.h +smtp_addr.o: ../../include/header_body_checks.h +smtp_addr.o: ../../include/header_opts.h +smtp_addr.o: ../../include/htable.h +smtp_addr.o: ../../include/inet_addr_list.h +smtp_addr.o: ../../include/inet_proto.h +smtp_addr.o: ../../include/mail_params.h +smtp_addr.o: ../../include/maps.h +smtp_addr.o: ../../include/match_list.h +smtp_addr.o: ../../include/midna_domain.h +smtp_addr.o: ../../include/mime_state.h +smtp_addr.o: ../../include/msg.h +smtp_addr.o: ../../include/msg_stats.h +smtp_addr.o: ../../include/myaddrinfo.h +smtp_addr.o: ../../include/myflock.h +smtp_addr.o: ../../include/mymalloc.h +smtp_addr.o: ../../include/name_code.h +smtp_addr.o: ../../include/name_mask.h +smtp_addr.o: ../../include/nvtable.h +smtp_addr.o: ../../include/own_inet_addr.h +smtp_addr.o: ../../include/recipient_list.h +smtp_addr.o: ../../include/resolve_clnt.h +smtp_addr.o: ../../include/scache.h +smtp_addr.o: ../../include/sock_addr.h +smtp_addr.o: ../../include/string_list.h +smtp_addr.o: ../../include/stringops.h +smtp_addr.o: ../../include/sys_defs.h +smtp_addr.o: ../../include/tls.h +smtp_addr.o: ../../include/tls_proxy.h +smtp_addr.o: ../../include/tok822.h +smtp_addr.o: ../../include/vbuf.h +smtp_addr.o: ../../include/vstream.h +smtp_addr.o: ../../include/vstring.h +smtp_addr.o: smtp.h +smtp_addr.o: smtp_addr.c +smtp_addr.o: smtp_addr.h +smtp_chat.o: ../../include/argv.h +smtp_chat.o: ../../include/attr.h +smtp_chat.o: ../../include/check_arg.h +smtp_chat.o: ../../include/cleanup_user.h +smtp_chat.o: ../../include/deliver_request.h +smtp_chat.o: ../../include/dict.h +smtp_chat.o: ../../include/dns.h +smtp_chat.o: ../../include/dsn.h +smtp_chat.o: ../../include/dsn_buf.h +smtp_chat.o: ../../include/dsn_util.h +smtp_chat.o: ../../include/header_body_checks.h +smtp_chat.o: ../../include/header_opts.h +smtp_chat.o: ../../include/hfrom_format.h +smtp_chat.o: ../../include/htable.h +smtp_chat.o: ../../include/int_filt.h +smtp_chat.o: ../../include/iostuff.h +smtp_chat.o: ../../include/line_wrap.h +smtp_chat.o: ../../include/mail_addr.h +smtp_chat.o: ../../include/mail_error.h +smtp_chat.o: ../../include/mail_params.h +smtp_chat.o: ../../include/mail_proto.h +smtp_chat.o: ../../include/maps.h +smtp_chat.o: ../../include/match_list.h +smtp_chat.o: ../../include/mime_state.h +smtp_chat.o: ../../include/msg.h +smtp_chat.o: ../../include/msg_stats.h +smtp_chat.o: ../../include/myaddrinfo.h +smtp_chat.o: ../../include/myflock.h +smtp_chat.o: ../../include/mymalloc.h +smtp_chat.o: ../../include/name_code.h +smtp_chat.o: ../../include/name_mask.h +smtp_chat.o: ../../include/nvtable.h +smtp_chat.o: ../../include/post_mail.h +smtp_chat.o: ../../include/recipient_list.h +smtp_chat.o: ../../include/resolve_clnt.h +smtp_chat.o: ../../include/scache.h +smtp_chat.o: ../../include/smtp_stream.h +smtp_chat.o: ../../include/smtputf8.h +smtp_chat.o: ../../include/sock_addr.h +smtp_chat.o: ../../include/string_list.h +smtp_chat.o: ../../include/stringops.h +smtp_chat.o: ../../include/sys_defs.h +smtp_chat.o: ../../include/tls.h +smtp_chat.o: ../../include/tls_proxy.h +smtp_chat.o: ../../include/tok822.h +smtp_chat.o: ../../include/vbuf.h +smtp_chat.o: ../../include/vstream.h +smtp_chat.o: ../../include/vstring.h +smtp_chat.o: smtp.h +smtp_chat.o: smtp_chat.c +smtp_connect.o: ../../include/argv.h +smtp_connect.o: ../../include/attr.h +smtp_connect.o: ../../include/check_arg.h +smtp_connect.o: ../../include/deliver_pass.h +smtp_connect.o: ../../include/deliver_request.h +smtp_connect.o: ../../include/dict.h +smtp_connect.o: ../../include/dns.h +smtp_connect.o: ../../include/dsn.h +smtp_connect.o: ../../include/dsn_buf.h +smtp_connect.o: ../../include/header_body_checks.h +smtp_connect.o: ../../include/header_opts.h +smtp_connect.o: ../../include/host_port.h +smtp_connect.o: ../../include/htable.h +smtp_connect.o: ../../include/inet_addr_list.h +smtp_connect.o: ../../include/inet_proto.h +smtp_connect.o: ../../include/iostuff.h +smtp_connect.o: ../../include/known_tcp_ports.h +smtp_connect.o: ../../include/mail_addr.h +smtp_connect.o: ../../include/mail_error.h +smtp_connect.o: ../../include/mail_params.h +smtp_connect.o: ../../include/mail_proto.h +smtp_connect.o: ../../include/maps.h +smtp_connect.o: ../../include/match_list.h +smtp_connect.o: ../../include/mime_state.h +smtp_connect.o: ../../include/msg.h +smtp_connect.o: ../../include/msg_stats.h +smtp_connect.o: ../../include/myaddrinfo.h +smtp_connect.o: ../../include/myflock.h +smtp_connect.o: ../../include/mymalloc.h +smtp_connect.o: ../../include/name_code.h +smtp_connect.o: ../../include/name_mask.h +smtp_connect.o: ../../include/nvtable.h +smtp_connect.o: ../../include/own_inet_addr.h +smtp_connect.o: ../../include/recipient_list.h +smtp_connect.o: ../../include/resolve_clnt.h +smtp_connect.o: ../../include/sane_connect.h +smtp_connect.o: ../../include/scache.h +smtp_connect.o: ../../include/sock_addr.h +smtp_connect.o: ../../include/split_at.h +smtp_connect.o: ../../include/string_list.h +smtp_connect.o: ../../include/stringops.h +smtp_connect.o: ../../include/sys_defs.h +smtp_connect.o: ../../include/timed_connect.h +smtp_connect.o: ../../include/tls.h +smtp_connect.o: ../../include/tls_proxy.h +smtp_connect.o: ../../include/tok822.h +smtp_connect.o: ../../include/vbuf.h +smtp_connect.o: ../../include/vstream.h +smtp_connect.o: ../../include/vstring.h +smtp_connect.o: smtp.h +smtp_connect.o: smtp_addr.h +smtp_connect.o: smtp_connect.c +smtp_connect.o: smtp_reuse.h +smtp_key.o: ../../include/argv.h +smtp_key.o: ../../include/attr.h +smtp_key.o: ../../include/base64_code.h +smtp_key.o: ../../include/check_arg.h +smtp_key.o: ../../include/deliver_request.h +smtp_key.o: ../../include/dict.h +smtp_key.o: ../../include/dns.h +smtp_key.o: ../../include/dsn.h +smtp_key.o: ../../include/dsn_buf.h +smtp_key.o: ../../include/header_body_checks.h +smtp_key.o: ../../include/header_opts.h +smtp_key.o: ../../include/htable.h +smtp_key.o: ../../include/mail_params.h +smtp_key.o: ../../include/maps.h +smtp_key.o: ../../include/match_list.h +smtp_key.o: ../../include/mime_state.h +smtp_key.o: ../../include/msg.h +smtp_key.o: ../../include/msg_stats.h +smtp_key.o: ../../include/myaddrinfo.h +smtp_key.o: ../../include/myflock.h +smtp_key.o: ../../include/mymalloc.h +smtp_key.o: ../../include/name_code.h +smtp_key.o: ../../include/name_mask.h +smtp_key.o: ../../include/nvtable.h +smtp_key.o: ../../include/recipient_list.h +smtp_key.o: ../../include/resolve_clnt.h +smtp_key.o: ../../include/scache.h +smtp_key.o: ../../include/sock_addr.h +smtp_key.o: ../../include/string_list.h +smtp_key.o: ../../include/sys_defs.h +smtp_key.o: ../../include/tls.h +smtp_key.o: ../../include/tls_proxy.h +smtp_key.o: ../../include/tok822.h +smtp_key.o: ../../include/vbuf.h +smtp_key.o: ../../include/vstream.h +smtp_key.o: ../../include/vstring.h +smtp_key.o: smtp.h +smtp_key.o: smtp_key.c +smtp_map11.o: ../../include/argv.h +smtp_map11.o: ../../include/attr.h +smtp_map11.o: ../../include/check_arg.h +smtp_map11.o: ../../include/deliver_request.h +smtp_map11.o: ../../include/dict.h +smtp_map11.o: ../../include/dns.h +smtp_map11.o: ../../include/dsn.h +smtp_map11.o: ../../include/dsn_buf.h +smtp_map11.o: ../../include/header_body_checks.h +smtp_map11.o: ../../include/header_opts.h +smtp_map11.o: ../../include/htable.h +smtp_map11.o: ../../include/mail_addr_form.h +smtp_map11.o: ../../include/mail_addr_map.h +smtp_map11.o: ../../include/maps.h +smtp_map11.o: ../../include/match_list.h +smtp_map11.o: ../../include/mime_state.h +smtp_map11.o: ../../include/msg.h +smtp_map11.o: ../../include/msg_stats.h +smtp_map11.o: ../../include/myaddrinfo.h +smtp_map11.o: ../../include/myflock.h +smtp_map11.o: ../../include/mymalloc.h +smtp_map11.o: ../../include/name_code.h +smtp_map11.o: ../../include/name_mask.h +smtp_map11.o: ../../include/nvtable.h +smtp_map11.o: ../../include/quote_822_local.h +smtp_map11.o: ../../include/quote_flags.h +smtp_map11.o: ../../include/recipient_list.h +smtp_map11.o: ../../include/resolve_clnt.h +smtp_map11.o: ../../include/scache.h +smtp_map11.o: ../../include/sock_addr.h +smtp_map11.o: ../../include/string_list.h +smtp_map11.o: ../../include/sys_defs.h +smtp_map11.o: ../../include/tls.h +smtp_map11.o: ../../include/tls_proxy.h +smtp_map11.o: ../../include/tok822.h +smtp_map11.o: ../../include/vbuf.h +smtp_map11.o: ../../include/vstream.h +smtp_map11.o: ../../include/vstring.h +smtp_map11.o: smtp.h +smtp_map11.o: smtp_map11.c +smtp_misc.o: ../../include/argv.h +smtp_misc.o: ../../include/attr.h +smtp_misc.o: ../../include/check_arg.h +smtp_misc.o: ../../include/deliver_request.h +smtp_misc.o: ../../include/dict.h +smtp_misc.o: ../../include/dns.h +smtp_misc.o: ../../include/dsn.h +smtp_misc.o: ../../include/dsn_buf.h +smtp_misc.o: ../../include/ext_prop.h +smtp_misc.o: ../../include/header_body_checks.h +smtp_misc.o: ../../include/header_opts.h +smtp_misc.o: ../../include/htable.h +smtp_misc.o: ../../include/mail_params.h +smtp_misc.o: ../../include/maps.h +smtp_misc.o: ../../include/match_list.h +smtp_misc.o: ../../include/mime_state.h +smtp_misc.o: ../../include/msg_stats.h +smtp_misc.o: ../../include/myaddrinfo.h +smtp_misc.o: ../../include/myflock.h +smtp_misc.o: ../../include/mymalloc.h +smtp_misc.o: ../../include/name_code.h +smtp_misc.o: ../../include/name_mask.h +smtp_misc.o: ../../include/nvtable.h +smtp_misc.o: ../../include/quote_821_local.h +smtp_misc.o: ../../include/quote_822_local.h +smtp_misc.o: ../../include/quote_flags.h +smtp_misc.o: ../../include/recipient_list.h +smtp_misc.o: ../../include/resolve_clnt.h +smtp_misc.o: ../../include/scache.h +smtp_misc.o: ../../include/sock_addr.h +smtp_misc.o: ../../include/string_list.h +smtp_misc.o: ../../include/sys_defs.h +smtp_misc.o: ../../include/tls.h +smtp_misc.o: ../../include/tls_proxy.h +smtp_misc.o: ../../include/tok822.h +smtp_misc.o: ../../include/vbuf.h +smtp_misc.o: ../../include/vstream.h +smtp_misc.o: ../../include/vstring.h +smtp_misc.o: smtp.h +smtp_misc.o: smtp_misc.c +smtp_params.o: smtp_params.c +smtp_proto.o: ../../include/argv.h +smtp_proto.o: ../../include/attr.h +smtp_proto.o: ../../include/bounce.h +smtp_proto.o: ../../include/check_arg.h +smtp_proto.o: ../../include/defer.h +smtp_proto.o: ../../include/deliver_request.h +smtp_proto.o: ../../include/dict.h +smtp_proto.o: ../../include/dns.h +smtp_proto.o: ../../include/dsn.h +smtp_proto.o: ../../include/dsn_buf.h +smtp_proto.o: ../../include/dsn_mask.h +smtp_proto.o: ../../include/ehlo_mask.h +smtp_proto.o: ../../include/ext_prop.h +smtp_proto.o: ../../include/header_body_checks.h +smtp_proto.o: ../../include/header_opts.h +smtp_proto.o: ../../include/htable.h +smtp_proto.o: ../../include/iostuff.h +smtp_proto.o: ../../include/lex_822.h +smtp_proto.o: ../../include/mail_addr_form.h +smtp_proto.o: ../../include/mail_addr_map.h +smtp_proto.o: ../../include/mail_params.h +smtp_proto.o: ../../include/mail_proto.h +smtp_proto.o: ../../include/mail_queue.h +smtp_proto.o: ../../include/maps.h +smtp_proto.o: ../../include/mark_corrupt.h +smtp_proto.o: ../../include/match_list.h +smtp_proto.o: ../../include/match_parent_style.h +smtp_proto.o: ../../include/mime_state.h +smtp_proto.o: ../../include/msg.h +smtp_proto.o: ../../include/msg_stats.h +smtp_proto.o: ../../include/myaddrinfo.h +smtp_proto.o: ../../include/myflock.h +smtp_proto.o: ../../include/mymalloc.h +smtp_proto.o: ../../include/namadr_list.h +smtp_proto.o: ../../include/name_code.h +smtp_proto.o: ../../include/name_mask.h +smtp_proto.o: ../../include/nvtable.h +smtp_proto.o: ../../include/off_cvt.h +smtp_proto.o: ../../include/quote_822_local.h +smtp_proto.o: ../../include/quote_flags.h +smtp_proto.o: ../../include/rec_type.h +smtp_proto.o: ../../include/recipient_list.h +smtp_proto.o: ../../include/record.h +smtp_proto.o: ../../include/resolve_clnt.h +smtp_proto.o: ../../include/scache.h +smtp_proto.o: ../../include/smtp_stream.h +smtp_proto.o: ../../include/smtputf8.h +smtp_proto.o: ../../include/sock_addr.h +smtp_proto.o: ../../include/split_at.h +smtp_proto.o: ../../include/string_list.h +smtp_proto.o: ../../include/stringops.h +smtp_proto.o: ../../include/sys_defs.h +smtp_proto.o: ../../include/tls.h +smtp_proto.o: ../../include/tls_proxy.h +smtp_proto.o: ../../include/tok822.h +smtp_proto.o: ../../include/uxtext.h +smtp_proto.o: ../../include/vbuf.h +smtp_proto.o: ../../include/vstream.h +smtp_proto.o: ../../include/vstring.h +smtp_proto.o: ../../include/vstring_vstream.h +smtp_proto.o: ../../include/xtext.h +smtp_proto.o: smtp.h +smtp_proto.o: smtp_proto.c +smtp_proto.o: smtp_sasl.h +smtp_rcpt.o: ../../include/argv.h +smtp_rcpt.o: ../../include/attr.h +smtp_rcpt.o: ../../include/bounce.h +smtp_rcpt.o: ../../include/check_arg.h +smtp_rcpt.o: ../../include/deliver_completed.h +smtp_rcpt.o: ../../include/deliver_request.h +smtp_rcpt.o: ../../include/dict.h +smtp_rcpt.o: ../../include/dns.h +smtp_rcpt.o: ../../include/dsn.h +smtp_rcpt.o: ../../include/dsn_buf.h +smtp_rcpt.o: ../../include/dsn_mask.h +smtp_rcpt.o: ../../include/header_body_checks.h +smtp_rcpt.o: ../../include/header_opts.h +smtp_rcpt.o: ../../include/htable.h +smtp_rcpt.o: ../../include/mail_params.h +smtp_rcpt.o: ../../include/maps.h +smtp_rcpt.o: ../../include/match_list.h +smtp_rcpt.o: ../../include/mime_state.h +smtp_rcpt.o: ../../include/msg.h +smtp_rcpt.o: ../../include/msg_stats.h +smtp_rcpt.o: ../../include/myaddrinfo.h +smtp_rcpt.o: ../../include/myflock.h +smtp_rcpt.o: ../../include/mymalloc.h +smtp_rcpt.o: ../../include/name_code.h +smtp_rcpt.o: ../../include/name_mask.h +smtp_rcpt.o: ../../include/nvtable.h +smtp_rcpt.o: ../../include/recipient_list.h +smtp_rcpt.o: ../../include/resolve_clnt.h +smtp_rcpt.o: ../../include/scache.h +smtp_rcpt.o: ../../include/sent.h +smtp_rcpt.o: ../../include/sock_addr.h +smtp_rcpt.o: ../../include/string_list.h +smtp_rcpt.o: ../../include/stringops.h +smtp_rcpt.o: ../../include/sys_defs.h +smtp_rcpt.o: ../../include/tls.h +smtp_rcpt.o: ../../include/tls_proxy.h +smtp_rcpt.o: ../../include/tok822.h +smtp_rcpt.o: ../../include/vbuf.h +smtp_rcpt.o: ../../include/vstream.h +smtp_rcpt.o: ../../include/vstring.h +smtp_rcpt.o: smtp.h +smtp_rcpt.o: smtp_rcpt.c +smtp_reuse.o: ../../include/argv.h +smtp_reuse.o: ../../include/attr.h +smtp_reuse.o: ../../include/check_arg.h +smtp_reuse.o: ../../include/deliver_request.h +smtp_reuse.o: ../../include/dict.h +smtp_reuse.o: ../../include/dns.h +smtp_reuse.o: ../../include/dsn.h +smtp_reuse.o: ../../include/dsn_buf.h +smtp_reuse.o: ../../include/header_body_checks.h +smtp_reuse.o: ../../include/header_opts.h +smtp_reuse.o: ../../include/htable.h +smtp_reuse.o: ../../include/mail_params.h +smtp_reuse.o: ../../include/maps.h +smtp_reuse.o: ../../include/match_list.h +smtp_reuse.o: ../../include/mime_state.h +smtp_reuse.o: ../../include/msg.h +smtp_reuse.o: ../../include/msg_stats.h +smtp_reuse.o: ../../include/myaddrinfo.h +smtp_reuse.o: ../../include/myflock.h +smtp_reuse.o: ../../include/mymalloc.h +smtp_reuse.o: ../../include/name_code.h +smtp_reuse.o: ../../include/name_mask.h +smtp_reuse.o: ../../include/nvtable.h +smtp_reuse.o: ../../include/recipient_list.h +smtp_reuse.o: ../../include/resolve_clnt.h +smtp_reuse.o: ../../include/scache.h +smtp_reuse.o: ../../include/sock_addr.h +smtp_reuse.o: ../../include/string_list.h +smtp_reuse.o: ../../include/stringops.h +smtp_reuse.o: ../../include/sys_defs.h +smtp_reuse.o: ../../include/tls.h +smtp_reuse.o: ../../include/tls_proxy.h +smtp_reuse.o: ../../include/tok822.h +smtp_reuse.o: ../../include/vbuf.h +smtp_reuse.o: ../../include/vstream.h +smtp_reuse.o: ../../include/vstring.h +smtp_reuse.o: smtp.h +smtp_reuse.o: smtp_reuse.c +smtp_reuse.o: smtp_reuse.h +smtp_sasl_auth_cache.o: ../../include/argv.h +smtp_sasl_auth_cache.o: ../../include/attr.h +smtp_sasl_auth_cache.o: ../../include/base64_code.h +smtp_sasl_auth_cache.o: ../../include/check_arg.h +smtp_sasl_auth_cache.o: ../../include/deliver_request.h +smtp_sasl_auth_cache.o: ../../include/dict.h +smtp_sasl_auth_cache.o: ../../include/dict_proxy.h +smtp_sasl_auth_cache.o: ../../include/dns.h +smtp_sasl_auth_cache.o: ../../include/dsn.h +smtp_sasl_auth_cache.o: ../../include/dsn_buf.h +smtp_sasl_auth_cache.o: ../../include/dsn_util.h +smtp_sasl_auth_cache.o: ../../include/header_body_checks.h +smtp_sasl_auth_cache.o: ../../include/header_opts.h +smtp_sasl_auth_cache.o: ../../include/htable.h +smtp_sasl_auth_cache.o: ../../include/maps.h +smtp_sasl_auth_cache.o: ../../include/match_list.h +smtp_sasl_auth_cache.o: ../../include/mime_state.h +smtp_sasl_auth_cache.o: ../../include/msg.h +smtp_sasl_auth_cache.o: ../../include/msg_stats.h +smtp_sasl_auth_cache.o: ../../include/myaddrinfo.h +smtp_sasl_auth_cache.o: ../../include/myflock.h +smtp_sasl_auth_cache.o: ../../include/mymalloc.h +smtp_sasl_auth_cache.o: ../../include/name_code.h +smtp_sasl_auth_cache.o: ../../include/name_mask.h +smtp_sasl_auth_cache.o: ../../include/nvtable.h +smtp_sasl_auth_cache.o: ../../include/recipient_list.h +smtp_sasl_auth_cache.o: ../../include/resolve_clnt.h +smtp_sasl_auth_cache.o: ../../include/scache.h +smtp_sasl_auth_cache.o: ../../include/sock_addr.h +smtp_sasl_auth_cache.o: ../../include/string_list.h +smtp_sasl_auth_cache.o: ../../include/stringops.h +smtp_sasl_auth_cache.o: ../../include/sys_defs.h +smtp_sasl_auth_cache.o: ../../include/tls.h +smtp_sasl_auth_cache.o: ../../include/tls_proxy.h +smtp_sasl_auth_cache.o: ../../include/tok822.h +smtp_sasl_auth_cache.o: ../../include/vbuf.h +smtp_sasl_auth_cache.o: ../../include/vstream.h +smtp_sasl_auth_cache.o: ../../include/vstring.h +smtp_sasl_auth_cache.o: smtp.h +smtp_sasl_auth_cache.o: smtp_sasl_auth_cache.c +smtp_sasl_auth_cache.o: smtp_sasl_auth_cache.h +smtp_sasl_glue.o: ../../include/argv.h +smtp_sasl_glue.o: ../../include/attr.h +smtp_sasl_glue.o: ../../include/check_arg.h +smtp_sasl_glue.o: ../../include/deliver_request.h +smtp_sasl_glue.o: ../../include/dict.h +smtp_sasl_glue.o: ../../include/dns.h +smtp_sasl_glue.o: ../../include/dsn.h +smtp_sasl_glue.o: ../../include/dsn_buf.h +smtp_sasl_glue.o: ../../include/header_body_checks.h +smtp_sasl_glue.o: ../../include/header_opts.h +smtp_sasl_glue.o: ../../include/htable.h +smtp_sasl_glue.o: ../../include/mail_addr_find.h +smtp_sasl_glue.o: ../../include/mail_addr_form.h +smtp_sasl_glue.o: ../../include/mail_params.h +smtp_sasl_glue.o: ../../include/maps.h +smtp_sasl_glue.o: ../../include/match_list.h +smtp_sasl_glue.o: ../../include/mime_state.h +smtp_sasl_glue.o: ../../include/msg.h +smtp_sasl_glue.o: ../../include/msg_stats.h +smtp_sasl_glue.o: ../../include/myaddrinfo.h +smtp_sasl_glue.o: ../../include/myflock.h +smtp_sasl_glue.o: ../../include/mymalloc.h +smtp_sasl_glue.o: ../../include/name_code.h +smtp_sasl_glue.o: ../../include/name_mask.h +smtp_sasl_glue.o: ../../include/nvtable.h +smtp_sasl_glue.o: ../../include/recipient_list.h +smtp_sasl_glue.o: ../../include/resolve_clnt.h +smtp_sasl_glue.o: ../../include/scache.h +smtp_sasl_glue.o: ../../include/smtp_stream.h +smtp_sasl_glue.o: ../../include/sock_addr.h +smtp_sasl_glue.o: ../../include/split_at.h +smtp_sasl_glue.o: ../../include/string_list.h +smtp_sasl_glue.o: ../../include/stringops.h +smtp_sasl_glue.o: ../../include/sys_defs.h +smtp_sasl_glue.o: ../../include/tls.h +smtp_sasl_glue.o: ../../include/tls_proxy.h +smtp_sasl_glue.o: ../../include/tok822.h +smtp_sasl_glue.o: ../../include/vbuf.h +smtp_sasl_glue.o: ../../include/vstream.h +smtp_sasl_glue.o: ../../include/vstring.h +smtp_sasl_glue.o: ../../include/xsasl.h +smtp_sasl_glue.o: smtp.h +smtp_sasl_glue.o: smtp_sasl.h +smtp_sasl_glue.o: smtp_sasl_auth_cache.h +smtp_sasl_glue.o: smtp_sasl_glue.c +smtp_sasl_proto.o: ../../include/argv.h +smtp_sasl_proto.o: ../../include/attr.h +smtp_sasl_proto.o: ../../include/check_arg.h +smtp_sasl_proto.o: ../../include/deliver_request.h +smtp_sasl_proto.o: ../../include/dict.h +smtp_sasl_proto.o: ../../include/dns.h +smtp_sasl_proto.o: ../../include/dsn.h +smtp_sasl_proto.o: ../../include/dsn_buf.h +smtp_sasl_proto.o: ../../include/header_body_checks.h +smtp_sasl_proto.o: ../../include/header_opts.h +smtp_sasl_proto.o: ../../include/htable.h +smtp_sasl_proto.o: ../../include/mail_params.h +smtp_sasl_proto.o: ../../include/maps.h +smtp_sasl_proto.o: ../../include/match_list.h +smtp_sasl_proto.o: ../../include/mime_state.h +smtp_sasl_proto.o: ../../include/msg.h +smtp_sasl_proto.o: ../../include/msg_stats.h +smtp_sasl_proto.o: ../../include/myaddrinfo.h +smtp_sasl_proto.o: ../../include/myflock.h +smtp_sasl_proto.o: ../../include/mymalloc.h +smtp_sasl_proto.o: ../../include/name_code.h +smtp_sasl_proto.o: ../../include/name_mask.h +smtp_sasl_proto.o: ../../include/nvtable.h +smtp_sasl_proto.o: ../../include/recipient_list.h +smtp_sasl_proto.o: ../../include/resolve_clnt.h +smtp_sasl_proto.o: ../../include/sasl_mech_filter.h +smtp_sasl_proto.o: ../../include/scache.h +smtp_sasl_proto.o: ../../include/sock_addr.h +smtp_sasl_proto.o: ../../include/string_list.h +smtp_sasl_proto.o: ../../include/stringops.h +smtp_sasl_proto.o: ../../include/sys_defs.h +smtp_sasl_proto.o: ../../include/tls.h +smtp_sasl_proto.o: ../../include/tls_proxy.h +smtp_sasl_proto.o: ../../include/tok822.h +smtp_sasl_proto.o: ../../include/vbuf.h +smtp_sasl_proto.o: ../../include/vstream.h +smtp_sasl_proto.o: ../../include/vstring.h +smtp_sasl_proto.o: smtp.h +smtp_sasl_proto.o: smtp_sasl.h +smtp_sasl_proto.o: smtp_sasl_proto.c +smtp_session.o: ../../include/argv.h +smtp_session.o: ../../include/attr.h +smtp_session.o: ../../include/check_arg.h +smtp_session.o: ../../include/debug_peer.h +smtp_session.o: ../../include/deliver_request.h +smtp_session.o: ../../include/dict.h +smtp_session.o: ../../include/dns.h +smtp_session.o: ../../include/dsn.h +smtp_session.o: ../../include/dsn_buf.h +smtp_session.o: ../../include/header_body_checks.h +smtp_session.o: ../../include/header_opts.h +smtp_session.o: ../../include/htable.h +smtp_session.o: ../../include/mail_params.h +smtp_session.o: ../../include/maps.h +smtp_session.o: ../../include/match_list.h +smtp_session.o: ../../include/mime_state.h +smtp_session.o: ../../include/msg.h +smtp_session.o: ../../include/msg_stats.h +smtp_session.o: ../../include/myaddrinfo.h +smtp_session.o: ../../include/myflock.h +smtp_session.o: ../../include/mymalloc.h +smtp_session.o: ../../include/name_code.h +smtp_session.o: ../../include/name_mask.h +smtp_session.o: ../../include/nvtable.h +smtp_session.o: ../../include/recipient_list.h +smtp_session.o: ../../include/resolve_clnt.h +smtp_session.o: ../../include/scache.h +smtp_session.o: ../../include/sock_addr.h +smtp_session.o: ../../include/string_list.h +smtp_session.o: ../../include/stringops.h +smtp_session.o: ../../include/sys_defs.h +smtp_session.o: ../../include/tls.h +smtp_session.o: ../../include/tls_proxy.h +smtp_session.o: ../../include/tok822.h +smtp_session.o: ../../include/vbuf.h +smtp_session.o: ../../include/vstream.h +smtp_session.o: ../../include/vstring.h +smtp_session.o: smtp.h +smtp_session.o: smtp_sasl.h +smtp_session.o: smtp_session.c +smtp_state.o: ../../include/argv.h +smtp_state.o: ../../include/attr.h +smtp_state.o: ../../include/check_arg.h +smtp_state.o: ../../include/debug_peer.h +smtp_state.o: ../../include/deliver_request.h +smtp_state.o: ../../include/dict.h +smtp_state.o: ../../include/dns.h +smtp_state.o: ../../include/dsn.h +smtp_state.o: ../../include/dsn_buf.h +smtp_state.o: ../../include/header_body_checks.h +smtp_state.o: ../../include/header_opts.h +smtp_state.o: ../../include/htable.h +smtp_state.o: ../../include/mail_params.h +smtp_state.o: ../../include/maps.h +smtp_state.o: ../../include/match_list.h +smtp_state.o: ../../include/mime_state.h +smtp_state.o: ../../include/msg.h +smtp_state.o: ../../include/msg_stats.h +smtp_state.o: ../../include/myaddrinfo.h +smtp_state.o: ../../include/myflock.h +smtp_state.o: ../../include/mymalloc.h +smtp_state.o: ../../include/name_code.h +smtp_state.o: ../../include/name_mask.h +smtp_state.o: ../../include/nvtable.h +smtp_state.o: ../../include/recipient_list.h +smtp_state.o: ../../include/resolve_clnt.h +smtp_state.o: ../../include/scache.h +smtp_state.o: ../../include/sock_addr.h +smtp_state.o: ../../include/string_list.h +smtp_state.o: ../../include/sys_defs.h +smtp_state.o: ../../include/tls.h +smtp_state.o: ../../include/tls_proxy.h +smtp_state.o: ../../include/tok822.h +smtp_state.o: ../../include/vbuf.h +smtp_state.o: ../../include/vstream.h +smtp_state.o: ../../include/vstring.h +smtp_state.o: smtp.h +smtp_state.o: smtp_sasl.h +smtp_state.o: smtp_state.c +smtp_tls_policy.o: ../../include/argv.h +smtp_tls_policy.o: ../../include/attr.h +smtp_tls_policy.o: ../../include/check_arg.h +smtp_tls_policy.o: ../../include/ctable.h +smtp_tls_policy.o: ../../include/deliver_request.h +smtp_tls_policy.o: ../../include/dict.h +smtp_tls_policy.o: ../../include/dns.h +smtp_tls_policy.o: ../../include/dsn.h +smtp_tls_policy.o: ../../include/dsn_buf.h +smtp_tls_policy.o: ../../include/header_body_checks.h +smtp_tls_policy.o: ../../include/header_opts.h +smtp_tls_policy.o: ../../include/htable.h +smtp_tls_policy.o: ../../include/mail_params.h +smtp_tls_policy.o: ../../include/maps.h +smtp_tls_policy.o: ../../include/match_list.h +smtp_tls_policy.o: ../../include/mime_state.h +smtp_tls_policy.o: ../../include/msg.h +smtp_tls_policy.o: ../../include/msg_stats.h +smtp_tls_policy.o: ../../include/myaddrinfo.h +smtp_tls_policy.o: ../../include/myflock.h +smtp_tls_policy.o: ../../include/mymalloc.h +smtp_tls_policy.o: ../../include/name_code.h +smtp_tls_policy.o: ../../include/name_mask.h +smtp_tls_policy.o: ../../include/nvtable.h +smtp_tls_policy.o: ../../include/recipient_list.h +smtp_tls_policy.o: ../../include/resolve_clnt.h +smtp_tls_policy.o: ../../include/scache.h +smtp_tls_policy.o: ../../include/sock_addr.h +smtp_tls_policy.o: ../../include/string_list.h +smtp_tls_policy.o: ../../include/stringops.h +smtp_tls_policy.o: ../../include/sys_defs.h +smtp_tls_policy.o: ../../include/tls.h +smtp_tls_policy.o: ../../include/tls_proxy.h +smtp_tls_policy.o: ../../include/tok822.h +smtp_tls_policy.o: ../../include/valid_hostname.h +smtp_tls_policy.o: ../../include/valid_utf8_hostname.h +smtp_tls_policy.o: ../../include/vbuf.h +smtp_tls_policy.o: ../../include/vstream.h +smtp_tls_policy.o: ../../include/vstring.h +smtp_tls_policy.o: smtp.h +smtp_tls_policy.o: smtp_tls_policy.c +smtp_trouble.o: ../../include/argv.h +smtp_trouble.o: ../../include/attr.h +smtp_trouble.o: ../../include/bounce.h +smtp_trouble.o: ../../include/check_arg.h +smtp_trouble.o: ../../include/defer.h +smtp_trouble.o: ../../include/deliver_completed.h +smtp_trouble.o: ../../include/deliver_request.h +smtp_trouble.o: ../../include/dict.h +smtp_trouble.o: ../../include/dns.h +smtp_trouble.o: ../../include/dsn.h +smtp_trouble.o: ../../include/dsn_buf.h +smtp_trouble.o: ../../include/header_body_checks.h +smtp_trouble.o: ../../include/header_opts.h +smtp_trouble.o: ../../include/htable.h +smtp_trouble.o: ../../include/mail_error.h +smtp_trouble.o: ../../include/mail_params.h +smtp_trouble.o: ../../include/maps.h +smtp_trouble.o: ../../include/match_list.h +smtp_trouble.o: ../../include/mime_state.h +smtp_trouble.o: ../../include/msg.h +smtp_trouble.o: ../../include/msg_stats.h +smtp_trouble.o: ../../include/myaddrinfo.h +smtp_trouble.o: ../../include/myflock.h +smtp_trouble.o: ../../include/mymalloc.h +smtp_trouble.o: ../../include/name_code.h +smtp_trouble.o: ../../include/name_mask.h +smtp_trouble.o: ../../include/nvtable.h +smtp_trouble.o: ../../include/recipient_list.h +smtp_trouble.o: ../../include/resolve_clnt.h +smtp_trouble.o: ../../include/scache.h +smtp_trouble.o: ../../include/smtp_stream.h +smtp_trouble.o: ../../include/sock_addr.h +smtp_trouble.o: ../../include/string_list.h +smtp_trouble.o: ../../include/stringops.h +smtp_trouble.o: ../../include/sys_defs.h +smtp_trouble.o: ../../include/tls.h +smtp_trouble.o: ../../include/tls_proxy.h +smtp_trouble.o: ../../include/tok822.h +smtp_trouble.o: ../../include/vbuf.h +smtp_trouble.o: ../../include/vstream.h +smtp_trouble.o: ../../include/vstring.h +smtp_trouble.o: smtp.h +smtp_trouble.o: smtp_sasl.h +smtp_trouble.o: smtp_trouble.c +smtp_unalias.o: ../../include/argv.h +smtp_unalias.o: ../../include/attr.h +smtp_unalias.o: ../../include/check_arg.h +smtp_unalias.o: ../../include/deliver_request.h +smtp_unalias.o: ../../include/dict.h +smtp_unalias.o: ../../include/dns.h +smtp_unalias.o: ../../include/dsn.h +smtp_unalias.o: ../../include/dsn_buf.h +smtp_unalias.o: ../../include/header_body_checks.h +smtp_unalias.o: ../../include/header_opts.h +smtp_unalias.o: ../../include/htable.h +smtp_unalias.o: ../../include/maps.h +smtp_unalias.o: ../../include/match_list.h +smtp_unalias.o: ../../include/mime_state.h +smtp_unalias.o: ../../include/msg.h +smtp_unalias.o: ../../include/msg_stats.h +smtp_unalias.o: ../../include/myaddrinfo.h +smtp_unalias.o: ../../include/myflock.h +smtp_unalias.o: ../../include/mymalloc.h +smtp_unalias.o: ../../include/name_code.h +smtp_unalias.o: ../../include/name_mask.h +smtp_unalias.o: ../../include/nvtable.h +smtp_unalias.o: ../../include/recipient_list.h +smtp_unalias.o: ../../include/resolve_clnt.h +smtp_unalias.o: ../../include/scache.h +smtp_unalias.o: ../../include/sock_addr.h +smtp_unalias.o: ../../include/string_list.h +smtp_unalias.o: ../../include/sys_defs.h +smtp_unalias.o: ../../include/tls.h +smtp_unalias.o: ../../include/tls_proxy.h +smtp_unalias.o: ../../include/tok822.h +smtp_unalias.o: ../../include/vbuf.h +smtp_unalias.o: ../../include/vstream.h +smtp_unalias.o: ../../include/vstring.h +smtp_unalias.o: smtp.h +smtp_unalias.o: smtp_unalias.c diff --git a/src/smtp/lmtp_params.c b/src/smtp/lmtp_params.c new file mode 100644 index 0000000..cc33646 --- /dev/null +++ b/src/smtp/lmtp_params.c @@ -0,0 +1,136 @@ + static const CONFIG_STR_TABLE lmtp_str_table[] = { + VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, + VAR_LMTP_FALLBACK, DEF_LMTP_FALLBACK, &var_fallback_relay, 0, 0, + VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0, + VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, + VAR_LMTP_SASL_PASSWD, DEF_LMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0, + VAR_LMTP_SASL_OPTS, DEF_LMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0, + VAR_LMTP_SASL_PATH, DEF_LMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0, +#ifdef USE_TLS + VAR_LMTP_SASL_TLS_OPTS, DEF_LMTP_SASL_TLS_OPTS, &var_smtp_sasl_tls_opts, 0, 0, + VAR_LMTP_SASL_TLSV_OPTS, DEF_LMTP_SASL_TLSV_OPTS, &var_smtp_sasl_tlsv_opts, 0, 0, + VAR_LMTP_TLS_CHAIN_FILES, DEF_LMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0, + VAR_LMTP_TLS_CERT_FILE, DEF_LMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0, + VAR_LMTP_TLS_KEY_FILE, DEF_LMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0, + VAR_LMTP_TLS_DCERT_FILE, DEF_LMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0, + VAR_LMTP_TLS_DKEY_FILE, DEF_LMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0, + VAR_LMTP_TLS_CA_FILE, DEF_LMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0, + VAR_LMTP_TLS_CA_PATH, DEF_LMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0, + VAR_LMTP_TLS_MAND_CIPH, DEF_LMTP_TLS_MAND_CIPH, &var_smtp_tls_mand_ciph, 1, 0, + VAR_LMTP_TLS_EXCL_CIPH, DEF_LMTP_TLS_EXCL_CIPH, &var_smtp_tls_excl_ciph, 0, 0, + VAR_LMTP_TLS_MAND_EXCL, DEF_LMTP_TLS_MAND_EXCL, &var_smtp_tls_mand_excl, 0, 0, + VAR_LMTP_TLS_MAND_PROTO, DEF_LMTP_TLS_MAND_PROTO, &var_smtp_tls_mand_proto, 0, 0, + VAR_LMTP_TLS_VFY_CMATCH, DEF_LMTP_TLS_VFY_CMATCH, &var_smtp_tls_vfy_cmatch, 1, 0, + VAR_LMTP_TLS_SEC_CMATCH, DEF_LMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0, + VAR_LMTP_TLS_FPT_CMATCH, DEF_LMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0, + VAR_LMTP_TLS_FPT_DGST, DEF_LMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, + VAR_LMTP_TLS_TAFILE, DEF_LMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0, + VAR_LMTP_TLS_PROTO, DEF_LMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0, + VAR_LMTP_TLS_CIPH, DEF_LMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0, + VAR_LMTP_TLS_ECCERT_FILE, DEF_LMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0, + VAR_LMTP_TLS_ECKEY_FILE, DEF_LMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0, + VAR_LMTP_TLS_LOGLEVEL, DEF_LMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_LMTP_TLS_SNI, DEF_LMTP_TLS_SNI, &var_smtp_tls_sni, 0, 0, +#endif + VAR_LMTP_SASL_MECHS, DEF_LMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0, + VAR_LMTP_SASL_TYPE, DEF_LMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0, + VAR_LMTP_BIND_ADDR, DEF_LMTP_BIND_ADDR, &var_smtp_bind_addr, 0, 0, + VAR_LMTP_BIND_ADDR6, DEF_LMTP_BIND_ADDR6, &var_smtp_bind_addr6, 0, 0, + VAR_LMTP_VRFY_TGT, DEF_LMTP_VRFY_TGT, &var_smtp_vrfy_tgt, 1, 0, + VAR_LMTP_HELO_NAME, DEF_LMTP_HELO_NAME, &var_smtp_helo_name, 1, 0, + VAR_LMTP_HOST_LOOKUP, DEF_LMTP_HOST_LOOKUP, &var_smtp_host_lookup, 1, 0, + VAR_LMTP_DNS_SUPPORT, DEF_LMTP_DNS_SUPPORT, &var_smtp_dns_support, 0, 0, + VAR_LMTP_CACHE_DEST, DEF_LMTP_CACHE_DEST, &var_smtp_cache_dest, 0, 0, + VAR_SCACHE_SERVICE, DEF_SCACHE_SERVICE, &var_scache_service, 1, 0, + VAR_LMTP_EHLO_DIS_WORDS, DEF_LMTP_EHLO_DIS_WORDS, &var_smtp_ehlo_dis_words, 0, 0, + VAR_LMTP_EHLO_DIS_MAPS, DEF_LMTP_EHLO_DIS_MAPS, &var_smtp_ehlo_dis_maps, 0, 0, + VAR_LMTP_TLS_PER_SITE, DEF_LMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0, + VAR_LMTP_TLS_LEVEL, DEF_LMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0, + VAR_LMTP_TLS_POLICY, DEF_LMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0, + VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0, + VAR_LMTP_GENERIC_MAPS, DEF_LMTP_GENERIC_MAPS, &var_smtp_generic_maps, 0, 0, + VAR_LMTP_TCP_PORT, DEF_LMTP_TCP_PORT, &var_smtp_tcp_port, 0, 0, + VAR_LMTP_PIX_BUG_WORDS, DEF_LMTP_PIX_BUG_WORDS, &var_smtp_pix_bug_words, 0, 0, + VAR_LMTP_PIX_BUG_MAPS, DEF_LMTP_PIX_BUG_MAPS, &var_smtp_pix_bug_maps, 0, 0, + VAR_LMTP_SASL_AUTH_CACHE_NAME, DEF_LMTP_SASL_AUTH_CACHE_NAME, &var_smtp_sasl_auth_cache_name, 0, 0, + VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0, + VAR_LMTP_HEAD_CHKS, DEF_LMTP_HEAD_CHKS, &var_smtp_head_chks, 0, 0, + VAR_LMTP_MIME_CHKS, DEF_LMTP_MIME_CHKS, &var_smtp_mime_chks, 0, 0, + VAR_LMTP_NEST_CHKS, DEF_LMTP_NEST_CHKS, &var_smtp_nest_chks, 0, 0, + VAR_LMTP_BODY_CHKS, DEF_LMTP_BODY_CHKS, &var_smtp_body_chks, 0, 0, + VAR_LMTP_RESP_FILTER, DEF_LMTP_RESP_FILTER, &var_smtp_resp_filter, 0, 0, + VAR_LMTP_ADDR_PREF, DEF_LMTP_ADDR_PREF, &var_smtp_addr_pref, 1, 0, + VAR_LMTP_DNS_RES_OPT, DEF_LMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0, + VAR_LMTP_DSN_FILTER, DEF_LMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0, + VAR_LMTP_DNS_RE_FILTER, DEF_LMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0, + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, + VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0, + 0, + }; + static const CONFIG_TIME_TABLE lmtp_time_table[] = { + VAR_LMTP_CONN_TMOUT, DEF_LMTP_CONN_TMOUT, &var_smtp_conn_tmout, 0, 0, + VAR_LMTP_HELO_TMOUT, DEF_LMTP_HELO_TMOUT, &var_smtp_helo_tmout, 1, 0, + VAR_LMTP_XFWD_TMOUT, DEF_LMTP_XFWD_TMOUT, &var_smtp_xfwd_tmout, 1, 0, + VAR_LMTP_MAIL_TMOUT, DEF_LMTP_MAIL_TMOUT, &var_smtp_mail_tmout, 1, 0, + VAR_LMTP_RCPT_TMOUT, DEF_LMTP_RCPT_TMOUT, &var_smtp_rcpt_tmout, 1, 0, + VAR_LMTP_DATA0_TMOUT, DEF_LMTP_DATA0_TMOUT, &var_smtp_data0_tmout, 1, 0, + VAR_LMTP_DATA1_TMOUT, DEF_LMTP_DATA1_TMOUT, &var_smtp_data1_tmout, 1, 0, + VAR_LMTP_DATA2_TMOUT, DEF_LMTP_DATA2_TMOUT, &var_smtp_data2_tmout, 1, 0, + VAR_LMTP_RSET_TMOUT, DEF_LMTP_RSET_TMOUT, &var_smtp_rset_tmout, 1, 0, + VAR_LMTP_QUIT_TMOUT, DEF_LMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0, + VAR_LMTP_PIX_THRESH, DEF_LMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0, + VAR_LMTP_PIX_DELAY, DEF_LMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0, + VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0, + VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0, + VAR_LMTP_CACHE_CONNT, DEF_LMTP_CACHE_CONNT, &var_smtp_cache_conn, 1, 0, + VAR_LMTP_REUSE_TIME, DEF_LMTP_REUSE_TIME, &var_smtp_reuse_time, 1, 0, +#ifdef USE_TLS + VAR_LMTP_STARTTLS_TMOUT, DEF_LMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0, +#endif + VAR_SCACHE_PROTO_TMOUT, DEF_SCACHE_PROTO_TMOUT, &var_scache_proto_tmout, 1, 0, + VAR_LMTP_SASL_AUTH_CACHE_TIME, DEF_LMTP_SASL_AUTH_CACHE_TIME, &var_smtp_sasl_auth_cache_time, 0, 0, + 0, + }; + static const CONFIG_INT_TABLE lmtp_int_table[] = { + VAR_LMTP_LINE_LIMIT, DEF_LMTP_LINE_LIMIT, &var_smtp_line_limit, 0, 0, + VAR_LMTP_MXADDR_LIMIT, DEF_LMTP_MXADDR_LIMIT, &var_smtp_mxaddr_limit, 0, 0, + VAR_LMTP_MXSESS_LIMIT, DEF_LMTP_MXSESS_LIMIT, &var_smtp_mxsess_limit, 0, 0, + VAR_LMTP_REUSE_COUNT, DEF_LMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0, +#ifdef USE_TLS + VAR_LMTP_TLS_SCERT_VD, DEF_LMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0, +#endif + VAR_LMTP_MIN_DATA_RATE, DEF_LMTP_MIN_DATA_RATE, &var_smtp_min_data_rate, 1, 0, + 0, + }; + static const CONFIG_BOOL_TABLE lmtp_bool_table[] = { + VAR_LMTP_SKIP_5XX, DEF_LMTP_SKIP_5XX, &var_smtp_skip_5xx_greeting, + VAR_LMTP_SKIP_QUIT_RESP, DEF_LMTP_SKIP_QUIT_RESP, &var_skip_quit_resp, + VAR_LMTP_SASL_ENABLE, DEF_LMTP_SASL_ENABLE, &var_smtp_sasl_enable, + VAR_LMTP_RAND_ADDR, DEF_LMTP_RAND_ADDR, &var_smtp_rand_addr, + VAR_LMTP_QUOTE_821_ENV, DEF_LMTP_QUOTE_821_ENV, &var_smtp_quote_821_env, + VAR_LMTP_DEFER_MXADDR, DEF_LMTP_DEFER_MXADDR, &var_smtp_defer_mxaddr, + VAR_LMTP_SEND_XFORWARD, DEF_LMTP_SEND_XFORWARD, &var_smtp_send_xforward, + VAR_LMTP_CACHE_DEMAND, DEF_LMTP_CACHE_DEMAND, &var_smtp_cache_demand, + VAR_LMTP_USE_TLS, DEF_LMTP_USE_TLS, &var_smtp_use_tls, + VAR_LMTP_ENFORCE_TLS, DEF_LMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + VAR_LMTP_TLS_CONN_REUSE, DEF_LMTP_TLS_CONN_REUSE, &var_smtp_tls_conn_reuse, +#ifdef USE_TLS + VAR_LMTP_TLS_ENFORCE_PN, DEF_LMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername, + VAR_LMTP_TLS_NOTEOFFER, DEF_LMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, + VAR_LMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_LMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply, + VAR_LMTP_TLS_FORCE_TLSA, DEF_LMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa, +#endif + VAR_LMTP_TLS_WRAPPER, DEF_LMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode, + VAR_LMTP_SENDER_AUTH, DEF_LMTP_SENDER_AUTH, &var_smtp_sender_auth, + VAR_LMTP_CNAME_OVERR, DEF_LMTP_CNAME_OVERR, &var_smtp_cname_overr, + VAR_LMTP_SASL_AUTH_SOFT_BOUNCE, DEF_LMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce, + VAR_LMTP_ASSUME_FINAL, DEF_LMTP_ASSUME_FINAL, &var_lmtp_assume_final, + VAR_LMTP_DUMMY_MAIL_AUTH, DEF_LMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth, + VAR_LMTP_BALANCE_INET_PROTO, DEF_LMTP_BALANCE_INET_PROTO, &var_smtp_balance_inet_proto, + VAR_LMTP_BIND_ADDR_ENFORCE, DEF_LMTP_BIND_ADDR_ENFORCE, &var_smtp_bind_addr_enforce, + 0, + }; + static const CONFIG_NBOOL_TABLE lmtp_nbool_table[] = { + VAR_LMTP_REQ_DEADLINE, DEF_LMTP_REQ_DEADLINE, &var_smtp_req_deadline, + 0, + }; diff --git a/src/smtp/smtp-only b/src/smtp/smtp-only new file mode 100644 index 0000000..ee9288d --- /dev/null +++ b/src/smtp/smtp-only @@ -0,0 +1,4 @@ +_ALWAYS_EHLO +_NEVER_EHLO +_IGN_MX_LOOKUP_ERR +_INSECURE_MX_POLICY diff --git a/src/smtp/smtp.c b/src/smtp/smtp.c new file mode 100644 index 0000000..791ec89 --- /dev/null +++ b/src/smtp/smtp.c @@ -0,0 +1,1652 @@ +/*++ +/* NAME +/* smtp 8 +/* SUMMARY +/* Postfix SMTP+LMTP client +/* SYNOPSIS +/* \fBsmtp\fR [generic Postfix daemon options] [flags=DORX] +/* DESCRIPTION +/* The Postfix SMTP+LMTP client implements the SMTP and LMTP mail +/* delivery protocols. It processes message delivery requests from +/* the queue manager. Each request specifies a queue file, a sender +/* address, a domain or host to deliver to, and recipient information. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* The SMTP+LMTP client updates the queue file and marks recipients +/* as finished, or it informs the queue manager that delivery should +/* be tried again at a later time. Delivery status reports are sent +/* to the \fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemon as +/* appropriate. +/* +/* The SMTP+LMTP client looks up a list of mail exchanger addresses for +/* the destination host, sorts the list by preference, and connects +/* to each listed address until it finds a server that responds. +/* +/* When a server is not reachable, or when mail delivery fails due +/* to a recoverable error condition, the SMTP+LMTP client will try to +/* deliver the mail to an alternate host. +/* +/* After a successful mail transaction, a connection may be saved +/* to the \fBscache\fR(8) connection cache server, so that it +/* may be used by any SMTP+LMTP client for a subsequent transaction. +/* +/* By default, connection caching is enabled temporarily for +/* destinations that have a high volume of mail in the active +/* queue. Connection caching can be enabled permanently for +/* specific destinations. +/* SMTP DESTINATION SYNTAX +/* .ad +/* .fi +/* The Postfix SMTP+LMTP client supports multiple destinations +/* separated by comma or whitespace (Postfix 3.5 and later). +/* SMTP destinations have the following form: +/* .IP \fIdomainname\fR +/* .IP \fIdomainname\fR:\fIport\fR +/* Look up the mail exchangers for the specified domain, and +/* connect to the specified port (default: \fBsmtp\fR). +/* .IP [\fIhostname\fR] +/* .IP [\fIhostname\fR]:\fIport\fR +/* Look up the address(es) of the specified host, and connect to +/* the specified port (default: \fBsmtp\fR). +/* .IP [\fIaddress\fR] +/* .IP [\fIaddress\fR]:\fIport\fR +/* Connect to the host at the specified address, and connect +/* to the specified port (default: \fBsmtp\fR). An IPv6 address +/* must be formatted as [\fBipv6\fR:\fIaddress\fR]. +/* LMTP DESTINATION SYNTAX +/* .ad +/* .fi +/* The Postfix SMTP+LMTP client supports multiple destinations +/* separated by comma or whitespace (Postfix 3.5 and later). +/* LMTP destinations have the following form: +/* .IP \fBunix\fR:\fIpathname\fR +/* Connect to the local UNIX-domain server that is bound to the specified +/* \fIpathname\fR. If the process runs chrooted, an absolute pathname +/* is interpreted relative to the Postfix queue directory. +/* .IP \fBinet\fR:\fIhostname\fR +/* .IP \fBinet\fR:\fIhostname\fR:\fIport\fR +/* .IP \fBinet\fR:[\fIaddress\fR] +/* .IP \fBinet\fR:[\fIaddress\fR]:\fIport\fR +/* Connect to the specified TCP port on the specified local or +/* remote host. If no port is specified, connect to the port defined as +/* \fBlmtp\fR in \fBservices\fR(4). +/* If no such service is found, the \fBlmtp_tcp_port\fR configuration +/* parameter (default value of 24) will be used. +/* An IPv6 address must be formatted as [\fBipv6\fR:\fIaddress\fR]. +/* SINGLE-RECIPIENT DELIVERY +/* .ad +/* .fi +/* By default, the Postfix SMTP+LMTP client delivers mail to +/* multiple recipients per delivery request. This is undesirable +/* when prepending a \fBDelivered-to:\fR or \fBX-Original-To:\fR +/* message header. To prevent Postfix from sending multiple +/* recipients per delivery request, specify +/* .sp +/* .nf +/* \fItransport\fB_destination_recipient_limit = 1\fR +/* .fi +/* +/* in the Postfix \fBmain.cf\fR file, where \fItransport\fR +/* is the name in the first column of the Postfix \fBmaster.cf\fR +/* entry for this mail delivery service. +/* COMMAND ATTRIBUTE SYNTAX +/* .ad +/* .fi +/* .IP "\fBflags=DORX\fR (optional)" +/* Optional message processing flags. +/* .RS +/* .IP \fBD\fR +/* Prepend a "\fBDelivered-To: \fIrecipient\fR" message header +/* with the envelope recipient address. Note: for this to work, +/* the \fItransport\fB_destination_recipient_limit\fR must be +/* 1 (see SINGLE-RECIPIENT DELIVERY above for details). +/* .sp +/* The \fBD\fR flag also enforces loop detection: if a message +/* already contains a \fBDelivered-To:\fR header with the same +/* recipient address, then the message is returned as +/* undeliverable. The address comparison is case insensitive. +/* .sp +/* This feature is available as of Postfix 3.5. +/* .IP \fBO\fR +/* Prepend an "\fBX-Original-To: \fIrecipient\fR" message +/* header with the recipient address as given to Postfix. Note: +/* for this to work, the +/* \fItransport\fB_destination_recipient_limit\fR must be 1 +/* (see SINGLE-RECIPIENT DELIVERY above for details). +/* .sp +/* This feature is available as of Postfix 3.5. +/* .IP \fBR\fR +/* Prepend a "\fBReturn-Path: <\fIsender\fB>\fR" message header +/* with the envelope sender address. +/* .sp +/* This feature is available as of Postfix 3.5. +/* .IP \fBX\fR +/* Indicates that the delivery is final. This flag affects +/* the status reported in "success" DSN (delivery status +/* notification) messages, and changes it from "relayed" into +/* "delivered". +/* .sp +/* This feature is available as of Postfix 3.5. +/* .RE +/* SECURITY +/* The SMTP+LMTP client is moderately security-sensitive. It +/* talks to SMTP or LMTP servers and to DNS servers on the +/* network. The SMTP+LMTP client can be run chrooted at fixed +/* low privilege. +/* STANDARDS +/* RFC 821 (SMTP protocol) +/* RFC 822 (ARPA Internet Text Messages) +/* RFC 1651 (SMTP service extensions) +/* RFC 1652 (8bit-MIME transport) +/* RFC 1870 (Message Size Declaration) +/* RFC 2033 (LMTP protocol) +/* RFC 2034 (SMTP Enhanced Error Codes) +/* RFC 2045 (MIME: Format of Internet Message Bodies) +/* RFC 2046 (MIME: Media Types) +/* RFC 2554 (AUTH command) +/* RFC 2821 (SMTP protocol) +/* RFC 2920 (SMTP Pipelining) +/* RFC 3207 (STARTTLS command) +/* RFC 3461 (SMTP DSN Extension) +/* RFC 3463 (Enhanced Status Codes) +/* RFC 4954 (AUTH command) +/* RFC 5321 (SMTP protocol) +/* RFC 6531 (Internationalized SMTP) +/* RFC 6533 (Internationalized Delivery Status Notifications) +/* RFC 7672 (SMTP security via opportunistic DANE TLS) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* Corrupted message files are marked so that the queue manager can +/* move them to the \fBcorrupt\fR queue for further inspection. +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces, protocol problems, and of +/* other trouble. +/* BUGS +/* SMTP and LMTP connection reuse for TLS (without closing the +/* SMTP or LMTP connection) is not supported before Postfix 3.4. +/* +/* SMTP and LMTP connection reuse assumes that SASL credentials +/* are valid for all destinations that map onto the same IP +/* address and TCP port. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Before Postfix version 2.3, the LMTP client is a separate +/* program that implements only a subset of the functionality +/* available with SMTP: there is no support for TLS, and +/* connections are cached in-process, making it ineffective +/* when the client is used for multiple domains. +/* +/* Most smtp_\fIxxx\fR configuration parameters have an +/* lmtp_\fIxxx\fR "mirror" parameter for the equivalent LMTP +/* feature. This document describes only those LMTP-related +/* parameters that aren't simply "mirror" parameters. +/* +/* Changes to \fBmain.cf\fR are picked up automatically, as \fBsmtp\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* .IP "\fBignore_mx_lookup_error (no)\fR" +/* Ignore DNS MX lookups that produce no response. +/* .IP "\fBsmtp_always_send_ehlo (yes)\fR" +/* Always send EHLO at the start of an SMTP session. +/* .IP "\fBsmtp_never_send_ehlo (no)\fR" +/* Never send EHLO at the start of an SMTP session. +/* .IP "\fBsmtp_defer_if_no_mx_address_found (no)\fR" +/* Defer mail delivery when no MX record resolves to an IP address. +/* .IP "\fBsmtp_line_length_limit (998)\fR" +/* The maximal length of message header and body lines that Postfix +/* will send via SMTP. +/* .IP "\fBsmtp_pix_workaround_delay_time (10s)\fR" +/* How long the Postfix SMTP client pauses before sending +/* "." in order to work around the PIX firewall +/* "." bug. +/* .IP "\fBsmtp_pix_workaround_threshold_time (500s)\fR" +/* How long a message must be queued before the Postfix SMTP client +/* turns on the PIX firewall "." +/* bug workaround for delivery through firewalls with "smtp fixup" +/* mode turned on. +/* .IP "\fBsmtp_pix_workarounds (disable_esmtp, delay_dotcrlf)\fR" +/* A list that specifies zero or more workarounds for CISCO PIX +/* firewall bugs. +/* .IP "\fBsmtp_pix_workaround_maps (empty)\fR" +/* Lookup tables, indexed by the remote SMTP server address, with +/* per-destination workarounds for CISCO PIX firewall bugs. +/* .IP "\fBsmtp_quote_rfc821_envelope (yes)\fR" +/* Quote addresses in Postfix SMTP client MAIL FROM and RCPT TO commands +/* as required +/* by RFC 5321. +/* .IP "\fBsmtp_reply_filter (empty)\fR" +/* A mechanism to transform replies from remote SMTP servers one +/* line at a time. +/* .IP "\fBsmtp_skip_5xx_greeting (yes)\fR" +/* Skip remote SMTP servers that greet with a 5XX status code. +/* .IP "\fBsmtp_skip_quit_response (yes)\fR" +/* Do not wait for the response to the SMTP QUIT command. +/* .PP +/* Available in Postfix version 2.0 and earlier: +/* .IP "\fBsmtp_skip_4xx_greeting (yes)\fR" +/* Skip SMTP servers that greet with a 4XX status code (go away, try +/* again later). +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtp_discard_ehlo_keyword_address_maps (empty)\fR" +/* Lookup tables, indexed by the remote SMTP server address, with +/* case insensitive lists of EHLO keywords (pipelining, starttls, auth, +/* etc.) that the Postfix SMTP client will ignore in the EHLO response from a +/* remote SMTP server. +/* .IP "\fBsmtp_discard_ehlo_keywords (empty)\fR" +/* A case insensitive list of EHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix SMTP client will ignore in the EHLO +/* response from a remote SMTP server. +/* .IP "\fBsmtp_generic_maps (empty)\fR" +/* Optional lookup tables that perform address rewriting in the +/* Postfix SMTP client, typically to transform a locally valid address into +/* a globally valid address when sending mail across the Internet. +/* .PP +/* Available in Postfix version 2.2.9 and later: +/* .IP "\fBsmtp_cname_overrides_servername (version dependent)\fR" +/* When the remote SMTP servername is a DNS CNAME, replace the +/* servername with the result from CNAME expansion for the purpose of +/* logging, SASL password lookup, TLS +/* policy decisions, or TLS certificate verification. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBlmtp_discard_lhlo_keyword_address_maps (empty)\fR" +/* Lookup tables, indexed by the remote LMTP server address, with +/* case insensitive lists of LHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix LMTP client will ignore in the LHLO +/* response +/* from a remote LMTP server. +/* .IP "\fBlmtp_discard_lhlo_keywords (empty)\fR" +/* A case insensitive list of LHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix LMTP client will ignore in the LHLO +/* response +/* from a remote LMTP server. +/* .PP +/* Available in Postfix version 2.4.4 and later: +/* .IP "\fBsend_cyrus_sasl_authzid (no)\fR" +/* When authenticating to a remote SMTP or LMTP server with the +/* default setting "no", send no SASL authoriZation ID (authzid); send +/* only the SASL authentiCation ID (authcid) plus the authcid's password. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtp_header_checks (empty)\fR" +/* Restricted \fBheader_checks\fR(5) tables for the Postfix SMTP client. +/* .IP "\fBsmtp_mime_header_checks (empty)\fR" +/* Restricted \fBmime_header_checks\fR(5) tables for the Postfix SMTP +/* client. +/* .IP "\fBsmtp_nested_header_checks (empty)\fR" +/* Restricted \fBnested_header_checks\fR(5) tables for the Postfix SMTP +/* client. +/* .IP "\fBsmtp_body_checks (empty)\fR" +/* Restricted \fBbody_checks\fR(5) tables for the Postfix SMTP client. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBtcp_windowsize (0)\fR" +/* An optional workaround for routers that break TCP window scaling. +/* .PP +/* Available in Postfix version 2.8 and later: +/* .IP "\fBsmtp_dns_resolver_options (empty)\fR" +/* DNS Resolver options for the Postfix SMTP client. +/* .PP +/* Available in Postfix version 2.9 - 3.6: +/* .IP "\fBsmtp_per_record_deadline (no)\fR" +/* Change the behavior of the smtp_*_timeout time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR" +/* Whether or not to append the "AUTH=<>" option to the MAIL +/* FROM command in SASL-authenticated SMTP sessions. +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtp_dns_support_level (empty)\fR" +/* Level of DNS support in the Postfix SMTP client. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtp_delivery_status_filter ($default_delivery_status_filter)\fR" +/* Optional filter for the \fBsmtp\fR(8) delivery agent to change the +/* delivery status code or explanatory text of successful or unsuccessful +/* deliveries. +/* .IP "\fBsmtp_dns_reply_filter (empty)\fR" +/* Optional filter for Postfix SMTP client DNS lookup results. +/* .PP +/* Available in Postfix version 3.3 and later: +/* .IP "\fBsmtp_balance_inet_protocols (yes)\fR" +/* When a remote destination resolves to a combination of IPv4 and +/* IPv6 addresses, ensure that the Postfix SMTP client can try both +/* address types before it runs into the smtp_mx_address_limit. +/* .PP +/* Available in Postfix 3.5 and later: +/* .IP "\fBinfo_log_address_format (external)\fR" +/* The email address form that will be used in non-debug logging +/* (info, warning, etc.). +/* .PP +/* Available in Postfix 3.6 and later: +/* .IP "\fBdnssec_probe (ns:.)\fR" +/* The DNS query type (default: "ns") and DNS query name (default: +/* ".") that Postfix may use to determine whether DNSSEC validation +/* is available. +/* .IP "\fBknown_tcp_ports (lmtp=24, smtp=25, smtps=submissions=465, submission=587)\fR" +/* Optional setting that avoids lookups in the \fBservices\fR(5) database. +/* .PP +/* Available in Postfix version 3.7 and later: +/* .IP "\fBsmtp_per_request_deadline (no)\fR" +/* Change the behavior of the smtp_*_timeout time limits, from a +/* time limit per plaintext or TLS read or write call, to a combined +/* time limit for sending a complete SMTP request and for receiving a +/* complete SMTP response. +/* .IP "\fBsmtp_min_data_rate (500)\fR" +/* The minimum plaintext data transfer rate in bytes/second for +/* DATA requests, when deadlines are enabled with smtp_per_request_deadline. +/* .IP "\fBheader_from_format (standard)\fR" +/* The format of the Postfix-generated \fBFrom:\fR header. +/* MIME PROCESSING CONTROLS +/* .ad +/* .fi +/* Available in Postfix version 2.0 and later: +/* .IP "\fBdisable_mime_output_conversion (no)\fR" +/* Disable the conversion of 8BITMIME format to 7BIT format. +/* .IP "\fBmime_boundary_length_limit (2048)\fR" +/* The maximal length of MIME multipart boundary strings. +/* .IP "\fBmime_nesting_limit (100)\fR" +/* The maximal recursion level that the MIME processor will handle. +/* EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtp_send_xforward_command (no)\fR" +/* Send the non-standard XFORWARD command when the Postfix SMTP server +/* EHLO response announces XFORWARD support. +/* SASL AUTHENTICATION CONTROLS +/* .ad +/* .fi +/* .IP "\fBsmtp_sasl_auth_enable (no)\fR" +/* Enable SASL authentication in the Postfix SMTP client. +/* .IP "\fBsmtp_sasl_password_maps (empty)\fR" +/* Optional Postfix SMTP client lookup tables with one username:password +/* entry per sender, remote hostname or next-hop domain. +/* .IP "\fBsmtp_sasl_security_options (noplaintext, noanonymous)\fR" +/* Postfix SMTP client SASL security options; as of Postfix 2.3 +/* the list of available +/* features depends on the SASL client implementation that is selected +/* with \fBsmtp_sasl_type\fR. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtp_sasl_mechanism_filter (empty)\fR" +/* If non-empty, a Postfix SMTP client filter for the remote SMTP +/* server's list of offered SASL mechanisms. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtp_sender_dependent_authentication (no)\fR" +/* Enable sender-dependent authentication in the Postfix SMTP client; this is +/* available only with SASL authentication, and disables SMTP connection +/* caching to ensure that mail from different senders will use the +/* appropriate credentials. +/* .IP "\fBsmtp_sasl_path (empty)\fR" +/* Implementation-specific information that the Postfix SMTP client +/* passes through to +/* the SASL plug-in implementation that is selected with +/* \fBsmtp_sasl_type\fR. +/* .IP "\fBsmtp_sasl_type (cyrus)\fR" +/* The SASL plug-in type that the Postfix SMTP client should use +/* for authentication. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtp_sasl_auth_cache_name (empty)\fR" +/* An optional table to prevent repeated SASL authentication +/* failures with the same remote SMTP server hostname, username and +/* password. +/* .IP "\fBsmtp_sasl_auth_cache_time (90d)\fR" +/* The maximal age of an smtp_sasl_auth_cache_name entry before it +/* is removed. +/* .IP "\fBsmtp_sasl_auth_soft_bounce (yes)\fR" +/* When a remote SMTP server rejects a SASL authentication request +/* with a 535 reply code, defer mail delivery instead of returning +/* mail as undeliverable. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBsmtp_send_dummy_mail_auth (no)\fR" +/* Whether or not to append the "AUTH=<>" option to the MAIL +/* FROM command in SASL-authenticated SMTP sessions. +/* STARTTLS SUPPORT CONTROLS +/* .ad +/* .fi +/* Detailed information about STARTTLS configuration may be found +/* in the TLS_README document. +/* .IP "\fBsmtp_tls_security_level (empty)\fR" +/* The default SMTP TLS security level for the Postfix SMTP client; +/* when a non-empty value is specified, this overrides the obsolete +/* parameters smtp_use_tls, smtp_enforce_tls, and smtp_tls_enforce_peername. +/* .IP "\fBsmtp_sasl_tls_security_options ($smtp_sasl_security_options)\fR" +/* The SASL authentication security options that the Postfix SMTP +/* client uses for TLS encrypted SMTP sessions. +/* .IP "\fBsmtp_starttls_timeout (300s)\fR" +/* Time limit for Postfix SMTP client write and read operations +/* during TLS startup and shutdown handshake procedures. +/* .IP "\fBsmtp_tls_CAfile (empty)\fR" +/* A file containing CA certificates of root CAs trusted to sign +/* either remote SMTP server certificates or intermediate CA certificates. +/* .IP "\fBsmtp_tls_CApath (empty)\fR" +/* Directory with PEM format Certification Authority certificates +/* that the Postfix SMTP client uses to verify a remote SMTP server +/* certificate. +/* .IP "\fBsmtp_tls_cert_file (empty)\fR" +/* File with the Postfix SMTP client RSA certificate in PEM format. +/* .IP "\fBsmtp_tls_mandatory_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP client will +/* use with +/* mandatory TLS encryption. +/* .IP "\fBsmtp_tls_exclude_ciphers (empty)\fR" +/* List of ciphers or cipher types to exclude from the Postfix +/* SMTP client cipher +/* list at all TLS security levels. +/* .IP "\fBsmtp_tls_mandatory_exclude_ciphers (empty)\fR" +/* Additional list of ciphers or cipher types to exclude from the +/* Postfix SMTP client cipher list at mandatory TLS security levels. +/* .IP "\fBsmtp_tls_dcert_file (empty)\fR" +/* File with the Postfix SMTP client DSA certificate in PEM format. +/* .IP "\fBsmtp_tls_dkey_file ($smtp_tls_dcert_file)\fR" +/* File with the Postfix SMTP client DSA private key in PEM format. +/* .IP "\fBsmtp_tls_key_file ($smtp_tls_cert_file)\fR" +/* File with the Postfix SMTP client RSA private key in PEM format. +/* .IP "\fBsmtp_tls_loglevel (0)\fR" +/* Enable additional Postfix SMTP client logging of TLS activity. +/* .IP "\fBsmtp_tls_note_starttls_offer (no)\fR" +/* Log the hostname of a remote SMTP server that offers STARTTLS, +/* when TLS is not already enabled for that server. +/* .IP "\fBsmtp_tls_policy_maps (empty)\fR" +/* Optional lookup tables with the Postfix SMTP client TLS security +/* policy by next-hop destination; when a non-empty value is specified, +/* this overrides the obsolete smtp_tls_per_site parameter. +/* .IP "\fBsmtp_tls_mandatory_protocols (see 'postconf -d' output)\fR" +/* TLS protocols that the Postfix SMTP client will use with mandatory +/* TLS encryption. +/* .IP "\fBsmtp_tls_scert_verifydepth (9)\fR" +/* The verification depth for remote SMTP server certificates. +/* .IP "\fBsmtp_tls_secure_cert_match (nexthop, dot-nexthop)\fR" +/* How the Postfix SMTP client verifies the server certificate +/* peername for the "secure" TLS security level. +/* .IP "\fBsmtp_tls_session_cache_database (empty)\fR" +/* Name of the file containing the optional Postfix SMTP client +/* TLS session cache. +/* .IP "\fBsmtp_tls_session_cache_timeout (3600s)\fR" +/* The expiration time of Postfix SMTP client TLS session cache +/* information. +/* .IP "\fBsmtp_tls_verify_cert_match (hostname)\fR" +/* How the Postfix SMTP client verifies the server certificate +/* peername for the +/* "verify" TLS security level. +/* .IP "\fBtls_daemon_random_bytes (32)\fR" +/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8) +/* process requests from the \fBtlsmgr\fR(8) server in order to seed its +/* internal pseudo random number generator (PRNG). +/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "high" grade ciphers. +/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "medium" or higher grade ciphers. +/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "low" or higher grade ciphers. +/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "export" or higher grade ciphers. +/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" +/* The OpenSSL cipherlist for "NULL" grade ciphers that provide +/* authentication without encryption. +/* .PP +/* Available in Postfix version 2.4 and later: +/* .IP "\fBsmtp_sasl_tls_verified_security_options ($smtp_sasl_tls_security_options)\fR" +/* The SASL authentication security options that the Postfix SMTP +/* client uses for TLS encrypted SMTP sessions with a verified server +/* certificate. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtp_tls_fingerprint_cert_match (empty)\fR" +/* List of acceptable remote SMTP server certificate fingerprints for +/* the "fingerprint" TLS security level (\fBsmtp_tls_security_level\fR = +/* fingerprint). +/* .IP "\fBsmtp_tls_fingerprint_digest (see 'postconf -d' output)\fR" +/* The message digest algorithm used to construct remote SMTP server +/* certificate fingerprints. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBsmtp_tls_protocols (see postconf -d output)\fR" +/* TLS protocols that the Postfix SMTP client will use with +/* opportunistic TLS encryption. +/* .IP "\fBsmtp_tls_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP client +/* will use with opportunistic TLS encryption. +/* .IP "\fBsmtp_tls_eccert_file (empty)\fR" +/* File with the Postfix SMTP client ECDSA certificate in PEM format. +/* .IP "\fBsmtp_tls_eckey_file ($smtp_tls_eccert_file)\fR" +/* File with the Postfix SMTP client ECDSA private key in PEM format. +/* .PP +/* Available in Postfix version 2.7 and later: +/* .IP "\fBsmtp_tls_block_early_mail_reply (no)\fR" +/* Try to detect a mail hijacking attack based on a TLS protocol +/* vulnerability (CVE-2009-3555), where an attacker prepends malicious +/* HELO, MAIL, RCPT, DATA commands to a Postfix SMTP client TLS session. +/* .PP +/* Available in Postfix version 2.8 and later: +/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR" +/* List or bit-mask of OpenSSL bug work-arounds to disable. +/* .PP +/* Available in Postfix version 2.11-3.1: +/* .IP "\fBtls_dane_digest_agility (on)\fR" +/* Configure RFC7671 DANE TLSA digest algorithm agility. +/* .IP "\fBtls_dane_trust_anchor_digest_enable (yes)\fR" +/* Enable support for RFC 6698 (DANE TLSA) DNS records that contain +/* digests of trust-anchors with certificate usage "2". +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtp_tls_trust_anchor_file (empty)\fR" +/* Zero or more PEM-format files with trust-anchor certificates +/* and/or public keys. +/* .IP "\fBsmtp_tls_force_insecure_host_tlsa_lookup (no)\fR" +/* Lookup the associated DANE TLSA RRset even when a hostname is +/* not an alias and its address records lie in an unsigned zone. +/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR" +/* The name of the \fBtlsmgr\fR(8) service entry in master.cf. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtp_tls_wrappermode (no)\fR" +/* Request that the Postfix SMTP client connects using the +/* legacy SMTPS protocol instead of using the STARTTLS command. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBsmtp_tls_dane_insecure_mx_policy (see 'postconf -d' output)\fR" +/* The TLS policy for MX hosts with "secure" TLSA records when the +/* nexthop destination security level is \fBdane\fR, but the MX +/* record was found via an "insecure" MX lookup. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtp_tls_connection_reuse (no)\fR" +/* Try to make multiple deliveries per TLS-encrypted connection. +/* .IP "\fBsmtp_tls_chain_files (empty)\fR" +/* List of one or more PEM files, each holding one or more private keys +/* directly followed by a corresponding certificate chain. +/* .IP "\fBsmtp_tls_servername (empty)\fR" +/* Optional name to send to the remote SMTP server in the TLS Server +/* Name Indication (SNI) extension. +/* .PP +/* Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later: +/* .IP "\fBtls_fast_shutdown_enable (yes)\fR" +/* A workaround for implementations that hang Postfix while shutting +/* down a TLS session, until Postfix times out. +/* .PP +/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later: +/* .IP "\fBtls_config_file (default)\fR" +/* Optional configuration file with baseline OpenSSL settings. +/* .IP "\fBtls_config_name (empty)\fR" +/* The application name passed by Postfix to OpenSSL library +/* initialization functions. +/* OBSOLETE STARTTLS CONTROLS +/* .ad +/* .fi +/* The following configuration parameters exist for compatibility +/* with Postfix versions before 2.3. Support for these will +/* be removed in a future release. +/* .IP "\fBsmtp_use_tls (no)\fR" +/* Opportunistic mode: use TLS when a remote SMTP server announces +/* STARTTLS support, otherwise send the mail in the clear. +/* .IP "\fBsmtp_enforce_tls (no)\fR" +/* Enforcement mode: require that remote SMTP servers use TLS +/* encryption, and never send mail in the clear. +/* .IP "\fBsmtp_tls_enforce_peername (yes)\fR" +/* With mandatory TLS encryption, require that the remote SMTP +/* server hostname matches the information in the remote SMTP server +/* certificate. +/* .IP "\fBsmtp_tls_per_site (empty)\fR" +/* Optional lookup tables with the Postfix SMTP client TLS usage +/* policy by next-hop destination and by remote SMTP server hostname. +/* .IP "\fBsmtp_tls_cipherlist (empty)\fR" +/* Obsolete Postfix < 2.3 control for the Postfix SMTP client TLS +/* cipher list. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBsmtp_connect_timeout (30s)\fR" +/* The Postfix SMTP client time limit for completing a TCP connection, or +/* zero (use the operating system built-in time limit). +/* .IP "\fBsmtp_helo_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the HELO or EHLO command, +/* and for receiving the initial remote SMTP server response. +/* .IP "\fBlmtp_lhlo_timeout (300s)\fR" +/* The Postfix LMTP client time limit for sending the LHLO command, +/* and for receiving the initial remote LMTP server response. +/* .IP "\fBsmtp_xforward_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the XFORWARD command, +/* and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_mail_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the MAIL FROM command, +/* and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_rcpt_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP RCPT TO +/* command, and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_data_init_timeout (120s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP DATA command, +/* and for receiving the remote SMTP server response. +/* .IP "\fBsmtp_data_xfer_timeout (180s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP message content. +/* .IP "\fBsmtp_data_done_timeout (600s)\fR" +/* The Postfix SMTP client time limit for sending the SMTP ".", and +/* for receiving the remote SMTP server response. +/* .IP "\fBsmtp_quit_timeout (300s)\fR" +/* The Postfix SMTP client time limit for sending the QUIT command, +/* and for receiving the remote SMTP server response. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtp_mx_address_limit (5)\fR" +/* The maximal number of MX (mail exchanger) IP addresses that can +/* result from Postfix SMTP client mail exchanger lookups, or zero (no +/* limit). +/* .IP "\fBsmtp_mx_session_limit (2)\fR" +/* The maximal number of SMTP sessions per delivery request before +/* the Postfix SMTP client +/* gives up or delivers to a fall-back relay host, or zero (no +/* limit). +/* .IP "\fBsmtp_rset_timeout (20s)\fR" +/* The Postfix SMTP client time limit for sending the RSET command, +/* and for receiving the remote SMTP server response. +/* .PP +/* Available in Postfix version 2.2 and earlier: +/* .IP "\fBlmtp_cache_connection (yes)\fR" +/* Keep Postfix LMTP client connections open for up to $max_idle +/* seconds. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtp_connection_cache_destinations (empty)\fR" +/* Permanently enable SMTP connection caching for the specified +/* destinations. +/* .IP "\fBsmtp_connection_cache_on_demand (yes)\fR" +/* Temporarily enable SMTP connection caching while a destination +/* has a high volume of mail in the active queue. +/* .IP "\fBsmtp_connection_reuse_time_limit (300s)\fR" +/* The amount of time during which Postfix will use an SMTP +/* connection repeatedly. +/* .IP "\fBsmtp_connection_cache_time_limit (2s)\fR" +/* When SMTP connection caching is enabled, the amount of time that +/* an unused SMTP client socket is kept open before it is closed. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBconnection_cache_protocol_timeout (5s)\fR" +/* Time limit for connection cache connect, send or receive +/* operations. +/* .PP +/* Available in Postfix version 2.9 - 3.6: +/* .IP "\fBsmtp_per_record_deadline (no)\fR" +/* Change the behavior of the smtp_*_timeout time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtp_connection_reuse_count_limit (0)\fR" +/* When SMTP connection caching is enabled, the number of times +/* that an SMTP session may be reused before it is closed, or zero (no +/* limit). +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtp_tls_connection_reuse (no)\fR" +/* Try to make multiple deliveries per TLS-encrypted connection. +/* .PP +/* Available in Postfix version 3.7 and later: +/* .IP "\fBsmtp_per_request_deadline (no)\fR" +/* Change the behavior of the smtp_*_timeout time limits, from a +/* time limit per plaintext or TLS read or write call, to a combined +/* time limit for sending a complete SMTP request and for receiving a +/* complete SMTP response. +/* .IP "\fBsmtp_min_data_rate (500)\fR" +/* The minimum plaintext data transfer rate in bytes/second for +/* DATA requests, when deadlines are enabled with smtp_per_request_deadline. +/* .PP +/* Implemented in the qmgr(8) daemon: +/* .IP "\fBtransport_destination_concurrency_limit ($default_destination_concurrency_limit)\fR" +/* A transport-specific override for the +/* default_destination_concurrency_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* .IP "\fBtransport_destination_recipient_limit ($default_destination_recipient_limit)\fR" +/* A transport-specific override for the +/* default_destination_recipient_limit parameter value, where +/* \fItransport\fR is the master.cf name of the message delivery +/* transport. +/* SMTPUTF8 CONTROLS +/* .ad +/* .fi +/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable preliminary SMTPUTF8 support for the protocols described +/* in RFC 6531..6533. +/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR" +/* Detect that a message requires SMTPUTF8 support for the specified +/* mail origin classes. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBenable_idna2003_compatibility (no)\fR" +/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008, +/* when converting UTF-8 domain names to/from the ASCII form that is +/* used for DNS lookups. +/* TROUBLE SHOOTING CONTROLS +/* .ad +/* .fi +/* .IP "\fBdebug_peer_level (2)\fR" +/* The increment in verbose logging level when a nexthop destination, +/* remote client or server name or network address matches a pattern +/* given with the debug_peer_list parameter. +/* .IP "\fBdebug_peer_list (empty)\fR" +/* Optional list of nexthop destination, remote client or server +/* name or network address patterns that, if matched, cause the verbose +/* logging level to increase by the amount specified in $debug_peer_level. +/* .IP "\fBerror_notice_recipient (postmaster)\fR" +/* The recipient of postmaster notifications about mail delivery +/* problems that are caused by policy, resource, software or protocol +/* errors. +/* .IP "\fBinternal_mail_filter_classes (empty)\fR" +/* What categories of Postfix-generated mail are subject to +/* before-queue content inspection by non_smtpd_milters, header_checks +/* and body_checks. +/* .IP "\fBnotify_classes (resource, software)\fR" +/* The list of error classes that are reported to the postmaster. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBbest_mx_transport (empty)\fR" +/* Where the Postfix SMTP client should deliver mail when it detects +/* a "mail loops back to myself" error condition. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBdisable_dns_lookups (no)\fR" +/* Disable DNS lookups in the Postfix SMTP and LMTP clients. +/* .IP "\fBinet_interfaces (all)\fR" +/* The network interface addresses that this mail system receives +/* mail on. +/* .IP "\fBinet_protocols (see 'postconf -d output')\fR" +/* The Internet protocols Postfix will attempt to use when making +/* or accepting connections. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBlmtp_assume_final (no)\fR" +/* When a remote LMTP server announces no DSN support, assume that +/* the +/* server performs final delivery, and send "delivered" delivery status +/* notifications instead of "relayed". +/* .IP "\fBlmtp_tcp_port (24)\fR" +/* The default TCP port that the Postfix LMTP client connects to. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBproxy_interfaces (empty)\fR" +/* The network interface addresses that this mail system receives mail +/* on by way of a proxy or network address translation unit. +/* .IP "\fBsmtp_address_preference (any)\fR" +/* The address type ("ipv6", "ipv4" or "any") that the Postfix +/* SMTP client will try first, when a destination has IPv6 and IPv4 +/* addresses with equal MX preference. +/* .IP "\fBsmtp_bind_address (empty)\fR" +/* An optional numerical network address that the Postfix SMTP client +/* should bind to when making an IPv4 connection. +/* .IP "\fBsmtp_bind_address6 (empty)\fR" +/* An optional numerical network address that the Postfix SMTP client +/* should bind to when making an IPv6 connection. +/* .IP "\fBsmtp_helo_name ($myhostname)\fR" +/* The hostname to send in the SMTP HELO or EHLO command. +/* .IP "\fBlmtp_lhlo_name ($myhostname)\fR" +/* The hostname to send in the LMTP LHLO command. +/* .IP "\fBsmtp_host_lookup (dns)\fR" +/* What mechanisms the Postfix SMTP client uses to look up a host's +/* IP address. +/* .IP "\fBsmtp_randomize_addresses (yes)\fR" +/* Randomize the order of equal-preference MX host addresses. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available with Postfix 2.2 and earlier: +/* .IP "\fBfallback_relay (empty)\fR" +/* Optional list of relay hosts for SMTP destinations that can't be +/* found or that are unreachable. +/* .PP +/* Available with Postfix 2.3 and later: +/* .IP "\fBsmtp_fallback_relay ($fallback_relay)\fR" +/* Optional list of relay hosts for SMTP destinations that can't be +/* found or that are unreachable. +/* .PP +/* Available with Postfix 3.0 and later: +/* .IP "\fBsmtp_address_verify_target (rcpt)\fR" +/* In the context of email address verification, the SMTP protocol +/* stage that determines whether an email address is deliverable. +/* .PP +/* Available with Postfix 3.1 and later: +/* .IP "\fBlmtp_fallback_relay (empty)\fR" +/* Optional list of relay hosts for LMTP destinations that can't be +/* found or that are unreachable. +/* .PP +/* Available with Postfix 3.2 and later: +/* .IP "\fBsmtp_tcp_port (smtp)\fR" +/* The default TCP port that the Postfix SMTP client connects to. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.7 and later: +/* .IP "\fBsmtp_bind_address_enforce (no)\fR" +/* Defer delivery when the Postfix SMTP client cannot apply the +/* smtp_bind_address or smtp_bind_address6 setting. +/* SEE ALSO +/* generic(5), output address rewriting +/* header_checks(5), message header content inspection +/* body_checks(5), body parts content inspection +/* qmgr(8), queue manager +/* bounce(8), delivery status reports +/* scache(8), connection cache server +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* tlsmgr(8), TLS session and PRNG management +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* SASL_README, Postfix SASL howto +/* TLS_README, Postfix STARTTLS howto +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Command pipelining in cooperation with: +/* Jon Ribbens +/* Oaktree Internet Solutions Ltd., +/* Internet House, +/* Canal Basin, +/* Coventry, +/* CV1 4LY, United Kingdom. +/* +/* SASL support originally by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Revised TLS and SMTP connection cache support by: +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* DNS library. */ + +#include + +/* Single server skeleton. */ + +#include + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + + /* + * Tunable parameters. These have compiled-in defaults that can be overruled + * by settings in the global Postfix configuration file. + */ +int var_smtp_conn_tmout; +int var_smtp_helo_tmout; +int var_smtp_xfwd_tmout; +int var_smtp_mail_tmout; +int var_smtp_rcpt_tmout; +int var_smtp_data0_tmout; +int var_smtp_data1_tmout; +int var_smtp_data2_tmout; +int var_smtp_rset_tmout; +int var_smtp_quit_tmout; +char *var_notify_classes; +int var_smtp_skip_5xx_greeting; +int var_ign_mx_lookup_err; +int var_skip_quit_resp; +char *var_fallback_relay; +char *var_bestmx_transp; +char *var_error_rcpt; +int var_smtp_always_ehlo; +int var_smtp_never_ehlo; +char *var_smtp_sasl_opts; +char *var_smtp_sasl_path; +char *var_smtp_sasl_passwd; +bool var_smtp_sasl_enable; +char *var_smtp_sasl_mechs; +char *var_smtp_sasl_type; +char *var_smtp_bind_addr; +char *var_smtp_bind_addr6; +char *var_smtp_vrfy_tgt; +bool var_smtp_rand_addr; +int var_smtp_pix_thresh; +int var_queue_run_delay; +int var_min_backoff_time; +int var_smtp_pix_delay; +int var_smtp_line_limit; +char *var_smtp_helo_name; +char *var_smtp_host_lookup; +bool var_smtp_quote_821_env; +bool var_smtp_defer_mxaddr; +bool var_smtp_send_xforward; +int var_smtp_mxaddr_limit; +int var_smtp_mxsess_limit; +int var_smtp_cache_conn; +int var_smtp_reuse_time; +int var_smtp_reuse_count; +char *var_smtp_cache_dest; +char *var_scache_service; /* You can now leave this here. */ +bool var_smtp_cache_demand; +char *var_smtp_ehlo_dis_words; +char *var_smtp_ehlo_dis_maps; +char *var_smtp_addr_pref; + +char *var_smtp_tls_level; +bool var_smtp_use_tls; +bool var_smtp_enforce_tls; +char *var_smtp_tls_per_site; +char *var_smtp_tls_policy; +bool var_smtp_tls_wrappermode; +bool var_smtp_tls_conn_reuse; +char *var_tlsproxy_service; + +#ifdef USE_TLS +char *var_smtp_sasl_tls_opts; +char *var_smtp_sasl_tlsv_opts; +int var_smtp_starttls_tmout; +char *var_smtp_tls_CAfile; +char *var_smtp_tls_CApath; +char *var_smtp_tls_chain_files; +char *var_smtp_tls_cert_file; +char *var_smtp_tls_mand_ciph; +char *var_smtp_tls_excl_ciph; +char *var_smtp_tls_mand_excl; +char *var_smtp_tls_dcert_file; +char *var_smtp_tls_dkey_file; +bool var_smtp_tls_enforce_peername; +char *var_smtp_tls_key_file; +char *var_smtp_tls_loglevel; +bool var_smtp_tls_note_starttls_offer; +char *var_smtp_tls_mand_proto; +char *var_smtp_tls_sec_cmatch; +int var_smtp_tls_scert_vd; +char *var_smtp_tls_vfy_cmatch; +char *var_smtp_tls_fpt_cmatch; +char *var_smtp_tls_fpt_dgst; +char *var_smtp_tls_tafile; +char *var_smtp_tls_proto; +char *var_smtp_tls_ciph; +char *var_smtp_tls_eccert_file; +char *var_smtp_tls_eckey_file; +char *var_smtp_tls_sni; +bool var_smtp_tls_blk_early_mail_reply; +bool var_smtp_tls_force_tlsa; +char *var_smtp_tls_insecure_mx_policy; + +#endif + +char *var_smtp_generic_maps; +char *var_prop_extension; +bool var_smtp_sender_auth; +char *var_smtp_tcp_port; +int var_scache_proto_tmout; +bool var_smtp_cname_overr; +char *var_smtp_pix_bug_words; +char *var_smtp_pix_bug_maps; +char *var_cyrus_conf_path; +char *var_smtp_head_chks; +char *var_smtp_mime_chks; +char *var_smtp_nest_chks; +char *var_smtp_body_chks; +char *var_smtp_resp_filter; +bool var_lmtp_assume_final; +char *var_smtp_dns_res_opt; +char *var_smtp_dns_support; +bool var_smtp_dummy_mail_auth; +char *var_smtp_dsn_filter; +char *var_smtp_dns_re_filter; +bool var_smtp_balance_inet_proto; +bool var_smtp_req_deadline; +int var_smtp_min_data_rate; + + /* Special handling of 535 AUTH errors. */ +char *var_smtp_sasl_auth_cache_name; +int var_smtp_sasl_auth_cache_time; +bool var_smtp_sasl_auth_soft_bounce; + +char *var_hfrom_format; +bool var_smtp_bind_addr_enforce; + + /* + * Global variables. + */ +int smtp_mode; +int smtp_host_lookup_mask; +int smtp_dns_support; +STRING_LIST *smtp_cache_dest; +SCACHE *smtp_scache; +MAPS *smtp_ehlo_dis_maps; +MAPS *smtp_generic_maps; +int smtp_ext_prop_mask; +unsigned smtp_dns_res_opt; +MAPS *smtp_pix_bug_maps; +HBC_CHECKS *smtp_header_checks; /* limited header checks */ +HBC_CHECKS *smtp_body_checks; /* limited body checks */ +SMTP_CLI_ATTR smtp_cli_attr; /* parsed command-line */ +int smtp_hfrom_format; /* postmaster notifications */ + +#ifdef USE_TLS + + /* + * OpenSSL client state (opaque handle) + */ +TLS_APPL_STATE *smtp_tls_ctx; +int smtp_tls_insecure_mx_policy; + +#endif + + /* + * IPv6 preference. + */ +static int smtp_addr_pref; + +/* get_cli_attr - get command-line attributes */ + +static void get_cli_attr(SMTP_CLI_ATTR *attr, char **argv) +{ + const char myname[] = "get_cli_attr"; + const char *last_flags = "flags="; /* i.e. empty */ + static const BYTE_MASK flags_map[] = { + 'D', SMTP_CLI_FLAG_DELIVERED_TO, + 'O', SMTP_CLI_FLAG_ORIG_RCPT, + 'R', SMTP_CLI_FLAG_RETURN_PATH, + 'X', SMTP_CLI_FLAG_FINAL_DELIVERY, + 0, + }; + + /* + * Initialize. + */ + attr->flags = 0; + + /* + * Iterate over the command-line attribute list. Errors are fatal. + */ + for ( /* void */ ; *argv != 0; argv++) { + + /* + * flags=stuff. Errors are fatal. + */ + if (strncasecmp("flags=", *argv, sizeof("flags=") - 1) == 0) { + last_flags = *argv; + if (msg_verbose) + msg_info("%s: %s", myname, last_flags); + attr->flags = byte_mask(*argv, flags_map, + *argv + sizeof("flags=") - 1); + } + + /* + * Bad. + */ + else + msg_fatal("unknown attribute name: %s", *argv); + } + + /* + * Backwards compatibility, redundancy, and obsolescence. + */ + if (!smtp_mode && var_lmtp_assume_final + && (attr->flags & SMTP_CLI_FLAG_FINAL_DELIVERY) == 0) { + attr->flags |= SMTP_CLI_FLAG_FINAL_DELIVERY; + msg_warn("%s is obsolete; instead, specify \"%sX\" in %s", + VAR_LMTP_ASSUME_FINAL, last_flags, MASTER_CONF_FILE); + } +} + +/* deliver_message - deliver message with extreme prejudice */ + +static int deliver_message(const char *service, DELIVER_REQUEST *request) +{ + SMTP_STATE *state; + int result; + + if (msg_verbose) + msg_info("deliver_message: from %s", request->sender); + + /* + * Sanity checks. The smtp server is unprivileged and chrooted, so we can + * afford to distribute the data censoring code, instead of having it all + * in one place. + */ + if (request->nexthop[0] == 0) + msg_fatal("empty nexthop hostname"); + if (request->rcpt_list.len <= 0) + msg_fatal("recipient count: %d", request->rcpt_list.len); + + /* + * D flag checks. + */ + if (smtp_cli_attr.flags & SMTP_CLI_FLAG_DELIVERED_TO) { + + /* + * The D flag cannot be specified for multi-recipient deliveries. + */ + if (request->rcpt_list.len > 1) { + msg_warn("flag `D' requires %s_destination_recipient_limit = 1", + service); + return (reject_deliver_request(service, request, "4.3.5", + "mail system configuration error")); + } + + /* + * The recipient cannot appear in a Delivered-To: header. + */ + else { + DELIVERED_HDR_INFO *delivered_info = delivered_hdr_init( + request->fp, request->data_offset, FOLD_ADDR_ALL); + VSTRING *generic_rcpt = vstring_alloc(100); + int have_delivered_loop; + + smtp_rewrite_generic_internal(generic_rcpt, + request->rcpt_list.info->address); + have_delivered_loop = delivered_hdr_find( + delivered_info, STR(generic_rcpt)); + vstring_free(generic_rcpt); + delivered_hdr_free(delivered_info); + if (have_delivered_loop) { + return (reject_deliver_request(service, request, "5.4.6", + "mail forwarding loop for %s", + request->rcpt_list.info->address)); + } + } + } + + /* + * The O flag cannot be specified for multi-recipient deliveries. + */ + if ((smtp_cli_attr.flags & SMTP_CLI_FLAG_ORIG_RCPT) + && request->rcpt_list.len > 1) { + msg_warn("flag `O' requires %s_destination_recipient_limit = 1", + service); + return (reject_deliver_request(service, request, "4.3.5", + "mail system configuration error")); + } + + /* + * Initialize. Bundle all information about the delivery request, so that + * we can produce understandable diagnostics when something goes wrong + * many levels below. The alternative would be to make everything global. + */ + state = smtp_state_alloc(); + state->request = request; + state->src = request->fp; + state->service = service; + state->misc_flags |= smtp_addr_pref; + state->debug_peer_per_nexthop = + debug_peer_check(request->nexthop, "noaddr"); + SMTP_RCPT_INIT(state); + + /* + * Establish an SMTP session and deliver this message to all requested + * recipients. At the end, notify the postmaster of any protocol errors. + * Optionally deliver mail locally when this machine is the best mail + * exchanger. + */ + result = smtp_connect(state); + + /* + * Clean up. + */ + smtp_state_free(state); + + return (result); +} + +/* smtp_service - perform service for client */ + +static void smtp_service(VSTREAM *client_stream, char *service, + char **unused_argv) +{ + DELIVER_REQUEST *request; + int status; + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * dedicated to remote SMTP delivery service. What we see below is a + * little protocol to (1) tell the queue manager that we are ready, (2) + * read a request from the queue manager, and (3) report the completion + * status of that request. All connection-management stuff is handled by + * the common code in single_server.c. + */ + if ((request = deliver_request_read(client_stream)) != 0) { + status = deliver_message(service, request); + deliver_request_done(client_stream, request, status); + } +} + +/* post_init - post-jail initialization */ + +static void post_init(char *unused_name, char **argv) +{ + static const NAME_MASK lookup_masks[] = { + SMTP_HOST_LOOKUP_DNS, SMTP_HOST_FLAG_DNS, + SMTP_HOST_LOOKUP_NATIVE, SMTP_HOST_FLAG_NATIVE, + 0, + }; + static const NAME_MASK dns_res_opt_masks[] = { + SMTP_DNS_RES_OPT_DEFNAMES, RES_DEFNAMES, + SMTP_DNS_RES_OPT_DNSRCH, RES_DNSRCH, + 0, + }; + static const NAME_CODE dns_support[] = { + SMTP_DNS_SUPPORT_DISABLED, SMTP_DNS_DISABLED, + SMTP_DNS_SUPPORT_ENABLED, SMTP_DNS_ENABLED, +#if (RES_USE_DNSSEC != 0) && (RES_USE_EDNS0 != 0) + SMTP_DNS_SUPPORT_DNSSEC, SMTP_DNS_DNSSEC, +#endif + 0, SMTP_DNS_INVALID, + }; + + if (*var_smtp_dns_support == 0) { + /* Backwards compatible empty setting */ + smtp_dns_support = + var_disable_dns ? SMTP_DNS_DISABLED : SMTP_DNS_ENABLED; + } else { + smtp_dns_support = + name_code(dns_support, NAME_CODE_FLAG_NONE, var_smtp_dns_support); + if (smtp_dns_support == SMTP_DNS_INVALID) + msg_fatal("invalid %s: \"%s\"", VAR_LMTP_SMTP(DNS_SUPPORT), + var_smtp_dns_support); + var_disable_dns = (smtp_dns_support == SMTP_DNS_DISABLED); + } + +#ifdef USE_TLS + if (smtp_mode) { + smtp_tls_insecure_mx_policy = + tls_level_lookup(var_smtp_tls_insecure_mx_policy); + switch (smtp_tls_insecure_mx_policy) { + case TLS_LEV_MAY: + case TLS_LEV_ENCRYPT: + case TLS_LEV_DANE: + break; + default: + msg_fatal("invalid %s: \"%s\"", VAR_SMTP_TLS_INSECURE_MX_POLICY, + var_smtp_tls_insecure_mx_policy); + } + } +#endif + + /* + * Select hostname lookup mechanisms. + */ + if (smtp_dns_support == SMTP_DNS_DISABLED) + smtp_host_lookup_mask = SMTP_HOST_FLAG_NATIVE; + else + smtp_host_lookup_mask = + name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks, + var_smtp_host_lookup); + if (msg_verbose) + msg_info("host name lookup methods: %s", + str_name_mask(VAR_LMTP_SMTP(HOST_LOOKUP), lookup_masks, + smtp_host_lookup_mask)); + + /* + * Session cache instance. + */ + if (*var_smtp_cache_dest || var_smtp_cache_demand) +#if 0 + smtp_scache = scache_multi_create(); +#else + smtp_scache = scache_clnt_create(var_scache_service, + var_scache_proto_tmout, + var_ipc_idle_limit, + var_ipc_ttl_limit); +#endif + + /* + * Select DNS query flags. + */ + smtp_dns_res_opt = name_mask(VAR_LMTP_SMTP(DNS_RES_OPT), dns_res_opt_masks, + var_smtp_dns_res_opt); + + /* + * Address verification. + */ + smtp_vrfy_init(); + + /* + * Look up service command-line attributes; these do not change during + * the process lifetime. + */ + get_cli_attr(&smtp_cli_attr, argv); + + /* + * header_from format, for postmaster notifications. + */ + smtp_hfrom_format = hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format); +} + +/* pre_init - pre-jail initialization */ + +static void pre_init(char *unused_name, char **unused_argv) +{ + int use_tls; + static const NAME_CODE addr_pref_map[] = { + INET_PROTO_NAME_IPV6, SMTP_MISC_FLAG_PREF_IPV6, + INET_PROTO_NAME_IPV4, SMTP_MISC_FLAG_PREF_IPV4, + INET_PROTO_NAME_ANY, 0, + 0, -1, + }; + + /* + * Turn on per-peer debugging. + */ + debug_peer_init(); + + /* + * SASL initialization. + */ + if (var_smtp_sasl_enable) +#ifdef USE_SASL_AUTH + smtp_sasl_initialize(); +#else + msg_warn("%s is true, but SASL support is not compiled in", + VAR_LMTP_SMTP(SASL_ENABLE)); +#endif + + if (*var_smtp_tls_level != 0) + switch (tls_level_lookup(var_smtp_tls_level)) { + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_DANE_ONLY: + case TLS_LEV_FPRINT: + case TLS_LEV_ENCRYPT: + var_smtp_use_tls = var_smtp_enforce_tls = 1; + break; + case TLS_LEV_DANE: + case TLS_LEV_MAY: + var_smtp_use_tls = 1; + var_smtp_enforce_tls = 0; + break; + case TLS_LEV_NONE: + var_smtp_use_tls = var_smtp_enforce_tls = 0; + break; + default: + /* tls_level_lookup() logs no warning. */ + /* session_tls_init() assumes that var_smtp_tls_level is sane. */ + msg_fatal("Invalid TLS level \"%s\"", var_smtp_tls_level); + } + use_tls = (var_smtp_use_tls || var_smtp_enforce_tls); + + /* + * Initialize the TLS data before entering the chroot jail + */ + if (use_tls || var_smtp_tls_per_site[0] || var_smtp_tls_policy[0]) { +#ifdef USE_TLS + TLS_CLIENT_INIT_PROPS props; + + tls_pre_jail_init(TLS_ROLE_CLIENT); + + /* + * We get stronger type safety and a cleaner interface by combining + * the various parameters into a single tls_client_props structure. + * + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + * + * With tlsproxy(8) turned on, this is still needed for DANE-related + * initializations. + */ + smtp_tls_ctx = + TLS_CLIENT_INIT(&props, + log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL), + log_level = var_smtp_tls_loglevel, + verifydepth = var_smtp_tls_scert_vd, + cache_type = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE), + chain_files = var_smtp_tls_chain_files, + cert_file = var_smtp_tls_cert_file, + key_file = var_smtp_tls_key_file, + dcert_file = var_smtp_tls_dcert_file, + dkey_file = var_smtp_tls_dkey_file, + eccert_file = var_smtp_tls_eccert_file, + eckey_file = var_smtp_tls_eckey_file, + CAfile = var_smtp_tls_CAfile, + CApath = var_smtp_tls_CApath, + mdalg = var_smtp_tls_fpt_dgst); + smtp_tls_list_init(); + tls_dane_loglevel(VAR_LMTP_SMTP(TLS_LOGLEVEL), var_smtp_tls_loglevel); +#else + msg_warn("TLS has been selected, but TLS support is not compiled in"); +#endif + } + + /* + * Flush client. + */ + flush_init(); + + /* + * Session cache domain list. + */ + if (*var_smtp_cache_dest) + smtp_cache_dest = string_list_init(VAR_SMTP_CACHE_DEST, + MATCH_FLAG_RETURN, + var_smtp_cache_dest); + + /* + * EHLO keyword filter. + */ + if (*var_smtp_ehlo_dis_maps) + smtp_ehlo_dis_maps = maps_create(VAR_LMTP_SMTP(EHLO_DIS_MAPS), + var_smtp_ehlo_dis_maps, + DICT_FLAG_LOCK); + + /* + * PIX bug workarounds. + */ + if (*var_smtp_pix_bug_maps) + smtp_pix_bug_maps = maps_create(VAR_LMTP_SMTP(PIX_BUG_MAPS), + var_smtp_pix_bug_maps, + DICT_FLAG_LOCK); + + /* + * Generic maps. + */ + if (*var_prop_extension) + smtp_ext_prop_mask = + ext_prop_mask(VAR_PROP_EXTENSION, var_prop_extension); + if (*var_smtp_generic_maps) + smtp_generic_maps = + maps_create(VAR_LMTP_SMTP(GENERIC_MAPS), var_smtp_generic_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + + /* + * Header/body checks. + */ + smtp_header_checks = hbc_header_checks_create( + VAR_LMTP_SMTP(HEAD_CHKS), var_smtp_head_chks, + VAR_LMTP_SMTP(MIME_CHKS), var_smtp_mime_chks, + VAR_LMTP_SMTP(NEST_CHKS), var_smtp_nest_chks, + smtp_hbc_callbacks); + smtp_body_checks = hbc_body_checks_create( + VAR_LMTP_SMTP(BODY_CHKS), var_smtp_body_chks, + smtp_hbc_callbacks); + + /* + * Server reply filter. + */ + if (*var_smtp_resp_filter) + smtp_chat_resp_filter = + dict_open(var_smtp_resp_filter, O_RDONLY, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + + /* + * Address family preference. + */ + if (*var_smtp_addr_pref) { + smtp_addr_pref = name_code(addr_pref_map, NAME_CODE_FLAG_NONE, + var_smtp_addr_pref); + if (smtp_addr_pref < 0) + msg_fatal("bad %s value: %s", VAR_LMTP_SMTP(ADDR_PREF), + var_smtp_addr_pref); + } + + /* + * DNS reply filter. + */ + if (*var_smtp_dns_re_filter) + dns_rr_filter_compile(VAR_LMTP_SMTP(DNS_RE_FILTER), + var_smtp_dns_re_filter); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + char *sane_procname; + +#include "smtp_params.c" +#include "lmtp_params.c" + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * XXX At this point, var_procname etc. are not initialized. + * + * The process name, "smtp" or "lmtp", determines the protocol, the DSN + * server reply type, SASL service information lookup, and more. Prepare + * for the possibility there may be another personality. + */ + sane_procname = sane_basename((VSTRING *) 0, argv[0]); + if (strcmp(sane_procname, "smtp") == 0) + smtp_mode = 1; + else if (strcmp(sane_procname, "lmtp") == 0) + smtp_mode = 0; + else + /* TODO: logging is not initialized. */ + msg_fatal("unexpected process name \"%s\" - " + "specify \"smtp\" or \"lmtp\"", var_procname); + + /* + * Initialize with the LMTP or SMTP parameter name space. + */ + single_server_main(argc, argv, smtp_service, + CA_MAIL_SERVER_TIME_TABLE(smtp_mode ? + smtp_time_table : lmtp_time_table), + CA_MAIL_SERVER_INT_TABLE(smtp_mode ? + smtp_int_table : lmtp_int_table), + CA_MAIL_SERVER_STR_TABLE(smtp_mode ? + smtp_str_table : lmtp_str_table), + CA_MAIL_SERVER_BOOL_TABLE(smtp_mode ? + smtp_bool_table : lmtp_bool_table), + CA_MAIL_SERVER_NBOOL_TABLE(smtp_mode ? + smtp_nbool_table : lmtp_nbool_table), + CA_MAIL_SERVER_PRE_INIT(pre_init), + CA_MAIL_SERVER_POST_INIT(post_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_BOUNCE_INIT(VAR_SMTP_DSN_FILTER, + &var_smtp_dsn_filter), + 0); +} diff --git a/src/smtp/smtp.h b/src/smtp/smtp.h new file mode 100644 index 0000000..0d5c80a --- /dev/null +++ b/src/smtp/smtp.h @@ -0,0 +1,771 @@ +/*++ +/* NAME +/* smtp 3h +/* SUMMARY +/* smtp client program +/* SYNOPSIS +/* #include "smtp.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include +#include +#include + + /* + * Postfix TLS library. + */ +#include + + /* + * tlsproxy client. + */ +#include + + /* + * Global iterator support. This is updated by the connection-management + * loop, and contains dynamic context that appears in lookup keys for SASL + * passwords, TLS policy, cached SMTP connections, and cached TLS session + * keys. + * + * For consistency and maintainability, context that is used in more than one + * lookup key is formatted with smtp_key_format(). + */ +typedef struct SMTP_ITERATOR { + /* Public members. */ + VSTRING *request_nexthop; /* delivery request nexhop or empty */ + VSTRING *dest; /* current nexthop */ + VSTRING *host; /* hostname or empty */ + VSTRING *addr; /* printable address or empty */ + unsigned port; /* network byte order or null */ + struct DNS_RR *rr; /* DNS resource record or null */ + struct DNS_RR *mx; /* DNS resource record or null */ + /* Private members. */ + VSTRING *saved_dest; /* saved current nexthop */ + struct SMTP_STATE *parent; /* parent linkage */ +} SMTP_ITERATOR; + +#define SMTP_ITER_INIT(iter, _dest, _host, _addr, _port, state) do { \ + vstring_strcpy((iter)->dest, (_dest)); \ + vstring_strcpy((iter)->host, (_host)); \ + vstring_strcpy((iter)->addr, (_addr)); \ + (iter)->port = (_port); \ + (iter)->mx = (iter)->rr = 0; \ + vstring_strcpy((iter)->saved_dest, ""); \ + (iter)->parent = (state); \ + } while (0) + +#define SMTP_ITER_SAVE_DEST(iter) do { \ + vstring_strcpy((iter)->saved_dest, STR((iter)->dest)); \ + } while (0) + +#define SMTP_ITER_RESTORE_DEST(iter) do { \ + vstring_strcpy((iter)->dest, STR((iter)->saved_dest)); \ + } while (0) + + /* + * TLS Policy support. + */ +#ifdef USE_TLS + +typedef struct SMTP_TLS_POLICY { + int level; /* TLS enforcement level */ + char *protocols; /* Acceptable SSL protocols */ + char *grade; /* Cipher grade: "export", ... */ + VSTRING *exclusions; /* Excluded SSL ciphers */ + ARGV *matchargv; /* Cert match patterns */ + DSN_BUF *why; /* Lookup error status */ + TLS_DANE *dane; /* DANE TLSA digests */ + char *sni; /* Optional SNI name when not DANE */ + int conn_reuse; /* enable connection reuse */ +} SMTP_TLS_POLICY; + + /* + * smtp_tls_policy.c + */ +extern void smtp_tls_list_init(void); +extern int smtp_tls_policy_cache_query(DSN_BUF *, SMTP_TLS_POLICY *, SMTP_ITERATOR *); +extern void smtp_tls_policy_cache_flush(void); + + /* + * Macros must use distinct names for local temporary variables, otherwise + * there will be bugs due to shadowing. This happened when an earlier + * version of smtp_tls_policy_dummy() invoked smtp_tls_policy_init(), but it + * could also happen without macro nesting. + * + * General principle: use all or part of the macro name in each temporary + * variable name. Then, append suffixes to the names if needed. + */ +#define smtp_tls_policy_dummy(t) do { \ + SMTP_TLS_POLICY *_tls_policy_dummy_tmp = (t); \ + smtp_tls_policy_init(_tls_policy_dummy_tmp, (DSN_BUF *) 0); \ + _tls_policy_dummy_tmp->level = TLS_LEV_NONE; \ + } while (0) + + /* This macro is not part of the module external interface. */ +#define smtp_tls_policy_init(t, w) do { \ + SMTP_TLS_POLICY *_tls_policy_init_tmp = (t); \ + _tls_policy_init_tmp->protocols = 0; \ + _tls_policy_init_tmp->grade = 0; \ + _tls_policy_init_tmp->exclusions = 0; \ + _tls_policy_init_tmp->matchargv = 0; \ + _tls_policy_init_tmp->why = (w); \ + _tls_policy_init_tmp->dane = 0; \ + _tls_policy_init_tmp->sni = 0; \ + _tls_policy_init_tmp->conn_reuse = 0; \ + } while (0) + +#endif + + /* + * State information associated with each SMTP delivery request. + * Session-specific state is stored separately. + */ +typedef struct SMTP_STATE { + int misc_flags; /* processing flags, see below */ + VSTREAM *src; /* queue file stream */ + const char *service; /* transport name */ + DELIVER_REQUEST *request; /* envelope info, offsets */ + struct SMTP_SESSION *session; /* network connection */ + int status; /* delivery status */ + ssize_t space_left; /* output length control */ + + /* + * Global iterator. + */ + SMTP_ITERATOR iterator[1]; /* Usage: state->iterator->member */ + + /* + * Global iterator. + */ +#ifdef USE_TLS + SMTP_TLS_POLICY tls[1]; /* Usage: state->tls->member */ +#endif + + /* + * Connection cache support. + */ + HTABLE *cache_used; /* cached addresses that were used */ + VSTRING *dest_label; /* cached logical/physical binding */ + VSTRING *dest_prop; /* binding properties, passivated */ + VSTRING *endp_label; /* cached session physical endpoint */ + VSTRING *endp_prop; /* endpoint properties, passivated */ + + /* + * Flags and counters to control the handling of mail delivery errors. + * There is some redundancy for sanity checking. At the end of an SMTP + * session all recipients should be marked one way or the other. + */ + int rcpt_left; /* recipients left over */ + int rcpt_drop; /* recipients marked as drop */ + int rcpt_keep; /* recipients marked as keep */ + + /* + * DSN Support introduced major bloat in error processing. + */ + DSN_BUF *why; /* on-the-fly formatting buffer */ + + /* + * Whether per-nexthop debug_peer support was requested. Otherwise, + * assume per-server debug_peer support. + */ + int debug_peer_per_nexthop; + + /* + * One-bit counters to avoid logging the same warning multiple times per + * delivery request. + */ + int logged_line_length_limit:1; +} SMTP_STATE; + + /* + * Primitives to enable/disable/test connection caching and reuse based on + * the delivery request next-hop destination (i.e. not smtp_fallback_relay). + * + * Connection cache lookup by the delivery request next-hop destination allows + * a reuse request to skip over bad hosts, and may result in a connection to + * a fall-back relay. Once we have found a 'good' host for a delivery + * request next-hop, clear the delivery request next-hop destination, to + * avoid caching less-preferred connections under that same delivery request + * next-hop. + */ +#define SET_SCACHE_REQUEST_NEXTHOP(state, nexthop) do { \ + vstring_strcpy((state)->iterator->request_nexthop, nexthop); \ + } while (0) + +#define CLEAR_SCACHE_REQUEST_NEXTHOP(state) do { \ + STR((state)->iterator->request_nexthop)[0] = 0; \ + } while (0) + +#define HAVE_SCACHE_REQUEST_NEXTHOP(state) \ + (STR((state)->iterator->request_nexthop)[0] != 0) + + + /* + * Server features. + */ +#define SMTP_FEATURE_ESMTP (1<<0) +#define SMTP_FEATURE_8BITMIME (1<<1) +#define SMTP_FEATURE_PIPELINING (1<<2) +#define SMTP_FEATURE_SIZE (1<<3) +#define SMTP_FEATURE_STARTTLS (1<<4) +#define SMTP_FEATURE_AUTH (1<<5) +#define SMTP_FEATURE_XFORWARD_NAME (1<<7) +#define SMTP_FEATURE_XFORWARD_ADDR (1<<8) +#define SMTP_FEATURE_XFORWARD_PROTO (1<<9) +#define SMTP_FEATURE_XFORWARD_HELO (1<<10) +#define SMTP_FEATURE_XFORWARD_DOMAIN (1<<11) +#define SMTP_FEATURE_BEST_MX (1<<12) /* for next-hop or fall-back */ +#define SMTP_FEATURE_RSET_REJECTED (1<<13) /* RSET probe rejected */ +#define SMTP_FEATURE_FROM_CACHE (1<<14) /* cached connection */ +#define SMTP_FEATURE_DSN (1<<15) /* DSN supported */ +#define SMTP_FEATURE_PIX_NO_ESMTP (1<<16) /* PIX smtp fixup mode */ +#define SMTP_FEATURE_PIX_DELAY_DOTCRLF (1<<17) /* PIX smtp fixup mode */ +#define SMTP_FEATURE_XFORWARD_PORT (1<<18) +#define SMTP_FEATURE_EARLY_TLS_MAIL_REPLY (1<<19) /* CVE-2009-3555 */ +#define SMTP_FEATURE_XFORWARD_IDENT (1<<20) +#define SMTP_FEATURE_SMTPUTF8 (1<<21) /* RFC 6531 */ +#define SMTP_FEATURE_FROM_PROXY (1<<22) /* proxied connection */ + + /* + * Features that passivate under the endpoint. + */ +#define SMTP_FEATURE_ENDPOINT_MASK \ + (~(SMTP_FEATURE_BEST_MX | SMTP_FEATURE_RSET_REJECTED \ + | SMTP_FEATURE_FROM_CACHE)) + + /* + * Features that passivate under the logical destination. + */ +#define SMTP_FEATURE_DESTINATION_MASK (SMTP_FEATURE_BEST_MX) + + /* + * Misc flags. + */ +#define SMTP_MISC_FLAG_LOOP_DETECT (1<<0) +#define SMTP_MISC_FLAG_IN_STARTTLS (1<<1) +#define SMTP_MISC_FLAG_FIRST_NEXTHOP (1<<2) +#define SMTP_MISC_FLAG_FINAL_NEXTHOP (1<<3) +#define SMTP_MISC_FLAG_FINAL_SERVER (1<<4) +#define SMTP_MISC_FLAG_CONN_LOAD (1<<5) +#define SMTP_MISC_FLAG_CONN_STORE (1<<6) +#define SMTP_MISC_FLAG_COMPLETE_SESSION (1<<7) +#define SMTP_MISC_FLAG_PREF_IPV6 (1<<8) +#define SMTP_MISC_FLAG_PREF_IPV4 (1<<9) + +#define SMTP_MISC_FLAG_CONN_CACHE_MASK \ + (SMTP_MISC_FLAG_CONN_LOAD | SMTP_MISC_FLAG_CONN_STORE) + + /* + * smtp.c + */ +#define SMTP_HAS_DSN(why) (STR((why)->status)[0] != 0) +#define SMTP_HAS_SOFT_DSN(why) (STR((why)->status)[0] == '4') +#define SMTP_HAS_HARD_DSN(why) (STR((why)->status)[0] == '5') +#define SMTP_HAS_LOOP_DSN(why) \ + (SMTP_HAS_DSN(why) && strcmp(STR((why)->status) + 1, ".4.6") == 0) + +#define SMTP_SET_SOFT_DSN(why) (STR((why)->status)[0] = '4') +#define SMTP_SET_HARD_DSN(why) (STR((why)->status)[0] = '5') + +extern int smtp_host_lookup_mask; /* host lookup methods to use */ + +#define SMTP_HOST_FLAG_DNS (1<<0) +#define SMTP_HOST_FLAG_NATIVE (1<<1) + +extern int smtp_dns_support; /* dns support level */ + +#define SMTP_DNS_INVALID (-1) /* smtp_dns_support_level = */ +#define SMTP_DNS_DISABLED 0 /* smtp_dns_support_level = disabled */ +#define SMTP_DNS_ENABLED 1 /* smtp_dns_support_level = enabled */ +#define SMTP_DNS_DNSSEC 2 /* smtp_dns_support_level = dnssec */ + +extern SCACHE *smtp_scache; /* connection cache instance */ +extern STRING_LIST *smtp_cache_dest; /* cached destinations */ + +extern MAPS *smtp_ehlo_dis_maps; /* ehlo keyword filter */ + +extern MAPS *smtp_pix_bug_maps; /* PIX workarounds */ + +extern MAPS *smtp_generic_maps; /* make internal address valid */ +extern int smtp_ext_prop_mask; /* address extension propagation */ +extern unsigned smtp_dns_res_opt; /* DNS query flags */ + +#ifdef USE_TLS + +extern TLS_APPL_STATE *smtp_tls_ctx; /* client-side TLS engine */ +extern int smtp_tls_insecure_mx_policy; /* DANE post insecure MX? */ + +#endif + +extern HBC_CHECKS *smtp_header_checks; /* limited header checks */ +extern HBC_CHECKS *smtp_body_checks; /* limited body checks */ + + /* + * smtp_session.c + */ + +typedef struct SMTP_SESSION { + VSTREAM *stream; /* network connection */ + SMTP_ITERATOR *iterator; /* dest, host, addr, port */ + char *namaddr; /* mail exchanger */ + char *helo; /* helo response */ + unsigned port; /* network byte order */ + char *namaddrport; /* mail exchanger, incl. port */ + + VSTRING *buffer; /* I/O buffer */ + VSTRING *scratch; /* scratch buffer */ + VSTRING *scratch2; /* scratch buffer */ + + int features; /* server features */ + off_t size_limit; /* server limit or unknown */ + + ARGV *history; /* transaction log */ + int error_mask; /* error classes */ + struct MIME_STATE *mime_state; /* mime state machine */ + + int send_proto_helo; /* XFORWARD support */ + + time_t expire_time; /* session reuse expiration time */ + int reuse_count; /* # of times reused (for logging) */ + int forbidden; /* No further I/O allowed */ + +#ifdef USE_SASL_AUTH + char *sasl_mechanism_list; /* server mechanism list */ + char *sasl_username; /* client username */ + char *sasl_passwd; /* client password */ + struct XSASL_CLIENT *sasl_client; /* SASL internal state */ + VSTRING *sasl_reply; /* client response */ +#endif + + /* + * TLS related state, don't forget to initialize in session_tls_init()! + */ +#ifdef USE_TLS + TLS_SESS_STATE *tls_context; /* TLS library session state */ + char *tls_nexthop; /* Nexthop domain for cert checks */ + int tls_retry_plain; /* Try plain when TLS handshake fails */ +#endif + + SMTP_STATE *state; /* back link */ +} SMTP_SESSION; + +extern SMTP_SESSION *smtp_session_alloc(VSTREAM *, SMTP_ITERATOR *, time_t, int); +extern void smtp_session_new_stream(SMTP_SESSION *, VSTREAM *, time_t, int); +extern int smtp_sess_plaintext_ok(SMTP_ITERATOR *, int); +extern void smtp_session_free(SMTP_SESSION *); +extern int smtp_session_passivate(SMTP_SESSION *, VSTRING *, VSTRING *); +extern SMTP_SESSION *smtp_session_activate(int, SMTP_ITERATOR *, VSTRING *, VSTRING *); + + /* + * What's in a name? + */ +#define SMTP_HNAME(rr) (var_smtp_cname_overr ? (rr)->rname : (rr)->qname) + + /* + * smtp_connect.c + */ +extern int smtp_connect(SMTP_STATE *); + + /* + * smtp_proto.c + */ +extern void smtp_vrfy_init(void); +extern int smtp_helo(SMTP_STATE *); +extern int smtp_xfer(SMTP_STATE *); +extern int smtp_rset(SMTP_STATE *); +extern int smtp_quit(SMTP_STATE *); + +extern HBC_CALL_BACKS smtp_hbc_callbacks[]; + + /* + * A connection is re-usable if session->expire_time is > 0 and the + * expiration time has not been reached. This is subtle because the timer + * can expire between sending a command and receiving the reply for that + * command. + * + * But wait, there is more! When SMTP command pipelining is enabled, there are + * two protocol loops that execute at very different times: one loop that + * generates commands, and one loop that receives replies to those commands. + * These will be called "sender loop" and "receiver loop", respectively. At + * well-defined protocol synchronization points, the sender loop pauses to + * let the receiver loop catch up. + * + * When we choose to reuse a connection, both the sender and receiver protocol + * loops end with "." (mail delivery) or "RSET" (address probe). When we + * choose not to reuse, both the sender and receiver protocol loops end with + * "QUIT". The problem is that we must make the same protocol choices in + * both the sender and receiver loops, even though those loops may execute + * at completely different times. + * + * We "freeze" the choice in the sender loop, just before we generate "." or + * "RSET". The reader loop leaves the connection cacheable even if the timer + * expires by the time the response arrives. The connection cleanup code + * will call smtp_quit() for connections with an expired cache expiration + * timer. + * + * We could have made the programmer's life a lot simpler by not making a + * choice at all, and always leaving it up to the connection cleanup code to + * call smtp_quit() for connections with an expired cache expiration timer. + * + * As a general principle, neither the sender loop nor the receiver loop must + * modify the connection caching state, if that can affect the receiver + * state machine for not-yet processed replies to already-generated + * commands. This restriction does not apply when we have to exit the + * protocol loops prematurely due to e.g., timeout or connection loss, so + * that those pending replies will never be received. + * + * But wait, there is even more! Only the first good connection for a specific + * destination may be cached under both the next-hop destination name and + * the server address; connections to alternate servers must be cached under + * the server address alone. This means we must distinguish between bad + * connections and other reasons why connections cannot be cached. + */ +#define THIS_SESSION_IS_CACHED \ + (!THIS_SESSION_IS_FORBIDDEN && session->expire_time > 0) + +#define THIS_SESSION_IS_EXPIRED \ + (THIS_SESSION_IS_CACHED \ + && (session->expire_time < vstream_ftime(session->stream) \ + || (var_smtp_reuse_count > 0 \ + && session->reuse_count >= var_smtp_reuse_count))) + +#define THIS_SESSION_IS_THROTTLED \ + (!THIS_SESSION_IS_FORBIDDEN && session->expire_time < 0) + +#define THIS_SESSION_IS_FORBIDDEN \ + (session->forbidden != 0) + + /* Bring the bad news. */ + +#define DONT_CACHE_THIS_SESSION \ + (session->expire_time = 0) + +#define DONT_CACHE_THROTTLED_SESSION \ + (session->expire_time = -1) + +#define DONT_USE_FORBIDDEN_SESSION \ + (session->forbidden = 1) + + /* Initialization. */ + +#define USE_NEWBORN_SESSION \ + (session->forbidden = 0) + +#define CACHE_THIS_SESSION_UNTIL(when) \ + (session->expire_time = (when)) + + /* + * Encapsulate the following so that we don't expose details of + * connection management and error handling to the SMTP protocol engine. + */ +#ifdef USE_SASL_AUTH +#define HAVE_SASL_CREDENTIALS \ + (var_smtp_sasl_enable \ + && *var_smtp_sasl_passwd \ + && smtp_sasl_passwd_lookup(session)) +#else +#define HAVE_SASL_CREDENTIALS (0) +#endif + +#define PREACTIVE_DELAY \ + (session->state->request->msg_stats.active_arrival.tv_sec - \ + session->state->request->msg_stats.incoming_arrival.tv_sec) + +#define TRACE_REQ_ONLY (DEL_REQ_TRACE_ONLY(state->request->flags)) + +#define PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE \ + (session->tls_context == 0 \ + && state->tls->level == TLS_LEV_MAY \ + && (TRACE_REQ_ONLY || PREACTIVE_DELAY >= var_min_backoff_time) \ + && !HAVE_SASL_CREDENTIALS) + +#define PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE \ + (session->tls_context != 0 \ + && SMTP_RCPT_LEFT(state) > SMTP_RCPT_MARK_COUNT(state) \ + && state->tls->level == TLS_LEV_MAY \ + && (TRACE_REQ_ONLY || PREACTIVE_DELAY >= var_min_backoff_time) \ + && !HAVE_SASL_CREDENTIALS) + + /* + * XXX The following will not retry recipients that were deferred while the + * SMTP_MISC_FLAG_FINAL_SERVER flag was already set. This includes the case + * when TLS fails in the middle of a delivery. + */ +#define RETRY_AS_PLAINTEXT do { \ + session->tls_retry_plain = 1; \ + state->misc_flags &= ~SMTP_MISC_FLAG_FINAL_SERVER; \ + } while (0) + + /* + * smtp_chat.c + */ +typedef struct SMTP_RESP { /* server response */ + int code; /* SMTP code */ + const char *dsn; /* enhanced status */ + char *str; /* full reply */ + VSTRING *dsn_buf; /* status buffer */ + VSTRING *str_buf; /* reply buffer */ +} SMTP_RESP; + +extern void PRINTFLIKE(2, 3) smtp_chat_cmd(SMTP_SESSION *, const char *,...); +extern DICT *smtp_chat_resp_filter; +extern SMTP_RESP *smtp_chat_resp(SMTP_SESSION *); +extern void smtp_chat_init(SMTP_SESSION *); +extern void smtp_chat_reset(SMTP_SESSION *); +extern void smtp_chat_notify(SMTP_SESSION *); + +#define SMTP_RESP_FAKE(resp, _dsn) \ + ((resp)->code = 0, \ + (resp)->dsn = (_dsn), \ + (resp)->str = DSN_BY_LOCAL_MTA, \ + (resp)) + +#define DSN_BY_LOCAL_MTA ((char *) 0) /* DSN issued by local MTA */ + +#define SMTP_RESP_SET_DSN(resp, _dsn) do { \ + vstring_strcpy((resp)->dsn_buf, (_dsn)); \ + (resp)->dsn = STR((resp)->dsn_buf); \ + } while (0) + + /* + * These operations implement a redundant mark-and-sweep algorithm that + * explicitly accounts for the fate of every recipient. The interface is + * documented in smtp_rcpt.c, which also implements the sweeping. The + * smtp_trouble.c module does most of the marking after failure. + * + * When a delivery fails or succeeds, take one of the following actions: + * + * - Mark the recipient as KEEP (deliver to alternate MTA) and do not update + * the delivery request status. + * + * - Mark the recipient as DROP (remove from delivery request), log whether + * delivery succeeded or failed, delete the recipient from the queue file + * and/or update defer or bounce logfiles, and update the delivery request + * status. + * + * At the end of a delivery attempt, all recipients must be marked one way or + * the other. Failure to do so will trigger a panic. + */ +#define SMTP_RCPT_STATE_KEEP 1 /* send to backup host */ +#define SMTP_RCPT_STATE_DROP 2 /* remove from request */ +#define SMTP_RCPT_INIT(state) do { \ + (state)->rcpt_drop = (state)->rcpt_keep = 0; \ + (state)->rcpt_left = state->request->rcpt_list.len; \ + } while (0) + +#define SMTP_RCPT_DROP(state, rcpt) do { \ + (rcpt)->u.status = SMTP_RCPT_STATE_DROP; (state)->rcpt_drop++; \ + } while (0) + +#define SMTP_RCPT_KEEP(state, rcpt) do { \ + (rcpt)->u.status = SMTP_RCPT_STATE_KEEP; (state)->rcpt_keep++; \ + } while (0) + +#define SMTP_RCPT_ISMARKED(rcpt) ((rcpt)->u.status != 0) + +#define SMTP_RCPT_LEFT(state) (state)->rcpt_left + +#define SMTP_RCPT_MARK_COUNT(state) ((state)->rcpt_drop + (state)->rcpt_keep) + +extern void smtp_rcpt_cleanup(SMTP_STATE *); +extern void smtp_rcpt_done(SMTP_STATE *, SMTP_RESP *, RECIPIENT *); + + /* + * smtp_trouble.c + */ +#define SMTP_THROTTLE 1 +#define SMTP_NOTHROTTLE 0 +extern int smtp_sess_fail(SMTP_STATE *); +extern int PRINTFLIKE(5, 6) smtp_misc_fail(SMTP_STATE *, int, const char *, + SMTP_RESP *, const char *,...); +extern void PRINTFLIKE(5, 6) smtp_rcpt_fail(SMTP_STATE *, RECIPIENT *, + const char *, SMTP_RESP *, + const char *,...); +extern int smtp_stream_except(SMTP_STATE *, int, const char *); + +#define smtp_site_fail(state, mta, resp, ...) \ + smtp_misc_fail((state), SMTP_THROTTLE, (mta), (resp), __VA_ARGS__) +#define smtp_mesg_fail(state, mta, resp, ...) \ + smtp_misc_fail((state), SMTP_NOTHROTTLE, (mta), (resp), __VA_ARGS__) + + /* + * smtp_unalias.c + */ +extern const char *smtp_unalias_name(const char *); +extern VSTRING *smtp_unalias_addr(VSTRING *, const char *); + + /* + * smtp_state.c + */ +extern SMTP_STATE *smtp_state_alloc(void); +extern void smtp_state_free(SMTP_STATE *); + + /* + * smtp_map11.c + */ +extern int smtp_map11_external(VSTRING *, MAPS *, int); +extern int smtp_map11_tree(TOK822 *, MAPS *, int); +extern int smtp_map11_internal(VSTRING *, MAPS *, int); + + /* + * smtp_key.c + */ +char *smtp_key_prefix(VSTRING *, const char *, SMTP_ITERATOR *, int); + +#define SMTP_KEY_FLAG_SERVICE (1<<0) /* service name */ +#define SMTP_KEY_FLAG_SENDER (1<<1) /* sender address */ +#define SMTP_KEY_FLAG_REQ_NEXTHOP (1<<2) /* delivery request nexthop */ +#define SMTP_KEY_FLAG_CUR_NEXTHOP (1<<3) /* current nexthop */ +#define SMTP_KEY_FLAG_HOSTNAME (1<<4) /* remote host name */ +#define SMTP_KEY_FLAG_ADDR (1<<5) /* remote address */ +#define SMTP_KEY_FLAG_PORT (1<<6) /* remote port */ +#define SMTP_KEY_FLAG_TLS_LEVEL (1<<7) /* requested TLS level */ + +#define SMTP_KEY_MASK_ALL \ + (SMTP_KEY_FLAG_SERVICE | SMTP_KEY_FLAG_SENDER | \ + SMTP_KEY_FLAG_REQ_NEXTHOP | \ + SMTP_KEY_FLAG_CUR_NEXTHOP | SMTP_KEY_FLAG_HOSTNAME | \ + SMTP_KEY_FLAG_ADDR | SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL) + + /* + * Conditional lookup-key flags for cached connections that may be + * SASL-authenticated with a per-{sender, nexthop, or hostname} credential. + * Each bit corresponds to one type of smtp_sasl_password_file lookup key, + * and is turned on only when the corresponding main.cf parameter is turned + * on. + */ +#define COND_SASL_SMTP_KEY_FLAG_SENDER \ + ((var_smtp_sender_auth && *var_smtp_sasl_passwd) ? \ + SMTP_KEY_FLAG_SENDER : 0) + +#define COND_SASL_SMTP_KEY_FLAG_CUR_NEXTHOP \ + (*var_smtp_sasl_passwd ? SMTP_KEY_FLAG_CUR_NEXTHOP : 0) + +#ifdef USE_TLS +#define COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP \ + (TLS_MUST_MATCH(state->tls->level) ? SMTP_KEY_FLAG_CUR_NEXTHOP : 0) +#else +#define COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP \ + (0) +#endif + +#define COND_SASL_SMTP_KEY_FLAG_HOSTNAME \ + (*var_smtp_sasl_passwd ? SMTP_KEY_FLAG_HOSTNAME : 0) + + /* + * Connection-cache destination lookup key, based on the delivery request + * nexthop. The SENDER attribute is a proxy for sender-dependent SASL + * credentials (or absence thereof), and prevents false connection sharing + * when different SASL credentials may be required for different deliveries + * to the same domain and port. Likewise, the delivery request nexthop + * (REQ_NEXTHOP) prevents false sharing of TLS identities (the destination + * key links only to appropriate endpoint lookup keys). The SERVICE + * attribute is a proxy for all request-independent configuration details. + */ +#define SMTP_KEY_MASK_SCACHE_DEST_LABEL \ + (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \ + | SMTP_KEY_FLAG_REQ_NEXTHOP) + + /* + * Connection-cache endpoint lookup key. The SENDER, CUR_NEXTHOP, HOSTNAME, + * PORT and TLS_LEVEL attributes are proxies for SASL credentials and TLS + * authentication (or absence thereof), and prevent false connection sharing + * when different SASL credentials or TLS identities may be required for + * different deliveries to the same IP address and port. The SERVICE + * attribute is a proxy for all request-independent configuration details. + */ +#define SMTP_KEY_MASK_SCACHE_ENDP_LABEL \ + (SMTP_KEY_FLAG_SERVICE | COND_SASL_SMTP_KEY_FLAG_SENDER \ + | COND_SASL_SMTP_KEY_FLAG_CUR_NEXTHOP \ + | COND_SASL_SMTP_KEY_FLAG_HOSTNAME \ + | COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP | SMTP_KEY_FLAG_ADDR | \ + SMTP_KEY_FLAG_PORT | SMTP_KEY_FLAG_TLS_LEVEL) + + /* + * Silly little macros. + */ +#define STR(s) vstring_str(s) +#define LEN(s) VSTRING_LEN(s) + +extern int smtp_mode; + +#define VAR_LMTP_SMTP(x) (smtp_mode ? VAR_SMTP_##x : VAR_LMTP_##x) +#define LMTP_SMTP_SUFFIX(x) (smtp_mode ? x##_SMTP : x##_LMTP) + + /* + * Parsed command-line attributes. These do not change during the process + * lifetime. + */ +typedef struct { + int flags; /* from flags=, see below */ +} SMTP_CLI_ATTR; + +#define SMTP_CLI_FLAG_DELIVERED_TO (1<<0) /* prepend Delivered-To: */ +#define SMTP_CLI_FLAG_ORIG_RCPT (1<<1) /* prepend X-Original-To: */ +#define SMTP_CLI_FLAG_RETURN_PATH (1<<2) /* prepend Return-Path: */ +#define SMTP_CLI_FLAG_FINAL_DELIVERY (1<<3) /* final, not relay */ + +#define SMTP_CLI_MASK_ADD_HEADERS (SMTP_CLI_FLAG_DELIVERED_TO | \ + SMTP_CLI_FLAG_ORIG_RCPT | SMTP_CLI_FLAG_RETURN_PATH) + +extern SMTP_CLI_ATTR smtp_cli_attr; + + /* + * smtp_misc.c. + */ +extern void smtp_rewrite_generic_internal(VSTRING *, const char *); +extern void smtp_quote_822_address_flags(VSTRING *, const char *, int); +extern void smtp_quote_821_address(VSTRING *, const char *); + + /* + * header_from_format support, for postmaster notifications. + */ +extern int smtp_hfrom_format; + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Victor Duchovni +/* Morgan Stanley +/*--*/ diff --git a/src/smtp/smtp_addr.c b/src/smtp/smtp_addr.c new file mode 100644 index 0000000..2b5c126 --- /dev/null +++ b/src/smtp/smtp_addr.c @@ -0,0 +1,702 @@ +/*++ +/* NAME +/* smtp_addr 3 +/* SUMMARY +/* SMTP server address lookup +/* SYNOPSIS +/* #include "smtp_addr.h" +/* +/* DNS_RR *smtp_domain_addr(name, mxrr, misc_flags, why, found_myself) +/* char *name; +/* DNS_RR **mxrr; +/* int misc_flags; +/* DSN_BUF *why; +/* int *found_myself; +/* +/* DNS_RR *smtp_host_addr(name, misc_flags, why) +/* char *name; +/* int misc_flags; +/* DSN_BUF *why; +/* DESCRIPTION +/* This module implements Internet address lookups. By default, +/* lookups are done via the Internet domain name service (DNS). +/* A reasonable number of CNAME indirections is permitted. When +/* DNS lookups are disabled, host address lookup is done with +/* getnameinfo() or gethostbyname(). +/* +/* smtp_domain_addr() looks up the network addresses for mail +/* exchanger hosts listed for the named domain. Addresses are +/* returned in most-preferred first order. The result is truncated +/* so that it contains only hosts that are more preferred than the +/* local mail server itself. The found_myself result parameter +/* is updated when the local MTA is MX host for the specified +/* destination. If MX records were found, the rname, qname, +/* and dnssec validation status of the MX RRset are returned +/* via mxrr, which the caller must free with dns_rr_free(). +/* +/* When no mail exchanger is listed in the DNS for \fIname\fR, the +/* request is passed to smtp_host_addr(). +/* +/* It is an error to call smtp_domain_addr() when DNS lookups are +/* disabled. +/* +/* smtp_host_addr() looks up all addresses listed for the named +/* host. The host can be specified as a numerical Internet network +/* address, or as a symbolic host name. +/* +/* Results from smtp_domain_addr() or smtp_host_addr() are +/* destroyed by dns_rr_free(), including null lists. +/* DIAGNOSTICS +/* Panics: interface violations. For example, calling smtp_domain_addr() +/* when DNS lookups are explicitly disabled. +/* +/* All routines either return a DNS_RR pointer, or return a null +/* pointer and update the \fIwhy\fR argument accordingly. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* DNS library. */ + +#include + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_addr.h" + +/* smtp_print_addr - print address list */ + +static void smtp_print_addr(const char *what, DNS_RR *addr_list) +{ + DNS_RR *addr; + MAI_HOSTADDR_STR hostaddr; + + msg_info("begin %s address list", what); + for (addr = addr_list; addr; addr = addr->next) { + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("skipping record type %s: %m", dns_strtype(addr->type)); + } else { + msg_info("pref %4d host %s/%s", + addr->pref, SMTP_HNAME(addr), + hostaddr.buf); + } + } + msg_info("end %s address list", what); +} + +/* smtp_addr_one - address lookup for one host name */ + +static DNS_RR *smtp_addr_one(DNS_RR *addr_list, const char *host, int res_opt, + unsigned pref, DSN_BUF *why) +{ + const char *myname = "smtp_addr_one"; + DNS_RR *addr = 0; + DNS_RR *rr; + int aierr; + struct addrinfo *res0; + struct addrinfo *res; + const INET_PROTO_INFO *proto_info = inet_proto_info(); + unsigned char *proto_family_list = proto_info->sa_family_list; + int found; + + if (msg_verbose) + msg_info("%s: host %s", myname, host); + + /* + * Interpret a numerical name as an address. + */ + if (hostaddr_to_sockaddr(host, (char *) 0, 0, &res0) == 0) { + if (strchr((char *) proto_family_list, res0->ai_family) != 0) { + if ((addr = dns_sa_to_rr(host, pref, res0->ai_addr)) == 0) + msg_fatal("host %s: conversion error for address family " + "%d: %m", host, res0->ai_addr->sa_family); + addr_list = dns_rr_append(addr_list, addr); + if (msg_verbose) + msg_info("%s: using numerical host %s", myname, host); + freeaddrinfo(res0); + return (addr_list); + } + freeaddrinfo(res0); + } + + /* + * Use DNS lookup, but keep the option open to use native name service. + * + * XXX A soft error dominates past and future hard errors. Therefore we + * should not clobber a soft error text and status code. + */ + if (smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) { + res_opt |= smtp_dns_res_opt; + switch (dns_lookup_v(host, res_opt, &addr, (VSTRING *) 0, + why->reason, DNS_REQ_FLAG_NONE, + proto_info->dns_atype_list)) { + case DNS_OK: + for (rr = addr; rr; rr = rr->next) + rr->pref = pref; + addr_list = dns_rr_append(addr_list, addr); + return (addr_list); + default: + dsb_status(why, "4.4.3"); + return (addr_list); + case DNS_FAIL: + dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.3" : "5.4.3"); + return (addr_list); + case DNS_INVAL: + dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); + return (addr_list); + case DNS_POLICY: + dsb_status(why, "4.7.0"); + return (addr_list); + case DNS_NOTFOUND: + dsb_status(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4"); + /* maybe native naming service will succeed */ + break; + } + } + + /* + * Use the native name service which also looks in /etc/hosts. + * + * XXX A soft error dominates past and future hard errors. Therefore we + * should not clobber a soft error text and status code. + */ +#define RETRY_AI_ERROR(e) \ + ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM) +#ifdef EAI_NODATA +#define DSN_NOHOST(e) \ + ((e) == EAI_AGAIN || (e) == EAI_NODATA || (e) == EAI_NONAME) +#else +#define DSN_NOHOST(e) \ + ((e) == EAI_AGAIN || (e) == EAI_NONAME) +#endif + + if (smtp_host_lookup_mask & SMTP_HOST_FLAG_NATIVE) { + if ((aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0)) != 0) { + dsb_simple(why, (SMTP_HAS_SOFT_DSN(why) || RETRY_AI_ERROR(aierr)) ? + (DSN_NOHOST(aierr) ? "4.4.4" : "4.3.0") : + (DSN_NOHOST(aierr) ? "5.4.4" : "5.3.0"), + "unable to look up host %s: %s", + host, MAI_STRERROR(aierr)); + } else { + for (found = 0, res = res0; res != 0; res = res->ai_next) { + if (strchr((char *) proto_family_list, res->ai_family) == 0) { + msg_info("skipping address family %d for host %s", + res->ai_family, host); + continue; + } + found++; + if ((addr = dns_sa_to_rr(host, pref, res->ai_addr)) == 0) + msg_fatal("host %s: conversion error for address family " + "%d: %m", host, res0->ai_addr->sa_family); + addr_list = dns_rr_append(addr_list, addr); + if (msg_verbose) { + MAI_HOSTADDR_STR hostaddr_str; + + SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen, + &hostaddr_str, (MAI_SERVPORT_STR *) 0, 0); + msg_info("%s: native lookup result: %s", + myname, hostaddr_str.buf); + } + } + freeaddrinfo(res0); + if (found == 0) { + dsb_simple(why, SMTP_HAS_SOFT_DSN(why) ? "4.4.4" : "5.4.4", + "%s: host not found", host); + } + return (addr_list); + } + } + + /* + * No further alternatives for host lookup. + */ + return (addr_list); +} + +/* smtp_addr_list - address lookup for a list of mail exchangers */ + +static DNS_RR *smtp_addr_list(DNS_RR *mx_names, DSN_BUF *why) +{ + DNS_RR *addr_list = 0; + DNS_RR *rr; + int res_opt = 0; + + if (mx_names->dnssec_valid) + res_opt = RES_USE_DNSSEC; +#ifdef USE_TLS + else if (smtp_tls_insecure_mx_policy > TLS_LEV_MAY) + res_opt = RES_USE_DNSSEC; +#endif + + /* + * As long as we are able to look up any host address, we ignore problems + * with DNS lookups (except if we're backup MX, and all the better MX + * hosts can't be found). + * + * XXX 2821: update the error status (0->FAIL upon unrecoverable lookup + * error, any->RETRY upon temporary lookup error) so that we can + * correctly handle the case of no resolvable MX host. Currently this is + * always treated as a soft error. RFC 2821 wants a more precise + * response. + * + * XXX dns_lookup() enables RES_DEFNAMES. This is wrong for names found in + * MX records - we should not append the local domain to dot-less names. + * + * XXX However, this is not the only problem. If we use the native name + * service for host lookup, then it will usually enable RES_DNSRCH which + * appends local domain information to all lookups. In particular, + * getaddrinfo() may invoke a resolver that runs in a different process + * (NIS server, nscd), so we can't even reliably turn this off by + * tweaking the in-process resolver flags. + */ + for (rr = mx_names; rr; rr = rr->next) { + if (rr->type != T_MX) + msg_panic("smtp_addr_list: bad resource type: %d", rr->type); + addr_list = smtp_addr_one(addr_list, (char *) rr->data, res_opt, + rr->pref, why); + } + return (addr_list); +} + +/* smtp_find_self - spot myself in a crowd of mail exchangers */ + +static DNS_RR *smtp_find_self(DNS_RR *addr_list) +{ + const char *myname = "smtp_find_self"; + INET_ADDR_LIST *self; + INET_ADDR_LIST *proxy; + DNS_RR *addr; + int i; + + self = own_inet_addr_list(); + proxy = proxy_inet_addr_list(); + + for (addr = addr_list; addr; addr = addr->next) { + + /* + * Find out if this mail system is listening on this address. + */ + for (i = 0; i < self->used; i++) + if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (self->addrs + i))) { + if (msg_verbose) + msg_info("%s: found self at pref %d", myname, addr->pref); + return (addr); + } + + /* + * Find out if this mail system has a proxy listening on this + * address. + */ + for (i = 0; i < proxy->used; i++) + if (DNS_RR_EQ_SA(addr, (struct sockaddr *) (proxy->addrs + i))) { + if (msg_verbose) + msg_info("%s: found proxy at pref %d", myname, addr->pref); + return (addr); + } + } + + /* + * Didn't find myself, or my proxy. + */ + if (msg_verbose) + msg_info("%s: not found", myname); + return (0); +} + +/* smtp_truncate_self - truncate address list at self and equivalents */ + +static DNS_RR *smtp_truncate_self(DNS_RR *addr_list, unsigned pref) +{ + DNS_RR *addr; + DNS_RR *last; + + for (last = 0, addr = addr_list; addr; last = addr, addr = addr->next) { + if (pref == addr->pref) { + if (msg_verbose) + smtp_print_addr("truncated", addr); + dns_rr_free(addr); + if (last == 0) { + addr_list = 0; + } else { + last->next = 0; + } + break; + } + } + return (addr_list); +} + +/* smtp_balance_inet_proto - balance IPv4/6 protocols within address limit */ + +static DNS_RR *smtp_balance_inet_proto(DNS_RR *addr_list, int misc_flags, + int addr_limit) +{ + const char myname[] = "smtp_balance_inet_proto"; + DNS_RR *rr; + DNS_RR *stripped_list; + DNS_RR *next; + int v6_count; + int v4_count; + int v6_target, v4_target; + int *p; + + /* + * Precondition: the input is sorted by MX preference (not necessarily IP + * address family preference), and addresses with the same or worse + * preference than 'myself' have been eliminated. Postcondition: the + * relative list order is unchanged, but some elements are removed. + */ + + /* + * Count the number of IPv6 and IPv4 addresses. + */ + for (v4_count = v6_count = 0, rr = addr_list; rr != 0; rr = rr->next) { + if (rr->type == T_A) { + v4_count++; + } else if (rr->type == T_AAAA) { + v6_count++; + } else { + msg_panic("%s: unexpected record type: %s", + myname, dns_strtype(rr->type)); + } + } + + /* + * Ensure that one address type will not out-crowd the other, while + * enforcing the address count limit. This works around a current problem + * where some destination announces primarily IPv6 MX addresses, the + * smtp_address_limit eliminates most or all IPv4 addresses, and the + * destination is not reachable over IPv6. + * + * Maybe: do all smtp_mx_address_limit enforcement here, and remove + * pre-existing enforcement elsewhere. That would obsolete the + * smtp_balance_inet_protocols configuration parameter. + */ + if (v4_count > 0 && v6_count > 0 && v4_count + v6_count > addr_limit) { + + /*- + * Decide how many IPv6 and IPv4 addresses to keep. The code below + * has three branches, corresponding to the regions R1, R2 and R3 + * in the figure. + * + * L = addr_limit + * X = excluded by condition (v4_count + v6_count > addr_limit) + * + * v4_count + * ^ + * | + * L \ R1 + * |X\ | + * |XXX\ | + * |XXXXX\ | R2 + * L/2 +-------\------- + * |XXXXXXX|X\ + * |XXXXXXX|XXX\ R3 + * |XXXXXXX|XXXXX\ + * 0 +-------+-------\--> v6_count + * 0 L/2 L + */ + if (v6_count <= addr_limit / 2) { /* Region R1 */ + v6_target = v6_count; + v4_target = addr_limit - v6_target; + } else if (v4_count <= addr_limit / 2) {/* Region R3 */ + v4_target = v4_count; + v6_target = addr_limit - v4_target; + } else { /* Region R2 */ + /* v4_count > addr_limit / 2 && v6_count > addr_limit / 2 */ + v4_target = (addr_limit + (addr_list->type == T_A)) / 2; + v6_target = addr_limit - v4_target; + } + if (msg_verbose) + msg_info("v6_target=%d, v4_target=%d", v6_target, v4_target); + + /* Enforce the address count targets. */ + stripped_list = 0; + for (rr = addr_list; rr != 0; rr = next) { + next = rr->next; + rr->next = 0; + if (rr->type == T_A) { + p = &v4_target; + } else if (rr->type == T_AAAA) { + p = &v6_target; + } else { + msg_panic("%s: unexpected record type: %s", + myname, dns_strtype(rr->type)); + } + if (*p > 0) { + stripped_list = dns_rr_append(stripped_list, rr); + *p -= 1; + } else { + dns_rr_free(rr); + } + } + if (v4_target > 0 || v6_target > 0) + msg_panic("%s: bad target count: v4_target=%d, v6_target=%d", + myname, v4_target, v6_target); + if (msg_verbose) + smtp_print_addr("smtp_balance_inet_proto result", stripped_list); + return (stripped_list); + } else { + return (addr_list); + } +} + +/* smtp_domain_addr - mail exchanger address lookup */ + +DNS_RR *smtp_domain_addr(const char *name, DNS_RR **mxrr, int misc_flags, + DSN_BUF *why, int *found_myself) +{ + DNS_RR *mx_names; + DNS_RR *addr_list = 0; + DNS_RR *self = 0; + unsigned best_pref; + unsigned best_found; + int r = 0; /* Resolver flags */ + const char *aname; + + dsb_reset(why); /* Paranoia */ + + /* + * Preferences from DNS use 0..32767, fall-backs use 32768+. + */ +#define IMPOSSIBLE_PREFERENCE (~0) + + /* + * Sanity check. + */ + if (smtp_dns_support == SMTP_DNS_DISABLED) + msg_panic("smtp_domain_addr: DNS lookup is disabled"); + if (smtp_dns_support == SMTP_DNS_DNSSEC) + r |= RES_USE_DNSSEC; + + /* + * IDNA support. + */ +#ifndef NO_EAI + if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", name, aname); + } else +#endif + aname = name; + + /* + * Look up the mail exchanger hosts listed for this name. Sort the + * results by preference. Look up the corresponding host addresses, and + * truncate the list so that it contains only hosts that are more + * preferred than myself. When no MX resource records exist, look up the + * addresses listed for this name. + * + * According to RFC 974: "It is possible that the list of MXs in the + * response to the query will be empty. This is a special case. If the + * list is empty, mailers should treat it as if it contained one RR, an + * MX RR with a preference value of 0, and a host name of REMOTE. (I.e., + * REMOTE is its only MX). In addition, the mailer should do no further + * processing on the list, but should attempt to deliver the message to + * REMOTE." + * + * Normally it is OK if an MX host cannot be found in the DNS; we'll just + * use a backup one, and silently ignore the better MX host. However, if + * the best backup that we can find in the DNS is the local machine, then + * we must remember that the local machine is not the primary MX host, or + * else we will claim that mail loops back. + * + * XXX Optionally do A lookups even when the MX lookup didn't complete. + * Unfortunately with some DNS servers this is not a transient problem. + * + * XXX Ideally we would perform A lookups only as far as needed. But as long + * as we're looking up all the hosts, it would be better to look up the + * least preferred host first, so that DNS lookup error messages make + * more sense. + * + * XXX 2821: RFC 2821 says that the sender must shuffle equal-preference MX + * hosts, whereas multiple A records per hostname must be used in the + * order as received. They make the bogus assumption that a hostname with + * multiple A records corresponds to one machine with multiple network + * interfaces. + * + * XXX 2821: Postfix recognizes the local machine by looking for its own IP + * address in the list of mail exchangers. RFC 2821 says one has to look + * at the mail exchanger hostname as well, making the bogus assumption + * that an IP address is listed only under one hostname. However, looking + * at hostnames provides a partial solution for MX hosts behind a NAT + * gateway. + */ + switch (dns_lookup(aname, T_MX, r, &mx_names, (VSTRING *) 0, why->reason)) { + default: + dsb_status(why, "4.4.3"); + if (var_ign_mx_lookup_err) + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + case DNS_INVAL: + dsb_status(why, "5.4.4"); + if (var_ign_mx_lookup_err) + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + case DNS_NULLMX: + dsb_status(why, "5.1.0"); + break; + case DNS_POLICY: + dsb_status(why, "4.7.0"); + break; + case DNS_FAIL: + dsb_status(why, "5.4.3"); + if (var_ign_mx_lookup_err) + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + case DNS_OK: + mx_names = dns_rr_sort(mx_names, dns_rr_compare_pref_any); + best_pref = (mx_names ? mx_names->pref : IMPOSSIBLE_PREFERENCE); + addr_list = smtp_addr_list(mx_names, why); + if (mxrr) + *mxrr = dns_rr_copy(mx_names); /* copies one record! */ + dns_rr_free(mx_names); + if (addr_list == 0) { + /* Text does not change. */ + if (var_smtp_defer_mxaddr) { + /* Don't clobber the null terminator. */ + if (SMTP_HAS_HARD_DSN(why)) + SMTP_SET_SOFT_DSN(why); /* XXX */ + /* Require some error status. */ + else if (!SMTP_HAS_SOFT_DSN(why)) + msg_panic("smtp_domain_addr: bad status"); + } + msg_warn("no MX host for %s has a valid address record", name); + break; + } + best_found = (addr_list ? addr_list->pref : IMPOSSIBLE_PREFERENCE); + if (msg_verbose) + smtp_print_addr(name, addr_list); + if ((misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) + && (self = smtp_find_self(addr_list)) != 0) { + addr_list = smtp_truncate_self(addr_list, self->pref); + if (addr_list == 0) { + if (best_pref != best_found) { + dsb_simple(why, "4.4.4", + "unable to find primary relay for %s", name); + } else { + dsb_simple(why, "5.4.6", "mail for %s loops back to myself", + name); + } + } + } +#define SMTP_COMPARE_ADDR(flags) \ + (((flags) & SMTP_MISC_FLAG_PREF_IPV6) ? dns_rr_compare_pref_ipv6 : \ + ((flags) & SMTP_MISC_FLAG_PREF_IPV4) ? dns_rr_compare_pref_ipv4 : \ + dns_rr_compare_pref_any) + + if (addr_list && addr_list->next) { + if (var_smtp_rand_addr) + addr_list = dns_rr_shuffle(addr_list); + addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); + if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto) + addr_list = smtp_balance_inet_proto(addr_list, misc_flags, + var_smtp_mxaddr_limit); + } + break; + case DNS_NOTFOUND: + addr_list = smtp_host_addr(aname, misc_flags, why); + break; + } + + /* + * Clean up. + */ + *found_myself |= (self != 0); + return (addr_list); +} + +/* smtp_host_addr - direct host lookup */ + +DNS_RR *smtp_host_addr(const char *host, int misc_flags, DSN_BUF *why) +{ + DNS_RR *addr_list; + int res_opt = 0; + const char *ahost; + + dsb_reset(why); /* Paranoia */ + + if (smtp_dns_support == SMTP_DNS_DNSSEC) + res_opt |= RES_USE_DNSSEC; + + /* + * IDNA support. + */ +#ifndef NO_EAI + if (!allascii(host) && (ahost = midna_domain_to_ascii(host)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", host, ahost); + } else +#endif + ahost = host; + + /* + * If the host is specified by numerical address, just convert the + * address to internal form. Otherwise, the host is specified by name. + */ +#define PREF0 0 + addr_list = smtp_addr_one((DNS_RR *) 0, ahost, res_opt, PREF0, why); + if (addr_list + && (misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) + && smtp_find_self(addr_list) != 0) { + dns_rr_free(addr_list); + dsb_simple(why, "5.4.6", "mail for %s loops back to myself", host); + return (0); + } + if (addr_list && addr_list->next) { + if (var_smtp_rand_addr) + addr_list = dns_rr_shuffle(addr_list); + /* The following changes the order of equal-preference hosts. */ + if (inet_proto_info()->ai_family_list[1] != 0) + addr_list = dns_rr_sort(addr_list, SMTP_COMPARE_ADDR(misc_flags)); + if (var_smtp_mxaddr_limit > 0 && var_smtp_balance_inet_proto) + addr_list = smtp_balance_inet_proto(addr_list, misc_flags, + var_smtp_mxaddr_limit); + } + if (msg_verbose) + smtp_print_addr(host, addr_list); + return (addr_list); +} diff --git a/src/smtp/smtp_addr.h b/src/smtp/smtp_addr.h new file mode 100644 index 0000000..8f20961 --- /dev/null +++ b/src/smtp/smtp_addr.h @@ -0,0 +1,31 @@ +/*++ +/* NAME +/* smtp_addr 3h +/* SUMMARY +/* SMTP server address lookup +/* SYNOPSIS +/* #include "smtp_addr.h" +/* DESCRIPTION +/* .nf + + /* + * DNS library. + */ +#include + + /* + * Internal interfaces. + */ +extern DNS_RR *smtp_host_addr(const char *, int, DSN_BUF *); +extern DNS_RR *smtp_domain_addr(const char *, DNS_RR **, int, DSN_BUF *, int *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtp/smtp_chat.c b/src/smtp/smtp_chat.c new file mode 100644 index 0000000..81c63e4 --- /dev/null +++ b/src/smtp/smtp_chat.c @@ -0,0 +1,503 @@ +/*++ +/* NAME +/* smtp_chat 3 +/* SUMMARY +/* SMTP client request/response support +/* SYNOPSIS +/* #include "smtp.h" +/* +/* typedef struct { +/* .in +4 +/* int code; /* SMTP code, not sanitized */ +/* char *dsn; /* enhanced status, sanitized */ +/* char *str; /* unmodified SMTP reply */ +/* VSTRING *dsn_buf; +/* VSTRING *str_buf; +/* .in -4 +/* } SMTP_RESP; +/* +/* void smtp_chat_cmd(session, format, ...) +/* SMTP_SESSION *session; +/* const char *format; +/* +/* DICT *smtp_chat_resp_filter; +/* +/* SMTP_RESP *smtp_chat_resp(session) +/* SMTP_SESSION *session; +/* +/* void smtp_chat_notify(session) +/* SMTP_SESSION *session; +/* +/* void smtp_chat_init(session) +/* SMTP_SESSION *session; +/* +/* void smtp_chat_reset(session) +/* SMTP_SESSION *session; +/* DESCRIPTION +/* This module implements SMTP client support for request/reply +/* conversations, and maintains a limited SMTP transaction log. +/* +/* smtp_chat_cmd() formats a command and sends it to an SMTP server. +/* Optionally, the command is logged. +/* +/* smtp_chat_resp() reads one SMTP server response. It extracts +/* the SMTP reply code and enhanced status code from the text, +/* and concatenates multi-line responses to one string, using +/* a newline as separator. Optionally, the server response +/* is logged. +/* .IP \(bu +/* Postfix never sanitizes the extracted SMTP reply code except +/* to ensure that it is a three-digit code. A malformed reply +/* results in a null extracted SMTP reply code value. +/* .IP \(bu +/* Postfix always sanitizes the extracted enhanced status code. +/* When the server's SMTP status code is 2xx, 4xx or 5xx, +/* Postfix requires that the first digit of the server's +/* enhanced status code matches the first digit of the server's +/* SMTP status code. In case of a mis-match, or when the +/* server specified no status code, the extracted enhanced +/* status code is set to 2.0.0, 4.0.0 or 5.0.0 instead. With +/* SMTP reply codes other than 2xx, 4xx or 5xx, the extracted +/* enhanced status code is set to a default value of 5.5.0 +/* (protocol error) for reasons outlined under the next bullet. +/* .IP \(bu +/* Since the SMTP reply code may violate the protocol even +/* when it is correctly formatted, Postfix uses the sanitized +/* extracted enhanced status code to decide whether an error +/* condition is permanent or transient. This means that the +/* caller may have to update the enhanced status code when it +/* discovers that a server reply violates the SMTP protocol, +/* even though it was correctly formatted. This happens when +/* the client and server get out of step due to a broken proxy +/* agent. +/* .PP +/* smtp_chat_resp_filter specifies an optional filter to +/* transform one server reply line before it is parsed. The +/* filter is invoked once for each line of a multi-line reply. +/* +/* smtp_chat_notify() sends a copy of the SMTP transaction log +/* to the postmaster for review. The postmaster notice is sent only +/* when delivery is possible immediately. It is an error to call +/* smtp_chat_notify() when no SMTP transaction log exists. +/* +/* smtp_chat_init() initializes the per-session transaction log. +/* This must be done at the beginning of a new SMTP session. +/* +/* smtp_chat_reset() resets the transaction log. This is +/* typically done at the beginning or end of an SMTP session, +/* or within a session to discard non-error information. +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem, server response exceeds +/* configurable limit. +/* All other exceptions are handled by long jumps (see smtp_stream(3)). +/* SEE ALSO +/* smtp_stream(3) SMTP session I/O support +/* msg(3) generic logging interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include /* 44BSD stdarg.h uses abort() */ +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "smtp.h" + + /* + * Server reply transformations. + */ +DICT *smtp_chat_resp_filter; + +/* smtp_chat_init - initialize SMTP transaction log */ + +void smtp_chat_init(SMTP_SESSION *session) +{ + session->history = 0; +} + +/* smtp_chat_reset - reset SMTP transaction log */ + +void smtp_chat_reset(SMTP_SESSION *session) +{ + if (session->history) { + argv_free(session->history); + session->history = 0; + } +} + +/* smtp_chat_append - append record to SMTP transaction log */ + +static void smtp_chat_append(SMTP_SESSION *session, const char *direction, + const char *data) +{ + char *line; + + if (session->history == 0) + session->history = argv_alloc(10); + line = concatenate(direction, data, (char *) 0); + argv_add(session->history, line, (char *) 0); + myfree(line); +} + +/* smtp_chat_cmd - send an SMTP command */ + +void smtp_chat_cmd(SMTP_SESSION *session, const char *fmt,...) +{ + va_list ap; + + /* + * Format the command, and update the transaction log. + */ + va_start(ap, fmt); + vstring_vsprintf(session->buffer, fmt, ap); + va_end(ap); + smtp_chat_append(session, "Out: ", STR(session->buffer)); + + /* + * Optionally log the command first, so we can see in the log what the + * program is trying to do. + */ + if (msg_verbose) + msg_info("> %s: %s", session->namaddrport, STR(session->buffer)); + + /* + * Send the command to the SMTP server. + */ + smtp_fputs(STR(session->buffer), LEN(session->buffer), session->stream); + + /* + * Force flushing of output does not belong here. It is done in the + * smtp_loop() main protocol loop when reading the server response, and + * in smtp_helo() when reading the EHLO response after sending the EHLO + * command. + * + * If we do forced flush here, then we must longjmp() on error, and a + * matching "prepare for disaster" error handler must be set up before + * every smtp_chat_cmd() call. + */ +#if 0 + + /* + * Flush unsent data to avoid timeouts after slow DNS lookups. + */ + if (time((time_t *) 0) - vstream_ftime(session->stream) > 10) + vstream_fflush(session->stream); + + /* + * Abort immediately if the connection is broken. + */ + if (vstream_ftimeout(session->stream)) + vstream_longjmp(session->stream, SMTP_ERR_TIME); + if (vstream_ferror(session->stream)) + vstream_longjmp(session->stream, SMTP_ERR_EOF); +#endif +} + +/* smtp_chat_resp - read and process SMTP server response */ + +SMTP_RESP *smtp_chat_resp(SMTP_SESSION *session) +{ + static SMTP_RESP rdata; + char *cp; + int last_char; + int three_digs = 0; + size_t len; + const char *new_reply; + int chat_append_flag; + int chat_append_skipped = 0; + + /* + * Initialize the response data buffer. + */ + if (rdata.str_buf == 0) { + rdata.dsn_buf = vstring_alloc(10); + rdata.str_buf = vstring_alloc(100); + } + + /* + * Censor out non-printable characters in server responses. Concatenate + * multi-line server responses. Separate the status code from the text. + * Leave further parsing up to the application. + * + * We can't parse or store input that exceeds var_line_limit, so we just + * skip over it to simplify the remainder of the code below. + */ + VSTRING_RESET(rdata.str_buf); + for (;;) { + last_char = smtp_get(session->buffer, session->stream, var_line_limit, + SMTP_GET_FLAG_SKIP); + /* XXX Update the per-line time limit. */ + printable(STR(session->buffer), '?'); + if (last_char != '\n') + msg_warn("%s: response longer than %d: %.30s...", + session->namaddrport, var_line_limit, STR(session->buffer)); + if (msg_verbose) + msg_info("< %s: %.100s", session->namaddrport, STR(session->buffer)); + + /* + * Defend against a denial of service attack by limiting the amount + * of multi-line text that we are willing to store. + */ + chat_append_flag = (LEN(rdata.str_buf) < var_line_limit); + if (chat_append_flag) + smtp_chat_append(session, "In: ", STR(session->buffer)); + else { + if (chat_append_skipped == 0) + msg_warn("%s: multi-line response longer than %d %.30s...", + session->namaddrport, var_line_limit, STR(rdata.str_buf)); + if (chat_append_skipped < INT_MAX) + chat_append_skipped++; + } + + /* + * Server reply substitution, for fault-injection testing, or for + * working around broken systems. Use with care. + */ + if (smtp_chat_resp_filter != 0) { + new_reply = dict_get(smtp_chat_resp_filter, STR(session->buffer)); + if (new_reply != 0) { + msg_info("%s: replacing server reply \"%s\" with \"%s\"", + session->namaddrport, STR(session->buffer), new_reply); + vstring_strcpy(session->buffer, new_reply); + if (chat_append_flag) { + smtp_chat_append(session, "Replaced-by: ", ""); + smtp_chat_append(session, " ", new_reply); + } + } else if (smtp_chat_resp_filter->error != 0) { + msg_warn("%s: table %s:%s lookup error for %s", + session->state->request->queue_id, + smtp_chat_resp_filter->type, + smtp_chat_resp_filter->name, + printable(STR(session->buffer), '?')); + vstream_longjmp(session->stream, SMTP_ERR_DATA); + } + } + if (chat_append_flag) { + if (LEN(rdata.str_buf)) + VSTRING_ADDCH(rdata.str_buf, '\n'); + vstring_strcat(rdata.str_buf, STR(session->buffer)); + } + + /* + * Parse into code and text. Do not ignore garbage (see below). + */ + for (cp = STR(session->buffer); *cp && ISDIGIT(*cp); cp++) + /* void */ ; + if ((three_digs = (cp - STR(session->buffer) == 3)) != 0) { + if (*cp == '-') + continue; + if (*cp == ' ' || *cp == 0) + break; + } + + /* + * XXX Do not simply ignore garbage in the server reply when ESMTP + * command pipelining is turned on. For example, after sending + * ".QUIT" and receiving garbage followed by a + * legitimate 2XX reply, Postfix recognizes the server's QUIT reply + * as the END-OF-DATA reply after garbage, causing mail to be lost. + * + * Without the ability to store per-domain status information in queue + * files, automatic workarounds are problematic: + * + * - Automatically deferring delivery creates a "repeated delivery" + * problem when garbage arrives after the DATA stage. Without the + * workaround, Postfix delivers only once. + * + * - Automatically deferring delivery creates a "no delivery" problem + * when the garbage arrives before the DATA stage. Without the + * workaround, mail might still get through. + * + * - Automatically turning off pipelining for delayed mail affects + * deliveries to correctly implemented servers, and may also affect + * delivery of large mailing lists. + * + * So we leave the decision with the administrator, but we don't force + * them to take action, like we would with automatic deferral. If + * loss of mail is not acceptable then they can turn off pipelining + * for specific sites, or they can turn off pipelining globally when + * they find that there are just too many broken sites. + * + * Fix 20190621: don't cache an SMTP session after an SMTP protocol + * error. The protocol may be in a bad state. Disable caching here so + * that the protocol engine will send QUIT. + */ + session->error_mask |= MAIL_ERROR_PROTOCOL; + DONT_CACHE_THIS_SESSION; + if (session->features & SMTP_FEATURE_PIPELINING) { + msg_warn("%s: non-%s response from %s: %.100s", + session->state->request->queue_id, + smtp_mode ? "ESMTP" : "LMTP", + session->namaddrport, STR(session->buffer)); + if (var_helpful_warnings) + msg_warn("to prevent loss of mail, turn off command pipelining " + "for %s with the %s parameter", + STR(session->iterator->addr), + VAR_LMTP_SMTP(EHLO_DIS_MAPS)); + } + } + + /* + * Extract RFC 821 reply code and RFC 2034 detail. Use a default detail + * code if none was given. + * + * Ignore out-of-protocol enhanced status codes: codes that accompany 3XX + * replies, or codes whose initial digit is out of sync with the reply + * code. + * + * XXX Potential stability problem. In order to save memory, the queue + * manager stores DSNs in a compact manner: + * + * - empty strings are represented by null pointers, + * + * - the status and reason are required to be non-empty. + * + * Other Postfix daemons inherit this behavior, because they use the same + * DSN support code. This means that everything that receives DSNs must + * cope with null pointers for the optional DSN attributes, and that + * everything that provides DSN information must provide a non-empty + * status and reason, otherwise the DSN support code wil panic(). + * + * Thus, when the remote server sends a malformed reply (or 3XX out of + * context) we should not panic() in DSN_COPY() just because we don't + * have a status. Robustness suggests that we supply a status here, and + * that we leave it up to the down-stream code to override the + * server-supplied status in case of an error we can't detect here, such + * as an out-of-order server reply. + */ + VSTRING_TERMINATE(rdata.str_buf); + vstring_strcpy(rdata.dsn_buf, "5.5.0"); /* SAFETY! protocol error */ + if (three_digs != 0) { + rdata.code = atoi(STR(session->buffer)); + if (strchr("245", STR(session->buffer)[0]) != 0) { + for (cp = STR(session->buffer) + 4; *cp == ' '; cp++) + /* void */ ; + if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) { + vstring_strncpy(rdata.dsn_buf, cp, len); + } else { + vstring_strcpy(rdata.dsn_buf, "0.0.0"); + STR(rdata.dsn_buf)[0] = STR(session->buffer)[0]; + } + } + } else { + rdata.code = 0; + } + rdata.dsn = STR(rdata.dsn_buf); + rdata.str = STR(rdata.str_buf); + return (&rdata); +} + +/* print_line - line_wrap callback */ + +static void print_line(const char *str, int len, int indent, void *context) +{ + VSTREAM *notice = (VSTREAM *) context; + + post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str); +} + +/* smtp_chat_notify - notify postmaster */ + +void smtp_chat_notify(SMTP_SESSION *session) +{ + const char *myname = "smtp_chat_notify"; + VSTREAM *notice; + char **cpp; + + /* + * Sanity checks. + */ + if (session->history == 0) + msg_panic("%s: no conversation history", myname); + if (msg_verbose) + msg_info("%s: notify postmaster", myname); + + /* + * Construct a message for the postmaster, explaining what this is all + * about. This is junk mail: don't send it when the mail posting service + * is unavailable, and use the double bounce sender address, to prevent + * mail bounce wars. Always prepend one space to message content that we + * generate from untrusted data. + */ +#define NULL_TRACE_FLAGS 0 +#define NO_QUEUE_ID ((VSTRING *) 0) +#define LENGTH 78 +#define INDENT 4 + + notice = post_mail_fopen_nowait(mail_addr_double_bounce(), + var_error_rcpt, + MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS, + SMTPUTF8_FLAG_NONE, NO_QUEUE_ID); + if (notice == 0) { + msg_warn("postmaster notify: %m"); + return; + } + if (smtp_hfrom_format == HFROM_FORMAT_CODE_STD) { + post_mail_fprintf(notice, "From: Mail Delivery System <%s>", + mail_addr_mail_daemon()); + post_mail_fprintf(notice, "To: Postmaster <%s>", var_error_rcpt); + } else { + post_mail_fprintf(notice, "From: %s (Mail Delivery System)", + mail_addr_mail_daemon()); + post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt); + } + post_mail_fprintf(notice, "Subject: %s %s client: errors from %s", + var_mail_name, smtp_mode ? "SMTP" : "LMTP", + session->namaddrport); + post_mail_fputs(notice, ""); + post_mail_fprintf(notice, "Unexpected response from %s.", + session->namaddrport); + post_mail_fputs(notice, ""); + post_mail_fputs(notice, "Transcript of session follows."); + post_mail_fputs(notice, ""); + argv_terminate(session->history); + for (cpp = session->history->argv; *cpp; cpp++) + line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line, + (void *) notice); + post_mail_fputs(notice, ""); + post_mail_fprintf(notice, "For other details, see the local mail logfile"); + (void) post_mail_fclose(notice); +} diff --git a/src/smtp/smtp_connect.c b/src/smtp/smtp_connect.c new file mode 100644 index 0000000..ed58180 --- /dev/null +++ b/src/smtp/smtp_connect.c @@ -0,0 +1,1231 @@ +/*++ +/* NAME +/* smtp_connect 3 +/* SUMMARY +/* connect to SMTP/LMTP server and deliver +/* SYNOPSIS +/* #include "smtp.h" +/* +/* int smtp_connect(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* This module implements SMTP/LMTP connection management and controls +/* mail delivery. +/* +/* smtp_connect() attempts to establish an SMTP/LMTP session with a host +/* that represents the destination domain, or with an optional fallback +/* relay when {the destination cannot be found, or when all the +/* destination servers are unavailable}. It skips over IP addresses +/* that fail to complete the SMTP/LMTP handshake and tries to find +/* an alternate server when an SMTP/LMTP session fails to deliver. +/* +/* This layer also controls what connections are retrieved from +/* the connection cache, and what connections are saved to the cache. +/* +/* The destination is either a host (or domain) name or a numeric +/* address. Symbolic or numeric service port information may be +/* appended, separated by a colon (":"). In the case of LMTP, +/* destinations may be specified as "unix:pathname", "inet:host" +/* or "inet:host:port". +/* +/* With SMTP, the Internet domain name service is queried for mail +/* exchanger hosts. Quote the domain name with `[' and `]' to +/* suppress mail exchanger lookups. +/* +/* Numerical address information should always be quoted with `[]'. +/* DIAGNOSTICS +/* The delivery status is the result value. +/* SEE ALSO +/* smtp_proto(3) SMTP client protocol +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Connection caching in cooperation with: +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef IPPORT_SMTP +#define IPPORT_SMTP 25 +#endif + +/* 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 + +/* DNS library. */ + +#include + +/* Application-specific. */ + +#include +#include +#include + + /* + * Forward declaration. + */ +static SMTP_SESSION *smtp_connect_sock(int, struct sockaddr *, int, + SMTP_ITERATOR *, DSN_BUF *, + int); + +/* smtp_connect_unix - connect to UNIX-domain address */ + +static SMTP_SESSION *smtp_connect_unix(SMTP_ITERATOR *iter, DSN_BUF *why, + int sess_flags) +{ + const char *myname = "smtp_connect_unix"; + struct sockaddr_un sock_un; + const char *addr = STR(iter->addr); + int len = strlen(addr); + int sock; + + dsb_reset(why); /* Paranoia */ + + /* + * Sanity checks. + */ + if (len >= (int) sizeof(sock_un.sun_path)) { + msg_warn("unix-domain name too long: %s", addr); + dsb_simple(why, "4.3.5", "Server configuration error"); + return (0); + } + + /* + * Initialize. + */ + memset((void *) &sock_un, 0, sizeof(sock_un)); + sock_un.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sock_un.sun_len = len + 1; +#endif + memcpy(sock_un.sun_path, addr, len + 1); + + /* + * Create a client socket. + */ + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + + /* + * Connect to the server. + */ + if (msg_verbose) + msg_info("%s: trying: %s...", myname, addr); + + return (smtp_connect_sock(sock, (struct sockaddr *) &sock_un, + sizeof(sock_un), iter, why, sess_flags)); +} + +/* smtp_connect_addr - connect to explicit address */ + +static SMTP_SESSION *smtp_connect_addr(SMTP_ITERATOR *iter, DSN_BUF *why, + int sess_flags) +{ + const char *myname = "smtp_connect_addr"; + struct sockaddr_storage ss; /* remote */ + struct sockaddr *sa = (struct sockaddr *) &ss; + SOCKADDR_SIZE salen = sizeof(ss); + MAI_HOSTADDR_STR hostaddr; + DNS_RR *addr = iter->rr; + unsigned port = iter->port; + int sock; + char *bind_addr; + char *bind_var; + char *saved_bind_addr = 0; + char *tail; + + dsb_reset(why); /* Paranoia */ + + /* + * Sanity checks. + */ + if (dns_rr_to_sa(addr, port, sa, &salen) != 0) { + msg_warn("%s: skip address type %s: %m", + myname, dns_strtype(addr->type)); + dsb_simple(why, "4.4.0", "network address conversion failed: %m"); + return (0); + } + + /* + * Initialize. + */ + if ((sock = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + +#define RETURN_EARLY() do { \ + if (saved_bind_addr) \ + myfree(saved_bind_addr); \ + (void) close(sock); \ + return (0); \ + } while (0) + + if (inet_windowsize > 0) + set_inet_windowsize(sock, inet_windowsize); + + /* + * Allow the sysadmin to specify the source address, for example, as "-o + * smtp_bind_address=x.x.x.x" in the master.cf file. + */ +#ifdef HAS_IPV6 + if (sa->sa_family == AF_INET6) { + bind_addr = var_smtp_bind_addr6; + bind_var = VAR_LMTP_SMTP(BIND_ADDR6); + } else +#endif + if (sa->sa_family == AF_INET) { + bind_addr = var_smtp_bind_addr; + bind_var = VAR_LMTP_SMTP(BIND_ADDR); + } else + bind_var = bind_addr = ""; + if (*bind_addr) { + int aierr; + struct addrinfo *res0; + + if (*bind_addr == '[') { + saved_bind_addr = mystrdup(bind_addr + 1); + if ((tail = split_at(saved_bind_addr, ']')) == 0 || *tail) + msg_fatal("%s: malformed %s parameter: %s", + myname, bind_var, bind_addr); + bind_addr = saved_bind_addr; + } + if ((aierr = hostaddr_to_sockaddr(bind_addr, (char *) 0, 0, &res0)) != 0) + msg_fatal("%s: bad %s parameter: %s: %s", + myname, bind_var, bind_addr, MAI_STRERROR(aierr)); + if (bind(sock, res0->ai_addr, res0->ai_addrlen) < 0) { + msg_warn("%s: bind %s: %m", myname, bind_addr); + if (var_smtp_bind_addr_enforce) { + freeaddrinfo(res0); + dsb_simple(why, "4.4.0", "server configuration error"); + RETURN_EARLY(); + } + } else if (msg_verbose) + msg_info("%s: bind %s", myname, bind_addr); + if (saved_bind_addr) + myfree(saved_bind_addr); + freeaddrinfo(res0); + } + + /* + * When running as a virtual host, bind to the virtual interface so that + * the mail appears to come from the "right" machine address. + * + * XXX The IPv6 patch expands the null host (as client endpoint) and uses + * the result as the loopback address list. + */ + else { + int count = 0; + struct sockaddr *own_addr = 0; + INET_ADDR_LIST *addr_list = own_inet_addr_list(); + struct sockaddr_storage *s; + + for (s = addr_list->addrs; s < addr_list->addrs + addr_list->used; s++) { + if (SOCK_ADDR_FAMILY(s) == sa->sa_family) { + if (count++ > 0) + break; + own_addr = SOCK_ADDR_PTR(s); + } + } + if (count == 1 && !sock_addr_in_loopback(own_addr)) { + if (bind(sock, own_addr, SOCK_ADDR_LEN(own_addr)) < 0) { + SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_warn("%s: bind %s: %m", myname, hostaddr.buf); + } else if (msg_verbose) { + SOCKADDR_TO_HOSTADDR(own_addr, SOCK_ADDR_LEN(own_addr), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_info("%s: bind %s", myname, hostaddr.buf); + } + } + } + + /* + * Connect to the server. + */ + if (msg_verbose) + msg_info("%s: trying: %s[%s] port %d...", + myname, STR(iter->host), STR(iter->addr), ntohs(port)); + + return (smtp_connect_sock(sock, sa, salen, iter, why, sess_flags)); +} + +/* smtp_connect_sock - connect a socket over some transport */ + +static SMTP_SESSION *smtp_connect_sock(int sock, struct sockaddr *sa, + int salen, + SMTP_ITERATOR *iter, + DSN_BUF *why, + int sess_flags) +{ + int conn_stat; + int saved_errno; + VSTREAM *stream; + time_t start_time; + const char *name = STR(iter->host); + const char *addr = STR(iter->addr); + unsigned port = iter->port; + + start_time = time((time_t *) 0); + if (var_smtp_conn_tmout > 0) { + non_blocking(sock, NON_BLOCKING); + conn_stat = timed_connect(sock, sa, salen, var_smtp_conn_tmout); + saved_errno = errno; + non_blocking(sock, BLOCKING); + errno = saved_errno; + } else { + conn_stat = sane_connect(sock, sa, salen); + } + if (conn_stat < 0) { + if (port) + dsb_simple(why, "4.4.1", "connect to %s[%s]:%d: %m", + name, addr, ntohs(port)); + else + dsb_simple(why, "4.4.1", "connect to %s[%s]: %m", name, addr); + close(sock); + return (0); + } + stream = vstream_fdopen(sock, O_RDWR); + + /* + * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. + */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) + vstream_tweak_tcp(stream); + + /* + * Bundle up what we have into a nice SMTP_SESSION object. + */ + return (smtp_session_alloc(stream, iter, start_time, sess_flags)); +} + +/* smtp_parse_destination - parse host/port destination */ + +static char *smtp_parse_destination(char *destination, char *def_service, + char **hostp, unsigned *portp) +{ + char *buf = mystrdup(destination); + char *service; + struct servent *sp; + char *protocol = "tcp"; /* XXX configurable? */ + unsigned port; + const char *err; + + if (msg_verbose) + msg_info("smtp_parse_destination: %s %s", destination, def_service); + + /* + * Parse the host/port information. We're working with a copy of the + * destination argument so the parsing can be destructive. + */ + if ((err = host_port(buf, hostp, (char *) 0, &service, def_service)) != 0) + msg_fatal("%s in server description: %s", err, destination); + + /* + * Convert service to port number, network byte order. + */ + service = (char *) filter_known_tcp_port(service); + if (alldig(service)) { + if ((port = atoi(service)) >= 65536 || port == 0) + msg_fatal("bad network port: %s for destination: %s", + service, destination); + *portp = htons(port); + } else { + if ((sp = getservbyname(service, protocol)) == 0) + msg_fatal("unknown service: %s/%s", service, protocol); + *portp = sp->s_port; + } + return (buf); +} + +/* smtp_cleanup_session - clean up after using a session */ + +static void smtp_cleanup_session(SMTP_STATE *state) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + int throttled; + + /* + * Inform the postmaster of trouble. + * + * XXX Don't send notifications about errors while sending notifications. + */ +#define POSSIBLE_NOTIFICATION(sender) \ + (*sender == 0 || strcmp(sender, mail_addr_double_bounce()) == 0) + + if (session->history != 0 + && (session->error_mask & name_mask(VAR_NOTIFY_CLASSES, + mail_error_masks, + var_notify_classes)) != 0 + && POSSIBLE_NOTIFICATION(request->sender) == 0) + smtp_chat_notify(session); + + /* + * When session caching is enabled, cache the first good session for this + * delivery request under the next-hop destination, and cache all good + * sessions under their server network address (destroying the session in + * the process). + * + * Caching under the next-hop destination name (rather than the fall-back + * destination) allows us to skip over non-responding primary or backup + * hosts. In fact, this is the only benefit of caching logical to + * physical bindings; caching a session under its own hostname provides + * no performance benefit, given the way smtp_connect() works. + */ + throttled = THIS_SESSION_IS_THROTTLED; /* smtp_quit() may fail */ + if (THIS_SESSION_IS_EXPIRED) + smtp_quit(state); /* also disables caching */ + if (THIS_SESSION_IS_CACHED + /* Redundant tests for safety... */ + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) { + smtp_save_session(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL, + SMTP_KEY_MASK_SCACHE_ENDP_LABEL); + } else { + smtp_session_free(session); + } + state->session = 0; + + /* + * If this session was good, reset the scache next-hop destination, so + * that we won't cache connections to less-preferred servers under the + * same next-hop destination. Otherwise we could end up skipping over the + * available and more-preferred servers. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state) && !throttled) + CLEAR_SCACHE_REQUEST_NEXTHOP(state); + + /* + * Clean up the lists with todo and dropped recipients. + */ + smtp_rcpt_cleanup(state); + + /* + * Reset profiling info. + * + * XXX When one delivery request results in multiple sessions, the set-up + * and transmission latencies of the earlier sessions will count as + * connection set-up time for the later sessions. + * + * XXX On the other hand, when we first try to connect to one or more dead + * hosts before we reach a good host, then all that time must be counted + * as connection set-up time for the session with the good host. + * + * XXX So this set-up attribution problem exists only when we actually + * engage in a session, spend a lot of time delivering a message, find + * that it fails, and then connect to an alternate host. + */ + memset((void *) &request->msg_stats.conn_setup_done, 0, + sizeof(request->msg_stats.conn_setup_done)); + memset((void *) &request->msg_stats.deliver_done, 0, + sizeof(request->msg_stats.deliver_done)); + request->msg_stats.reuse_count = 0; +} + +static void smtp_cache_policy(SMTP_STATE *state, const char *dest) +{ + DELIVER_REQUEST *request = state->request; + + state->misc_flags &= ~SMTP_MISC_FLAG_CONN_CACHE_MASK; + + if (smtp_cache_dest && string_list_match(smtp_cache_dest, dest)) { + state->misc_flags |= SMTP_MISC_FLAG_CONN_CACHE_MASK; + } else if (var_smtp_cache_demand) { + if (request->flags & DEL_REQ_FLAG_CONN_LOAD) + state->misc_flags |= SMTP_MISC_FLAG_CONN_LOAD; + if (request->flags & DEL_REQ_FLAG_CONN_STORE) + state->misc_flags |= SMTP_MISC_FLAG_CONN_STORE; + } +} + +/* smtp_connect_local - connect to local server */ + +static void smtp_connect_local(SMTP_STATE *state, const char *path) +{ + const char *myname = "smtp_connect_local"; + SMTP_ITERATOR *iter = state->iterator; + SMTP_SESSION *session; + DSN_BUF *why = state->why; + + /* + * Do not silently ignore an unused setting. + */ + if (*var_fallback_relay) + msg_warn("ignoring \"%s = %s\" setting for non-TCP connections", + VAR_LMTP_FALLBACK, var_fallback_relay); + + /* + * It's too painful to weave this code into the SMTP connection + * management routine. + * + * Connection cache management is based on the UNIX-domain pathname, without + * the "unix:" prefix. + */ + smtp_cache_policy(state, path); + if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK) + SET_SCACHE_REQUEST_NEXTHOP(state, path); + + /* + * Here we ensure that the iter->addr member refers to a copy of the + * UNIX-domain pathname, so that smtp_save_session() will cache the + * connection using the pathname as the physical endpoint name. + * + * We set dest=path for backwards compatibility. + */ +#define NO_PORT 0 + + SMTP_ITER_INIT(iter, path, var_myhostname, path, NO_PORT, state); + + /* + * Opportunistic TLS for unix domain sockets does not make much sense, + * since the channel is private, mere encryption without authentication + * is just wasted cycles and opportunity for breakage. Since we are not + * willing to retry after TLS handshake failures here, we downgrade "may" + * no "none". Nothing is lost, and much waste is avoided. + * + * We don't know who is authenticating whom, so if a client cert is + * available, "encrypt" may be a sensible policy. Otherwise, we also + * downgrade "encrypt" to "none", this time just to avoid waste. + * + * We use smtp_reuse_nexthop() instead of smtp_reuse_addr(), so that we can + * reuse a SASL-authenticated connection (however unlikely this scenario + * may be). The smtp_reuse_addr() interface currently supports only reuse + * of SASL-unauthenticated connections. + */ +#ifdef USE_TLS + if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { + msg_warn("TLS policy lookup error for %s/%s: %s", + STR(iter->host), STR(iter->addr), STR(why->reason)); + return; + } +#endif + if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 + || (session = smtp_reuse_nexthop(state, + SMTP_KEY_MASK_SCACHE_DEST_LABEL)) == 0) + session = smtp_connect_unix(iter, why, state->misc_flags); + if ((state->session = session) != 0) { + session->state = state; +#ifdef USE_TLS + session->tls_nexthop = var_myhostname; /* for TLS_LEV_SECURE */ + if (state->tls->level == TLS_LEV_MAY) { + msg_warn("%s: opportunistic TLS encryption is not appropriate " + "for unix-domain destinations.", myname); + state->tls->level = TLS_LEV_NONE; + } +#endif + /* All delivery errors bounce or defer. */ + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + + /* + * When a TLS handshake fails, the stream is marked "dead" to avoid + * further I/O over a broken channel. + */ + if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 + && smtp_helo(state) != 0) { + if (!THIS_SESSION_IS_FORBIDDEN + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) + smtp_quit(state); + } else { + smtp_xfer(state); + } + + /* + * With opportunistic TLS disabled we don't expect to be asked to + * retry connections without TLS, and so we expect the final server + * flag to stay on. + */ + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) + msg_panic("%s: unix-domain destination not final!", myname); + smtp_cleanup_session(state); + } + + /* + * Cleanup. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + CLEAR_SCACHE_REQUEST_NEXTHOP(state); +} + +/* smtp_scrub_address_list - delete all cached addresses from list */ + +static void smtp_scrub_addr_list(HTABLE *cached_addr, DNS_RR **addr_list) +{ + MAI_HOSTADDR_STR hostaddr; + DNS_RR *addr; + DNS_RR *next; + + /* + * XXX Extend the DNS_RR structure with fields for the printable address + * and/or binary sockaddr representations, so that we can avoid repeated + * binary->string transformations for the same address. + */ + for (addr = *addr_list; addr; addr = next) { + next = addr->next; + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("cannot convert type %s record to printable address", + dns_strtype(addr->type)); + continue; + } + if (htable_locate(cached_addr, hostaddr.buf)) + *addr_list = dns_rr_remove(*addr_list, addr); + } +} + +/* smtp_update_addr_list - common address list update */ + +static void smtp_update_addr_list(DNS_RR **addr_list, const char *server_addr, + int session_count) +{ + DNS_RR *addr; + DNS_RR *next; + int aierr; + struct addrinfo *res0; + + if (*addr_list == 0) + return; + + /* + * Truncate the address list if we are not going to use it anyway. + */ + if (session_count == var_smtp_mxsess_limit + || session_count == var_smtp_mxaddr_limit) { + dns_rr_free(*addr_list); + *addr_list = 0; + return; + } + + /* + * Convert server address to internal form, and look it up in the address + * list. + * + * XXX smtp_reuse_session() breaks if we remove two or more adjacent list + * elements but do not truncate the list to zero length. + * + * XXX Extend the SMTP_SESSION structure with sockaddr information so that + * we can avoid repeated string->binary transformations for the same + * address. + */ + if ((aierr = hostaddr_to_sockaddr(server_addr, (char *) 0, 0, &res0)) != 0) { + msg_warn("hostaddr_to_sockaddr %s: %s", + server_addr, MAI_STRERROR(aierr)); + } else { + for (addr = *addr_list; addr; addr = next) { + next = addr->next; + if (DNS_RR_EQ_SA(addr, (struct sockaddr *) res0->ai_addr)) { + *addr_list = dns_rr_remove(*addr_list, addr); + break; + } + } + freeaddrinfo(res0); + } +} + +/* smtp_reuse_session - try to use existing connection, return session count */ + +static int smtp_reuse_session(SMTP_STATE *state, DNS_RR **addr_list, + int domain_best_pref) +{ + int session_count = 0; + DNS_RR *addr; + DNS_RR *next; + MAI_HOSTADDR_STR hostaddr; + SMTP_SESSION *session; + SMTP_ITERATOR *iter = state->iterator; + DSN_BUF *why = state->why; + + /* + * First, search the cache by delivery request nexthop. We truncate the + * server address list when all the sessions for this destination are + * used up, to reduce the number of variables that need to be checked + * later. + * + * Note: connection reuse by delivery request nexthop restores the "best MX" + * bit. + * + * smtp_reuse_nexthop() clobbers the iterators's "dest" attribute. We save + * and restore it here, so that subsequent connections will use the + * proper nexthop information. + * + * We don't use TLS level info for nexthop-based connection cache storage + * keys. The combination of (service, nexthop, etc.) should be stable + * over the time range of interest, and the policy is still enforced on + * an individual connection to an MX host, before that connection is + * stored under a nexthop- or host-based storage key. + */ +#ifdef USE_TLS + smtp_tls_policy_dummy(state->tls); +#endif + SMTP_ITER_SAVE_DEST(state->iterator); + if (*addr_list && SMTP_RCPT_LEFT(state) > 0 + && HAVE_SCACHE_REQUEST_NEXTHOP(state) + && (session = smtp_reuse_nexthop(state, SMTP_KEY_MASK_SCACHE_DEST_LABEL)) != 0) { + session_count = 1; + smtp_update_addr_list(addr_list, STR(iter->addr), session_count); + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && *addr_list == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + smtp_xfer(state); + smtp_cleanup_session(state); + } + SMTP_ITER_RESTORE_DEST(state->iterator); + + /* + * Second, search the cache by primary MX address. Again, we use address + * list truncation so that we have to check fewer variables later. + * + * XXX This loop is safe because smtp_update_addr_list() either truncates + * the list to zero length, or removes at most one list element. + * + * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated + * connections. Furthermore, we rely on smtp_reuse_addr() to look up an + * existing SASL-unauthenticated connection only when a new connection + * would be guaranteed not to require SASL authentication. + * + * In addition, we rely on smtp_reuse_addr() to look up an existing + * plaintext connection only when a new connection would be guaranteed + * not to use TLS. + * + * For more precise control over reuse, the iterator should look up SASL and + * TLS policy as it evaluates mail exchangers in order, instead of + * relying on duplicate lookup request code in smtp_reuse(3) and + * smtp_session(3). + */ + for (addr = *addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) { + if (addr->pref != domain_best_pref) + break; + next = addr->next; + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("cannot convert type %s record to printable address", + dns_strtype(addr->type)); + /* XXX Assume there is no code at the end of this loop. */ + continue; + } + vstring_strcpy(iter->addr, hostaddr.buf); + vstring_strcpy(iter->host, SMTP_HNAME(addr)); + iter->rr = addr; +#ifdef USE_TLS + if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { + msg_warn("TLS policy lookup error for %s/%s: %s", + STR(iter->dest), STR(iter->host), STR(why->reason)); + continue; + /* XXX Assume there is no code at the end of this loop. */ + } +#endif + if ((session = smtp_reuse_addr(state, + SMTP_KEY_MASK_SCACHE_ENDP_LABEL)) != 0) { + session->features |= SMTP_FEATURE_BEST_MX; + session_count += 1; + smtp_update_addr_list(addr_list, STR(iter->addr), session_count); + if (*addr_list == 0) + next = 0; + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && next == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + smtp_xfer(state); + smtp_cleanup_session(state); + } + } + return (session_count); +} + +/* smtp_connect_inet - establish network connection */ + +static void smtp_connect_inet(SMTP_STATE *state, const char *nexthop, + char *def_service) +{ + DELIVER_REQUEST *request = state->request; + SMTP_ITERATOR *iter = state->iterator; + ARGV *sites; + char *dest; + char **cpp; + int non_fallback_sites; + int retry_plain = 0; + DSN_BUF *why = state->why; + + /* + * For sanity, require that at least one of INET or INET6 is enabled. + * Otherwise, we can't look up interface information, and we can't + * convert names or addresses. + */ + if (inet_proto_info()->ai_family_list[0] == 0) { + dsb_simple(why, "4.4.4", "all network protocols are disabled"); + return; + } + + /* + * Do a null destination sanity check in case the primary destination is + * a list that consists of only separators. + */ + sites = argv_split(nexthop, CHARS_COMMA_SP); + if (sites->argc == 0) + msg_panic("null destination: \"%s\"", nexthop); + non_fallback_sites = sites->argc; + argv_split_append(sites, var_fallback_relay, CHARS_COMMA_SP); + + /* + * Don't give up after a hard host lookup error until we have tried the + * fallback relay servers. + * + * Don't bounce mail after a host lookup problem with a relayhost or with a + * fallback relay. + * + * Don't give up after a qualifying soft error until we have tried all + * qualifying backup mail servers. + * + * All this means that error handling and error reporting depends on whether + * the error qualifies for trying to deliver to a backup mail server, or + * whether we're looking up a relayhost or fallback relay. The challenge + * then is to build this into the pre-existing SMTP client without + * getting lost in the complexity. + */ +#define IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites) \ + (*(cpp) && (cpp) >= (sites)->argv + (non_fallback_sites)) + + for (cpp = sites->argv, (state->misc_flags |= SMTP_MISC_FLAG_FIRST_NEXTHOP); + SMTP_RCPT_LEFT(state) > 0 && (dest = *cpp) != 0; + cpp++, (state->misc_flags &= ~SMTP_MISC_FLAG_FIRST_NEXTHOP)) { + char *dest_buf; + char *domain; + unsigned port; + DNS_RR *addr_list; + DNS_RR *addr; + DNS_RR *next; + int addr_count; + int sess_count; + SMTP_SESSION *session; + int lookup_mx; + unsigned domain_best_pref; + MAI_HOSTADDR_STR hostaddr; + + if (cpp[1] == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; + + /* + * Parse the destination. If no TCP port is specified, use the port + * that is reserved for the protocol (SMTP or LMTP). + */ + dest_buf = smtp_parse_destination(dest, def_service, &domain, &port); + if (var_helpful_warnings && var_smtp_tls_wrappermode == 0 + && ntohs(port) == 465) { + msg_info("SMTPS wrappermode (TCP port 465) requires setting " + "\"%s = yes\", and \"%s = encrypt\" (or stronger)", + VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); + } +#define NO_HOST "" /* safety */ +#define NO_ADDR "" /* safety */ + + SMTP_ITER_INIT(iter, dest, NO_HOST, NO_ADDR, port, state); + + /* + * Resolve an SMTP or LMTP server. In the case of SMTP, skip mail + * exchanger lookups when a quoted host is specified or when DNS + * lookups are disabled. + */ + if (msg_verbose) + msg_info("connecting to %s port %d", domain, ntohs(port)); + if (smtp_mode) { + if (ntohs(port) == IPPORT_SMTP) + state->misc_flags |= SMTP_MISC_FLAG_LOOP_DETECT; + else + state->misc_flags &= ~SMTP_MISC_FLAG_LOOP_DETECT; + lookup_mx = (smtp_dns_support != SMTP_DNS_DISABLED && *dest != '['); + } else + lookup_mx = 0; + if (!lookup_mx) { + addr_list = smtp_host_addr(domain, state->misc_flags, why); + /* XXX We could be an MX host for this destination... */ + } else { + int i_am_mx = 0; + + addr_list = smtp_domain_addr(domain, &iter->mx, state->misc_flags, + why, &i_am_mx); + /* If we're MX host, don't connect to non-MX backups. */ + if (i_am_mx) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; + } + + /* + * Don't try fall-back hosts if mail loops to myself. That would just + * make the problem worse. + */ + if (addr_list == 0 && SMTP_HAS_LOOP_DSN(why)) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_NEXTHOP; + + /* + * No early loop exit or we have a memory leak with dest_buf. + */ + if (addr_list) + domain_best_pref = addr_list->pref; + + /* + * When connection caching is enabled, store the first good + * connection for this delivery request under the delivery request + * next-hop name. Good connections will also be stored under their + * specific server IP address. + * + * XXX smtp_session_cache_destinations specifies domain names without + * :port, because : is already used for maptype:mapname. Because of + * this limitation we use the bare domain without the optional [] or + * non-default TCP port. + * + * Opportunistic (a.k.a. on-demand) session caching on request by the + * queue manager. This is turned temporarily when a destination has a + * high volume of mail in the active queue. When the surge reaches + * its end, the queue manager requests that connections be retrieved + * but not stored. + */ + if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_FIRST_NEXTHOP)) { + smtp_cache_policy(state, domain); + if (state->misc_flags & SMTP_MISC_FLAG_CONN_CACHE_MASK) + SET_SCACHE_REQUEST_NEXTHOP(state, dest); + } + + /* + * Delete visited cached hosts from the address list. + * + * Optionally search the connection cache by domain name or by primary + * MX address before we try to create new connections. + * + * Enforce the MX session and MX address counts per next-hop or + * fall-back destination. smtp_reuse_session() will truncate the + * address list when either limit is reached. + */ + if (addr_list && (state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD)) { + if (state->cache_used->used > 0) + smtp_scrub_addr_list(state->cache_used, &addr_list); + sess_count = addr_count = + smtp_reuse_session(state, &addr_list, domain_best_pref); + } else + sess_count = addr_count = 0; + + /* + * Connect to an SMTP server: create primary MX connections, and + * reuse or create backup MX connections. + * + * At the start of an SMTP session, all recipients are unmarked. In the + * course of an SMTP session, recipients are marked as KEEP (deliver + * to alternate mail server) or DROP (remove from recipient list). At + * the end of an SMTP session, weed out the recipient list. Unmark + * any left-over recipients and try to deliver them to a backup mail + * server. + * + * Cache the first good session under the next-hop destination name. + * Cache all good sessions under their physical endpoint. + * + * Don't query the session cache for primary MX hosts. We already did + * that in smtp_reuse_session(), and if any were found in the cache, + * they were already deleted from the address list. + * + * Currently, we use smtp_reuse_addr() only for SASL-unauthenticated + * connections. Furthermore, we rely on smtp_reuse_addr() to look up + * an existing SASL-unauthenticated connection only when a new + * connection would be guaranteed not to require SASL authentication. + * + * In addition, we rely on smtp_reuse_addr() to look up an existing + * plaintext connection only when a new connection would be + * guaranteed not to use TLS. + */ + for (addr = addr_list; SMTP_RCPT_LEFT(state) > 0 && addr; addr = next) { + next = addr->next; + if (++addr_count == var_smtp_mxaddr_limit) + next = 0; + if (dns_rr_to_pa(addr, &hostaddr) == 0) { + msg_warn("cannot convert type %s record to printable address", + dns_strtype(addr->type)); + /* XXX Assume there is no code at the end of this loop. */ + continue; + } + vstring_strcpy(iter->addr, hostaddr.buf); + vstring_strcpy(iter->host, SMTP_HNAME(addr)); + iter->rr = addr; +#ifdef USE_TLS + if (!smtp_tls_policy_cache_query(why, state->tls, iter)) { + msg_warn("TLS policy lookup for %s/%s: %s", + STR(iter->dest), STR(iter->host), STR(why->reason)); + continue; + /* XXX Assume there is no code at the end of this loop. */ + } + if (var_smtp_tls_wrappermode + && state->tls->level < TLS_LEV_ENCRYPT) { + msg_warn("%s requires \"%s = encrypt\" (or stronger)", + VAR_LMTP_SMTP(TLS_WRAPPER), VAR_LMTP_SMTP(TLS_LEVEL)); + continue; + /* XXX Assume there is no code at the end of this loop. */ + } + /* Disable TLS when retrying after a handshake failure */ + if (retry_plain) { + state->tls->level = TLS_LEV_NONE; + retry_plain = 0; + } +#endif + if ((state->misc_flags & SMTP_MISC_FLAG_CONN_LOAD) == 0 + || addr->pref == domain_best_pref + || !(session = smtp_reuse_addr(state, + SMTP_KEY_MASK_SCACHE_ENDP_LABEL))) + session = smtp_connect_addr(iter, why, state->misc_flags); + if ((state->session = session) != 0) { + session->state = state; +#ifdef USE_TLS + session->tls_nexthop = domain; +#endif + if (addr->pref == domain_best_pref) + session->features |= SMTP_FEATURE_BEST_MX; + /* Don't count handshake errors towards the session limit. */ + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && next == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + if ((session->features & SMTP_FEATURE_FROM_CACHE) == 0 + && smtp_helo(state) != 0) { +#ifdef USE_TLS + + /* + * When an opportunistic TLS handshake fails, try the + * same address again, with TLS disabled. See also the + * RETRY_AS_PLAINTEXT macro. + */ + if ((retry_plain = session->tls_retry_plain) != 0) { + --addr_count; + next = addr; + } +#endif + + /* + * When a TLS handshake fails, the stream is marked + * "dead" to avoid further I/O over a broken channel. + */ + if (!THIS_SESSION_IS_FORBIDDEN + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) + smtp_quit(state); + } else { + /* Do count delivery errors towards the session limit. */ + if (++sess_count == var_smtp_mxsess_limit) + next = 0; + if ((state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + && next == 0) + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; + smtp_xfer(state); +#ifdef USE_TLS + + /* + * When opportunistic TLS fails after the STARTTLS + * handshake, try the same address again, with TLS + * disabled. See also the RETRY_AS_PLAINTEXT macro. + */ + if ((retry_plain = session->tls_retry_plain) != 0) { + --sess_count; + --addr_count; + next = addr; + } +#endif + } + smtp_cleanup_session(state); + } else { + /* The reason already includes the IP address and TCP port. */ + msg_info("%s", STR(why->reason)); + } + /* XXX Code above assumes there is no code at this loop ending. */ + } + dns_rr_free(addr_list); + if (iter->mx) { + dns_rr_free(iter->mx); + iter->mx = 0; /* Just in case */ + } + myfree(dest_buf); + if (state->misc_flags & SMTP_MISC_FLAG_FINAL_NEXTHOP) + break; + } + + /* + * We still need to deliver, bounce or defer some left-over recipients: + * either mail loops or some backup mail server was unavailable. + */ + if (SMTP_RCPT_LEFT(state) > 0) { + + /* + * In case of a "no error" indication we make up an excuse: we did + * find the host address, but we did not attempt to connect to it. + * This can happen when the fall-back relay was already tried via a + * cached connection, so that the address list scrubber left behind + * an empty list. + */ + if (!SMTP_HAS_DSN(why)) { + dsb_simple(why, "4.3.0", + "server unavailable or unable to receive mail"); + } + + /* + * Pay attention to what could be configuration problems, and pretend + * that these are recoverable rather than bouncing the mail. + */ + else if (!SMTP_HAS_SOFT_DSN(why)) { + + /* + * The fall-back destination did not resolve as expected, or it + * is refusing to talk to us, or mail for it loops back to us. + */ + if (IS_FALLBACK_RELAY(cpp, sites, non_fallback_sites)) { + msg_warn("%s configuration problem", VAR_SMTP_FALLBACK); + vstring_strcpy(why->status, "4.3.5"); + /* XXX Keep the diagnostic code and MTA. */ + } + + /* + * The next-hop relayhost did not resolve as expected, or it is + * refusing to talk to us, or mail for it loops back to us. + * + * XXX There is no equivalent safety net for mis-configured + * sender-dependent relay hosts. The trivial-rewrite resolver + * would have to flag the result, and the queue manager would + * have to provide that information to delivery agents. + */ + else if (smtp_mode && strcmp(sites->argv[0], var_relayhost) == 0) { + msg_warn("%s configuration problem", VAR_RELAYHOST); + vstring_strcpy(why->status, "4.3.5"); + /* XXX Keep the diagnostic code and MTA. */ + } + + /* + * Mail for the next-hop destination loops back to myself. Pass + * the mail to the best_mx_transport or bounce it. + */ + else if (smtp_mode && SMTP_HAS_LOOP_DSN(why) && *var_bestmx_transp) { + dsb_reset(why); /* XXX */ + state->status = deliver_pass_all(MAIL_CLASS_PRIVATE, + var_bestmx_transp, + request); + SMTP_RCPT_LEFT(state) = 0; /* XXX */ + } + } + } + + /* + * Cleanup. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + CLEAR_SCACHE_REQUEST_NEXTHOP(state); + argv_free(sites); +} + +/* smtp_connect - establish SMTP connection */ + +int smtp_connect(SMTP_STATE *state) +{ + DELIVER_REQUEST *request = state->request; + char *destination = request->nexthop; + + /* + * All deliveries proceed along the same lines, whether they are over TCP + * or UNIX-domain sockets, and whether they use SMTP or LMTP: get a + * connection from the cache or create a new connection; deliver mail; + * update the connection cache or disconnect. + * + * The major differences appear at a higher level: the expansion from + * destination to address list, and whether to stop before we reach the + * end of that list. + */ + + /* + * With LMTP we have direct-to-host delivery only. The destination may + * have multiple IP addresses. + */ + if (!smtp_mode) { + if (strncmp(destination, "unix:", 5) == 0) { + smtp_connect_local(state, destination + 5); + } else { + if (strncmp(destination, "inet:", 5) == 0) + destination += 5; + smtp_connect_inet(state, destination, var_smtp_tcp_port); + } + } + + /* + * XXX We don't add support for "unix:" or "inet:" prefixes in SMTP + * destinations, because that would break compatibility with existing + * Postfix configurations that have a host with such a name. + */ + else { + smtp_connect_inet(state, destination, var_smtp_tcp_port); + } + + /* + * We still need to bounce or defer some left-over recipients: either + * (SMTP) mail loops or some server was unavailable. + * + * We could avoid this (and the "final server" complexity) by keeping one + * DSN structure per recipient in memory, by updating those in-memory + * structures with each delivery attempt, and by always flushing all + * deferred recipients at the end. We'd probably still want to bounce + * recipients immediately, so we'd end up with another chunk of code for + * defer logging only. + */ + if (SMTP_RCPT_LEFT(state) > 0) { + state->misc_flags |= SMTP_MISC_FLAG_FINAL_SERVER; /* XXX */ + smtp_sess_fail(state); + + /* + * Sanity check. Don't silently lose recipients. + */ + smtp_rcpt_cleanup(state); + if (SMTP_RCPT_LEFT(state) > 0) + msg_panic("smtp_connect: left-over recipients"); + } + return (state->status); +} diff --git a/src/smtp/smtp_key.c b/src/smtp/smtp_key.c new file mode 100644 index 0000000..643f4db --- /dev/null +++ b/src/smtp/smtp_key.c @@ -0,0 +1,215 @@ +/*++ +/* NAME +/* smtp_key 3 +/* SUMMARY +/* cache/table lookup key management +/* SYNOPSIS +/* #include "smtp.h" +/* +/* char *smtp_key_prefix(buffer, delim_na, iterator, context_flags) +/* VSTRING *buffer; +/* const char *delim_na; +/* SMTP_ITERATOR *iterator; +/* int context_flags; +/* DESCRIPTION +/* The Postfix SMTP server accesses caches and lookup tables, +/* using lookup keys that contain information from various +/* contexts: per-server configuration, per-request envelope, +/* and results from DNS queries. +/* +/* These lookup keys sometimes share the same context information. +/* The primary purpose of this API is to ensure that this +/* shared context is used consistently, and that its use is +/* made explicit (both are needed to verify that there is no +/* false cache sharing). +/* +/* smtp_key_prefix() constructs a lookup key prefix from context +/* that may be shared with other lookup keys. The user is free +/* to append additional application-specific context. The result +/* value is a pointer to the result text. +/* +/* Arguments: +/* .IP buffer +/* Storage for the result. +/* .IP delim_na +/* The field delimiter character, and the optional place holder +/* character for a) information that is unavailable, b) +/* information that is inapplicable, or c) that would result +/* in an empty field. Key fields that contain "delim_na" +/* characters will be base64-encoded. +/* Do not specify "delim_na" characters that are part of the +/* base64 character set. +/* .IP iterator +/* Information that will be selected by the specified flags. +/* .IP context_flags +/* Bit-wise OR of one or more of the following. +/* .RS +/* .IP SMTP_KEY_FLAG_SERVICE +/* The global service name. This is a proxy for +/* destination-independent and request-independent context. +/* .IP SMTP_KEY_FLAG_SENDER +/* The envelope sender address. This is a proxy for sender-dependent +/* context, such as per-sender SASL authentication. +/* .IP SMTP_KEY_FLAG_REQ_NEXTHOP +/* The delivery request nexthop destination, including optional +/* [] and :port (the same form that users specify in a SASL +/* password or TLS policy lookup table). This is a proxy for +/* destination-dependent, but host-independent context. +/* .IP SMTP_KEY_FLAG_CUR_NEXTHOP +/* The current iterator's nexthop destination (delivery request +/* nexthop or fallback nexthop, including optional [] and +/* :port). +/* .IP SMTP_KEY_FLAG_HOSTNAME +/* The current iterator's remote hostname. +/* .IP SMTP_KEY_FLAG_ADDR +/* The current iterator's remote address. +/* .IP SMTP_KEY_FLAG_PORT +/* The current iterator's remote port. +/* .RE +/* DIAGNOSTICS +/* Panic: undefined flag or zero flags. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include /* ntohs() for Solaris or BSD */ +#include /* ntohs() for Linux or BSD */ +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Global library. + */ +#include + + /* + * Application-specific. + */ +#include + + /* + * We use a configurable field terminator and optional place holder for data + * that is unavailable or inapplicable. We base64-encode content that + * contains these characters, and content that needs obfuscation. + */ + +/* smtp_key_append_na - append place-holder key field */ + +static void smtp_key_append_na(VSTRING *buffer, const char *delim_na) +{ + if (delim_na[1] != 0) + VSTRING_ADDCH(buffer, delim_na[1]); + VSTRING_ADDCH(buffer, delim_na[0]); +} + +/* smtp_key_append_str - append string-valued key field */ + +static void smtp_key_append_str(VSTRING *buffer, const char *str, + const char *delim_na) +{ + if (str == 0 || str[0] == 0) { + smtp_key_append_na(buffer, delim_na); + } else if (str[strcspn(str, delim_na)] != 0) { + base64_encode_opt(buffer, str, strlen(str), BASE64_FLAG_APPEND); + VSTRING_ADDCH(buffer, delim_na[0]); + } else { + vstring_sprintf_append(buffer, "%s%c", str, delim_na[0]); + } +} + +/* smtp_key_append_uint - append unsigned-valued key field */ + +static void smtp_key_append_uint(VSTRING *buffer, unsigned num, + const char *delim_na) +{ + vstring_sprintf_append(buffer, "%u%c", num, delim_na[0]); +} + +/* smtp_key_prefix - format common elements in lookup key */ + +char *smtp_key_prefix(VSTRING *buffer, const char *delim_na, + SMTP_ITERATOR *iter, int flags) +{ + static const char myname[] = "smtp_key_prefix"; + SMTP_STATE *state = iter->parent; /* private member */ + + /* + * Sanity checks. + */ + if (state == 0) + msg_panic("%s: no parent state", myname); + if (flags & ~SMTP_KEY_MASK_ALL) + msg_panic("%s: unknown key flags 0x%x", + myname, flags & ~SMTP_KEY_MASK_ALL); + if (flags == 0) + msg_panic("%s: zero flags", myname); + + /* + * Initialize. + */ + VSTRING_RESET(buffer); + + /* + * Per-service and per-request context. + */ + if (flags & SMTP_KEY_FLAG_SERVICE) + smtp_key_append_str(buffer, state->service, delim_na); + if (flags & SMTP_KEY_FLAG_SENDER) + smtp_key_append_str(buffer, state->request->sender, delim_na); + + /* + * Per-destination context, non-canonicalized form. + */ + if (flags & SMTP_KEY_FLAG_REQ_NEXTHOP) + smtp_key_append_str(buffer, STR(iter->request_nexthop), delim_na); + if (flags & SMTP_KEY_FLAG_CUR_NEXTHOP) + smtp_key_append_str(buffer, STR(iter->dest), delim_na); + + /* + * Per-host context, canonicalized form. + */ + if (flags & SMTP_KEY_FLAG_HOSTNAME) + smtp_key_append_str(buffer, STR(iter->host), delim_na); + if (flags & SMTP_KEY_FLAG_ADDR) + smtp_key_append_str(buffer, STR(iter->addr), delim_na); + if (flags & SMTP_KEY_FLAG_PORT) + smtp_key_append_uint(buffer, ntohs(iter->port), delim_na); + + /* + * Requested TLS level, if applicable. TODO(tlsproxy) should the lookup + * engine also try the requested TLS level and 'stronger', in case a + * server hosts multiple domains with different TLS requirements? + */ + if (flags & SMTP_KEY_FLAG_TLS_LEVEL) +#ifdef USE_TLS + smtp_key_append_uint(buffer, state->tls->level, delim_na); +#else + smtp_key_append_na(buffer, delim_na); +#endif + + VSTRING_TERMINATE(buffer); + + return STR(buffer); +} diff --git a/src/smtp/smtp_map11.c b/src/smtp/smtp_map11.c new file mode 100644 index 0000000..bca8641 --- /dev/null +++ b/src/smtp/smtp_map11.c @@ -0,0 +1,325 @@ +/*++ +/* NAME +/* smtp_map11 3 +/* SUMMARY +/* one-to-one address mapping +/* SYNOPSIS +/* #include +/* +/* int smtp_map11_internal(addr, maps, propagate) +/* VSTRING *addr; +/* MAPS *maps; +/* int propagate; +/* +/* int smtp_map11_external(addr, maps, propagate) +/* VSTRING *addr; +/* MAPS *maps; +/* int propagate; +/* +/* int smtp_map11_tree(tree, maps, propagate) +/* TOK822 *tree; +/* MAPS *maps; +/* int propagate; +/* DESCRIPTION +/* This module performs non-recursive one-to-one address mapping. +/* An unmatched address extension is propagated when +/* \fIpropagate\fR is non-zero. +/* +/* smtp_map11_internal() looks up the RFC 822 internal (unquoted) +/* string form of an address in the maps specified via the +/* \fImaps\fR argument. +/* +/* smtp_map11_external() is a wrapper around the smtp_map11_internal() +/* routine that transforms from external (quoted) string form +/* to internal form and back. +/* +/* smtp_map11_tree() is a wrapper around the smtp_map11_internal() +/* routine that transforms from internal parse tree form to +/* internal form and back. +/* DIAGNOSTICS +/* Table lookup errors are fatal. +/* SEE ALSO +/* mail_addr_map(3) address mappings +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include + +/* smtp_map11_internal - one-to-one table lookups */ + +int smtp_map11_internal(VSTRING *addr, MAPS *maps, int propagate) +{ + const char *myname = "smtp_map11_internal"; + ARGV *new_addr; + const char *result; + + if ((new_addr = mail_addr_map_internal(maps, STR(addr), propagate)) != 0) { + if (new_addr->argc > 1) + msg_warn("multi-valued %s result for %s", maps->title, STR(addr)); + result = new_addr->argv[0]; + if (msg_verbose) + msg_info("%s: %s -> %s", myname, STR(addr), result); + vstring_strcpy(addr, result); + argv_free(new_addr); + return (1); + } else { + if (maps->error != 0) + msg_fatal("%s map lookup problem for %s", maps->title, STR(addr)); + if (msg_verbose) + msg_info("%s: %s not found", myname, STR(addr)); + return (0); + } +} + +/* smtp_map11_tree - rewrite address node */ + +int smtp_map11_tree(TOK822 *tree, MAPS *maps, int propagate) +{ + VSTRING *int_buf = vstring_alloc(100); + VSTRING *ext_buf = vstring_alloc(100); + int ret; + + tok822_internalize(int_buf, tree->head, TOK822_STR_DEFL); + ret = smtp_map11_internal(int_buf, maps, propagate); + tok822_free_tree(tree->head); + quote_822_local(ext_buf, STR(int_buf)); + tree->head = tok822_scan(STR(ext_buf), &tree->tail); + vstring_free(int_buf); + vstring_free(ext_buf); + return (ret); +} + +/* smtp_map11_external - rewrite address external form */ + +int smtp_map11_external(VSTRING *addr, MAPS *maps, int propagate) +{ + VSTRING *temp = vstring_alloc(100); + int ret; + + unquote_822_local(temp, STR(addr)); + ret = smtp_map11_internal(temp, maps, propagate); + quote_822_local(addr, STR(temp)); + vstring_free(temp); + return (ret); +} + +#ifdef TEST +#include + +#include +#include +#include +#include + +#include +#include + +/* canon_addr_external - surrogate to avoid trivial-rewrite dependency */ + +VSTRING *canon_addr_external(VSTRING *result, const char *addr) +{ + char *at; + + vstring_strcpy(result, addr); + if ((at = strrchr(addr, '@')) == 0 + || (at + 1)[strcspn(at + 1, "\"\\")] != 0) + vstring_sprintf_append(result, "@%s", var_myorigin); + return (result); +} + +static NORETURN usage(const char *progname) +{ + msg_fatal("usage: %s [-v]", progname); +} + +int main(int argc, char **argv) +{ + VSTRING *read_buf = vstring_alloc(100); + MAPS *maps = 0; + int lineno; + int first_line; + char *bp; + char *cmd; + VSTRING *addr_buf = vstring_alloc(100); + char *addr_field; + char *res_field; + int ch; + int errs = 0; + + /* + * Initialize. + */ + msg_vstream_init(basename(argv[0]), VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "v")) > 0) { + switch (ch) { + case 'v': + msg_verbose++; + break; + default: + usage(argv[0]); + } + } + if (argc != optind) + usage(argv[0]); + + util_utf8_enable = 1; + mail_params_init(); + + /* + * TODO: Data-driven parameter settings. + */ +#define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0) + + UPDATE(var_myhostname, "localhost.localdomain"); + UPDATE(var_mydomain, "localdomain"); + UPDATE(var_myorigin, "localdomain"); + UPDATE(var_mydest, "localhost.localdomain"); + UPDATE(var_rcpt_delim, "+"); + + /* + * The read-eval-print loop. + */ + while (readllines(read_buf, VSTREAM_IN, &lineno, &first_line)) { + bp = STR(read_buf); + if (msg_verbose) + msg_info("> %s", bp); + if ((cmd = mystrtok(&bp, CHARS_SPACE)) == 0 || *cmd == '#') + continue; + while (ISSPACE(*bp)) + bp++; + if (*bp == 0) + msg_fatal("missing parameter for command %s", cmd); + + /* + * Open maps. + */ + if (strcmp(cmd, "maps") == 0) { + if (maps) + maps_free(maps); + maps = maps_create(bp, bp, DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + vstream_printf("%s\n", bp); + continue; + } + + /* + * Lookup and verify. + */ + else if (maps != 0 && (strcmp(cmd, "external") == 0 + || strcmp(cmd, "internal") == 0 + || strcmp(cmd, "tree") == 0)) { + int have_result = 0; + + + /* + * Parse the input and expectations. + */ + if ((addr_field = mystrtok(&bp, ":")) == 0) + msg_fatal("missing address field"); + res_field = mystrtok(&bp, ":"); + if (mystrtok(&bp, ":") != 0) + msg_fatal("garbage after result field"); + + /* + * Perform the mapping. + */ + if (strcmp(cmd, "external") == 0) { + vstring_strcpy(addr_buf, addr_field); + have_result = smtp_map11_external(addr_buf, maps, 1); + } else if (maps && strcmp(cmd, "internal") == 0) { + vstring_strcpy(addr_buf, addr_field); + have_result = smtp_map11_internal(addr_buf, maps, 1); + } else if (maps && strcmp(cmd, "tree") == 0) { + TOK822 *tree; + TOK822 **addr_list; + TOK822 **tpp; + + tree = tok822_parse(addr_field); + addr_list = tok822_grep(tree, TOK822_ADDR); + for (tpp = addr_list; *tpp; tpp++) + have_result |= smtp_map11_tree(tpp[0], maps, 1); + myfree((void *) addr_list); + if (have_result) + tok822_externalize(addr_buf, tree, TOK822_STR_DEFL); + tok822_free_tree(tree); + } + + /* + * Summarize. + */ + vstream_printf("%s:%s -> %s\n", + cmd, addr_field, have_result ? + STR(addr_buf) : maps->error ? + "(error)" : "(no match)"); + vstream_fflush(VSTREAM_OUT); + + /* + * Enforce expectations. + */ + if (res_field && have_result) { + if (strcmp(res_field, STR(addr_buf)) != 0) { + msg_warn("expect result '%s' but got '%s'", + res_field, STR(addr_buf)); + errs = 1; + } + } else if (res_field && !have_result) { + msg_warn("expect result '%s' but got none", res_field); + errs = 1; + } else if (!res_field && have_result) { + msg_warn("expected no result but got '%s'", STR(addr_buf)); + errs = 1; + } + vstream_fflush(VSTREAM_OUT); + } + + /* + * Unknown request. + */ + else { + msg_fatal("bad request: %s", cmd); + } + } + vstring_free(addr_buf); + vstring_free(read_buf); + maps_free(maps); + return (errs != 0); +} + +#endif diff --git a/src/smtp/smtp_map11.in b/src/smtp/smtp_map11.in new file mode 100644 index 0000000..242859f --- /dev/null +++ b/src/smtp/smtp_map11.in @@ -0,0 +1,33 @@ +# Table with external-form mapping. +maps inline:{ + { foo@example.com = bar@com.example } + { bar@example.com = bar } + { baz@example.com = @com.example } + { splitme@example.com = "split me"@com.example } } + +# Tests for external form. +external foo@example.com:bar@com.example +external bar@example.com:bar@localdomain +external baz@example.com:baz@com.example +external foo@example.net +external splitme@example.com:"split me"@com.example +external splitme+ext@example.com:"split me+ext"@com.example +external "baz+first last"@example.com:"baz+first last"@com.example + +# Same tests for tree form. +tree foo@example.com:bar@com.example +tree bar@example.com:bar@localdomain +tree baz@example.com:baz@com.example +tree foo@example.net +tree splitme@example.com:"split me"@com.example +tree splitme+ext@example.com:"split me+ext"@com.example +tree "baz+first last"@example.com:"baz+first last"@com.example + +# Same tests for internal form. +internal foo@example.com:bar@com.example +internal bar@example.com:bar@localdomain +internal baz@example.com:baz@com.example +internal foo@example.net +internal splitme@example.com:split me@com.example +internal splitme+ext@example.com:split me+ext@com.example +internal baz+first last@example.com:baz+first last@com.example diff --git a/src/smtp/smtp_map11.ref b/src/smtp/smtp_map11.ref new file mode 100644 index 0000000..7e6f2ed --- /dev/null +++ b/src/smtp/smtp_map11.ref @@ -0,0 +1,22 @@ +inline:{ { foo@example.com = bar@com.example } { bar@example.com = bar } { baz@example.com = @com.example } { splitme@example.com = "split me"@com.example } } +external:foo@example.com -> bar@com.example +external:bar@example.com -> bar@localdomain +external:baz@example.com -> baz@com.example +external:foo@example.net -> (no match) +external:splitme@example.com -> "split me"@com.example +external:splitme+ext@example.com -> "split me+ext"@com.example +external:"baz+first last"@example.com -> "baz+first last"@com.example +tree:foo@example.com -> bar@com.example +tree:bar@example.com -> bar@localdomain +tree:baz@example.com -> baz@com.example +tree:foo@example.net -> (no match) +tree:splitme@example.com -> "split me"@com.example +tree:splitme+ext@example.com -> "split me+ext"@com.example +tree:"baz+first last"@example.com -> "baz+first last"@com.example +internal:foo@example.com -> bar@com.example +internal:bar@example.com -> bar@localdomain +internal:baz@example.com -> baz@com.example +internal:foo@example.net -> (no match) +internal:splitme@example.com -> split me@com.example +internal:splitme+ext@example.com -> split me+ext@com.example +internal:baz+first last@example.com -> baz+first last@com.example diff --git a/src/smtp/smtp_misc.c b/src/smtp/smtp_misc.c new file mode 100644 index 0000000..43a176f --- /dev/null +++ b/src/smtp/smtp_misc.c @@ -0,0 +1,100 @@ +/*++ +/* NAME +/* smtp_misc 3 +/* SUMMARY +/* SMTP client address rewriting +/* SYNOPSIS +/* #include +/* +/* void smtp_rewrite_generic_internal( +/* VSTRING *dst, +/* const char *src); +/* +/* void smtp_quote_822_address_flags( +/* VSTRING *dst, +/* const char *src, +/* int flags); +/* +/* void smtp_quote_821_address( +/* VSTRING *dst, +/* const char *src); +/* DESCRIPTION +/* smtp_rewrite_generic_internal() rewrites a non-empty address +/* if generic mapping is enabled, otherwise copies it literally. +/* +/* smtp_quote_822_address_flags() is a wrapper around +/* quote_822_local_flags(), except for the empty address which +/* is copied literally. +/* +/* smtp_quote_821_address() is a wrapper around quote_821_local(), +/* except for the empty address or with "smtp_quote_rfc821_envelope +/* = no"; in those cases the address is copied literally. +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include +#include +#include +#include + + /* + * Application-specific. + */ +#include + +/* smtp_rewrite_generic_internal - generic non-empty address rewriting */ + +void smtp_rewrite_generic_internal(VSTRING *dst, const char *src) +{ + vstring_strcpy(dst, src); + if (*src && smtp_generic_maps) + smtp_map11_internal(dst, smtp_generic_maps, + smtp_ext_prop_mask & EXT_PROP_GENERIC); +} + +/* smtp_quote_822_address_flags - quote non-empty header address */ + +void smtp_quote_822_address_flags(VSTRING *dst, const char *src, int flags) +{ + if (*src) { + quote_822_local_flags(dst, src, flags); + } else if (flags & QUOTE_FLAG_APPEND) { + vstring_strcat(dst, src); + } else { + vstring_strcpy(dst, src); + } +} + +/* smtp_quote_821_address - quote non-empty envelope address */ + +void smtp_quote_821_address(VSTRING *dst, const char *src) +{ + if (*src && var_smtp_quote_821_env) { + quote_821_local(dst, src); + } else { + vstring_strcpy(dst, src); + } +} diff --git a/src/smtp/smtp_params.c b/src/smtp/smtp_params.c new file mode 100644 index 0000000..cd54f8f --- /dev/null +++ b/src/smtp/smtp_params.c @@ -0,0 +1,140 @@ + static const CONFIG_STR_TABLE smtp_str_table[] = { + VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, + VAR_SMTP_FALLBACK, DEF_SMTP_FALLBACK, &var_fallback_relay, 0, 0, + VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0, + VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, + VAR_SMTP_SASL_PASSWD, DEF_SMTP_SASL_PASSWD, &var_smtp_sasl_passwd, 0, 0, + VAR_SMTP_SASL_OPTS, DEF_SMTP_SASL_OPTS, &var_smtp_sasl_opts, 0, 0, + VAR_SMTP_SASL_PATH, DEF_SMTP_SASL_PATH, &var_smtp_sasl_path, 0, 0, +#ifdef USE_TLS + VAR_SMTP_SASL_TLS_OPTS, DEF_SMTP_SASL_TLS_OPTS, &var_smtp_sasl_tls_opts, 0, 0, + VAR_SMTP_SASL_TLSV_OPTS, DEF_SMTP_SASL_TLSV_OPTS, &var_smtp_sasl_tlsv_opts, 0, 0, + VAR_SMTP_TLS_CHAIN_FILES, DEF_SMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0, + VAR_SMTP_TLS_CERT_FILE, DEF_SMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0, + VAR_SMTP_TLS_KEY_FILE, DEF_SMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0, + VAR_SMTP_TLS_DCERT_FILE, DEF_SMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0, + VAR_SMTP_TLS_DKEY_FILE, DEF_SMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0, + VAR_SMTP_TLS_CA_FILE, DEF_SMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0, + VAR_SMTP_TLS_CA_PATH, DEF_SMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0, + VAR_SMTP_TLS_MAND_CIPH, DEF_SMTP_TLS_MAND_CIPH, &var_smtp_tls_mand_ciph, 1, 0, + VAR_SMTP_TLS_EXCL_CIPH, DEF_SMTP_TLS_EXCL_CIPH, &var_smtp_tls_excl_ciph, 0, 0, + VAR_SMTP_TLS_MAND_EXCL, DEF_SMTP_TLS_MAND_EXCL, &var_smtp_tls_mand_excl, 0, 0, + VAR_SMTP_TLS_MAND_PROTO, DEF_SMTP_TLS_MAND_PROTO, &var_smtp_tls_mand_proto, 0, 0, + VAR_SMTP_TLS_VFY_CMATCH, DEF_SMTP_TLS_VFY_CMATCH, &var_smtp_tls_vfy_cmatch, 1, 0, + VAR_SMTP_TLS_SEC_CMATCH, DEF_SMTP_TLS_SEC_CMATCH, &var_smtp_tls_sec_cmatch, 1, 0, + VAR_SMTP_TLS_FPT_CMATCH, DEF_SMTP_TLS_FPT_CMATCH, &var_smtp_tls_fpt_cmatch, 0, 0, + VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, + VAR_SMTP_TLS_TAFILE, DEF_SMTP_TLS_TAFILE, &var_smtp_tls_tafile, 0, 0, + VAR_SMTP_TLS_PROTO, DEF_SMTP_TLS_PROTO, &var_smtp_tls_proto, 0, 0, + VAR_SMTP_TLS_CIPH, DEF_SMTP_TLS_CIPH, &var_smtp_tls_ciph, 1, 0, + VAR_SMTP_TLS_ECCERT_FILE, DEF_SMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0, + VAR_SMTP_TLS_ECKEY_FILE, DEF_SMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0, + VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_SMTP_TLS_SNI, DEF_SMTP_TLS_SNI, &var_smtp_tls_sni, 0, 0, + VAR_SMTP_TLS_INSECURE_MX_POLICY, DEF_SMTP_TLS_INSECURE_MX_POLICY, &var_smtp_tls_insecure_mx_policy, 0, 0, +#endif + VAR_SMTP_SASL_MECHS, DEF_SMTP_SASL_MECHS, &var_smtp_sasl_mechs, 0, 0, + VAR_SMTP_SASL_TYPE, DEF_SMTP_SASL_TYPE, &var_smtp_sasl_type, 1, 0, + VAR_SMTP_BIND_ADDR, DEF_SMTP_BIND_ADDR, &var_smtp_bind_addr, 0, 0, + VAR_SMTP_BIND_ADDR6, DEF_SMTP_BIND_ADDR6, &var_smtp_bind_addr6, 0, 0, + VAR_SMTP_VRFY_TGT, DEF_SMTP_VRFY_TGT, &var_smtp_vrfy_tgt, 1, 0, + VAR_SMTP_HELO_NAME, DEF_SMTP_HELO_NAME, &var_smtp_helo_name, 1, 0, + VAR_SMTP_HOST_LOOKUP, DEF_SMTP_HOST_LOOKUP, &var_smtp_host_lookup, 1, 0, + VAR_SMTP_DNS_SUPPORT, DEF_SMTP_DNS_SUPPORT, &var_smtp_dns_support, 0, 0, + VAR_SMTP_CACHE_DEST, DEF_SMTP_CACHE_DEST, &var_smtp_cache_dest, 0, 0, + VAR_SCACHE_SERVICE, DEF_SCACHE_SERVICE, &var_scache_service, 1, 0, + VAR_SMTP_EHLO_DIS_WORDS, DEF_SMTP_EHLO_DIS_WORDS, &var_smtp_ehlo_dis_words, 0, 0, + VAR_SMTP_EHLO_DIS_MAPS, DEF_SMTP_EHLO_DIS_MAPS, &var_smtp_ehlo_dis_maps, 0, 0, + VAR_SMTP_TLS_PER_SITE, DEF_SMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0, + VAR_SMTP_TLS_LEVEL, DEF_SMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0, + VAR_SMTP_TLS_POLICY, DEF_SMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0, + VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0, + VAR_SMTP_GENERIC_MAPS, DEF_SMTP_GENERIC_MAPS, &var_smtp_generic_maps, 0, 0, + VAR_SMTP_TCP_PORT, DEF_SMTP_TCP_PORT, &var_smtp_tcp_port, 0, 0, + VAR_SMTP_PIX_BUG_WORDS, DEF_SMTP_PIX_BUG_WORDS, &var_smtp_pix_bug_words, 0, 0, + VAR_SMTP_PIX_BUG_MAPS, DEF_SMTP_PIX_BUG_MAPS, &var_smtp_pix_bug_maps, 0, 0, + VAR_SMTP_SASL_AUTH_CACHE_NAME, DEF_SMTP_SASL_AUTH_CACHE_NAME, &var_smtp_sasl_auth_cache_name, 0, 0, + VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0, + VAR_SMTP_HEAD_CHKS, DEF_SMTP_HEAD_CHKS, &var_smtp_head_chks, 0, 0, + VAR_SMTP_MIME_CHKS, DEF_SMTP_MIME_CHKS, &var_smtp_mime_chks, 0, 0, + VAR_SMTP_NEST_CHKS, DEF_SMTP_NEST_CHKS, &var_smtp_nest_chks, 0, 0, + VAR_SMTP_BODY_CHKS, DEF_SMTP_BODY_CHKS, &var_smtp_body_chks, 0, 0, + VAR_SMTP_RESP_FILTER, DEF_SMTP_RESP_FILTER, &var_smtp_resp_filter, 0, 0, + VAR_SMTP_ADDR_PREF, DEF_SMTP_ADDR_PREF, &var_smtp_addr_pref, 1, 0, + VAR_SMTP_DNS_RES_OPT, DEF_SMTP_DNS_RES_OPT, &var_smtp_dns_res_opt, 0, 0, + VAR_SMTP_DSN_FILTER, DEF_SMTP_DSN_FILTER, &var_smtp_dsn_filter, 0, 0, + VAR_SMTP_DNS_RE_FILTER, DEF_SMTP_DNS_RE_FILTER, &var_smtp_dns_re_filter, 0, 0, + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, + VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0, + 0, + }; + static const CONFIG_TIME_TABLE smtp_time_table[] = { + VAR_SMTP_CONN_TMOUT, DEF_SMTP_CONN_TMOUT, &var_smtp_conn_tmout, 0, 0, + VAR_SMTP_HELO_TMOUT, DEF_SMTP_HELO_TMOUT, &var_smtp_helo_tmout, 1, 0, + VAR_SMTP_XFWD_TMOUT, DEF_SMTP_XFWD_TMOUT, &var_smtp_xfwd_tmout, 1, 0, + VAR_SMTP_MAIL_TMOUT, DEF_SMTP_MAIL_TMOUT, &var_smtp_mail_tmout, 1, 0, + VAR_SMTP_RCPT_TMOUT, DEF_SMTP_RCPT_TMOUT, &var_smtp_rcpt_tmout, 1, 0, + VAR_SMTP_DATA0_TMOUT, DEF_SMTP_DATA0_TMOUT, &var_smtp_data0_tmout, 1, 0, + VAR_SMTP_DATA1_TMOUT, DEF_SMTP_DATA1_TMOUT, &var_smtp_data1_tmout, 1, 0, + VAR_SMTP_DATA2_TMOUT, DEF_SMTP_DATA2_TMOUT, &var_smtp_data2_tmout, 1, 0, + VAR_SMTP_RSET_TMOUT, DEF_SMTP_RSET_TMOUT, &var_smtp_rset_tmout, 1, 0, + VAR_SMTP_QUIT_TMOUT, DEF_SMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0, + VAR_SMTP_PIX_THRESH, DEF_SMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0, + VAR_SMTP_PIX_DELAY, DEF_SMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0, + VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0, + VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0, + VAR_SMTP_CACHE_CONNT, DEF_SMTP_CACHE_CONNT, &var_smtp_cache_conn, 1, 0, + VAR_SMTP_REUSE_TIME, DEF_SMTP_REUSE_TIME, &var_smtp_reuse_time, 1, 0, +#ifdef USE_TLS + VAR_SMTP_STARTTLS_TMOUT, DEF_SMTP_STARTTLS_TMOUT, &var_smtp_starttls_tmout, 1, 0, +#endif + VAR_SCACHE_PROTO_TMOUT, DEF_SCACHE_PROTO_TMOUT, &var_scache_proto_tmout, 1, 0, + VAR_SMTP_SASL_AUTH_CACHE_TIME, DEF_SMTP_SASL_AUTH_CACHE_TIME, &var_smtp_sasl_auth_cache_time, 0, 0, + 0, + }; + static const CONFIG_INT_TABLE smtp_int_table[] = { + VAR_SMTP_LINE_LIMIT, DEF_SMTP_LINE_LIMIT, &var_smtp_line_limit, 0, 0, + VAR_SMTP_MXADDR_LIMIT, DEF_SMTP_MXADDR_LIMIT, &var_smtp_mxaddr_limit, 0, 0, + VAR_SMTP_MXSESS_LIMIT, DEF_SMTP_MXSESS_LIMIT, &var_smtp_mxsess_limit, 0, 0, + VAR_SMTP_REUSE_COUNT, DEF_SMTP_REUSE_COUNT, &var_smtp_reuse_count, 0, 0, +#ifdef USE_TLS + VAR_SMTP_TLS_SCERT_VD, DEF_SMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0, +#endif + VAR_SMTP_MIN_DATA_RATE, DEF_SMTP_MIN_DATA_RATE, &var_smtp_min_data_rate, 1, 0, + 0, + }; + static const CONFIG_BOOL_TABLE smtp_bool_table[] = { + VAR_SMTP_SKIP_5XX, DEF_SMTP_SKIP_5XX, &var_smtp_skip_5xx_greeting, + VAR_IGN_MX_LOOKUP_ERR, DEF_IGN_MX_LOOKUP_ERR, &var_ign_mx_lookup_err, + VAR_SMTP_SKIP_QUIT_RESP, DEF_SMTP_SKIP_QUIT_RESP, &var_skip_quit_resp, + VAR_SMTP_ALWAYS_EHLO, DEF_SMTP_ALWAYS_EHLO, &var_smtp_always_ehlo, + VAR_SMTP_NEVER_EHLO, DEF_SMTP_NEVER_EHLO, &var_smtp_never_ehlo, + VAR_SMTP_SASL_ENABLE, DEF_SMTP_SASL_ENABLE, &var_smtp_sasl_enable, + VAR_SMTP_RAND_ADDR, DEF_SMTP_RAND_ADDR, &var_smtp_rand_addr, + VAR_SMTP_QUOTE_821_ENV, DEF_SMTP_QUOTE_821_ENV, &var_smtp_quote_821_env, + VAR_SMTP_DEFER_MXADDR, DEF_SMTP_DEFER_MXADDR, &var_smtp_defer_mxaddr, + VAR_SMTP_SEND_XFORWARD, DEF_SMTP_SEND_XFORWARD, &var_smtp_send_xforward, + VAR_SMTP_CACHE_DEMAND, DEF_SMTP_CACHE_DEMAND, &var_smtp_cache_demand, + VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls, + VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + VAR_SMTP_TLS_CONN_REUSE, DEF_SMTP_TLS_CONN_REUSE, &var_smtp_tls_conn_reuse, +#ifdef USE_TLS + VAR_SMTP_TLS_ENFORCE_PN, DEF_SMTP_TLS_ENFORCE_PN, &var_smtp_tls_enforce_peername, + VAR_SMTP_TLS_NOTEOFFER, DEF_SMTP_TLS_NOTEOFFER, &var_smtp_tls_note_starttls_offer, + VAR_SMTP_TLS_BLK_EARLY_MAIL_REPLY, DEF_SMTP_TLS_BLK_EARLY_MAIL_REPLY, &var_smtp_tls_blk_early_mail_reply, + VAR_SMTP_TLS_FORCE_TLSA, DEF_SMTP_TLS_FORCE_TLSA, &var_smtp_tls_force_tlsa, +#endif + VAR_SMTP_TLS_WRAPPER, DEF_SMTP_TLS_WRAPPER, &var_smtp_tls_wrappermode, + VAR_SMTP_SENDER_AUTH, DEF_SMTP_SENDER_AUTH, &var_smtp_sender_auth, + VAR_SMTP_CNAME_OVERR, DEF_SMTP_CNAME_OVERR, &var_smtp_cname_overr, + VAR_SMTP_SASL_AUTH_SOFT_BOUNCE, DEF_SMTP_SASL_AUTH_SOFT_BOUNCE, &var_smtp_sasl_auth_soft_bounce, + VAR_LMTP_ASSUME_FINAL, DEF_LMTP_ASSUME_FINAL, &var_lmtp_assume_final, + VAR_SMTP_DUMMY_MAIL_AUTH, DEF_SMTP_DUMMY_MAIL_AUTH, &var_smtp_dummy_mail_auth, + VAR_SMTP_BALANCE_INET_PROTO, DEF_SMTP_BALANCE_INET_PROTO, &var_smtp_balance_inet_proto, + VAR_SMTP_BIND_ADDR_ENFORCE, DEF_SMTP_BIND_ADDR_ENFORCE, &var_smtp_bind_addr_enforce, + 0, + }; + static const CONFIG_NBOOL_TABLE smtp_nbool_table[] = { + VAR_SMTP_REQ_DEADLINE, DEF_SMTP_REQ_DEADLINE, &var_smtp_req_deadline, + 0, + }; diff --git a/src/smtp/smtp_proto.c b/src/smtp/smtp_proto.c new file mode 100644 index 0000000..2ceb0f3 --- /dev/null +++ b/src/smtp/smtp_proto.c @@ -0,0 +1,2515 @@ +/*++ +/* NAME +/* smtp_proto 3 +/* SUMMARY +/* client SMTP/LMTP protocol +/* SYNOPSIS +/* #include "smtp.h" +/* +/* int smtp_helo(state) +/* SMTP_STATE *state; +/* +/* int smtp_xfer(state) +/* SMTP_STATE *state; +/* +/* int smtp_rset(state) +/* SMTP_STATE *state; +/* +/* int smtp_quit(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* In the subsequent text, SMTP implies LMTP. +/* This module implements the client side of the SMTP protocol. +/* +/* smtp_helo() performs the initial handshake with the SMTP server. +/* When TLS is enabled, this includes STARTTLS negotiations. +/* +/* smtp_xfer() sends message envelope information followed by the +/* message data, and finishes the SMTP conversation. These operations +/* are combined in one function, in order to implement SMTP pipelining. +/* Recipients are marked as "done" in the mail queue file when +/* bounced or delivered. The message delivery status is updated +/* accordingly. +/* +/* smtp_rset() sends a single RSET command and waits for the +/* response. In case of a negative reply it sets the +/* CANT_RSET_THIS_SESSION flag. +/* +/* smtp_quit() sends a single QUIT command and waits for the +/* response if configured to do so. It always turns off connection +/* caching. +/* DIAGNOSTICS +/* smtp_helo(), smtp_xfer(), smtp_rset() and smtp_quit() return +/* 0 in case of success, -1 in case of failure. For smtp_xfer(), +/* smtp_rset() and smtp_quit(), success means the ability to +/* perform an SMTP conversation, not necessarily the ability +/* to deliver mail, or the achievement of server happiness. +/* +/* In case of a rejected or failed connection, a connection +/* is marked as "bad, do not cache". Otherwise, connection +/* caching may be turned off (without being marked "bad") at +/* the discretion of the code that implements the individual +/* protocol steps. +/* +/* Warnings: corrupt message file. A corrupt message is marked +/* as "corrupt" by changing its queue file permissions. +/* BUGS +/* Some SMTP servers will abort when the number of recipients +/* for one message exceeds their capacity. This behavior violates +/* the SMTP protocol. +/* The only way around this is to limit the number of recipients +/* per transaction to an artificially-low value. +/* SEE ALSO +/* smtp(3h) internal data structures +/* smtp_chat(3) query/reply SMTP support +/* smtp_trouble(3) error handlers +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Pipelining code in cooperation with: +/* Jon Ribbens +/* Oaktree Internet Solutions Ltd., +/* Internet House, +/* Canal Basin, +/* Coventry, +/* CV1 4LY, United Kingdom. +/* +/* Connection caching in cooperation with: +/* Victor Duchovni +/* Morgan Stanley +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ + +/* System library. */ + +#include +#include +#include /* shutdown(2) */ +#include /* ntohs() */ +#include +#include +#include /* 44BSD stdarg.h uses abort() */ +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + + /* + * Sender and receiver state. A session does not necessarily go through a + * linear progression, but states are guaranteed to not jump backwards. + * Normal sessions go from MAIL->RCPT->DATA->DOT->QUIT->LAST. The states + * MAIL, RCPT, and DATA may also be followed by ABORT->QUIT->LAST. + * + * When connection caching is enabled, the QUIT state is suppressed. Normal + * sessions proceed as MAIL->RCPT->DATA->DOT->LAST, while aborted sessions + * end with ABORT->LAST. The connection is left open for a limited time. An + * RSET probe should be sent before attempting to reuse an open connection + * for a new transaction. + * + * The code to send an RSET probe is a special case with its own initial state + * and with its own dedicated state transitions. The session proceeds as + * RSET->LAST. This code is kept inside the main protocol engine for + * consistent error handling and error reporting. It is not to be confused + * with the code that sends RSET to abort a mail transaction in progress. + * + * The code to send QUIT without message delivery transaction jumps into the + * main state machine. If this introduces complications, then we should + * introduce a second QUIT state with its own dedicated state transitions, + * just like we did for RSET probes. + * + * By default, the receiver skips the QUIT response. Some SMTP servers + * disconnect after responding to ".", and some SMTP servers wait before + * responding to QUIT. + * + * Client states that are associated with sending mail (up to and including + * SMTP_STATE_DOT) must have smaller numerical values than the non-sending + * states (SMTP_STATE_ABORT .. SMTP_STATE_LAST). + */ +#define SMTP_STATE_XFORWARD_NAME_ADDR 0 +#define SMTP_STATE_XFORWARD_PROTO_HELO 1 +#define SMTP_STATE_MAIL 2 +#define SMTP_STATE_RCPT 3 +#define SMTP_STATE_DATA 4 +#define SMTP_STATE_DOT 5 +#define SMTP_STATE_ABORT 6 +#define SMTP_STATE_RSET 7 +#define SMTP_STATE_QUIT 8 +#define SMTP_STATE_LAST 9 + +int *xfer_timeouts[SMTP_STATE_LAST] = { + &var_smtp_xfwd_tmout, /* name/addr */ + &var_smtp_xfwd_tmout, /* helo/proto */ + &var_smtp_mail_tmout, + &var_smtp_rcpt_tmout, + &var_smtp_data0_tmout, + &var_smtp_data2_tmout, + &var_smtp_rset_tmout, + &var_smtp_rset_tmout, + &var_smtp_quit_tmout, +}; + +char *xfer_states[SMTP_STATE_LAST] = { + "sending XFORWARD name/address", + "sending XFORWARD protocol/helo_name", + "sending MAIL FROM", + "sending RCPT TO", + "sending DATA command", + "sending end of data -- message may be sent more than once", + "sending final RSET", + "sending RSET probe", + "sending QUIT", +}; + +char *xfer_request[SMTP_STATE_LAST] = { + "XFORWARD name/address command", + "XFORWARD helo/protocol command", + "MAIL FROM command", + "RCPT TO command", + "DATA command", + "end of DATA command", + "final RSET command", + "RSET probe", + "QUIT command", +}; + + /* + * Note: MIME downgrade never happens for mail that must be delivered with + * SMTPUTF8 (the sender requested SMTPUTF8, AND the delivery request + * involves at least one UTF-8 envelope address or header value. + */ +#define SMTP_MIME_DOWNGRADE(session, request) \ + (var_disable_mime_oconv == 0 \ + && (session->features & SMTP_FEATURE_8BITMIME) == 0 \ + && strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) != 0) + +#ifdef USE_TLS + +static int smtp_start_tls(SMTP_STATE *); + +#endif + + /* + * Call-back information for header/body checks. We don't provide call-backs + * for actions that change the message delivery time or destination. + */ +static void smtp_hbc_logger(void *, const char *, const char *, const char *, const char *); +static void smtp_text_out(void *, int, const char *, ssize_t, off_t); + +HBC_CALL_BACKS smtp_hbc_callbacks[1] = { + smtp_hbc_logger, + smtp_text_out, +}; + +static int smtp_vrfy_tgt; + +/* smtp_vrfy_init - initialize */ + +void smtp_vrfy_init(void) +{ + static const NAME_CODE vrfy_init_table[] = { + SMTP_VRFY_TGT_RCPT, SMTP_STATE_RCPT, + SMTP_VRFY_TGT_DATA, SMTP_STATE_DATA, + 0, + }; + + if ((smtp_vrfy_tgt = name_code(vrfy_init_table, NAME_CODE_FLAG_NONE, + var_smtp_vrfy_tgt)) == 0) + msg_fatal("bad protocol stage: \"%s = %s\"", + VAR_SMTP_VRFY_TGT, var_smtp_vrfy_tgt); +} + +/* smtp_helo - perform initial handshake with SMTP server */ + +int smtp_helo(SMTP_STATE *state) +{ + const char *myname = "smtp_helo"; + SMTP_SESSION *session = state->session; + DELIVER_REQUEST *request = state->request; + SMTP_ITERATOR *iter = state->iterator; + SMTP_RESP *resp; + SMTP_RESP fake; + int except; + char *lines; + char *words; + char *word; + int n; + static const NAME_CODE xforward_features[] = { + XFORWARD_NAME, SMTP_FEATURE_XFORWARD_NAME, + XFORWARD_ADDR, SMTP_FEATURE_XFORWARD_ADDR, + XFORWARD_PORT, SMTP_FEATURE_XFORWARD_PORT, + XFORWARD_PROTO, SMTP_FEATURE_XFORWARD_PROTO, + XFORWARD_HELO, SMTP_FEATURE_XFORWARD_HELO, + XFORWARD_IDENT, SMTP_FEATURE_XFORWARD_IDENT, + XFORWARD_DOMAIN, SMTP_FEATURE_XFORWARD_DOMAIN, + 0, 0, + }; + const char *ehlo_words; + int discard_mask; + static const NAME_MASK pix_bug_table[] = { + PIX_BUG_DISABLE_ESMTP, SMTP_FEATURE_PIX_NO_ESMTP, + PIX_BUG_DELAY_DOTCRLF, SMTP_FEATURE_PIX_DELAY_DOTCRLF, + 0, + }; + const char *pix_bug_words; + const char *pix_bug_source; + int pix_bug_mask; + +#ifdef USE_TLS + int saved_features = session->features; + int tls_helo_status; + +#endif + const char *NOCLOBBER where; + + /* + * Skip the plaintext SMTP handshake when connecting in SMTPS mode. + */ +#ifdef USE_TLS + if (var_smtp_tls_wrappermode + && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { + /* XXX Mix-up of per-session and per-request flags. */ + state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS; + smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout, + var_smtp_req_deadline, 0); + tls_helo_status = smtp_start_tls(state); + state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS; + return (tls_helo_status); + } +#endif + + /* + * Prepare for disaster. + */ + smtp_stream_setup(state->session->stream, var_smtp_helo_tmout, + var_smtp_req_deadline, 0); + if ((except = vstream_setjmp(state->session->stream)) != 0) + return (smtp_stream_except(state, except, where)); + + /* + * If not recursing after STARTTLS, examine the server greeting banner + * and decide if we are going to send EHLO as the next command. + */ + if (var_smtp_tls_wrappermode + || (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { + + /* + * Read and parse the server's SMTP greeting banner. + */ + where = "receiving the initial server greeting"; + switch ((resp = smtp_chat_resp(session))->code / 100) { + case 2: + break; + case 5: + if (var_smtp_skip_5xx_greeting) + STR(resp->dsn_buf)[0] = '4'; + /* FALLTHROUGH */ + default: + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + } + + /* + * If the policy table specifies a bogus TLS security level, fail + * now. + */ +#ifdef USE_TLS + if (state->tls->level == TLS_LEV_INVALID) + /* Warning is already logged. */ + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.0"), + "client TLS configuration problem")); +#endif + + /* + * XXX Some PIX firewall versions require flush before "." so + * it does not span a packet boundary. This hurts performance so it + * is not on by default. + */ + if (resp->str[strspn(resp->str, "20 *\t\n")] == 0) { + /* Best effort only. Ignore errors. */ + if (smtp_pix_bug_maps != 0 + && (pix_bug_words = + maps_find(smtp_pix_bug_maps, + STR(iter->addr), 0)) != 0) { + pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_MAPS); + } else { + pix_bug_words = var_smtp_pix_bug_words; + pix_bug_source = VAR_LMTP_SMTP(PIX_BUG_WORDS); + } + if (*pix_bug_words) { + pix_bug_mask = name_mask_opt(pix_bug_source, pix_bug_table, + pix_bug_words, + NAME_MASK_ANY_CASE | NAME_MASK_IGNORE); + if ((pix_bug_mask & SMTP_FEATURE_PIX_DELAY_DOTCRLF) + && request->msg_stats.incoming_arrival.tv_sec + > vstream_ftime(state->session->stream) - var_smtp_pix_thresh) + pix_bug_mask &= ~SMTP_FEATURE_PIX_DELAY_DOTCRLF; + msg_info("%s: enabling PIX workarounds: %s for %s", + request->queue_id, + str_name_mask("pix workaround bitmask", + pix_bug_table, pix_bug_mask), + session->namaddrport); + session->features |= pix_bug_mask; + } + } + + /* + * See if we are talking to ourself. This should not be possible with + * the way we implement DNS lookups. However, people are known to + * sometimes screw up the naming service. And, mailer loops are still + * possible when our own mailer routing tables are mis-configured. + */ + words = resp->str; + (void) mystrtok(&words, "- \t\n"); + for (n = 0; (word = mystrtok(&words, " \t\n")) != 0; n++) { + if (n == 0 && strcasecmp(word, var_myhostname) == 0) { + if (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) + msg_warn("host %s greeted me with my own hostname %s", + session->namaddrport, var_myhostname); + } else if (strcasecmp(word, "ESMTP") == 0) + session->features |= SMTP_FEATURE_ESMTP; + } + if (smtp_mode) { + if (var_smtp_always_ehlo + && (session->features & SMTP_FEATURE_PIX_NO_ESMTP) == 0) + session->features |= SMTP_FEATURE_ESMTP; + if (var_smtp_never_ehlo + || (session->features & SMTP_FEATURE_PIX_NO_ESMTP) != 0) + session->features &= ~SMTP_FEATURE_ESMTP; + } else { + session->features |= SMTP_FEATURE_ESMTP; + } + } + + /* + * If recursing after STARTTLS, there is no server greeting banner. + * Always send EHLO as the next command. + */ + else { + session->features |= SMTP_FEATURE_ESMTP; + } + + /* + * Return the compliment. Fall back to SMTP if our ESMTP recognition + * heuristic failed. + */ + if (smtp_mode) { + where = "performing the EHLO handshake"; + if (session->features & SMTP_FEATURE_ESMTP) { + smtp_chat_cmd(session, "EHLO %s", var_smtp_helo_name); + if ((resp = smtp_chat_resp(session))->code / 100 != 2) { + if (resp->code == 421) + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + else + session->features &= ~SMTP_FEATURE_ESMTP; + } + } + if ((session->features & SMTP_FEATURE_ESMTP) == 0) { + where = "performing the HELO handshake"; + smtp_chat_cmd(session, "HELO %s", var_smtp_helo_name); + if ((resp = smtp_chat_resp(session))->code / 100 != 2) + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + } + } else { + where = "performing the LHLO handshake"; + smtp_chat_cmd(session, "LHLO %s", var_smtp_helo_name); + if ((resp = smtp_chat_resp(session))->code / 100 != 2) + return (smtp_site_fail(state, STR(iter->host), resp, + "host %s refused to talk to me: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + } + + /* + * No early returns allowed, to ensure consistent handling of TLS and + * SASL policies. + */ + if (session->features & SMTP_FEATURE_ESMTP) { + + /* + * Determine what server EHLO keywords to ignore, typically to avoid + * inter-operability problems. + */ + if (smtp_ehlo_dis_maps == 0 + || (ehlo_words = maps_find(smtp_ehlo_dis_maps, + STR(iter->addr), 0)) == 0) + ehlo_words = var_smtp_ehlo_dis_words; + if (smtp_ehlo_dis_maps && smtp_ehlo_dis_maps->error) { + msg_warn("%s: %s map lookup error for %s", + session->state->request->queue_id, + smtp_ehlo_dis_maps->title, STR(iter->addr)); + vstream_longjmp(session->stream, SMTP_ERR_DATA); + } + discard_mask = ehlo_mask(ehlo_words); + if (discard_mask && !(discard_mask & EHLO_MASK_SILENT)) + msg_info("discarding EHLO keywords: %s", + str_ehlo_mask(discard_mask)); + + /* + * Pick up some useful features offered by the SMTP server. XXX Until + * we have a portable routine to convert from string to off_t with + * proper overflow detection, ignore the message size limit + * advertised by the SMTP server. Otherwise, we might do the wrong + * thing when the server advertises a really huge message size limit. + * + * XXX Allow for "code (SP|-) ehlo-keyword (SP|=) ehlo-param...", + * because MicroSoft implemented AUTH based on an old draft. + */ + lines = resp->str; + for (n = 0; (words = mystrtok(&lines, "\n")) != 0; /* see below */ ) { + if (mystrtok(&words, "- ") + && (word = mystrtok(&words, " \t=")) != 0) { + if (n == 0) { + if (session->helo != 0) + myfree(session->helo); + + /* + * XXX: Keep the original case: we don't expect a single + * SMTP server to randomly change the case of its helo + * response. If different capitalization is detected, we + * should assume disjoint TLS caches. + */ + session->helo = mystrdup(word); + if (strcasecmp(word, var_myhostname) == 0 + && (state->misc_flags & SMTP_MISC_FLAG_LOOP_DETECT) != 0) { + msg_warn("host %s replied to HELO/EHLO" + " with my own hostname %s", + session->namaddrport, var_myhostname); + if (session->features & SMTP_FEATURE_BEST_MX) + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.4.6"), + "mail for %s loops back to myself", + request->nexthop)); + else + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.4.6"), + "mail for %s loops back to myself", + request->nexthop)); + } + } else if (strcasecmp(word, "8BITMIME") == 0) { + if ((discard_mask & EHLO_MASK_8BITMIME) == 0) + session->features |= SMTP_FEATURE_8BITMIME; + } else if (strcasecmp(word, "PIPELINING") == 0) { + if ((discard_mask & EHLO_MASK_PIPELINING) == 0) + session->features |= SMTP_FEATURE_PIPELINING; + } else if (strcasecmp(word, "XFORWARD") == 0) { + if ((discard_mask & EHLO_MASK_XFORWARD) == 0) + while ((word = mystrtok(&words, " \t")) != 0) + session->features |= + name_code(xforward_features, + NAME_CODE_FLAG_NONE, word); + } else if (strcasecmp(word, "SIZE") == 0) { + if ((discard_mask & EHLO_MASK_SIZE) == 0) { + session->features |= SMTP_FEATURE_SIZE; + if ((word = mystrtok(&words, " \t")) != 0) { + if (!alldig(word)) + msg_warn("bad EHLO SIZE limit \"%s\" from %s", + word, session->namaddrport); + else + session->size_limit = off_cvt_string(word); + } + } +#ifdef USE_TLS + } else if (strcasecmp(word, "STARTTLS") == 0) { + /* Ignored later if we already sent STARTTLS. */ + if ((discard_mask & EHLO_MASK_STARTTLS) == 0) + session->features |= SMTP_FEATURE_STARTTLS; +#endif +#ifdef USE_SASL_AUTH + } else if (var_smtp_sasl_enable + && strcasecmp(word, "AUTH") == 0) { + if ((discard_mask & EHLO_MASK_AUTH) == 0) + smtp_sasl_helo_auth(session, words); +#endif + } else if (strcasecmp(word, "DSN") == 0) { + if ((discard_mask & EHLO_MASK_DSN) == 0) + session->features |= SMTP_FEATURE_DSN; + } else if (strcasecmp(word, "SMTPUTF8") == 0) { + if ((discard_mask & EHLO_MASK_SMTPUTF8) == 0) + session->features |= SMTP_FEATURE_SMTPUTF8; + } + n++; + } + } + } + if (msg_verbose) + msg_info("server features: 0x%x size %.0f", + session->features, (double) session->size_limit); + + /* + * Decide if this delivery requires SMTPUTF8 server support. + * + * For now, we require that the remote SMTP server supports SMTPUTF8 when + * the sender requested SMTPUTF8 support. + * + * XXX EAI Refine this to: the sender requested SMTPUTF8 support AND the + * delivery request involves at least one UTF-8 envelope address or + * header value. + * + * If the sender requested SMTPUTF8 support but the delivery request + * involves no UTF-8 envelope address or header value, then we could + * still deliver such mail to a non-SMTPUTF8 server, except that we must + * either uxtext-encode ORCPT parameters or not send them. We cannot + * encode the ORCPT in xtext, because legacy SMTP requires that the + * unencoded address consist entirely of printable (graphic and white + * space) characters from the US-ASCII repertoire (RFC 3461 section 4). A + * correct uxtext encoder will produce a result that an xtext decoder + * will pass through unchanged. + * + * XXX Should we try to encode headers with RFC 2047 when delivering to a + * non-SMTPUTF8 server? That could make life easier for mailing lists. + */ +#define DELIVERY_REQUIRES_SMTPUTF8 \ + ((request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) \ + && (request->smtputf8 & ~SMTPUTF8_FLAG_REQUESTED)) + + /* + * Require that the server supports SMTPUTF8 when delivery requires + * SMTPUTF8. + * + * Fix 20140706: moved this before negotiating TLS, AUTH, and so on. + */ + if ((session->features & SMTP_FEATURE_SMTPUTF8) == 0 + && DELIVERY_REQUIRES_SMTPUTF8) + return (smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.6.7"), + "SMTPUTF8 is required, " + "but was not offered by host %s", + session->namaddr)); + + /* + * Fix 20140706: don't do silly things when the remote server announces + * SMTPUTF8 but not 8BITMIME support. Our primary mission is to deliver + * mail, not to force people into compliance. + */ + if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0 + && (session->features & SMTP_FEATURE_8BITMIME) == 0) { + msg_info("host %s offers SMTPUTF8 support, but not 8BITMIME", + session->namaddr); + session->features |= SMTP_FEATURE_8BITMIME; + } + + /* + * We use SMTP command pipelining if the server said it supported it. + * Since we use blocking I/O, RFC 2197 says that we should inspect the + * TCP window size and not send more than this amount of information. + * Unfortunately this information is unavailable using the sockets + * interface. However, we *can* get the TCP send buffer size on the local + * TCP/IP stack. We should be able to fill this buffer without being + * blocked, and then the kernel will effectively do non-blocking I/O for + * us by automatically writing out the contents of its send buffer while + * we are reading in the responses. In addition to TCP buffering we have + * to be aware of application-level buffering by the vstream module, + * which is limited to a couple kbytes. + * + * XXX No need to do this before and after STARTTLS, but it's not a big deal + * if we do. + * + * XXX When TLS is turned on, the SMTP-level writes will be encapsulated as + * TLS messages. Thus, the TCP-level payload will be larger than the + * SMTP-level payload. This has implications for the PIPELINING engine. + * + * To avoid deadlock, the PIPELINING engine needs to request a TCP send + * buffer size that can hold the unacknowledged commands plus the TLS + * encapsulation overhead. + * + * The PIPELINING engine keeps the unacknowledged command size <= the + * default VSTREAM buffer size (to avoid small-write performance issues + * when the VSTREAM buffer size is at its default size). With a default + * VSTREAM buffer size of 4096 there is no reason to increase the + * unacknowledged command size as the TCP MSS increases. It's safer to + * spread the remote SMTP server's recipient processing load over time, + * than dumping a very large recipient list all at once. + * + * For TLS encapsulation overhead we make a conservative guess: take the + * current protocol overhead of ~40 bytes, double the number for future + * proofing (~80 bytes), then round up the result to the nearest power of + * 2 (128 bytes). Plus, be prepared for worst-case compression that + * expands data by 1 kbyte, so that the worst-case SMTP payload per TLS + * message becomes 15 kbytes. + */ +#define PIPELINING_BUFSIZE VSTREAM_BUFSIZE +#ifdef USE_TLS +#define TLS_WORST_PAYLOAD 16384 +#define TLS_WORST_COMP_OVERHD 1024 +#define TLS_WORST_PROTO_OVERHD 128 +#define TLS_WORST_SMTP_PAYLOAD (TLS_WORST_PAYLOAD - TLS_WORST_COMP_OVERHD) +#define TLS_WORST_TOTAL_OVERHD (TLS_WORST_COMP_OVERHD + TLS_WORST_PROTO_OVERHD) +#endif + + if (session->features & SMTP_FEATURE_PIPELINING) { + SOCKOPT_SIZE optlen; + int tcp_bufsize; + int enc_overhead = 0; + + optlen = sizeof(tcp_bufsize); + if (getsockopt(vstream_fileno(session->stream), SOL_SOCKET, + SO_SNDBUF, (char *) &tcp_bufsize, &optlen) < 0) + msg_fatal("%s: getsockopt: %m", myname); +#ifdef USE_TLS + if (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) + enc_overhead += + (1 + (PIPELINING_BUFSIZE - 1) + / TLS_WORST_SMTP_PAYLOAD) * TLS_WORST_TOTAL_OVERHD; +#endif + if (tcp_bufsize < PIPELINING_BUFSIZE + enc_overhead) { + tcp_bufsize = PIPELINING_BUFSIZE + enc_overhead; + if (setsockopt(vstream_fileno(session->stream), SOL_SOCKET, + SO_SNDBUF, (char *) &tcp_bufsize, optlen) < 0) + msg_fatal("%s: setsockopt: %m", myname); + } + if (msg_verbose) + msg_info("Using %s PIPELINING, TCP send buffer size is %d, " + "PIPELINING buffer size is %d", + smtp_mode ? "ESMTP" : "LMTP", + tcp_bufsize, PIPELINING_BUFSIZE); + } +#ifdef USE_TLS + + /* + * Skip this part if we already sent STARTTLS. + */ + if ((state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) == 0) { + + /* + * Optionally log unused STARTTLS opportunities. + */ + if ((session->features & SMTP_FEATURE_STARTTLS) && + var_smtp_tls_note_starttls_offer && + state->tls->level <= TLS_LEV_NONE) + msg_info("Host offered STARTTLS: [%s]", STR(iter->host)); + + /* + * Decide whether or not to send STARTTLS. + */ + if ((session->features & SMTP_FEATURE_STARTTLS) != 0 + && smtp_tls_ctx != 0 && state->tls->level >= TLS_LEV_MAY) { + + /* + * Prepare for disaster. + */ + smtp_stream_setup(state->session->stream, var_smtp_starttls_tmout, + var_smtp_req_deadline, 0); + if ((except = vstream_setjmp(state->session->stream)) != 0) + return (smtp_stream_except(state, except, + "receiving the STARTTLS response")); + + /* + * Send STARTTLS. Recurse when the server accepts STARTTLS, after + * resetting the SASL and EHLO features lists. + * + * Reset the SASL mechanism list to avoid spurious warnings. + * + * Use the smtp_sasl_tls_security_options feature to allow SASL + * mechanisms that may not be allowed with plain-text + * connections. + */ + smtp_chat_cmd(session, "STARTTLS"); + if ((resp = smtp_chat_resp(session))->code / 100 == 2) { +#ifdef USE_SASL_AUTH + if (session->features & SMTP_FEATURE_AUTH) + smtp_sasl_cleanup(session); +#endif + session->features = saved_features; + /* XXX Mix-up of per-session and per-request flags. */ + state->misc_flags |= SMTP_MISC_FLAG_IN_STARTTLS; + tls_helo_status = smtp_start_tls(state); + state->misc_flags &= ~SMTP_MISC_FLAG_IN_STARTTLS; + return (tls_helo_status); + } + + /* + * Give up if we must use TLS but the server rejects STARTTLS + * although support for it was announced in the EHLO response. + */ + session->features &= ~SMTP_FEATURE_STARTTLS; + if (TLS_REQUIRED(state->tls->level)) + return (smtp_site_fail(state, STR(iter->host), resp, + "TLS is required, but host %s refused to start TLS: %s", + session->namaddr, + translit(resp->str, "\n", " "))); + /* Else try to continue in plain-text mode. */ + } + + /* + * Give up if we must use TLS but can't for various reasons. + * + * 200412 Be sure to provide the default clause at the bottom of this + * block. When TLS is required we must never, ever, end up in + * plain-text mode. + */ + if (TLS_REQUIRED(state->tls->level)) { + if (!(session->features & SMTP_FEATURE_STARTTLS)) { + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.4"), + "TLS is required, but was not offered by host %s", + session->namaddr)); + } else if (smtp_tls_ctx == 0) { + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.5"), + "TLS is required, but our TLS engine is unavailable")); + } else { + msg_warn("%s: TLS is required but unavailable, don't know why", + myname); + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.0"), + "TLS is required, but unavailable")); + } + } + } +#endif +#ifdef USE_SASL_AUTH + if (var_smtp_sasl_enable && (session->features & SMTP_FEATURE_AUTH)) + return (smtp_sasl_helo_login(state)); +#endif + + return (0); +} + +#ifdef USE_TLS + +/* smtp_start_tls - turn on TLS and recurse into the HELO dialog */ + +static int smtp_start_tls(SMTP_STATE *state) +{ + SMTP_SESSION *session = state->session; + SMTP_ITERATOR *iter = state->iterator; + TLS_CLIENT_START_PROPS start_props; + VSTRING *serverid; + SMTP_RESP fake; + TLS_CLIENT_INIT_PROPS init_props; + VSTREAM *tlsproxy; + VSTRING *port_buf; + + /* + * When the TLS handshake succeeds, we can reuse a connection only if TLS + * remains turned on for the lifetime of that connection. This requires + * that the TLS library state is maintained in some proxy process, for + * example, in tlsproxy(8). We then store the proxy file handle in the + * connection cache, and reuse that file handle. + * + * Otherwise, we must turn off connection caching. We can't turn off TLS in + * one SMTP client process, save the open connection to a cache which is + * shared with all SMTP clients, migrate the connection to another SMTP + * client, and resume TLS there. When the TLS handshake fails, we can't + * reuse the SMTP connection either, because the conversation is in an + * unknown state. + */ + if (state->tls->conn_reuse == 0) + DONT_CACHE_THIS_SESSION; + + /* + * The following assumes sites that use TLS in a perverse configuration: + * multiple hosts per hostname, or even multiple hosts per IP address. + * All this without a shared TLS session cache, and they still want to + * use TLS session caching??? + * + * The TLS session cache records the trust chain verification status of + * cached sessions. Different transports may have different CAfile or + * CApath settings, perhaps to allow authenticated connections to sites + * with private CA certs without trusting said private certs for other + * sites. So we cannot assume that a trust chain valid for one transport + * is valid for another. Therefore the client session id must include + * either the transport name or the values of CAfile and CApath. We use + * the transport name. + * + * XXX: We store only one session per lookup key. Ideally the key maps + * 1-to-1 to a server TLS session cache. We use the IP address, port and + * ehlo response name to build a lookup key that works for split caches + * (that announce distinct names) behind a load balancer. + * + * XXX: The TLS library will salt the serverid with further details of the + * protocol and cipher requirements including the server ehlo response. + * Deferring the helo to the digested suffix results in more predictable + * SSL session lookup key lengths. + */ + serverid = vstring_alloc(10); + smtp_key_prefix(serverid, "&", state->iterator, SMTP_KEY_FLAG_SERVICE + | SMTP_KEY_FLAG_CUR_NEXTHOP /* With port */ + | SMTP_KEY_FLAG_HOSTNAME + | SMTP_KEY_FLAG_ADDR); + + if (state->tls->conn_reuse) { + TLS_CLIENT_PARAMS tls_params; + + /* + * Send all our wishes in one big request. + */ + TLS_PROXY_CLIENT_INIT_PROPS(&init_props, + log_param = VAR_LMTP_SMTP(TLS_LOGLEVEL), + log_level = var_smtp_tls_loglevel, + verifydepth = var_smtp_tls_scert_vd, + cache_type + = LMTP_SMTP_SUFFIX(TLS_MGR_SCACHE), + chain_files = var_smtp_tls_chain_files, + cert_file = var_smtp_tls_cert_file, + key_file = var_smtp_tls_key_file, + dcert_file = var_smtp_tls_dcert_file, + dkey_file = var_smtp_tls_dkey_file, + eccert_file = var_smtp_tls_eccert_file, + eckey_file = var_smtp_tls_eckey_file, + CAfile = var_smtp_tls_CAfile, + CApath = var_smtp_tls_CApath, + mdalg = var_smtp_tls_fpt_dgst); + TLS_PROXY_CLIENT_START_PROPS(&start_props, + timeout = var_smtp_starttls_tmout, + tls_level = state->tls->level, + nexthop = session->tls_nexthop, + host = STR(iter->host), + namaddr = session->namaddrport, + sni = state->tls->sni, + serverid = vstring_str(serverid), + helo = session->helo, + protocols = state->tls->protocols, + cipher_grade = state->tls->grade, + cipher_exclusions + = vstring_str(state->tls->exclusions), + matchargv = state->tls->matchargv, + mdalg = var_smtp_tls_fpt_dgst, + dane = state->tls->dane); + + /* + * The tlsproxy(8) server enforces timeouts that are larger than + * those specified by the tlsproxy(8) client. These timeouts are a + * safety net for the case that the tlsproxy(8) client fails to + * enforce time limits. Normally, the tlsproxy(8) client would time + * out and trigger a plaintext event in the tlsproxy(8) server, and + * cause it to tear down the session. + * + * However, the tlsproxy(8) server has no insight into the SMTP + * protocol, and therefore it cannot by itself support different + * timeouts at different SMTP protocol stages. Instead, we specify + * the largest timeout (end-of-data) and rely on the SMTP client to + * time out first, which normally results in a plaintext event in the + * tlsproxy(8) server. Unfortunately, we cannot permit plaintext + * events during the TLS handshake, so we specify a separate timeout + * for that stage (the end-of-data timeout would be unreasonably + * large anyway). + */ +#define PROXY_OPEN_FLAGS \ + (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_SEND_CONTEXT) + + port_buf = vstring_alloc(100); /* minimize fragmentation */ + vstring_sprintf(port_buf, "%d", ntohs(iter->port)); + tlsproxy = + tls_proxy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS, + session->stream, STR(iter->addr), + STR(port_buf), var_smtp_starttls_tmout, + var_smtp_data2_tmout, state->service, + tls_proxy_client_param_from_config(&tls_params), + &init_props, &start_props); + vstring_free(port_buf); + + /* + * To insert tlsproxy(8) between this process and the remote SMTP + * server, we swap the file descriptors between the tlsproxy and + * session->stream VSTREAMS, so that we don't lose all the + * user-configurable session->stream attributes (such as longjump + * buffers or timeouts). + * + * TODO: the tlsproxy RPCs should return more error detail than a "NO" + * result. OTOH, the in-process TLS engine does not return such info + * either. + * + * If the tlsproxy request fails we do not fall back to the in-process + * TLS stack. Reason: the admin enabled connection reuse to respect + * receiver policy; silently violating such policy would not be + * useful. + * + * We also don't fall back to the in-process TLS stack under low-traffic + * conditions, to avoid frustrating attempts to debug a problem with + * using the tlsproxy(8) service. + */ + if (tlsproxy == 0) { + session->tls_context = 0; + } else { + vstream_control(tlsproxy, + CA_VSTREAM_CTL_DOUBLE, + CA_VSTREAM_CTL_END); + vstream_control(session->stream, + CA_VSTREAM_CTL_SWAP_FD(tlsproxy), + CA_VSTREAM_CTL_END); + (void) vstream_fclose(tlsproxy); /* direct-to-server stream! */ + + /* + * There must not be any pending data in the stream buffers + * before we read the TLS context attributes. + */ + vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); + + /* + * After plumbing the plaintext stream, receive the TLS context + * object. For this we use the same VSTREAM buffer that we also + * use to receive subsequent SMTP commands, therefore we must be + * prepared for the possibility that the remote SMTP server + * starts talking immediately. The tlsproxy implementation sends + * the TLS context before remote content. The attribute protocol + * is robust enough that an adversary cannot insert their own TLS + * context attributes. + */ + session->tls_context = tls_proxy_context_receive(session->stream); + if (session->tls_context) { + session->features |= SMTP_FEATURE_FROM_PROXY; + tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW, + session->tls_context); + } + } + } else { /* state->tls->conn_reuse */ + + /* + * As of Postfix 2.5, tls_client_start() tries hard to always + * complete the TLS handshake. It records the verification and match + * status in the resulting TLScontext. It is now up to the + * application to abort the TLS connection if it chooses. + * + * XXX When tls_client_start() fails then we don't know what state the + * SMTP connection is in, so we give up on this connection even if we + * are not required to use TLS. + * + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + */ + session->tls_context = + TLS_CLIENT_START(&start_props, + ctx = smtp_tls_ctx, + stream = session->stream, + fd = -1, + timeout = var_smtp_starttls_tmout, + tls_level = state->tls->level, + nexthop = session->tls_nexthop, + host = STR(iter->host), + namaddr = session->namaddrport, + sni = state->tls->sni, + serverid = vstring_str(serverid), + helo = session->helo, + protocols = state->tls->protocols, + cipher_grade = state->tls->grade, + cipher_exclusions + = vstring_str(state->tls->exclusions), + matchargv = state->tls->matchargv, + mdalg = var_smtp_tls_fpt_dgst, + dane = state->tls->dane); + + /* + * At this point there must not be any pending data in the stream + * buffers. + */ + vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); + } /* state->tls->conn_reuse */ + + vstring_free(serverid); + + if (session->tls_context == 0) { + + /* + * We must avoid further I/O, the peer is in an undefined state. + */ + DONT_USE_FORBIDDEN_SESSION; + + /* + * If TLS is optional, try delivery to the same server over a + * plaintext connection. Otherwise we would defer mail forever with + * destinations that have no alternate MX host. + * + * Don't fall back to plaintext if we were willing to use SASL-over-TLS + * authentication. If the server doesn't announce SASL support over + * plaintext connections, then we don't want delivery to fail with + * "relay access denied". + * + * If TLS is opportunistic, don't throttle the destination, otherwise if + * the mail is volume is high enough we may have difficulty ever + * draining even the deferred mail, as new mail provides a constant + * stream of negative feedback. + */ + if (PLAINTEXT_FALLBACK_OK_AFTER_STARTTLS_FAILURE) + RETRY_AS_PLAINTEXT; + return (smtp_misc_fail(state, state->tls->level == TLS_LEV_MAY ? + SMTP_NOTHROTTLE : SMTP_THROTTLE, + DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.5"), + "Cannot start TLS: handshake failure")); + } + + /* + * If we are verifying the server certificate and are not happy with the + * result, abort the delivery here. We have a usable TLS session with the + * server, so no need to disable I/O, ... we can even be polite and send + * "QUIT". + * + * See src/tls/tls_level.c and src/tls/tls.h. Levels above "encrypt" require + * matching. + * + * NOTE: We use "IS_MATCHED" to satisfy policy, but "IS_SECURED" to log + * effective security. Thus "half-dane" is never "Verified" only + * "Trusted", but matching is enforced here. + * + * NOTE: When none of the TLSA records were usable, "dane" and "half-dane" + * fall back to "encrypt", updating the tls_context level accordingly, so + * we must check that here, and not state->tls->level. + */ + if (TLS_MUST_MATCH(session->tls_context->level)) + if (!TLS_CERT_IS_MATCHED(session->tls_context)) + return (smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.5"), + "Server certificate not verified")); + + /* + * At this point we have to re-negotiate the "EHLO" to reget the + * feature-list. + */ + return (smtp_helo(state)); +} + +#endif + +/* smtp_hbc_logger - logging call-back for header/body checks */ + +static void smtp_hbc_logger(void *context, const char *action, + const char *where, const char *content, + const char *text) +{ + const SMTP_STATE *state = (SMTP_STATE *) context; + + if (*text) { + msg_info("%s: %s: %s %.60s: %s", + state->request->queue_id, action, where, content, text); + } else { + msg_info("%s: %s: %s %.60s", + state->request->queue_id, action, where, content); + } +} + +/* smtp_text_out - output one header/body record */ + +static void smtp_text_out(void *context, int rec_type, + const char *text, ssize_t len, + off_t unused_offset) +{ + SMTP_STATE *state = (SMTP_STATE *) context; + SMTP_SESSION *session = state->session; + ssize_t data_left; + const char *data_start; + + /* + * Deal with an impedance mismatch between Postfix queue files (record + * length <= $message_line_length_limit) and SMTP (DATA record length <= + * $smtp_line_length_limit). The code below does a little too much work + * when the SMTP line length limit is disabled, but it avoids code + * duplication, and thus, it avoids testing and maintenance problems. + */ + data_left = len; + data_start = text; + do { + if (state->space_left == var_smtp_line_limit + && data_left > 0 && *data_start == '.') + smtp_fputc('.', session->stream); + if (ENFORCING_SIZE_LIMIT(var_smtp_line_limit) + && data_left >= state->space_left) { + smtp_fputs(data_start, state->space_left, session->stream); + data_start += state->space_left; + data_left -= state->space_left; + state->space_left = var_smtp_line_limit; + if (data_left > 0 || rec_type == REC_TYPE_CONT) { + smtp_fputc(' ', session->stream); + state->space_left -= 1; + + /* + * XXX This can insert a line break into the middle of a + * multi-byte character (not necessarily UTF-8). Note that + * multibyte characters can span queue file records, for + * example if line_length_limit == smtp_line_length_limit. + */ + if (state->logged_line_length_limit == 0) { + msg_info("%s: breaking line > %d bytes with SPACE", + state->request->queue_id, var_smtp_line_limit); + state->logged_line_length_limit = 1; + } + } + } else { + if (rec_type == REC_TYPE_CONT) { + smtp_fwrite(data_start, data_left, session->stream); + state->space_left -= data_left; + } else { + smtp_fputs(data_start, data_left, session->stream); + state->space_left = var_smtp_line_limit; + } + break; + } + } while (data_left > 0); +} + +/* smtp_format_out - output one header/body record */ + +static void PRINTFLIKE(3, 4) smtp_format_out(void *, int, const char *,...); + +static void smtp_format_out(void *context, int rec_type, const char *fmt,...) +{ + static VSTRING *vp; + va_list ap; + + if (vp == 0) + vp = vstring_alloc(100); + va_start(ap, fmt); + vstring_vsprintf(vp, fmt, ap); + va_end(ap); + smtp_text_out(context, rec_type, vstring_str(vp), VSTRING_LEN(vp), 0); +} + +/* smtp_header_out - output one message header */ + +static void smtp_header_out(void *context, int unused_header_class, + const HEADER_OPTS *unused_info, + VSTRING *buf, off_t offset) +{ + char *start = vstring_str(buf); + char *line; + char *next_line; + + /* + * This code destroys the header. We could try to avoid clobbering it, + * but we're not going to use the data any further. + */ + for (line = start; line; line = next_line) { + next_line = split_at(line, '\n'); + smtp_text_out(context, REC_TYPE_NORM, line, next_line ? + next_line - line - 1 : strlen(line), offset); + } +} + +/* smtp_header_rewrite - rewrite message header before output */ + +static void smtp_header_rewrite(void *context, int header_class, + const HEADER_OPTS *header_info, + VSTRING *buf, off_t offset) +{ + SMTP_STATE *state = (SMTP_STATE *) context; + int did_rewrite = 0; + char *line; + char *start; + char *next_line; + char *end_line; + char *result; + + /* + * Apply optional header filtering. + */ + if (smtp_header_checks) { + result = hbc_header_checks(context, smtp_header_checks, header_class, + header_info, buf, offset); + if (result == 0) + return; + if (result == HBC_CHECKS_STAT_ERROR) { + msg_warn("%s: smtp header checks lookup error", + state->request->queue_id); + vstream_longjmp(state->session->stream, SMTP_ERR_DATA); + } + if (result != STR(buf)) { + vstring_strcpy(buf, result); + myfree(result); + } + } + + /* + * Rewrite primary header addresses that match the smtp_generic_maps. The + * cleanup server already enforces that all headers have proper lengths + * and that all addresses are in proper form, so we don't have to repeat + * that. + */ + if (smtp_generic_maps && header_info && header_class == MIME_HDR_PRIMARY + && (header_info->flags & (HDR_OPT_SENDER | HDR_OPT_RECIP)) != 0) { + TOK822 *tree; + TOK822 **addr_list; + TOK822 **tpp; + + tree = tok822_parse(vstring_str(buf) + + strlen(header_info->name) + 1); + addr_list = tok822_grep(tree, TOK822_ADDR); + for (tpp = addr_list; *tpp; tpp++) + did_rewrite |= smtp_map11_tree(tpp[0], smtp_generic_maps, + smtp_ext_prop_mask & EXT_PROP_GENERIC); + if (did_rewrite) { + vstring_truncate(buf, strlen(header_info->name)); + vstring_strcat(buf, ": "); + tok822_externalize(buf, tree, TOK822_STR_HEAD); + } + myfree((void *) addr_list); + tok822_free_tree(tree); + } + + /* + * Pass through unmodified headers without reconstruction. + */ + if (did_rewrite == 0) { + smtp_header_out(context, header_class, header_info, buf, offset); + return; + } + + /* + * A rewritten address list contains one address per line. The code below + * replaces newlines by spaces, to fit as many addresses on a line as + * possible (without rearranging the order of addresses). Prepending + * white space to the beginning of lines is delegated to the output + * routine. + * + * Code derived from cleanup_fold_header(). + */ + for (line = start = vstring_str(buf); line != 0; line = next_line) { + end_line = line + strcspn(line, "\n"); + if (line > start) { + if (end_line - start < 70) { /* TAB counts as one */ + line[-1] = ' '; + } else { + start = line; + } + } + next_line = *end_line ? end_line + 1 : 0; + } + + /* + * Prepend a tab to continued header lines that went through the address + * rewriting machinery. Just like smtp_header_out(), this code destroys + * the header. We could try to avoid clobbering it, but we're not going + * to use the data any further. + * + * Code derived from cleanup_out_header(). + */ + for (line = start = vstring_str(buf); line != 0; line = next_line) { + next_line = split_at(line, '\n'); + if (line == start || IS_SPACE_TAB(*line)) { + smtp_text_out(state, REC_TYPE_NORM, line, next_line ? + next_line - line - 1 : strlen(line), offset); + } else { + smtp_format_out(state, REC_TYPE_NORM, "\t%s", line); + } + } +} + +/* smtp_body_rewrite - rewrite message body before output */ + +static void smtp_body_rewrite(void *context, int type, + const char *buf, ssize_t len, + off_t offset) +{ + SMTP_STATE *state = (SMTP_STATE *) context; + char *result; + + /* + * Apply optional body filtering. + */ + if (smtp_body_checks) { + result = hbc_body_checks(context, smtp_body_checks, buf, len, offset); + if (result == buf) { + smtp_text_out(state, type, buf, len, offset); + } else if (result == HBC_CHECKS_STAT_ERROR) { + msg_warn("%s: smtp body checks lookup error", + state->request->queue_id); + vstream_longjmp(state->session->stream, SMTP_ERR_DATA); + } else if (result != 0) { + smtp_text_out(state, type, result, strlen(result), offset); + myfree(result); + } + } +} + +/* smtp_mime_fail - MIME problem */ + +static void smtp_mime_fail(SMTP_STATE *state, int mime_errs) +{ + const MIME_STATE_DETAIL *detail; + SMTP_RESP fake; + + detail = mime_state_detail(mime_errs); + smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, detail->dsn), + "%s", detail->text); +} + +/* smtp_out_raw_or_mime - output buffer, raw output or MIME-aware */ + +static int smtp_out_raw_or_mime(SMTP_STATE *state, int rec_type, VSTRING *buf) +{ + SMTP_SESSION *session = state->session; + int mime_errs; + + if (session->mime_state == 0) { + smtp_text_out((void *) state, rec_type, vstring_str(buf), + VSTRING_LEN(buf), (off_t) 0); + } else { + mime_errs = + mime_state_update(session->mime_state, rec_type, + vstring_str(buf), VSTRING_LEN(buf)); + if (mime_errs) { + smtp_mime_fail(state, mime_errs); + return (-1); + } + } + return (0); +} + +/* smtp_out_add_header - format address header, uses session->scratch* */ + +static int smtp_out_add_header(SMTP_STATE *state, const char *label, + const char *lt, const char *addr, + const char *gt) +{ + SMTP_SESSION *session = state->session; + + smtp_rewrite_generic_internal(session->scratch2, addr); + vstring_sprintf(session->scratch, "%s: %s", label, lt); + smtp_quote_822_address_flags(session->scratch, + vstring_str(session->scratch2), + QUOTE_FLAG_DEFAULT | QUOTE_FLAG_APPEND); + vstring_strcat(session->scratch, gt); + return (smtp_out_raw_or_mime(state, REC_TYPE_NORM, session->scratch)); +} + +/* smtp_out_add_headers - output additional headers, uses session->scratch* */ + +static int smtp_out_add_headers(SMTP_STATE *state) +{ + /* Prepend headers in the same order as mail_copy.c. */ + if (smtp_cli_attr.flags & SMTP_CLI_FLAG_RETURN_PATH) + if (smtp_out_add_header(state, "Return-Path", "<", + state->request->sender, ">") < 0) + return (-1); + if (smtp_cli_attr.flags & SMTP_CLI_FLAG_ORIG_RCPT) + if (smtp_out_add_header(state, "X-Original-To", "", + state->request->rcpt_list.info->orig_addr, "") < 0) + return (-1); + if (smtp_cli_attr.flags & SMTP_CLI_FLAG_DELIVERED_TO) + if (smtp_out_add_header(state, "Delivered-To", "", + state->request->rcpt_list.info->address, "") < 0) + return (-1); + return (0); +} + +/* smtp_loop - exercise the SMTP protocol engine */ + +static int smtp_loop(SMTP_STATE *state, NOCLOBBER int send_state, + NOCLOBBER int recv_state) +{ + const char *myname = "smtp_loop"; + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + SMTP_ITERATOR *iter = state->iterator; + SMTP_RESP *resp; + RECIPIENT *rcpt; + VSTRING *next_command = vstring_alloc(100); + int *NOCLOBBER survivors = 0; + NOCLOBBER int next_state; + NOCLOBBER int next_rcpt; + NOCLOBBER int send_rcpt; + NOCLOBBER int recv_rcpt; + NOCLOBBER int nrcpt; + NOCLOBBER int recv_done; + int except; + int rec_type; + NOCLOBBER int prev_type = 0; + NOCLOBBER int mail_from_rejected; + NOCLOBBER int downgrading; + int mime_errs; + SMTP_RESP fake; + int fail_status; + + /* Caution: changes to RETURN() also affect code outside the main loop. */ + +#define RETURN(x) do { \ + if (recv_state != SMTP_STATE_LAST) \ + DONT_CACHE_THIS_SESSION; \ + vstring_free(next_command); \ + if (survivors) \ + myfree((void *) survivors); \ + if (session->mime_state) \ + session->mime_state = mime_state_free(session->mime_state); \ + return (x); \ + } while (0) + +#define SENDER_IS_AHEAD \ + (recv_state < send_state || recv_rcpt != send_rcpt) + +#define SENDER_IN_WAIT_STATE \ + (send_state == SMTP_STATE_DOT || send_state == SMTP_STATE_LAST) + +#define SENDING_MAIL \ + (recv_state <= SMTP_STATE_DOT) + +#define CANT_RSET_THIS_SESSION \ + (session->features |= SMTP_FEATURE_RSET_REJECTED) + + /* + * Pipelining support requires two loops: one loop for sending and one + * for receiving. Each loop has its own independent state. Most of the + * time the sender can run ahead of the receiver by as much as the TCP + * send buffer permits. There are only two places where the sender must + * wait for status information from the receiver: once after sending DATA + * and once after sending QUIT. + * + * The sender state advances until the TCP send buffer would overflow, or + * until the sender needs status information from the receiver. At that + * point the receiver starts processing responses. Once the receiver has + * caught up with the sender, the sender resumes sending commands. If the + * receiver detects a serious problem (MAIL FROM rejected, all RCPT TO + * commands rejected, DATA rejected) it forces the sender to abort the + * SMTP dialog with RSET and QUIT. + */ + nrcpt = 0; + next_rcpt = send_rcpt = recv_rcpt = recv_done = 0; + mail_from_rejected = 0; + + /* + * Prepare for disaster. This should not be needed because the design + * guarantees that no output is flushed before smtp_chat_resp() is + * called. + * + * 1) Every SMTP command fits entirely in a VSTREAM output buffer. + * + * 2) smtp_loop() never invokes smtp_chat_cmd() without making sure that + * there is sufficient space for the command in the output buffer. + * + * 3) smtp_loop() flushes the output buffer to avoid server timeouts. + * + * Changing any of these would violate the design, and would likely break + * SMTP pipelining. + * + * We set up the error handler anyway (only upon entry to avoid wasting + * resources) because 1) there is code below that expects that VSTREAM + * timeouts are enabled, and 2) this allows us to detect if someone broke + * Postfix by introducing spurious flush before read operations. + */ + if (send_state < SMTP_STATE_XFORWARD_NAME_ADDR + || send_state > SMTP_STATE_QUIT) + msg_panic("%s: bad sender state %d (receiver state %d)", + myname, send_state, recv_state); + smtp_stream_setup(session->stream, *xfer_timeouts[send_state], + var_smtp_req_deadline, 0); + if ((except = vstream_setjmp(session->stream)) != 0) { + msg_warn("smtp_proto: spurious flush before read in send state %d", + send_state); + RETURN(SENDING_MAIL ? smtp_stream_except(state, except, + xfer_states[send_state]) : -1); + } + + /* + * The main protocol loop. + */ + do { + + /* + * Build the next command. + */ + switch (send_state) { + + /* + * Sanity check. + */ + default: + msg_panic("%s: bad sender state %d", myname, send_state); + + /* + * Build the XFORWARD command. With properly sanitized + * information, the command length stays within the 512 byte + * command line length limit. + * + * XXX smtpd_xforward_preset() initializes some fields as "unknown" + * and some as null; historically, pickup(8) does not send any of + * these, and the queue manager presets absent fields to "not + * available" except for the rewrite context which is preset to + * local by way of migration aid. These definitions need to be + * centralized for maintainability. + */ +#ifndef CAN_FORWARD_CLIENT_NAME +#define _ATTR_AVAIL_AND_KNOWN_(val) \ + (DEL_REQ_ATTR_AVAIL(val) && strcasecmp((val), "unknown")) +#define CAN_FORWARD_CLIENT_NAME _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_CLIENT_ADDR _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_CLIENT_PORT _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_PROTO_NAME _ATTR_AVAIL_AND_KNOWN_ +#define CAN_FORWARD_HELO_NAME DEL_REQ_ATTR_AVAIL +#define CAN_FORWARD_IDENT_NAME DEL_REQ_ATTR_AVAIL +#define CAN_FORWARD_RWR_CONTEXT DEL_REQ_ATTR_AVAIL +#endif + + case SMTP_STATE_XFORWARD_NAME_ADDR: + vstring_strcpy(next_command, XFORWARD_CMD); + if ((session->features & SMTP_FEATURE_XFORWARD_NAME) + && CAN_FORWARD_CLIENT_NAME(request->client_name)) { + vstring_strcat(next_command, " " XFORWARD_NAME "="); + xtext_quote_append(next_command, request->client_name, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_ADDR) + && CAN_FORWARD_CLIENT_ADDR(request->client_addr)) { + vstring_strcat(next_command, " " XFORWARD_ADDR "="); + xtext_quote_append(next_command, request->client_addr, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_PORT) + && CAN_FORWARD_CLIENT_PORT(request->client_port)) { + vstring_strcat(next_command, " " XFORWARD_PORT "="); + xtext_quote_append(next_command, request->client_port, ""); + } + if (session->send_proto_helo) + next_state = SMTP_STATE_XFORWARD_PROTO_HELO; + else + next_state = SMTP_STATE_MAIL; + break; + + case SMTP_STATE_XFORWARD_PROTO_HELO: + vstring_strcpy(next_command, XFORWARD_CMD); + if ((session->features & SMTP_FEATURE_XFORWARD_PROTO) + && CAN_FORWARD_PROTO_NAME(request->client_proto)) { + vstring_strcat(next_command, " " XFORWARD_PROTO "="); + xtext_quote_append(next_command, request->client_proto, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_HELO) + && CAN_FORWARD_HELO_NAME(request->client_helo)) { + vstring_strcat(next_command, " " XFORWARD_HELO "="); + xtext_quote_append(next_command, request->client_helo, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_IDENT) + && CAN_FORWARD_IDENT_NAME(request->log_ident)) { + vstring_strcat(next_command, " " XFORWARD_IDENT "="); + xtext_quote_append(next_command, request->log_ident, ""); + } + if ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN) + && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context)) { + vstring_strcat(next_command, " " XFORWARD_DOMAIN "="); + xtext_quote_append(next_command, + strcmp(request->rewrite_context, MAIL_ATTR_RWR_LOCAL) ? + XFORWARD_DOM_REMOTE : XFORWARD_DOM_LOCAL, ""); + } + next_state = SMTP_STATE_MAIL; + break; + + /* + * Build the MAIL FROM command. + */ + case SMTP_STATE_MAIL: + request->msg_stats.reuse_count = session->reuse_count; + GETTIMEOFDAY(&request->msg_stats.conn_setup_done); + smtp_rewrite_generic_internal(session->scratch2, request->sender); + smtp_quote_821_address(session->scratch, + vstring_str(session->scratch2)); + vstring_sprintf(next_command, "MAIL FROM:<%s>", + vstring_str(session->scratch)); + /* XXX Don't announce SIZE if we're going to MIME downgrade. */ + if (session->features & SMTP_FEATURE_SIZE /* RFC 1870 */ + && !SMTP_MIME_DOWNGRADE(session, request)) + vstring_sprintf_append(next_command, " SIZE=%lu", + request->data_size); + if (session->features & SMTP_FEATURE_8BITMIME) { /* RFC 1652 */ + if (strcmp(request->encoding, MAIL_ATTR_ENC_8BIT) == 0) + vstring_strcat(next_command, " BODY=8BITMIME"); + else if (strcmp(request->encoding, MAIL_ATTR_ENC_7BIT) == 0) + vstring_strcat(next_command, " BODY=7BIT"); + else if (strcmp(request->encoding, MAIL_ATTR_ENC_NONE) != 0) + msg_warn("%s: unknown content encoding: %s", + request->queue_id, request->encoding); + } + if (session->features & SMTP_FEATURE_DSN) { + if (request->dsn_envid[0]) { + vstring_sprintf_append(next_command, " ENVID="); + xtext_quote_append(next_command, request->dsn_envid, "+="); + } + if (request->dsn_ret) + vstring_sprintf_append(next_command, " RET=%s", + dsn_ret_str(request->dsn_ret)); + } + + /* + * Request SMTPUTF8 when the remote SMTP server supports SMTPUTF8 + * and the sender requested SMTPUTF8 support. + * + * If the sender requested SMTPUTF8 but the remote SMTP server does + * not support SMTPUTF8, then we have already determined earlier + * that delivering this message without SMTPUTF8 will not break + * the SMTPUTF8 promise that was made to the sender. + */ + if ((session->features & SMTP_FEATURE_SMTPUTF8) != 0 + && (request->smtputf8 & SMTPUTF8_FLAG_REQUESTED) != 0) + vstring_strcat(next_command, " SMTPUTF8"); + + /* + * We authenticate the local MTA only, but not the sender. + */ +#ifdef USE_SASL_AUTH + if (var_smtp_sasl_enable + && var_smtp_dummy_mail_auth + && (session->features & SMTP_FEATURE_AUTH)) + vstring_strcat(next_command, " AUTH=<>"); +#endif + + /* + * CVE-2009-3555 (TLS renegotiation). Try to detect a mail + * hijacking attack that prepends malicious EHLO/MAIL/RCPT/DATA + * commands to our TLS session. + * + * For the attack to succeed, the remote SMTP server must reply to + * the malicious EHLO/MAIL/RCPT/DATA commands after completing + * TLS (re)negotiation, so that the replies arrive in our TLS + * session (otherwise the Postfix SMTP client would time out + * waiting for an answer). With some luck we can detect this + * specific attack as a server MAIL reply that arrives before we + * send our own MAIL command. + * + * We don't apply this test to the HELO command because the result + * would be very timing sensitive, and we don't apply this test + * to RCPT and DATA replies because these may be pipelined for + * legitimate reasons. + */ +#ifdef USE_TLS + if (var_smtp_tls_blk_early_mail_reply + && (state->misc_flags & SMTP_MISC_FLAG_IN_STARTTLS) != 0 + && (vstream_peek(session->stream) > 0 + || peekfd(vstream_fileno(session->stream)) > 0)) + session->features |= SMTP_FEATURE_EARLY_TLS_MAIL_REPLY; +#endif + + /* + * We now return to our regular broadcast. + */ + next_state = SMTP_STATE_RCPT; + break; + + /* + * Build one RCPT TO command before we have seen the MAIL FROM + * response. + */ + case SMTP_STATE_RCPT: + rcpt = request->rcpt_list.info + send_rcpt; + smtp_rewrite_generic_internal(session->scratch2, rcpt->address); + smtp_quote_821_address(session->scratch, + vstring_str(session->scratch2)); + vstring_sprintf(next_command, "RCPT TO:<%s>", + vstring_str(session->scratch)); + if (session->features & SMTP_FEATURE_DSN) { + /* XXX DSN xtext encode address value not type. */ + const char *orcpt_type_addr = rcpt->dsn_orcpt; + + /* Fix 20140706: don't use empty rcpt->orig_addr. */ + if (orcpt_type_addr[0] == 0 && rcpt->orig_addr[0] != 0) { + quote_822_local(session->scratch, rcpt->orig_addr); + vstring_sprintf(session->scratch2, "%s;%s", + /* Fix 20140707: sender must request SMTPUTF8. */ + (request->smtputf8 != 0 + && !allascii(vstring_str(session->scratch))) ? + "utf-8" : "rfc822", + vstring_str(session->scratch)); + orcpt_type_addr = vstring_str(session->scratch2); + } + if (orcpt_type_addr[0] != 0) { + /* Fix 20140706: don't send unquoted ORCPT. */ + /* Fix 20140707: quoting method must match orcpt type. */ + /* Fix 20140707: handle uxtext encoder errors. */ + if (strncasecmp(orcpt_type_addr, "utf-8;", 6) == 0) { + if (uxtext_quote(session->scratch, + orcpt_type_addr, "+=") != 0) + vstring_sprintf_append(next_command, " ORCPT=%s", + vstring_str(session->scratch)); + } else { + xtext_quote(session->scratch, orcpt_type_addr, "="); + vstring_sprintf_append(next_command, " ORCPT=%s", + vstring_str(session->scratch)); + } + } + if (rcpt->dsn_notify) + vstring_sprintf_append(next_command, " NOTIFY=%s", + dsn_notify_str(rcpt->dsn_notify)); + } + if ((next_rcpt = send_rcpt + 1) == SMTP_RCPT_LEFT(state)) + next_state = (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_RCPT) ? + SMTP_STATE_ABORT : SMTP_STATE_DATA; + break; + + /* + * Build the DATA command before we have seen all the RCPT TO + * responses. + */ + case SMTP_STATE_DATA: + vstring_strcpy(next_command, "DATA"); + next_state = SMTP_STATE_DOT; + break; + + /* + * Build the "." command after we have seen the DATA response + * (DATA is a protocol synchronization point). + * + * Changing the connection caching state here is safe because it + * affects none of the not-yet processed replies to + * already-generated commands. + */ + case SMTP_STATE_DOT: + vstring_strcpy(next_command, "."); + if (THIS_SESSION_IS_EXPIRED) + DONT_CACHE_THIS_SESSION; + next_state = THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT; + break; + + /* + * The SMTP_STATE_ABORT sender state is entered by the sender + * when it has verified all recipients; or it is entered by the + * receiver when all recipients are verified or rejected, and is + * then left before the bottom of the main loop. + * + * Changing the connection caching state here is safe because there + * are no not-yet processed replies to already-generated + * commands. + */ + case SMTP_STATE_ABORT: + vstring_strcpy(next_command, "RSET"); + if (THIS_SESSION_IS_EXPIRED) + DONT_CACHE_THIS_SESSION; + next_state = THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT; + break; + + /* + * Build the RSET command. This is entered as initial state from + * smtp_rset() and has its own dedicated state transitions. It is + * used to find out the status of a cached session before + * attempting mail delivery. + */ + case SMTP_STATE_RSET: + vstring_strcpy(next_command, "RSET"); + next_state = SMTP_STATE_LAST; + break; + + /* + * Build the QUIT command before we have seen the "." or RSET + * response. This is entered as initial state from smtp_quit(), + * or is reached near the end of any non-cached session. + * + * Changing the connection caching state here is safe. If this + * command is pipelined together with a preceding command, then + * connection caching was already turned off. Do not clobber the + * "bad connection" flag. + */ + case SMTP_STATE_QUIT: + vstring_strcpy(next_command, "QUIT"); + next_state = SMTP_STATE_LAST; + if (THIS_SESSION_IS_CACHED) + DONT_CACHE_THIS_SESSION; + break; + + /* + * The final sender state has no action associated with it. + */ + case SMTP_STATE_LAST: + VSTRING_RESET(next_command); + break; + } + VSTRING_TERMINATE(next_command); + + /* + * Process responses until the receiver has caught up. Vstreams + * automatically flush buffered output when reading new data. + * + * Flush unsent output if command pipelining is off or if no I/O + * happened for a while. This limits the accumulation of client-side + * delays in pipelined sessions. + * + * The PIPELINING engine will flush the VSTREAM buffer if the sender + * could otherwise produce more output than fits the PIPELINING + * buffer. This generally works because we know exactly how much + * output we produced since the last time that the sender and + * receiver synchronized the SMTP state. However this logic is not + * applicable after the sender enters the DATA phase, where it does + * not synchronize with the receiver until the .. + * Thus, the PIPELINING engine no longer knows how much data is + * pending in the TCP send buffer. For this reason, if PIPELINING is + * enabled, we always pipeline QUIT after .. This is + * safe because once the receiver reads ., its TCP + * stack either has already received the QUIT, or else it + * acknowledges all bytes up to and including ., + * making room in the sender's TCP stack for QUIT. + */ +#define CHECK_PIPELINING_BUFSIZE \ + (recv_state != SMTP_STATE_DOT || send_state != SMTP_STATE_QUIT) + + if (SENDER_IN_WAIT_STATE + || (SENDER_IS_AHEAD + && ((session->features & SMTP_FEATURE_PIPELINING) == 0 + || (CHECK_PIPELINING_BUFSIZE + && (VSTRING_LEN(next_command) + 2 + + vstream_bufstat(session->stream, VSTREAM_BST_OUT_PEND) + > PIPELINING_BUFSIZE)) + || time((time_t *) 0) + - vstream_ftime(session->stream) > 10))) { + while (SENDER_IS_AHEAD) { + + /* + * Sanity check. + */ + if (recv_state < SMTP_STATE_XFORWARD_NAME_ADDR + || recv_state > SMTP_STATE_QUIT) + msg_panic("%s: bad receiver state %d (sender state %d)", + myname, recv_state, send_state); + + /* + * Receive the next server response. Use the proper timeout, + * and log the proper client state in case of trouble. + * + * XXX If we lose the connection before sending end-of-data, + * find out if the server sent a premature end-of-data reply. + * If this read attempt fails, report "lost connection while + * sending message body", not "lost connection while sending + * end-of-data". + * + * "except" becomes zero just above the protocol loop, and stays + * zero or triggers an early return from the loop. In just + * one case: loss of the connection when sending the message + * body, we record the exception, and keep processing in the + * hope of detecting a premature 5XX. We must be careful to + * not clobber this non-zero value once it is set. The + * variable need not survive longjmp() calls, since the only + * setjmp() which does not return early is the one sets this + * condition, subsequent failures always return early. + */ +#define LOST_CONNECTION_INSIDE_DATA (except == SMTP_ERR_EOF) + + smtp_stream_setup(session->stream, *xfer_timeouts[recv_state], + var_smtp_req_deadline, 0); + if (LOST_CONNECTION_INSIDE_DATA) { + if (vstream_setjmp(session->stream) != 0) + RETURN(smtp_stream_except(state, SMTP_ERR_EOF, + "sending message body")); + } else { + if ((except = vstream_setjmp(session->stream)) != 0) + RETURN(SENDING_MAIL ? smtp_stream_except(state, except, + xfer_states[recv_state]) : -1); + } + resp = smtp_chat_resp(session); + + /* + * Process the response. + */ + switch (recv_state) { + + /* + * Process the XFORWARD response. + */ + case SMTP_STATE_XFORWARD_NAME_ADDR: + if (resp->code / 100 != 2) + msg_warn("host %s said: %s (in reply to %s)", + session->namaddrport, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_XFORWARD_NAME_ADDR]); + if (session->send_proto_helo) + recv_state = SMTP_STATE_XFORWARD_PROTO_HELO; + else + recv_state = SMTP_STATE_MAIL; + break; + + case SMTP_STATE_XFORWARD_PROTO_HELO: + if (resp->code / 100 != 2) + msg_warn("host %s said: %s (in reply to %s)", + session->namaddrport, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_XFORWARD_PROTO_HELO]); + recv_state = SMTP_STATE_MAIL; + break; + + /* + * Process the MAIL FROM response. When the server + * rejects the sender, set the mail_from_rejected flag so + * that the receiver may apply a course correction. + */ + case SMTP_STATE_MAIL: + if (resp->code / 100 != 2) { + smtp_mesg_fail(state, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_MAIL]); + mail_from_rejected = 1; + } + + /* + * CVE-2009-3555 (TLS renegotiation). Whatever it was + * that arrived before we sent our MAIL FROM command, it + * was not a fatal-level TLS alert message. It could be a + * warning-level TLS alert message, or a ChangeCipherSpec + * message, but such messages are not normally sent in + * the middle of a TLS session. We disconnect and try + * again later. + */ +#ifdef USE_TLS + if (var_smtp_tls_blk_early_mail_reply + && (session->features & SMTP_FEATURE_EARLY_TLS_MAIL_REPLY)) { + smtp_site_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "4.7.0"), + "unexpected server message"); + msg_warn("server %s violates %s policy", + session->namaddr, + VAR_LMTP_SMTP(TLS_BLK_EARLY_MAIL_REPLY)); + mail_from_rejected = 1; + } +#endif + + /* + * We now return to our regular broadcast. + */ + recv_state = SMTP_STATE_RCPT; + break; + + /* + * Process one RCPT TO response. If MAIL FROM was + * rejected, ignore RCPT TO responses: all recipients are + * dead already. When all recipients are rejected the + * receiver may apply a course correction. + * + * XXX 2821: Section 4.5.3.1 says that a 552 RCPT TO reply + * must be treated as if the server replied with 452. + * However, this causes "too much mail data" to be + * treated as a recoverable error, which is wrong. I'll + * stick with RFC 821. + */ + case SMTP_STATE_RCPT: + if (!mail_from_rejected) { +#ifdef notdef + if (resp->code == 552) { + resp->code = 452; + resp->dsn[0] = '4'; + } +#endif + rcpt = request->rcpt_list.info + recv_rcpt; + if (resp->code / 100 == 2) { + if (!smtp_mode) { + if (survivors == 0) + survivors = (int *) + mymalloc(request->rcpt_list.len + * sizeof(int)); + survivors[nrcpt] = recv_rcpt; + } + ++nrcpt; + /* If trace-only, mark the recipient done. */ + if (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_RCPT) { + translit(resp->str, "\n", " "); + smtp_rcpt_done(state, resp, rcpt); + } + } else { + smtp_rcpt_fail(state, rcpt, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_RCPT]); + } + } + /* If trace-only, send RSET instead of DATA. */ + if (++recv_rcpt == SMTP_RCPT_LEFT(state)) + recv_state = (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_RCPT) ? + SMTP_STATE_ABORT : SMTP_STATE_DATA; + /* XXX Also: record if non-delivering session. */ + break; + + /* + * Process the DATA response. When the server rejects + * DATA, set nrcpt to a negative value so that the + * receiver can apply a course correction. + */ + case SMTP_STATE_DATA: + recv_state = SMTP_STATE_DOT; + if (resp->code / 100 != 3) { + if (nrcpt > 0) + smtp_mesg_fail(state, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_DATA]); + nrcpt = -1; + } + + /* + * In the case of a successful address probe with target + * equal to DATA, the remote server is now in the DATA + * state, and therefore we must not make any further + * attempt to send or receive on this connection. This + * means that we cannot not reuse the general-purpose + * course-correction logic below which sends RSET (and + * perhaps QUIT). Instead we "jump" straight to the exit + * and force an unceremonious disconnect. + */ + else if (DEL_REQ_TRACE_ONLY(request->flags) + && smtp_vrfy_tgt == SMTP_STATE_DATA) { + for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (!SMTP_RCPT_ISMARKED(rcpt)) { + translit(resp->str, "\n", " "); + SMTP_RESP_SET_DSN(resp, "2.0.0"); + smtp_rcpt_done(state, resp, rcpt); + } + } + DONT_CACHE_THIS_SESSION; + send_state = recv_state = SMTP_STATE_LAST; + } + break; + + /* + * Process the end of message response. Ignore the + * response when no recipient was accepted: all + * recipients are dead already, and the next receiver + * state is SMTP_STATE_LAST/QUIT regardless. Otherwise, + * if the message transfer fails, bounce all remaining + * recipients, else cross off the recipients that were + * delivered. + */ + case SMTP_STATE_DOT: + GETTIMEOFDAY(&request->msg_stats.deliver_done); + if (smtp_mode) { + if (nrcpt > 0) { + if (resp->code / 100 != 2) { + smtp_mesg_fail(state, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_DOT]); + } else { + for (nrcpt = 0; nrcpt < recv_rcpt; nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (!SMTP_RCPT_ISMARKED(rcpt)) { + translit(resp->str, "\n", " "); + smtp_rcpt_done(state, resp, rcpt); + } + } + } + } + } + + /* + * With LMTP we have one response per accepted RCPT TO + * command. Stay in the SMTP_STATE_DOT state until we + * have collected all responses. + */ + else { + if (nrcpt > 0) { + rcpt = request->rcpt_list.info + + survivors[recv_done++]; + if (resp->code / 100 != 2) { + smtp_rcpt_fail(state, rcpt, STR(iter->host), resp, + "host %s said: %s (in reply to %s)", + session->namaddr, + translit(resp->str, "\n", " "), + xfer_request[SMTP_STATE_DOT]); + } else { + translit(resp->str, "\n", " "); + smtp_rcpt_done(state, resp, rcpt); + } + } + if (msg_verbose) + msg_info("%s: got %d of %d end-of-data replies", + myname, recv_done, nrcpt); + if (recv_done < nrcpt) + break; + } + + /* + * XXX Do not change the connection caching state here, + * even if the connection caching timer expired between + * generating the command and processing the reply, + * otherwise the sender and receiver loops get out of + * sync. The caller will call smtp_quit() if appropriate. + */ + if (var_skip_quit_resp || THIS_SESSION_IS_CACHED + || LOST_CONNECTION_INSIDE_DATA) + recv_state = SMTP_STATE_LAST; + else + recv_state = SMTP_STATE_QUIT; + break; + + /* + * Receive the RSET response. + * + * The SMTP_STATE_ABORT sender state is entered by the + * sender when it has verified all recipients; or it is + * entered by the receiver when all recipients are + * verified or rejected, and is then left before the + * bottom of the main loop. + * + * XXX Do not change the connection caching state here, even + * if the server rejected RSET or if the connection + * caching timer expired between generating the command + * and processing the reply, otherwise the sender and + * receiver loops get out of sync. The caller will call + * smtp_quit() if appropriate. + */ + case SMTP_STATE_ABORT: + recv_state = (var_skip_quit_resp || THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT); + break; + + /* + * This is the initial receiver state from smtp_rset(). + * It is used to find out the status of a cached session + * before attempting mail delivery. + */ + case SMTP_STATE_RSET: + if (resp->code / 100 != 2) + CANT_RSET_THIS_SESSION; + recv_state = SMTP_STATE_LAST; + break; + + /* + * Receive, but otherwise ignore, the QUIT response. + */ + case SMTP_STATE_QUIT: + recv_state = SMTP_STATE_LAST; + break; + } + } + + /* + * At this point, the sender and receiver are fully synchronized. + */ + + /* + * We know the server response to every command that was sent. + * Apply a course correction if necessary: the sender wants to + * send RCPT TO but MAIL FROM was rejected; the sender wants to + * send DATA but all recipients were rejected; the sender wants + * to deliver the message but DATA was rejected. + */ + if ((send_state == SMTP_STATE_RCPT && mail_from_rejected) + || (send_state == SMTP_STATE_DATA && nrcpt == 0) + || (send_state == SMTP_STATE_DOT && nrcpt < 0)) { + send_state = recv_state = SMTP_STATE_ABORT; + send_rcpt = recv_rcpt = 0; + vstring_strcpy(next_command, "RSET"); + if (THIS_SESSION_IS_EXPIRED) + DONT_CACHE_THIS_SESSION; + next_state = THIS_SESSION_IS_CACHED ? + SMTP_STATE_LAST : SMTP_STATE_QUIT; + /* XXX Also: record if non-delivering session. */ + next_rcpt = 0; + } + } + + /* + * Make the next sender state the current sender state. + */ + if (send_state == SMTP_STATE_LAST) + continue; + + /* + * Special case if the server accepted the DATA command. If the + * server accepted at least one recipient send the entire message. + * Otherwise, just send "." as per RFC 2197. + * + * XXX If there is a hard MIME error while downgrading to 7-bit mail, + * disconnect ungracefully, because there is no other way to cancel a + * transaction in progress. + */ + if (send_state == SMTP_STATE_DOT && nrcpt > 0) { + + smtp_stream_setup(session->stream, var_smtp_data1_tmout, + var_smtp_req_deadline, var_smtp_min_data_rate); + + if ((except = vstream_setjmp(session->stream)) == 0) { + + if (vstream_fseek(state->src, request->data_offset, SEEK_SET) < 0) + msg_fatal("seek queue file: %m"); + + downgrading = SMTP_MIME_DOWNGRADE(session, request); + + /* + * XXX Don't downgrade just because generic_maps is turned + * on. + */ +#define SMTP_ANY_CHECKS (smtp_header_checks || smtp_body_checks) + + if (downgrading || smtp_generic_maps || SMTP_ANY_CHECKS) + session->mime_state = mime_state_alloc(downgrading ? + MIME_OPT_DOWNGRADE + | MIME_OPT_REPORT_NESTING : + SMTP_ANY_CHECKS == 0 ? + MIME_OPT_DISABLE_MIME : + 0, + smtp_generic_maps + || smtp_header_checks ? + smtp_header_rewrite : + smtp_header_out, + (MIME_STATE_ANY_END) 0, + smtp_body_checks ? + smtp_body_rewrite : + smtp_text_out, + (MIME_STATE_ANY_END) 0, + (MIME_STATE_ERR_PRINT) 0, + (void *) state); + state->space_left = var_smtp_line_limit; + + if ((smtp_cli_attr.flags & SMTP_CLI_MASK_ADD_HEADERS) != 0 + && smtp_out_add_headers(state) < 0) + RETURN(0); + + while ((rec_type = rec_get(state->src, session->scratch, 0)) > 0) { + if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT) + break; + if (smtp_out_raw_or_mime(state, rec_type, + session->scratch) < 0) + RETURN(0); + prev_type = rec_type; + } + + if (session->mime_state) { + + /* + * The cleanup server normally ends MIME content with a + * normal text record. The following code is needed to + * flush an internal buffer when someone submits 8-bit + * mail not ending in newline via /usr/sbin/sendmail + * while MIME input processing is turned off, and MIME + * 8bit->7bit conversion is requested upon delivery. + * + * Or some error while doing generic address mapping. + */ + mime_errs = + mime_state_update(session->mime_state, rec_type, "", 0); + if (mime_errs) { + smtp_mime_fail(state, mime_errs); + RETURN(0); + } + } else if (prev_type == REC_TYPE_CONT) /* missing newline */ + smtp_fputs("", 0, session->stream); + if (session->features & SMTP_FEATURE_PIX_DELAY_DOTCRLF) { + smtp_flush(session->stream);/* hurts performance */ + sleep(var_smtp_pix_delay); /* not to mention this */ + } + if (vstream_ferror(state->src)) + msg_fatal("queue file read error"); + if (rec_type != REC_TYPE_XTRA) { + msg_warn("%s: bad record type: %d in message content", + request->queue_id, rec_type); + fail_status = smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.3.0"), + "unreadable mail queue entry"); + /* Bailing out, abort stream with prejudice */ + (void) vstream_fpurge(session->stream, VSTREAM_PURGE_BOTH); + DONT_USE_FORBIDDEN_SESSION; + /* If bounce_append() succeeded, status is still 0 */ + if (state->status == 0) + (void) mark_corrupt(state->src); + /* Don't override smtp_mesg_fail() here. */ + RETURN(fail_status); + } + } else { + if (!LOST_CONNECTION_INSIDE_DATA) + RETURN(smtp_stream_except(state, except, + "sending message body")); + + /* + * We will clear the stream error flag to try and read a + * premature 5XX response, so it is important to flush any + * unwritten data. Otherwise, we will try to flush it again + * before reading, which may incur an unnecessary delay and + * will prevent the reading of any response that is not + * already buffered (bundled with the DATA 354 response). + * + * Not much point in sending QUIT at this point, skip right to + * SMTP_STATE_LAST. The read engine above will likewise avoid + * looking for a QUIT response. + */ + (void) vstream_fpurge(session->stream, VSTREAM_PURGE_WRITE); + next_state = SMTP_STATE_LAST; + } + } + + /* + * Copy the next command to the buffer and update the sender state. + */ + if (except == 0) { + smtp_chat_cmd(session, "%s", vstring_str(next_command)); + } else { + DONT_CACHE_THIS_SESSION; + } + send_state = next_state; + send_rcpt = next_rcpt; + } while (recv_state != SMTP_STATE_LAST); + RETURN(0); +} + +/* smtp_xfer - send a batch of envelope information and the message data */ + +int smtp_xfer(SMTP_STATE *state) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + SMTP_RESP fake; + int send_state; + int recv_state; + int send_name_addr; + int result; + + /* + * Sanity check. Recipients should be unmarked at this point. + */ + if (SMTP_RCPT_LEFT(state) <= 0) + msg_panic("smtp_xfer: bad recipient count: %d", + SMTP_RCPT_LEFT(state)); + if (SMTP_RCPT_ISMARKED(request->rcpt_list.info)) + msg_panic("smtp_xfer: bad recipient status: %d", + request->rcpt_list.info->u.status); + + /* + * See if we should even try to send this message at all. This code sits + * here rather than in the EHLO processing code, because of SMTP + * connection caching. + */ + if (session->size_limit > 0 && session->size_limit < request->data_size) { + smtp_mesg_fail(state, DSN_BY_LOCAL_MTA, + SMTP_RESP_FAKE(&fake, "5.3.4"), + "message size %lu exceeds size limit %.0f of server %s", + request->data_size, (double) session->size_limit, + session->namaddr); + /* Redundant. We abort this delivery attempt. */ + state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION; + return (0); + } + + /* + * Use XFORWARD to forward the origin of this email message across an + * SMTP-based content filter. Send client attribute information only if + * it exists (i.e. remote submission). Local submissions have no client + * attributes; the mail will appear to originate from the content filter + * which is acceptable. + */ + send_name_addr = + var_smtp_send_xforward + && (((session->features & SMTP_FEATURE_XFORWARD_NAME) + && CAN_FORWARD_CLIENT_NAME(request->client_name)) + || ((session->features & SMTP_FEATURE_XFORWARD_ADDR) + && CAN_FORWARD_CLIENT_ADDR(request->client_addr)) + || ((session->features & SMTP_FEATURE_XFORWARD_PORT) + && CAN_FORWARD_CLIENT_PORT(request->client_port))); + session->send_proto_helo = + var_smtp_send_xforward + && (((session->features & SMTP_FEATURE_XFORWARD_PROTO) + && CAN_FORWARD_PROTO_NAME(request->client_proto)) + || ((session->features & SMTP_FEATURE_XFORWARD_HELO) + && CAN_FORWARD_HELO_NAME(request->client_helo)) + || ((session->features & SMTP_FEATURE_XFORWARD_IDENT) + && CAN_FORWARD_IDENT_NAME(request->log_ident)) + || ((session->features & SMTP_FEATURE_XFORWARD_DOMAIN) + && CAN_FORWARD_RWR_CONTEXT(request->rewrite_context))); + if (send_name_addr) + recv_state = send_state = SMTP_STATE_XFORWARD_NAME_ADDR; + else if (session->send_proto_helo) + recv_state = send_state = SMTP_STATE_XFORWARD_PROTO_HELO; + else + recv_state = send_state = SMTP_STATE_MAIL; + + /* + * Remember this session's "normal completion", even if the server 4xx-ed + * some or all recipients. Connection or handshake errors with a later MX + * host should not cause this destination be marked as unreachable. + */ + result = smtp_loop(state, send_state, recv_state); + + if (result == 0 + /* Just in case */ + && vstream_ferror(session->stream) == 0 + && vstream_feof(session->stream) == 0) + state->misc_flags |= SMTP_MISC_FLAG_COMPLETE_SESSION; + + return (result); +} + +/* smtp_rset - send a lone RSET command */ + +int smtp_rset(SMTP_STATE *state) +{ + + /* + * This works because SMTP_STATE_RSET is a dedicated sender/recipient + * entry state, with SMTP_STATE_LAST as next sender/recipient state. + */ + return (smtp_loop(state, SMTP_STATE_RSET, SMTP_STATE_RSET)); +} + +/* smtp_quit - send a lone QUIT command */ + +int smtp_quit(SMTP_STATE *state) +{ + + /* + * This works because SMTP_STATE_QUIT is the last state with a sender + * action, with SMTP_STATE_LAST as the next sender/recipient state. + */ + return (smtp_loop(state, SMTP_STATE_QUIT, var_skip_quit_resp ? + SMTP_STATE_LAST : SMTP_STATE_QUIT)); +} diff --git a/src/smtp/smtp_rcpt.c b/src/smtp/smtp_rcpt.c new file mode 100644 index 0000000..6608ea8 --- /dev/null +++ b/src/smtp/smtp_rcpt.c @@ -0,0 +1,226 @@ +/*++ +/* NAME +/* smtp_rcpt 3 +/* SUMMARY +/* application-specific recipient list operations +/* SYNOPSIS +/* #include +/* +/* SMTP_RCPT_INIT(state) +/* SMTP_STATE *state; +/* +/* SMTP_RCPT_DROP(state, rcpt) +/* SMTP_STATE *state; +/* RECIPIENT *rcpt; +/* +/* SMTP_RCPT_KEEP(state, rcpt) +/* SMTP_STATE *state; +/* RECIPIENT *rcpt; +/* +/* SMTP_RCPT_ISMARKED(rcpt) +/* RECIPIENT *rcpt; +/* +/* void smtp_rcpt_cleanup(SMTP_STATE *state) +/* SMTP_STATE *state; +/* +/* int SMTP_RCPT_LEFT(state) +/* SMTP_STATE *state; +/* +/* int SMTP_RCPT_MARK_COUNT(state) +/* SMTP_STATE *state; +/* +/* void smtp_rcpt_done(state, resp, rcpt) +/* SMTP_STATE *state; +/* SMTP_RESP *resp; +/* RECIPIENT *rcpt; +/* DESCRIPTION +/* This module implements application-specific mark and sweep +/* operations on recipient lists. Operation is as follows: +/* .IP \(bu +/* In the course of a delivery attempt each recipient is +/* marked either as DROP (remove from recipient list) or KEEP +/* (deliver to alternate mail server). +/* .IP \(bu +/* After a delivery attempt any recipients marked DROP are deleted +/* from the request, and the left-over recipients are unmarked. +/* .PP +/* The mark/sweep algorithm is implemented in a redundant manner, +/* and ensures that all recipients are explicitly accounted for. +/* +/* Operations with upper case names are implemented by macros +/* whose arguments may be evaluated more than once. +/* +/* SMTP_RCPT_INIT() initializes application-specific recipient +/* information and must be called before the first delivery attempt. +/* +/* SMTP_RCPT_DROP() marks the specified recipient as DROP (remove +/* from recipient list). It is an error to mark an already marked +/* recipient. +/* +/* SMTP_RCPT_KEEP() marks the specified recipient as KEEP (deliver +/* to alternate mail server). It is an error to mark an already +/* marked recipient. +/* +/* SMTP_RCPT_ISMARKED() returns non-zero when the specified +/* recipient is marked. +/* +/* SMTP_RCPT_LEFT() returns the number of left_over recipients +/* (the total number of marked and non-marked recipients). +/* +/* SMTP_RCPT_MARK_COUNT() returns the number of left_over +/* recipients that are marked. +/* +/* smtp_rcpt_cleanup() cleans up the in-memory recipient list. +/* It removes the recipients marked DROP from the left-over +/* recipients, unmarks the left-over recipients, and enforces +/* the requirement that all recipients are marked upon entry. +/* +/* smtp_rcpt_done() logs that a recipient is completed and upon +/* success it marks the recipient as done in the queue file. +/* Finally, it marks the in-memory recipient as DROP. +/* +/* Note: smtp_rcpt_done() may change the order of the recipient +/* list. +/* DIAGNOSTICS +/* Panic: interface violation. +/* +/* When a recipient can't be logged as completed, the recipient is +/* logged as deferred instead. +/* BUGS +/* The single recipient list abstraction dates from the time +/* that the SMTP client would give up after one SMTP session, +/* so that each recipient was either bounced, delivered or +/* deferred. Implicitly, all recipients were marked as DROP. +/* +/* This abstraction is less convenient when an SMTP client +/* must be able to deliver left-over recipients to a backup +/* host. It might be more natural to have an input list with +/* recipients to deliver, and an output list with left-over +/* recipients. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include /* smtp_rcpt_cleanup */ +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include /* smtp_rcpt_done */ +#include /* smtp_rcpt_done */ +#include /* smtp_rcpt_done */ +#include /* smtp_rcpt_done */ + +/* Application-specific. */ + +#include + +/* smtp_rcpt_done - mark recipient as done or else */ + +void smtp_rcpt_done(SMTP_STATE *state, SMTP_RESP *resp, RECIPIENT *rcpt) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + SMTP_ITERATOR *iter = state->iterator; + DSN_BUF *why = state->why; + const char *dsn_action = "relayed"; + int status; + + /* + * Assume this was intermediate delivery when the server announced DSN + * support, and don't send a DSN "SUCCESS" notification. + */ + if (session->features & SMTP_FEATURE_DSN) + rcpt->dsn_notify &= ~DSN_NOTIFY_SUCCESS; + + /* + * Assume this was final delivery when the LMTP server announced no DSN + * support. In backwards compatibility mode, send a "relayed" instead of + * a "delivered" DSN "SUCCESS" notification. Do not attempt to "simplify" + * the expression. The redundancy is for clarity. It is trivially + * eliminated by the compiler. There is no need to sacrifice clarity for + * the sake of "performance". + */ + if ((session->features & SMTP_FEATURE_DSN) == 0 + && !smtp_mode + && (smtp_cli_attr.flags & SMTP_CLI_FLAG_FINAL_DELIVERY) != 0) + dsn_action = "delivered"; + + /* + * Report success and delete the recipient from the delivery request. + * Defer if the success can't be reported. + * + * Note: the DSN action is ignored in case of address probes. + */ + dsb_update(why, resp->dsn, dsn_action, DSB_MTYPE_DNS, STR(iter->host), + DSB_DTYPE_SMTP, resp->str, "%s", resp->str); + + status = sent(DEL_REQ_TRACE_FLAGS(request->flags), + request->queue_id, &request->msg_stats, rcpt, + session->namaddrport, DSN_FROM_DSN_BUF(why)); + if (status == 0) + if (request->flags & DEL_REQ_FLAG_SUCCESS) + deliver_completed(state->src, rcpt->offset); + SMTP_RCPT_DROP(state, rcpt); + state->status |= status; +} + +/* smtp_rcpt_cleanup_callback - qsort callback */ + +static int smtp_rcpt_cleanup_callback(const void *a, const void *b) +{ + return (((RECIPIENT *) a)->u.status - ((RECIPIENT *) b)->u.status); +} + +/* smtp_rcpt_cleanup - purge completed recipients from request */ + +void smtp_rcpt_cleanup(SMTP_STATE *state) +{ + RECIPIENT_LIST *rcpt_list = &state->request->rcpt_list; + RECIPIENT *rcpt; + + /* + * Sanity checks. + */ + if (state->rcpt_drop + state->rcpt_keep != state->rcpt_left) + msg_panic("smtp_rcpt_cleanup: recipient count mismatch: %d+%d!=%d", + state->rcpt_drop, state->rcpt_keep, state->rcpt_left); + + /* + * Recipients marked KEEP sort before recipients marked DROP. Skip the + * sorting in the common case that all recipients are marked the same. + */ + if (state->rcpt_drop > 0 && state->rcpt_keep > 0) + qsort((void *) rcpt_list->info, state->rcpt_left, + sizeof(rcpt_list->info[0]), smtp_rcpt_cleanup_callback); + + /* + * Truncate the recipient list and unmark the left-over recipients. + */ + state->rcpt_left = state->rcpt_keep; + for (rcpt = rcpt_list->info; rcpt < rcpt_list->info + state->rcpt_left; rcpt++) + rcpt->u.status = 0; + state->rcpt_drop = state->rcpt_keep = 0; +} diff --git a/src/smtp/smtp_reuse.c b/src/smtp/smtp_reuse.c new file mode 100644 index 0000000..f93ba29 --- /dev/null +++ b/src/smtp/smtp_reuse.c @@ -0,0 +1,269 @@ +/*++ +/* NAME +/* smtp_reuse 3 +/* SUMMARY +/* SMTP session cache glue +/* SYNOPSIS +/* #include +/* #include +/* +/* void smtp_save_session(state, name_key_flags, endp_key_flags) +/* SMTP_STATE *state; +/* int name_key_flags; +/* int endp_key_flags; +/* +/* SMTP_SESSION *smtp_reuse_nexthop(state, name_key_flags) +/* SMTP_STATE *state; +/* int name_key_flags; +/* +/* SMTP_SESSION *smtp_reuse_addr(state, endp_key_flags) +/* SMTP_STATE *state; +/* int endp_key_flags; +/* DESCRIPTION +/* This module implements the SMTP client specific interface to +/* the generic session cache infrastructure. +/* +/* The caller needs to include additional state in _key_flags +/* to avoid false sharing of SASL-authenticated or TLS-authenticated +/* sessions. +/* +/* smtp_save_session() stores the current session under the +/* delivery request next-hop logical destination (if applicable) +/* and under the remote server address. The SMTP_SESSION object +/* is destroyed. +/* +/* smtp_reuse_nexthop() looks up a cached session by its +/* delivery request next-hop destination, and verifies that +/* the session is still alive. The restored session information +/* includes the "best MX" bit and overrides the iterator dest, +/* host and addr fields. The result is null in case of failure. +/* +/* smtp_reuse_addr() looks up a cached session by its server +/* address, and verifies that the session is still alive. +/* The restored session information does not include the "best +/* MX" bit, and does not override the iterator dest, host and +/* addr fields. The result is null in case of failure. +/* +/* Arguments: +/* .IP state +/* SMTP client state, including the current session, the original +/* next-hop domain, etc. +/* .IP name_key_flags +/* Explicit declaration of context that should be used to look +/* up a cached connection by its logical destination. +/* See smtp_key(3) for details. +/* .IP endp_key_flags +/* Explicit declaration of context that should be used to look +/* up a cached connection by its server address. +/* See smtp_key(3) for details. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include +#include + + /* + * Key field delimiter, and place holder field value for + * unavailable/inapplicable information. + */ +#define SMTP_REUSE_KEY_DELIM_NA "\n*" + +/* smtp_save_session - save session under next-hop name and server address */ + +void smtp_save_session(SMTP_STATE *state, int name_key_flags, + int endp_key_flags) +{ + SMTP_SESSION *session = state->session; + int fd; + + /* + * Encode the delivery request next-hop destination, if applicable. Reuse + * storage that is also used for cache lookup queries. + * + * HAVE_SCACHE_REQUEST_NEXTHOP() controls whether or not to reuse or cache a + * connection by its delivery request next-hop destination. The idea is + * 1) to allow a reuse request to skip over bad hosts, and 2) to avoid + * caching a less-preferred connection when a more-preferred connection + * was possible. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + smtp_key_prefix(state->dest_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, name_key_flags); + + /* + * Encode the physical endpoint name. Reuse storage that is also used for + * cache lookup queries. + */ + smtp_key_prefix(state->endp_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, endp_key_flags); + + /* + * Passivate the SMTP_SESSION object, destroying the object in the + * process. Reuse storage that is also used for cache lookup results. + */ + fd = smtp_session_passivate(session, state->dest_prop, state->endp_prop); + state->session = 0; + + /* + * Save the session under the delivery request next-hop name, if + * applicable. + * + * XXX The logical to physical binding can be kept for as long as the DNS + * allows us to (but that could result in the caching of lots of unused + * bindings). The session should be idle for no more than 30 seconds or + * so. + */ + if (HAVE_SCACHE_REQUEST_NEXTHOP(state)) + scache_save_dest(smtp_scache, var_smtp_cache_conn, + STR(state->dest_label), STR(state->dest_prop), + STR(state->endp_label)); + + /* + * Save every good session under its physical endpoint address. + */ + scache_save_endp(smtp_scache, var_smtp_cache_conn, STR(state->endp_label), + STR(state->endp_prop), fd); +} + +/* smtp_reuse_common - common session reuse code */ + +static SMTP_SESSION *smtp_reuse_common(SMTP_STATE *state, int fd, + const char *label) +{ + const char *myname = "smtp_reuse_common"; + SMTP_ITERATOR *iter = state->iterator; + SMTP_SESSION *session; + + /* + * Re-activate the SMTP_SESSION object. + */ + session = smtp_session_activate(fd, state->iterator, state->dest_prop, + state->endp_prop); + if (session == 0) { + msg_warn("%s: bad cached session attribute for %s", myname, label); + (void) close(fd); + return (0); + } + state->session = session; + session->state = state; + + /* + * Send an RSET probe to verify that the session is still good. + */ + if (smtp_rset(state) < 0 + || (session->features & SMTP_FEATURE_RSET_REJECTED) != 0) { + smtp_session_free(session); + return (state->session = 0); + } + + /* + * Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. + */ + vstream_tweak_sock(session->stream); + + /* + * Update the list of used cached addresses. + */ + htable_enter(state->cache_used, STR(iter->addr), (void *) 0); + + return (session); +} + +/* smtp_reuse_nexthop - reuse session cached under nexthop name */ + +SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *state, int name_key_flags) +{ + SMTP_SESSION *session; + int fd; + + /* + * Look up the session by its logical name. + */ + smtp_key_prefix(state->dest_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, name_key_flags); + if ((fd = scache_find_dest(smtp_scache, STR(state->dest_label), + state->dest_prop, state->endp_prop)) < 0) + return (0); + + /* + * Re-activate the SMTP_SESSION object, and verify that the session is + * still good. + */ + session = smtp_reuse_common(state, fd, STR(state->dest_label)); + return (session); +} + +/* smtp_reuse_addr - reuse session cached under numerical address */ + +SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *state, int endp_key_flags) +{ + SMTP_SESSION *session; + int fd; + + /* + * Address-based reuse is safe for security levels that require TLS + * certificate checks, as long as the current nexhop is included in the + * cache lookup key (COND_TLS_SMTP_KEY_FLAG_CUR_NEXTHOP). This is + * sufficient to prevent the reuse of a TLS-authenticated connection to + * the same MX hostname, IP address, and port, but for a different + * current nexthop destination with a different TLS policy. + */ + + /* + * Look up the session by its IP address. This means that we have no + * destination-to-address binding properties. + */ + smtp_key_prefix(state->endp_label, SMTP_REUSE_KEY_DELIM_NA, + state->iterator, endp_key_flags); + if ((fd = scache_find_endp(smtp_scache, STR(state->endp_label), + state->endp_prop)) < 0) + return (0); + VSTRING_RESET(state->dest_prop); + VSTRING_TERMINATE(state->dest_prop); + + /* + * Re-activate the SMTP_SESSION object, and verify that the session is + * still good. + */ + session = smtp_reuse_common(state, fd, STR(state->endp_label)); + + return (session); +} diff --git a/src/smtp/smtp_reuse.h b/src/smtp/smtp_reuse.h new file mode 100644 index 0000000..8c6cb48 --- /dev/null +++ b/src/smtp/smtp_reuse.h @@ -0,0 +1,27 @@ +/*++ +/* NAME +/* smtp_reuse 3h +/* SUMMARY +/* SMTP session cache glue +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Internal interfaces. + */ +extern void smtp_save_session(SMTP_STATE *, int, int); +extern SMTP_SESSION *smtp_reuse_nexthop(SMTP_STATE *, int); +extern SMTP_SESSION *smtp_reuse_addr(SMTP_STATE *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtp/smtp_sasl.h b/src/smtp/smtp_sasl.h new file mode 100644 index 0000000..756d13d --- /dev/null +++ b/src/smtp/smtp_sasl.h @@ -0,0 +1,43 @@ +/*++ +/* NAME +/* smtp_sasl 3h +/* SUMMARY +/* Postfix SASL interface for SMTP client +/* SYNOPSIS +/* #include "smtp_sasl.h" +/* DESCRIPTION +/* .nf + + /* + * SASL protocol functions + */ +extern void smtp_sasl_initialize(void); +extern void smtp_sasl_connect(SMTP_SESSION *); +extern int smtp_sasl_passwd_lookup(SMTP_SESSION *); +extern void smtp_sasl_start(SMTP_SESSION *, const char *, const char *); +extern int smtp_sasl_authenticate(SMTP_SESSION *, DSN_BUF *); +extern void smtp_sasl_cleanup(SMTP_SESSION *); + +extern void smtp_sasl_helo_auth(SMTP_SESSION *, const char *); +extern int smtp_sasl_helo_login(SMTP_STATE *); + +extern void smtp_sasl_passivate(SMTP_SESSION *, VSTRING *); +extern int smtp_sasl_activate(SMTP_SESSION *, char *); +extern STRING_LIST *smtp_sasl_mechs; + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtp/smtp_sasl_auth_cache.c b/src/smtp/smtp_sasl_auth_cache.c new file mode 100644 index 0000000..f3104ca --- /dev/null +++ b/src/smtp/smtp_sasl_auth_cache.c @@ -0,0 +1,272 @@ +/*++ +/* NAME +/* smtp_sasl_auth_cache 3 +/* SUMMARY +/* Postfix SASL authentication reply cache +/* SYNOPSIS +/* #include "smtp.h" +/* #include "smtp_sasl_auth_cache.h" +/* +/* SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(map, ttl) +/* const char *map +/* int ttl; +/* +/* void smtp_sasl_auth_cache_store(auth_cache, session, resp) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* const SMTP_SESSION *session; +/* const SMTP_RESP *resp; +/* +/* int smtp_sasl_auth_cache_find(auth_cache, session) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* const SMTP_SESSION *session; +/* +/* char *smtp_sasl_auth_cache_dsn(auth_cache) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* +/* char *smtp_sasl_auth_cache_text(auth_cache) +/* SMTP_SASL_AUTH_CACHE *auth_cache; +/* DESCRIPTION +/* This module maintains a cache of SASL authentication server replies. +/* This can be used to avoid repeated login failure errors. +/* +/* smtp_sasl_auth_cache_init() opens or creates the named cache. +/* +/* smtp_sasl_auth_cache_store() stores information about a +/* SASL login attempt together with the server status and +/* complete response. +/* +/* smtp_sasl_auth_cache_find() returns non-zero when a cache +/* entry exists for the given host, username and password. +/* +/* smtp_sasl_auth_cache_dsn() and smtp_sasl_auth_cache_text() +/* return the status and complete server response as found +/* with smtp_sasl_auth_cache_find(). +/* +/* Arguments: +/* .IP map +/* Lookup table name. The name must be singular and must start +/* with "proxy:". +/* .IP ttl +/* The time after which a cache entry is considered expired. +/* .IP session +/* Session context. +/* .IP resp +/* Remote SMTP server response, to be stored into the cache. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Original author: +/* Keean Schupke +/* Fry-IT Ltd. +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library + */ +#include +#include +#include +#include +#include + + /* + * Global library + */ +#include +#include + + /* + * Application-specific + */ +#include "smtp.h" +#include "smtp_sasl_auth_cache.h" + + /* + * XXX This feature stores passwords, so we must mask them with a strong + * cryptographic hash. This requires OpenSSL support. + * + * XXX It would be even better if the stored hash were salted. + */ +#ifdef HAVE_SASL_AUTH_CACHE + +/* smtp_sasl_auth_cache_init - per-process initialization (pre jail) */ + +SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *map, int ttl) +{ + const char *myname = "smtp_sasl_auth_cache_init"; + SMTP_SASL_AUTH_CACHE *auth_cache; + + /* + * Sanity checks. + */ +#define HAS_MULTIPLE_VALUES(s) ((s)[strcspn((s), CHARS_COMMA_SP)] != 0) + + if (*map == 0) + msg_panic("%s: empty SASL authentication cache name", myname); + if (ttl < 0) + msg_panic("%s: bad SASL authentication cache ttl: %d", myname, ttl); + if (HAS_MULTIPLE_VALUES(map)) + msg_fatal("SASL authentication cache name \"%s\" " + "contains multiple values", map); + + /* + * XXX To avoid multiple writers the map needs to be maintained by the + * proxywrite service. We would like to have a DICT_FLAG_REQ_PROXY flag + * so that the library can enforce this, but that requires moving the + * dict_proxy module one level down in the build dependency hierarchy. + */ +#define CACHE_DICT_OPEN_FLAGS \ + (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_UTF8_REQUEST) +#define PROXY_COLON DICT_TYPE_PROXY ":" +#define PROXY_COLON_LEN (sizeof(PROXY_COLON) - 1) + + if (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) != 0) + msg_fatal("SASL authentication cache name \"%s\" must start with \"" + PROXY_COLON, map); + + auth_cache = (SMTP_SASL_AUTH_CACHE *) mymalloc(sizeof(*auth_cache)); + auth_cache->dict = dict_open(map, O_CREAT | O_RDWR, CACHE_DICT_OPEN_FLAGS); + auth_cache->ttl = ttl; + auth_cache->dsn = mystrdup(""); + auth_cache->text = mystrdup(""); + return (auth_cache); +} + + /* + * Each cache lookup key contains a server host name and user name. Each + * cache value contains a time stamp, a hashed password, and the server + * response. With this organization, we don't have to worry about cache + * pollution, because we can detect if a cache entry has expired, or if the + * password has changed. + */ + +/* smtp_sasl_auth_cache_make_key - format auth failure cache lookup key */ + +static char *smtp_sasl_auth_cache_make_key(const char *host, const char *user) +{ + VSTRING *buf = vstring_alloc(100); + + vstring_sprintf(buf, "%s;%s", host, user); + return (vstring_export(buf)); +} + +/* smtp_sasl_auth_cache_make_pass - hash the auth failure cache password */ + +static char *smtp_sasl_auth_cache_make_pass(const char *password) +{ + VSTRING *buf = vstring_alloc(2 * SHA_DIGEST_LENGTH); + + base64_encode(buf, (const char *) SHA1((const unsigned char *) password, + strlen(password), 0), + SHA_DIGEST_LENGTH); + return (vstring_export(buf)); +} + +/* smtp_sasl_auth_cache_make_value - format auth failure cache value */ + +static char *smtp_sasl_auth_cache_make_value(const char *password, + const char *dsn, + const char *rep_str) +{ + VSTRING *val_buf = vstring_alloc(100); + char *pwd_hash; + unsigned long now = (unsigned long) time((time_t *) 0); + + pwd_hash = smtp_sasl_auth_cache_make_pass(password); + vstring_sprintf(val_buf, "%lu;%s;%s;%s", now, pwd_hash, dsn, rep_str); + myfree(pwd_hash); + return (vstring_export(val_buf)); +} + +/* smtp_sasl_auth_cache_valid_value - validate auth failure cache value */ + +static int smtp_sasl_auth_cache_valid_value(SMTP_SASL_AUTH_CACHE *auth_cache, + const char *entry, + const char *password) +{ + ssize_t len = strlen(entry); + char *cache_hash = mymalloc(len); + char *curr_hash; + unsigned long now = (unsigned long) time((time_t *) 0); + unsigned long time_stamp; + int valid; + + auth_cache->dsn = myrealloc(auth_cache->dsn, len); + auth_cache->text = myrealloc(auth_cache->text, len); + + if (sscanf(entry, "%lu;%[^;];%[^;];%[^\n]", &time_stamp, cache_hash, + auth_cache->dsn, auth_cache->text) != 4 + || !dsn_valid(auth_cache->dsn)) { + msg_warn("bad smtp_sasl_auth_cache entry: %.100s", entry); + valid = 0; + } else if (time_stamp + auth_cache->ttl < now) { + valid = 0; + } else { + curr_hash = smtp_sasl_auth_cache_make_pass(password); + valid = (strcmp(cache_hash, curr_hash) == 0); + myfree(curr_hash); + } + myfree(cache_hash); + return (valid); +} + +/* smtp_sasl_auth_cache_find - search auth failure cache */ + +int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *auth_cache, + const SMTP_SESSION *session) +{ + SMTP_ITERATOR *iter = session->iterator; + char *key; + const char *entry; + int valid = 0; + + key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username); + if ((entry = dict_get(auth_cache->dict, key)) != 0) + if ((valid = smtp_sasl_auth_cache_valid_value(auth_cache, entry, + session->sasl_passwd)) == 0) + /* Remove expired, password changed, or malformed cache entry. */ + if (dict_del(auth_cache->dict, key) != 0) + msg_warn("SASL auth failure map %s: entry not deleted: %s", + auth_cache->dict->name, key); + if (auth_cache->dict->error) + msg_warn("SASL auth failure map %s: lookup failed for %s", + auth_cache->dict->name, key); + myfree(key); + return (valid); +} + +/* smtp_sasl_auth_cache_store - update auth failure cache */ + +void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *auth_cache, + const SMTP_SESSION *session, + const SMTP_RESP *resp) +{ + SMTP_ITERATOR *iter = session->iterator; + char *key; + char *value; + + key = smtp_sasl_auth_cache_make_key(STR(iter->host), session->sasl_username); + value = smtp_sasl_auth_cache_make_value(session->sasl_passwd, + resp->dsn, resp->str); + dict_put(auth_cache->dict, key, value); + + myfree(value); + myfree(key); +} + +#endif diff --git a/src/smtp/smtp_sasl_auth_cache.h b/src/smtp/smtp_sasl_auth_cache.h new file mode 100644 index 0000000..cbbdb0d --- /dev/null +++ b/src/smtp/smtp_sasl_auth_cache.h @@ -0,0 +1,62 @@ +#ifndef _SMTP_SASL_AUTH_CACHE_H_INCLUDED_ +#define _SMTP_SASL_AUTH_CACHE_H_INCLUDED_ + +/*++ +/* NAME +/* smtp_sasl_auth_cache 3h +/* SUMMARY +/* Postfix SASL authentication failure cache +/* SYNOPSIS +/* #include "smtp.h" +/* #include "smtp_sasl_auth_cache.h" +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * This code stores hashed passwords which requires OpenSSL. + */ +#if defined(USE_TLS) && defined(USE_SASL_AUTH) +#define HAVE_SASL_AUTH_CACHE + + /* + * External interface. + */ +typedef struct { + DICT *dict; + int ttl; + char *dsn; + char *text; +} SMTP_SASL_AUTH_CACHE; + +extern SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache_init(const char *, int); +extern void smtp_sasl_auth_cache_store(SMTP_SASL_AUTH_CACHE *, const SMTP_SESSION *, const SMTP_RESP *); +extern int smtp_sasl_auth_cache_find(SMTP_SASL_AUTH_CACHE *, const SMTP_SESSION *); + +#define smtp_sasl_auth_cache_dsn(cp) ((cp)->dsn) +#define smtp_sasl_auth_cache_text(cp) ((cp)->text) + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/smtp/smtp_sasl_glue.c b/src/smtp/smtp_sasl_glue.c new file mode 100644 index 0000000..ef8e8c4 --- /dev/null +++ b/src/smtp/smtp_sasl_glue.c @@ -0,0 +1,512 @@ +/*++ +/* NAME +/* smtp_sasl_glue 3 +/* SUMMARY +/* Postfix SASL interface for SMTP client +/* SYNOPSIS +/* #include smtp_sasl.h +/* +/* void smtp_sasl_initialize() +/* +/* void smtp_sasl_connect(session) +/* SMTP_SESSION *session; +/* +/* void smtp_sasl_start(session, sasl_opts_name, sasl_opts_val) +/* SMTP_SESSION *session; +/* +/* int smtp_sasl_passwd_lookup(session) +/* SMTP_SESSION *session; +/* +/* int smtp_sasl_authenticate(session, why) +/* SMTP_SESSION *session; +/* DSN_BUF *why; +/* +/* void smtp_sasl_cleanup(session) +/* SMTP_SESSION *session; +/* +/* void smtp_sasl_passivate(session, buf) +/* SMTP_SESSION *session; +/* VSTRING *buf; +/* +/* int smtp_sasl_activate(session, buf) +/* SMTP_SESSION *session; +/* char *buf; +/* DESCRIPTION +/* smtp_sasl_initialize() initializes the SASL library. This +/* routine must be called once at process startup, before any +/* chroot operations. +/* +/* smtp_sasl_connect() performs per-session initialization. This +/* routine must be called once at the start of each connection. +/* +/* smtp_sasl_start() performs per-session initialization. This +/* routine must be called once per session before doing any SASL +/* authentication. The sasl_opts_name and sasl_opts_val parameters are +/* the postfix configuration parameters setting the security +/* policy of the SASL authentication. +/* +/* smtp_sasl_passwd_lookup() looks up the username/password +/* for the current SMTP server. The result is zero in case +/* of failure, a long jump in case of error. +/* +/* smtp_sasl_authenticate() implements the SASL authentication +/* dialog. The result is < 0 in case of protocol failure, zero in +/* case of unsuccessful authentication, > 0 in case of success. +/* The why argument is updated with a reason for failure. +/* This routine must be called only when smtp_sasl_passwd_lookup() +/* succeeds. +/* +/* smtp_sasl_cleanup() cleans up. It must be called at the +/* end of every SMTP session that uses SASL authentication. +/* This routine is a noop for non-SASL sessions. +/* +/* smtp_sasl_passivate() appends flattened SASL attributes to the +/* specified buffer. The SASL attributes are not destroyed. +/* +/* smtp_sasl_activate() restores SASL attributes from the +/* specified buffer. The buffer is modified. A result < 0 +/* means there was an error. +/* +/* Arguments: +/* .IP session +/* Session context. +/* .IP mech_list +/* String of SASL mechanisms (separated by blanks) +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Original author: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library + */ +#include +#include +#include +#include + + /* + * Global library + */ +#include +#include +#include +#include +#include + + /* + * XSASL library. + */ +#include + + /* + * Application-specific + */ +#include "smtp.h" +#include "smtp_sasl.h" +#include "smtp_sasl_auth_cache.h" + +#ifdef USE_SASL_AUTH + + /* + * Per-host login/password information. + */ +static MAPS *smtp_sasl_passwd_map; + + /* + * Supported SASL mechanisms. + */ +STRING_LIST *smtp_sasl_mechs; + + /* + * SASL implementation handle. + */ +static XSASL_CLIENT_IMPL *smtp_sasl_impl; + + /* + * The 535 SASL authentication failure cache. + */ +#ifdef HAVE_SASL_AUTH_CACHE +static SMTP_SASL_AUTH_CACHE *smtp_sasl_auth_cache; + +#endif + +/* smtp_sasl_passwd_lookup - password lookup routine */ + +int smtp_sasl_passwd_lookup(SMTP_SESSION *session) +{ + const char *myname = "smtp_sasl_passwd_lookup"; + SMTP_STATE *state = session->state; + SMTP_ITERATOR *iter = session->iterator; + const char *value; + char *passwd; + + /* + * Sanity check. + */ + if (smtp_sasl_passwd_map == 0) + msg_panic("%s: passwd map not initialized", myname); + + /* + * Look up the per-server password information. Try the hostname first, + * then try the destination. + * + * XXX Instead of using nexthop (the intended destination) we use dest + * (either the intended destination, or a fall-back destination). + * + * XXX SASL authentication currently depends on the host/domain but not on + * the TCP port. If the port is not :25, we should append it to the table + * lookup key. Code for this was briefly introduced into 2.2 snapshots, + * but didn't canonicalize the TCP port, and did not append the port to + * the MX hostname. + */ + smtp_sasl_passwd_map->error = 0; + if ((smtp_mode + && var_smtp_sender_auth && state->request->sender[0] + && (value = mail_addr_find(smtp_sasl_passwd_map, + state->request->sender, (char **) 0)) != 0) + || (smtp_sasl_passwd_map->error == 0 + && (value = maps_find(smtp_sasl_passwd_map, + STR(iter->host), 0)) != 0) + || (smtp_sasl_passwd_map->error == 0 + && (value = maps_find(smtp_sasl_passwd_map, + STR(iter->dest), 0)) != 0)) { + if (session->sasl_username) + myfree(session->sasl_username); + session->sasl_username = mystrdup(value); + passwd = split_at(session->sasl_username, ':'); + if (session->sasl_passwd) + myfree(session->sasl_passwd); + session->sasl_passwd = mystrdup(passwd ? passwd : ""); + if (msg_verbose) + msg_info("%s: host `%s' user `%s' pass `%s'", + myname, STR(iter->host), + session->sasl_username, session->sasl_passwd); + return (1); + } else if (smtp_sasl_passwd_map->error) { + msg_warn("%s: %s lookup error", + state->request->queue_id, smtp_sasl_passwd_map->title); + vstream_longjmp(session->stream, SMTP_ERR_DATA); + } else { + if (msg_verbose) + msg_info("%s: no auth info found (sender=`%s', host=`%s')", + myname, state->request->sender, STR(iter->host)); + return (0); + } +} + +/* smtp_sasl_initialize - per-process initialization (pre jail) */ + +void smtp_sasl_initialize(void) +{ + + /* + * Sanity check. + */ + if (smtp_sasl_passwd_map || smtp_sasl_impl) + msg_panic("smtp_sasl_initialize: repeated call"); + if (*var_smtp_sasl_passwd == 0) + msg_fatal("specify a password table via the `%s' configuration parameter", + VAR_LMTP_SMTP(SASL_PASSWD)); + + /* + * Open the per-host password table and initialize the SASL library. Use + * shared locks for reading, just in case someone updates the table. + */ + smtp_sasl_passwd_map = maps_create(VAR_LMTP_SMTP(SASL_PASSWD), + var_smtp_sasl_passwd, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if ((smtp_sasl_impl = xsasl_client_init(var_smtp_sasl_type, + var_smtp_sasl_path)) == 0) + msg_fatal("SASL library initialization"); + + /* + * Initialize optional supported mechanism matchlist + */ + if (*var_smtp_sasl_mechs) + smtp_sasl_mechs = string_list_init(VAR_SMTP_SASL_MECHS, + MATCH_FLAG_NONE, + var_smtp_sasl_mechs); + + /* + * Initialize the 535 SASL authentication failure cache. + */ + if (*var_smtp_sasl_auth_cache_name) { +#ifdef HAVE_SASL_AUTH_CACHE + smtp_sasl_auth_cache = + smtp_sasl_auth_cache_init(var_smtp_sasl_auth_cache_name, + var_smtp_sasl_auth_cache_time); +#else + msg_warn("not compiled with TLS support -- " + "ignoring the %s setting", VAR_LMTP_SMTP(SASL_AUTH_CACHE_NAME)); +#endif + } +} + +/* smtp_sasl_connect - per-session client initialization */ + +void smtp_sasl_connect(SMTP_SESSION *session) +{ + + /* + * This initialization happens whenever we instantiate an SMTP session + * object. We don't instantiate a SASL client until we actually need one. + */ + session->sasl_mechanism_list = 0; + session->sasl_username = 0; + session->sasl_passwd = 0; + session->sasl_client = 0; + session->sasl_reply = 0; +} + +/* smtp_sasl_start - per-session SASL initialization */ + +void smtp_sasl_start(SMTP_SESSION *session, const char *sasl_opts_name, + const char *sasl_opts_val) +{ + XSASL_CLIENT_CREATE_ARGS create_args; + SMTP_ITERATOR *iter = session->iterator; + + if (msg_verbose) + msg_info("starting new SASL client"); + if ((session->sasl_client = + XSASL_CLIENT_CREATE(smtp_sasl_impl, &create_args, + stream = session->stream, + service = var_procname, + server_name = STR(iter->host), + security_options = sasl_opts_val)) == 0) + msg_fatal("SASL per-connection initialization failed"); + session->sasl_reply = vstring_alloc(20); +} + +/* smtp_sasl_authenticate - run authentication protocol */ + +int smtp_sasl_authenticate(SMTP_SESSION *session, DSN_BUF *why) +{ + const char *myname = "smtp_sasl_authenticate"; + SMTP_ITERATOR *iter = session->iterator; + SMTP_RESP *resp; + const char *mechanism; + int result; + char *line; + int steps = 0; + + /* + * Sanity check. + */ + if (session->sasl_mechanism_list == 0) + msg_panic("%s: no mechanism list", myname); + + if (msg_verbose) + msg_info("%s: %s: SASL mechanisms %s", + myname, session->namaddrport, session->sasl_mechanism_list); + + /* + * Avoid repeated login failures after a recent 535 error. + */ +#ifdef HAVE_SASL_AUTH_CACHE + if (smtp_sasl_auth_cache + && smtp_sasl_auth_cache_find(smtp_sasl_auth_cache, session)) { + char *resp_dsn = smtp_sasl_auth_cache_dsn(smtp_sasl_auth_cache); + char *resp_str = smtp_sasl_auth_cache_text(smtp_sasl_auth_cache); + + if (var_smtp_sasl_auth_soft_bounce && resp_dsn[0] == '5') + resp_dsn[0] = '4'; + dsb_update(why, resp_dsn, DSB_DEF_ACTION, DSB_MTYPE_DNS, + STR(iter->host), var_procname, resp_str, + "SASL [CACHED] authentication failed; server %s said: %s", + STR(iter->host), resp_str); + return (0); + } +#endif + + /* + * Start the client side authentication protocol. + */ + result = xsasl_client_first(session->sasl_client, + session->sasl_mechanism_list, + session->sasl_username, + session->sasl_passwd, + &mechanism, session->sasl_reply); + if (result != XSASL_AUTH_OK) { + dsb_update(why, "4.7.0", DSB_DEF_ACTION, DSB_SKIP_RMTA, + DSB_DTYPE_SASL, STR(session->sasl_reply), + "SASL authentication failed; " + "cannot authenticate to server %s: %s", + session->namaddr, STR(session->sasl_reply)); + return (-1); + } + /*- + * Send the AUTH command and the optional initial client response. + * + * https://tools.ietf.org/html/rfc4954#page-4 + * Note that the AUTH command is still subject to the line length + * limitations defined in [SMTP]. If use of the initial response argument + * would cause the AUTH command to exceed this length, the client MUST NOT + * use the initial response parameter... + * + * https://tools.ietf.org/html/rfc5321#section-4.5.3.1.4 + * The maximum total length of a command line including the command word + * and the is 512 octets. + * + * Defer the initial response if the resulting command exceeds the limit. + */ + if (LEN(session->sasl_reply) > 0 + && strlen(mechanism) + LEN(session->sasl_reply) + 8 <= 512) { + smtp_chat_cmd(session, "AUTH %s %s", mechanism, + STR(session->sasl_reply)); + VSTRING_RESET(session->sasl_reply); /* no deferred initial reply */ + } else { + smtp_chat_cmd(session, "AUTH %s", mechanism); + } + + /* + * Step through the authentication protocol until the server tells us + * that we are done. If session->sasl_reply is non-empty we have a + * deferred initial reply and expect an empty initial challenge from the + * server. If the server's initial challenge is non-empty we have a SASL + * protocol violation with both sides wanting to go first. + */ + while ((resp = smtp_chat_resp(session))->code / 100 == 3) { + + /* + * Sanity check. + */ + if (++steps > 100) { + dsb_simple(why, "4.3.0", "SASL authentication failed; " + "authentication protocol loop with server %s", + session->namaddr); + return (-1); + } + + /* + * Process a server challenge. + */ + line = resp->str; + (void) mystrtok(&line, "- \t\n"); /* skip over result code */ + + if (LEN(session->sasl_reply) > 0) { + + /* + * Deferred initial response, the server challenge must be empty. + * Cleared after actual transmission to the server. + */ + if (*line) { + dsb_update(why, "4.7.0", DSB_DEF_ACTION, + DSB_SKIP_RMTA, DSB_DTYPE_SASL, "protocol error", + "SASL authentication failed; non-empty initial " + "%s challenge from server %s: %s", mechanism, + session->namaddr, STR(session->sasl_reply)); + return (-1); + } + } else { + result = xsasl_client_next(session->sasl_client, line, + session->sasl_reply); + if (result != XSASL_AUTH_OK) { + dsb_update(why, "4.7.0", DSB_DEF_ACTION, /* Fix 200512 */ + DSB_SKIP_RMTA, DSB_DTYPE_SASL, STR(session->sasl_reply), + "SASL authentication failed; " + "cannot authenticate to server %s: %s", + session->namaddr, STR(session->sasl_reply)); + return (-1); /* Fix 200512 */ + } + } + + /* + * Send a client response. + */ + smtp_chat_cmd(session, "%s", STR(session->sasl_reply)); + VSTRING_RESET(session->sasl_reply); /* clear initial reply */ + } + + /* + * We completed the authentication protocol. + */ + if (resp->code / 100 != 2) { +#ifdef HAVE_SASL_AUTH_CACHE + /* Update the 535 authentication failure cache. */ + if (smtp_sasl_auth_cache && resp->code == 535) + smtp_sasl_auth_cache_store(smtp_sasl_auth_cache, session, resp); +#endif + if (var_smtp_sasl_auth_soft_bounce && resp->code / 100 == 5) + STR(resp->dsn_buf)[0] = '4'; + dsb_update(why, resp->dsn, DSB_DEF_ACTION, + DSB_MTYPE_DNS, STR(iter->host), + var_procname, resp->str, + "SASL authentication failed; server %s said: %s", + session->namaddr, resp->str); + return (0); + } + return (1); +} + +/* smtp_sasl_cleanup - per-session cleanup */ + +void smtp_sasl_cleanup(SMTP_SESSION *session) +{ + if (session->sasl_username) { + myfree(session->sasl_username); + session->sasl_username = 0; + } + if (session->sasl_passwd) { + myfree(session->sasl_passwd); + session->sasl_passwd = 0; + } + if (session->sasl_mechanism_list) { + /* allocated in smtp_sasl_helo_auth */ + myfree(session->sasl_mechanism_list); + session->sasl_mechanism_list = 0; + } + if (session->sasl_client) { + if (msg_verbose) + msg_info("disposing SASL state information"); + xsasl_client_free(session->sasl_client); + session->sasl_client = 0; + } + if (session->sasl_reply) { + vstring_free(session->sasl_reply); + session->sasl_reply = 0; + } +} + +/* smtp_sasl_passivate - append serialized SASL attributes */ + +void smtp_sasl_passivate(SMTP_SESSION *session, VSTRING *buf) +{ +} + +/* smtp_sasl_activate - de-serialize SASL attributes */ + +int smtp_sasl_activate(SMTP_SESSION *session, char *buf) +{ + return (0); +} + +#endif diff --git a/src/smtp/smtp_sasl_proto.c b/src/smtp/smtp_sasl_proto.c new file mode 100644 index 0000000..30ae9ec --- /dev/null +++ b/src/smtp/smtp_sasl_proto.c @@ -0,0 +1,171 @@ +/*++ +/* NAME +/* smtp_sasl_proto 3 +/* SUMMARY +/* Postfix SASL interface for SMTP client +/* SYNOPSIS +/* #include smtp_sasl.h +/* +/* void smtp_sasl_helo_auth(state, words) +/* SMTP_STATE *state; +/* const char *words; +/* +/* int smtp_sasl_helo_login(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* This module contains random chunks of code that implement +/* the SMTP protocol interface for SASL negotiation. The goal +/* is to reduce clutter in the main SMTP client source code. +/* +/* smtp_sasl_helo_auth() processes the AUTH option in the +/* SMTP server's EHLO response. +/* +/* smtp_sasl_helo_login() authenticates the SMTP client to the +/* SMTP server, using the authentication mechanism information +/* given by the server. The result is a Postfix delivery status +/* code in case of trouble. +/* +/* Arguments: +/* .IP state +/* Session context. +/* .IP words +/* List of SASL authentication mechanisms (separated by blanks) +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Original author: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + +#ifdef USE_SASL_AUTH + +/* smtp_sasl_helo_auth - handle AUTH option in EHLO reply */ + +void smtp_sasl_helo_auth(SMTP_SESSION *session, const char *words) +{ + const char *mech_list = sasl_mech_filter(smtp_sasl_mechs, words); + char *junk; + + /* + * XXX If the server offers no compatible authentication mechanisms, then + * pretend that the server doesn't support SASL authentication. + * + * XXX If the server offers multiple different lists, concatenate them. Let + * the SASL library worry about duplicates. + */ + if (session->sasl_mechanism_list) { + if (strcasecmp(session->sasl_mechanism_list, mech_list) != 0 + && strlen(mech_list) > 0 + && strlen(session->sasl_mechanism_list) < var_line_limit) { + junk = concatenate(session->sasl_mechanism_list, " ", mech_list, + (char *) 0); + myfree(session->sasl_mechanism_list); + session->sasl_mechanism_list = junk; + } + return; + } + if (strlen(mech_list) > 0) { + session->sasl_mechanism_list = mystrdup(mech_list); + } else { + msg_warn(*words ? "%s offered no supported AUTH mechanisms: '%s'" : + "%s offered null AUTH mechanism list%s", + session->namaddrport, words); + } + session->features |= SMTP_FEATURE_AUTH; +} + +/* smtp_sasl_helo_login - perform SASL login */ + +int smtp_sasl_helo_login(SMTP_STATE *state) +{ + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + int ret; + + /* + * Skip authentication when no authentication info exists for this + * server, so that we talk to each other like strangers. + */ + if (smtp_sasl_passwd_lookup(session) == 0) { + session->features &= ~SMTP_FEATURE_AUTH; + return 0; + } + + /* + * Otherwise, if authentication information exists, assume that + * authentication is required, and assume that an authentication error is + * recoverable from the message delivery point of view. An authentication + * error is unrecoverable from a session point of view - the session will + * not be reused. + */ + ret = 0; + if (session->sasl_mechanism_list == 0) { + dsb_simple(why, "4.7.0", "SASL authentication failed: " + "server %s offered no compatible authentication mechanisms for this type of connection security", + session->namaddr); + ret = smtp_sess_fail(state); + /* Session reuse is disabled. */ + } else { +#ifndef USE_TLS + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_OPTS), var_smtp_sasl_opts); +#else + if (session->tls_context == 0) + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_OPTS), + var_smtp_sasl_opts); + else if (TLS_CERT_IS_MATCHED(session->tls_context)) + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_TLSV_OPTS), + var_smtp_sasl_tlsv_opts); + else + smtp_sasl_start(session, VAR_LMTP_SMTP(SASL_TLS_OPTS), + var_smtp_sasl_tls_opts); +#endif + if (smtp_sasl_authenticate(session, why) <= 0) { + ret = smtp_sess_fail(state); + /* Session reuse is disabled. */ + } + } + return (ret); +} + +#endif diff --git a/src/smtp/smtp_session.c b/src/smtp/smtp_session.c new file mode 100644 index 0000000..9f13978 --- /dev/null +++ b/src/smtp/smtp_session.c @@ -0,0 +1,447 @@ +/*++ +/* NAME +/* smtp_session 3 +/* SUMMARY +/* SMTP_SESSION structure management +/* SYNOPSIS +/* #include "smtp.h" +/* +/* SMTP_SESSION *smtp_session_alloc(stream, iter, start, flags) +/* VSTREAM *stream; +/* SMTP_ITERATOR *iter; +/* time_t start; +/* int flags; +/* +/* void smtp_session_free(session) +/* SMTP_SESSION *session; +/* +/* int smtp_session_passivate(session, dest_prop, endp_prop) +/* SMTP_SESSION *session; +/* VSTRING *dest_prop; +/* VSTRING *endp_prop; +/* +/* SMTP_SESSION *smtp_session_activate(fd, iter, dest_prop, endp_prop) +/* int fd; +/* SMTP_ITERATOR *iter; +/* VSTRING *dest_prop; +/* VSTRING *endp_prop; +/* DESCRIPTION +/* smtp_session_alloc() allocates memory for an SMTP_SESSION structure +/* and initializes it with the given stream and destination, host name +/* and address information. The host name and address strings are +/* copied. The port is in network byte order. +/* +/* smtp_session_free() destroys an SMTP_SESSION structure and its +/* members, making memory available for reuse. It will handle the +/* case of a null stream and will assume it was given a different +/* purpose. +/* +/* smtp_session_passivate() flattens an SMTP session (including +/* TLS context) so that it can be cached. The SMTP_SESSION +/* structure is destroyed. +/* +/* smtp_session_activate() inflates a flattened SMTP session +/* so that it can be used. The input property arguments are +/* modified. +/* +/* Arguments: +/* .IP stream +/* A full-duplex stream. +/* .IP iter +/* The literal next-hop or fall-back destination including +/* the optional [] and including the :port or :service; +/* the name of the remote host; +/* the printable address of the remote host; +/* the remote port in network byte order. +/* .IP start +/* The time when this connection was opened. +/* .IP flags +/* Zero or more of the following: +/* .RS +/* .IP SMTP_MISC_FLAG_CONN_LOAD +/* Enable re-use of cached SMTP or LMTP connections. +/* .IP SMTP_MISC_FLAG_CONN_STORE +/* Enable saving of cached SMTP or LMTP connections. +/* .RE +/* SMTP_MISC_FLAG_CONN_MASK corresponds with both _LOAD and _STORE. +/* .IP dest_prop +/* Destination specific session properties: the server is the +/* best MX host for the current logical destination, the dest, +/* host, and addr properties. When dest_prop is non-empty, it +/* overrides the iterator dest, host, and addr properties. It +/* is the caller's responsibility to save the current nexthop +/* with SMTP_ITER_SAVE_DEST() and to restore it afterwards +/* with SMTP_ITER_RESTORE_DEST() before trying alternatives. +/* .IP endp_prop +/* Endpoint specific session properties: all the features +/* advertised by the remote server. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Viktor Dukhovni +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* TLS Library. */ + +#include + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + + /* + * Local, because these are meaningful only for code in this file. + */ +#define SESS_ATTR_DEST "destination" +#define SESS_ATTR_HOST "host_name" +#define SESS_ATTR_ADDR "host_addr" +#define SESS_ATTR_DEST_FEATURES "destination_features" + +#define SESS_ATTR_TLS_LEVEL "tls_level" +#define SESS_ATTR_REUSE_COUNT "reuse_count" +#define SESS_ATTR_ENDP_FEATURES "endpoint_features" +#define SESS_ATTR_EXPIRE_TIME "expire_time" + +/* smtp_session_alloc - allocate and initialize SMTP_SESSION structure */ + +SMTP_SESSION *smtp_session_alloc(VSTREAM *stream, SMTP_ITERATOR *iter, + time_t start, int flags) +{ + SMTP_SESSION *session; + const char *host = STR(iter->host); + const char *addr = STR(iter->addr); + unsigned port = iter->port; + + session = (SMTP_SESSION *) mymalloc(sizeof(*session)); + session->stream = stream; + session->iterator = iter; + session->namaddr = concatenate(host, "[", addr, "]", (char *) 0); + session->helo = 0; + session->port = port; + session->features = 0; + + session->size_limit = 0; + session->error_mask = 0; + session->buffer = vstring_alloc(100); + session->scratch = vstring_alloc(100); + session->scratch2 = vstring_alloc(100); + smtp_chat_init(session); + session->mime_state = 0; + + if (session->port) { + vstring_sprintf(session->buffer, "%s:%d", + session->namaddr, ntohs(session->port)); + session->namaddrport = mystrdup(STR(session->buffer)); + } else + session->namaddrport = mystrdup(session->namaddr); + + session->send_proto_helo = 0; + + if (flags & SMTP_MISC_FLAG_CONN_STORE) + CACHE_THIS_SESSION_UNTIL(start + var_smtp_reuse_time); + else + DONT_CACHE_THIS_SESSION; + session->reuse_count = 0; + USE_NEWBORN_SESSION; /* He's not dead Jim! */ + +#ifdef USE_SASL_AUTH + smtp_sasl_connect(session); +#endif + +#ifdef USE_TLS + session->tls_context = 0; + session->tls_retry_plain = 0; + session->tls_nexthop = 0; +#endif + session->state = 0; + debug_peer_check(host, addr); + return (session); +} + +/* smtp_session_free - destroy SMTP_SESSION structure and contents */ + +void smtp_session_free(SMTP_SESSION *session) +{ +#ifdef USE_TLS + if (session->stream) { + vstream_fflush(session->stream); + } + if (session->tls_context) { + if (session->features & + (SMTP_FEATURE_FROM_CACHE | SMTP_FEATURE_FROM_PROXY)) + tls_proxy_context_free(session->tls_context); + else + tls_client_stop(smtp_tls_ctx, session->stream, + var_smtp_starttls_tmout, 0, session->tls_context); + } +#endif + if (session->stream) + vstream_fclose(session->stream); + myfree(session->namaddr); + myfree(session->namaddrport); + if (session->helo) + myfree(session->helo); + + vstring_free(session->buffer); + vstring_free(session->scratch); + vstring_free(session->scratch2); + + if (session->history) + smtp_chat_reset(session); + if (session->mime_state) + mime_state_free(session->mime_state); + +#ifdef USE_SASL_AUTH + smtp_sasl_cleanup(session); +#endif + + if (session->state->debug_peer_per_nexthop == 0) + debug_peer_restore(); + myfree((void *) session); +} + +/* smtp_session_passivate - passivate an SMTP_SESSION object */ + +int smtp_session_passivate(SMTP_SESSION *session, VSTRING *dest_prop, + VSTRING *endp_prop) +{ + SMTP_ITERATOR *iter = session->iterator; + VSTREAM *mp; + int fd; + + /* + * Encode the delivery request next-hop to endpoint binding properties: + * whether or not this server is best MX host for the delivery request + * next-hop or fall-back logical destination (this information is needed + * for loop handling in smtp_proto()). + * + * TODO: save SASL username and password information so that we can + * correctly save a reused authenticated connection. + * + * These memory writes should never fail. + */ + if ((mp = vstream_memopen(dest_prop, O_WRONLY)) == 0 + || attr_print_plain(mp, ATTR_FLAG_NONE, + SEND_ATTR_STR(SESS_ATTR_DEST, STR(iter->dest)), + SEND_ATTR_STR(SESS_ATTR_HOST, STR(iter->host)), + SEND_ATTR_STR(SESS_ATTR_ADDR, STR(iter->addr)), + SEND_ATTR_INT(SESS_ATTR_DEST_FEATURES, + session->features & SMTP_FEATURE_DESTINATION_MASK), + ATTR_TYPE_END) != 0 + || vstream_fclose(mp) != 0) + msg_fatal("smtp_session_passivate: can't save dest properties: %m"); + + /* + * Encode the physical endpoint properties: all the session properties + * except for "session from cache", "best MX", or "RSET failure". Plus + * the TLS level, reuse count, and connection expiration time. + * + * XXX Should also record how many non-delivering mail transactions there + * were during this session, and perhaps other statistics, so that we + * don't reuse a session too much. + * + * TODO: passivate SASL username and password information so that we can + * correctly save a reused authenticated connection. + * + * These memory writes should never fail. + */ + if ((mp = vstream_memopen(endp_prop, O_WRONLY)) == 0 + || attr_print_plain(mp, ATTR_FLAG_NONE, +#ifdef USE_TLS + SEND_ATTR_INT(SESS_ATTR_TLS_LEVEL, + session->state->tls->level), +#endif + SEND_ATTR_INT(SESS_ATTR_REUSE_COUNT, + session->reuse_count), + SEND_ATTR_INT(SESS_ATTR_ENDP_FEATURES, + session->features & SMTP_FEATURE_ENDPOINT_MASK), + SEND_ATTR_LONG(SESS_ATTR_EXPIRE_TIME, + (long) session->expire_time), + ATTR_TYPE_END) != 0 + + /* + * Append the passivated TLS context. These memory writes should never + * fail. + */ +#ifdef USE_TLS + || (session->tls_context + && attr_print_plain(mp, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_context_print, + (void *) session->tls_context), + ATTR_TYPE_END) != 0) +#endif + || vstream_fclose(mp) != 0) + msg_fatal("smtp_session_passivate: cannot save TLS context: %m"); + + /* + * Salvage the underlying file descriptor, and destroy the session + * object. + */ + fd = vstream_fileno(session->stream); + vstream_fdclose(session->stream); + session->stream = 0; + smtp_session_free(session); + + return (fd); +} + +/* smtp_session_activate - re-activate a passivated SMTP_SESSION object */ + +SMTP_SESSION *smtp_session_activate(int fd, SMTP_ITERATOR *iter, + VSTRING *dest_prop, + VSTRING *endp_prop) +{ + const char *myname = "smtp_session_activate"; + VSTREAM *mp; + SMTP_SESSION *session; + int endp_features; /* server features */ + int dest_features; /* server features */ + long expire_time; /* session re-use expiration time */ + int reuse_count; /* # times reused */ + +#ifdef USE_TLS + TLS_SESS_STATE *tls_context = 0; + SMTP_TLS_POLICY *tls = iter->parent->tls; + +#define TLS_PROXY_CONTEXT_FREE() do { \ + if (tls_context) \ + tls_proxy_context_free(tls_context); \ + } while (0) +#else +#define TLS_PROXY_CONTEXT_FREE() /* nothing */ +#endif + +#define SMTP_SESSION_ACTIVATE_ERR_RETURN() do { \ + TLS_PROXY_CONTEXT_FREE(); \ + return (0); \ + } while (0) + + /* + * Sanity check: if TLS is required, the cached properties must contain a + * TLS context. + */ + if ((mp = vstream_memopen(endp_prop, O_RDONLY)) == 0 + || attr_scan_plain(mp, ATTR_FLAG_NONE, +#ifdef USE_TLS + RECV_ATTR_INT(SESS_ATTR_TLS_LEVEL, + &tls->level), +#endif + RECV_ATTR_INT(SESS_ATTR_REUSE_COUNT, + &reuse_count), + RECV_ATTR_INT(SESS_ATTR_ENDP_FEATURES, + &endp_features), + RECV_ATTR_LONG(SESS_ATTR_EXPIRE_TIME, + &expire_time), + ATTR_TYPE_END) != 4 +#ifdef USE_TLS + || ((tls->level > TLS_LEV_MAY + || (tls->level == TLS_LEV_MAY && vstream_peek(mp) > 0)) + && attr_scan_plain(mp, ATTR_FLAG_NONE, + RECV_ATTR_FUNC(tls_proxy_context_scan, + (void *) &tls_context), + ATTR_TYPE_END) != 1) +#endif + || vstream_fclose(mp) != 0) { + msg_warn("smtp_session_activate: bad cached endp properties"); + SMTP_SESSION_ACTIVATE_ERR_RETURN(); + } + + /* + * Clobber the iterator's current nexthop, host and address fields with + * cached-connection information. This is done when a session is looked + * up by delivery request nexthop instead of address and port. It is the + * caller's responsibility to save and restore the delivery request + * nexthop with SMTP_ITER_SAVE_DEST() and SMTP_ITER_RESTORE_DEST(). + * + * TODO: Eliminate the duplication between SMTP_ITERATOR and SMTP_SESSION. + * + * TODO: restore SASL username and password information so that we can + * correctly save a reused authenticated connection. + */ + if (dest_prop && VSTRING_LEN(dest_prop)) { + if ((mp = vstream_memopen(dest_prop, O_RDONLY)) == 0 + || attr_scan_plain(mp, ATTR_FLAG_NONE, + RECV_ATTR_STR(SESS_ATTR_DEST, iter->dest), + RECV_ATTR_STR(SESS_ATTR_HOST, iter->host), + RECV_ATTR_STR(SESS_ATTR_ADDR, iter->addr), + RECV_ATTR_INT(SESS_ATTR_DEST_FEATURES, + &dest_features), + ATTR_TYPE_END) != 4 + || vstream_fclose(mp) != 0) { + msg_warn("smtp_session_passivate: bad cached dest properties"); + SMTP_SESSION_ACTIVATE_ERR_RETURN(); + } + } else { + dest_features = 0; + } +#ifdef USE_TLS + if (msg_verbose) + msg_info("%s: tls_level=%d", myname, tls->level); +#endif + + /* + * Allright, bundle up what we have sofar. + */ +#define NO_FLAGS 0 + + session = smtp_session_alloc(vstream_fdopen(fd, O_RDWR), iter, + (time_t) 0, NO_FLAGS); + session->features = + (endp_features | dest_features | SMTP_FEATURE_FROM_CACHE); +#ifdef USE_TLS + session->tls_context = tls_context; +#endif + CACHE_THIS_SESSION_UNTIL(expire_time); + session->reuse_count = ++reuse_count; + + if (msg_verbose) + msg_info("%s: dest=%s host=%s addr=%s port=%u features=0x%x, " + "ttl=%ld, reuse=%d", + myname, STR(iter->dest), STR(iter->host), + STR(iter->addr), ntohs(iter->port), + endp_features | dest_features, + (long) (expire_time - time((time_t *) 0)), + reuse_count); + +#if USE_TLS + if (tls_context) + tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_USED, + session->tls_context); +#endif + + return (session); +} diff --git a/src/smtp/smtp_state.c b/src/smtp/smtp_state.c new file mode 100644 index 0000000..6b81fa4 --- /dev/null +++ b/src/smtp/smtp_state.c @@ -0,0 +1,124 @@ +/*++ +/* NAME +/* smtp_state 3 +/* SUMMARY +/* initialize/cleanup shared state +/* SYNOPSIS +/* #include "smtp.h" +/* +/* SMTP_STATE *smtp_state_alloc() +/* +/* void smtp_state_free(state) +/* SMTP_STATE *state; +/* DESCRIPTION +/* smtp_state_init() initializes the shared state, and allocates +/* memory for buffers etc. +/* +/* smtp_cleanup() destroys memory allocated by smtp_state_init(). +/* STANDARDS +/* DIAGNOSTICS +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + +/* smtp_state_alloc - initialize */ + +SMTP_STATE *smtp_state_alloc(void) +{ + SMTP_STATE *state = (SMTP_STATE *) mymalloc(sizeof(*state)); + + state->misc_flags = 0; + state->src = 0; + state->service = 0; + state->request = 0; + state->session = 0; + state->status = 0; + state->space_left = 0; + state->iterator->request_nexthop = vstring_alloc(100); + state->iterator->dest = vstring_alloc(100); + state->iterator->host = vstring_alloc(100); + state->iterator->addr = vstring_alloc(100); + state->iterator->saved_dest = vstring_alloc(100); + if (var_smtp_cache_conn) { + state->dest_label = vstring_alloc(10); + state->dest_prop = vstring_alloc(10); + state->endp_label = vstring_alloc(10); + state->endp_prop = vstring_alloc(10); + state->cache_used = htable_create(1); + } else { + state->dest_label = 0; + state->dest_prop = 0; + state->endp_label = 0; + state->endp_prop = 0; + state->cache_used = 0; + } + state->why = dsb_create(); + state->debug_peer_per_nexthop = 0; + state->logged_line_length_limit = 0; + return (state); +} + +/* smtp_state_free - destroy state */ + +void smtp_state_free(SMTP_STATE *state) +{ +#ifdef USE_TLS + /* The TLS policy cache lifetime is one delivery. */ + smtp_tls_policy_cache_flush(); +#endif + vstring_free(state->iterator->request_nexthop); + vstring_free(state->iterator->dest); + vstring_free(state->iterator->host); + vstring_free(state->iterator->addr); + vstring_free(state->iterator->saved_dest); + if (state->dest_label) + vstring_free(state->dest_label); + if (state->dest_prop) + vstring_free(state->dest_prop); + if (state->endp_label) + vstring_free(state->endp_label); + if (state->endp_prop) + vstring_free(state->endp_prop); + if (state->cache_used) + htable_free(state->cache_used, (void (*) (void *)) 0); + if (state->why) + dsb_free(state->why); + if (state->debug_peer_per_nexthop) + debug_peer_restore(); + + myfree((void *) state); +} diff --git a/src/smtp/smtp_tls_policy.c b/src/smtp/smtp_tls_policy.c new file mode 100644 index 0000000..92a231d --- /dev/null +++ b/src/smtp/smtp_tls_policy.c @@ -0,0 +1,927 @@ +/*++ +/* NAME +/* smtp_tls_policy 3 +/* SUMMARY +/* SMTP_TLS_POLICY structure management +/* SYNOPSIS +/* #include "smtp.h" +/* +/* void smtp_tls_list_init() +/* +/* int smtp_tls_policy_cache_query(why, tls, iter) +/* DSN_BUF *why; +/* SMTP_TLS_POLICY *tls; +/* SMTP_ITERATOR *iter; +/* +/* void smtp_tls_policy_dummy(tls) +/* SMTP_TLS_POLICY *tls; +/* +/* void smtp_tls_policy_cache_flush() +/* DESCRIPTION +/* smtp_tls_list_init() initializes lookup tables used by the TLS +/* policy engine. +/* +/* smtp_tls_policy_cache_query() returns a shallow copy of the +/* cached SMTP_TLS_POLICY structure for the iterator's +/* destination, host, port and DNSSEC validation status. +/* This copy is guaranteed to be valid until the next +/* smtp_tls_policy_cache_query() or smtp_tls_policy_cache_flush() +/* call. The caller can override the TLS security level without +/* corrupting the policy cache. +/* When any required table or DNS lookups fail, the TLS level +/* is set to TLS_LEV_INVALID, the "why" argument is updated +/* with the error reason and the result value is zero (false). +/* +/* smtp_tls_policy_dummy() initializes a trivial, non-cached, +/* policy with TLS disabled. +/* +/* smtp_tls_policy_cache_flush() destroys the TLS policy cache +/* and contents. +/* +/* Arguments: +/* .IP why +/* A pointer to a DSN_BUF which holds error status information when +/* the TLS policy lookup fails. +/* .IP tls +/* Pointer to TLS policy storage. +/* .IP iter +/* The literal next-hop or fall-back destination including +/* the optional [] and including the :port or :service; +/* the name of the remote host after MX and CNAME expansions +/* (see smtp_cname_overrides_servername for the handling +/* of hostnames that resolve to a CNAME record); +/* the printable address of the remote host; +/* the remote port in network byte order; +/* the DNSSEC validation status of the host name lookup after +/* MX and CNAME expansions. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Viktor Dukhovni +/*--*/ + +/* System library. */ + +#include + +#ifdef USE_TLS + +#include /* ntohs() for Solaris or BSD */ +#include /* ntohs() for Linux or BSD */ +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* DNS library. */ + +#include + +/* Application-specific. */ + +#include "smtp.h" + +/* XXX Cache size should scale with [sl]mtp_mx_address_limit. */ +#define CACHE_SIZE 20 +static CTABLE *policy_cache; + +static int global_tls_level(void); +static void dane_init(SMTP_TLS_POLICY *, SMTP_ITERATOR *); + +static MAPS *tls_policy; /* lookup table(s) */ +static MAPS *tls_per_site; /* lookup table(s) */ + +/* smtp_tls_list_init - initialize per-site policy lists */ + +void smtp_tls_list_init(void) +{ + if (*var_smtp_tls_policy) { + tls_policy = maps_create(VAR_LMTP_SMTP(TLS_POLICY), + var_smtp_tls_policy, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + if (*var_smtp_tls_per_site) + msg_warn("%s ignored when %s is not empty.", + VAR_LMTP_SMTP(TLS_PER_SITE), VAR_LMTP_SMTP(TLS_POLICY)); + return; + } + if (*var_smtp_tls_per_site) { + tls_per_site = maps_create(VAR_LMTP_SMTP(TLS_PER_SITE), + var_smtp_tls_per_site, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + } +} + +/* policy_name - printable tls policy level */ + +static const char *policy_name(int tls_level) +{ + const char *name = str_tls_level(tls_level); + + if (name == 0) + name = "unknown"; + return name; +} + +#define MARK_INVALID(why, levelp) do { \ + dsb_simple((why), "4.7.5", "client TLS configuration problem"); \ + *(levelp) = TLS_LEV_INVALID; } while (0) + +/* tls_site_lookup - look up per-site TLS security level */ + +static void tls_site_lookup(SMTP_TLS_POLICY *tls, int *site_level, + const char *site_name, const char *site_class) +{ + const char *lookup; + + /* + * Look up a non-default policy. In case of multiple lookup results, the + * precedence order is a permutation of the TLS enforcement level order: + * VERIFY, ENCRYPT, NONE, MAY, NOTFOUND. I.e. we override MAY with a more + * specific policy including NONE, otherwise we choose the stronger + * enforcement level. + */ + if ((lookup = maps_find(tls_per_site, site_name, 0)) != 0) { + if (!strcasecmp(lookup, "NONE")) { + /* NONE overrides MAY or NOTFOUND. */ + if (*site_level <= TLS_LEV_MAY) + *site_level = TLS_LEV_NONE; + } else if (!strcasecmp(lookup, "MAY")) { + /* MAY overrides NOTFOUND but not NONE. */ + if (*site_level < TLS_LEV_NONE) + *site_level = TLS_LEV_MAY; + } else if (!strcasecmp(lookup, "MUST_NOPEERMATCH")) { + if (*site_level < TLS_LEV_ENCRYPT) + *site_level = TLS_LEV_ENCRYPT; + } else if (!strcasecmp(lookup, "MUST")) { + if (*site_level < TLS_LEV_VERIFY) + *site_level = TLS_LEV_VERIFY; + } else { + msg_warn("%s: unknown TLS policy '%s' for %s %s", + tls_per_site->title, lookup, site_class, site_name); + MARK_INVALID(tls->why, site_level); + return; + } + } else if (tls_per_site->error) { + msg_warn("%s: %s \"%s\": per-site table lookup error", + tls_per_site->title, site_class, site_name); + dsb_simple(tls->why, "4.3.0", "Temporary lookup error"); + *site_level = TLS_LEV_INVALID; + return; + } + return; +} + +/* tls_policy_lookup_one - look up destination TLS policy */ + +static void tls_policy_lookup_one(SMTP_TLS_POLICY *tls, int *site_level, + const char *site_name, + const char *site_class) +{ + const char *lookup; + char *policy; + char *saved_policy; + char *tok; + const char *err; + char *name; + char *val; + static VSTRING *cbuf; + +#undef FREE_RETURN +#define FREE_RETURN do { myfree(saved_policy); return; } while (0) + +#define INVALID_RETURN(why, levelp) do { \ + MARK_INVALID((why), (levelp)); FREE_RETURN; } while (0) + +#define WHERE \ + STR(vstring_sprintf(cbuf, "%s, %s \"%s\"", \ + tls_policy->title, site_class, site_name)) + + if (cbuf == 0) + cbuf = vstring_alloc(10); + + if ((lookup = maps_find(tls_policy, site_name, 0)) == 0) { + if (tls_policy->error) { + msg_warn("%s: policy table lookup error", WHERE); + MARK_INVALID(tls->why, site_level); + } + return; + } + saved_policy = policy = mystrdup(lookup); + + if ((tok = mystrtok(&policy, CHARS_COMMA_SP)) == 0) { + msg_warn("%s: invalid empty policy", WHERE); + INVALID_RETURN(tls->why, site_level); + } + *site_level = tls_level_lookup(tok); + if (*site_level == TLS_LEV_INVALID) { + /* tls_level_lookup() logs no warning. */ + msg_warn("%s: invalid security level \"%s\"", WHERE, tok); + INVALID_RETURN(tls->why, site_level); + } + + /* + * Warn about ignored attributes when TLS is disabled. + */ + if (*site_level < TLS_LEV_MAY) { + while ((tok = mystrtok(&policy, CHARS_COMMA_SP)) != 0) + msg_warn("%s: ignoring attribute \"%s\" with TLS disabled", + WHERE, tok); + FREE_RETURN; + } + + /* + * Errors in attributes may have security consequences, don't ignore + * errors that can degrade security. + */ + while ((tok = mystrtok(&policy, CHARS_COMMA_SP)) != 0) { + if ((err = split_nameval(tok, &name, &val)) != 0) { + msg_warn("%s: malformed attribute/value pair \"%s\": %s", + WHERE, tok, err); + INVALID_RETURN(tls->why, site_level); + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "ciphers")) { + if (*val == 0) { + msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + if (tls->grade) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + tls->grade = mystrdup(val); + continue; + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "protocols")) { + if (tls->protocols) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + tls->protocols = mystrdup(val); + continue; + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "servername")) { + if (tls->sni) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + if (valid_hostname(val, DONT_GRIPE)) + tls->sni = mystrdup(val); + else { + msg_warn("%s: \"%s=%s\" specifies an invalid hostname", + WHERE, name, val); + INVALID_RETURN(tls->why, site_level); + } + continue; + } + /* Multiple instances per policy. */ + if (!strcasecmp(name, "match")) { + if (*val == 0) { + msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + switch (*site_level) { + default: + msg_warn("%s: attribute \"%s\" invalid at security level " + "\"%s\"", WHERE, name, policy_name(*site_level)); + INVALID_RETURN(tls->why, site_level); + break; + case TLS_LEV_FPRINT: + if (!tls->dane) + tls->dane = tls_dane_alloc(); + tls_dane_add_fpt_digests(tls->dane, val, "|", smtp_mode); + break; + case TLS_LEV_VERIFY: + case TLS_LEV_SECURE: + if (tls->matchargv == 0) + tls->matchargv = argv_split(val, ":"); + else + argv_split_append(tls->matchargv, val, ":"); + break; + } + continue; + } + /* Only one instance per policy. */ + if (!strcasecmp(name, "exclude")) { + if (tls->exclusions) { + msg_warn("%s: attribute \"%s\" is specified multiple times", + WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + tls->exclusions = vstring_strcpy(vstring_alloc(10), val); + continue; + } + /* Multiple instances per policy. */ + if (!strcasecmp(name, "tafile")) { + /* Only makes sense if we're using CA-based trust */ + if (!TLS_MUST_PKIX(*site_level)) { + msg_warn("%s: attribute \"%s\" invalid at security level" + " \"%s\"", WHERE, name, policy_name(*site_level)); + INVALID_RETURN(tls->why, site_level); + } + if (*val == 0) { + msg_warn("%s: attribute \"%s\" has empty value", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + if (!tls->dane) + tls->dane = tls_dane_alloc(); + if (!tls_dane_load_trustfile(tls->dane, val)) { + INVALID_RETURN(tls->why, site_level); + } + continue; + } + /* Last one wins. */ + if (!strcasecmp(name, "connection_reuse")) { + if (strcasecmp(val, "yes") == 0) { + tls->conn_reuse = 1; + } else if (strcasecmp(val, "no") == 0) { + tls->conn_reuse = 0; + } else { + msg_warn("%s: attribute \"%s\" has bad value: \"%s\"", + WHERE, name, val); + INVALID_RETURN(tls->why, site_level); + } + continue; + } + msg_warn("%s: invalid attribute name: \"%s\"", WHERE, name); + INVALID_RETURN(tls->why, site_level); + } + + FREE_RETURN; +} + +/* tls_policy_lookup - look up destination TLS policy */ + +static void tls_policy_lookup(SMTP_TLS_POLICY *tls, int *site_level, + const char *site_name, + const char *site_class) +{ + + /* + * Only one lookup with [nexthop]:port, [nexthop] or nexthop:port These + * are never the domain part of localpart@domain, rather they are + * explicit nexthops from transport:nexthop, and match only the + * corresponding policy. Parent domain matching (below) applies only to + * sub-domains of the recipient domain. + * + * XXX UNIX-domain connections query with the pathname as destination. + */ + if (!valid_utf8_hostname(var_smtputf8_enable, site_name, DONT_GRIPE)) { + tls_policy_lookup_one(tls, site_level, site_name, site_class); + return; + } + do { + tls_policy_lookup_one(tls, site_level, site_name, site_class); + } while (*site_level == TLS_LEV_NOTFOUND + && (site_name = strchr(site_name + 1, '.')) != 0); +} + +/* load_tas - load one or more ta files */ + +static int load_tas(TLS_DANE *dane, const char *files) +{ + int ret = 0; + char *save = mystrdup(files); + char *buf = save; + char *file; + + do { + if ((file = mystrtok(&buf, CHARS_COMMA_SP)) != 0) + ret = tls_dane_load_trustfile(dane, file); + } while (file && ret); + + myfree(save); + return (ret); +} + +/* set_cipher_grade - Set cipher grade and exclusions */ + +static void set_cipher_grade(SMTP_TLS_POLICY *tls) +{ + const char *mand_exclude = ""; + const char *also_exclude = ""; + + /* + * Use main.cf cipher level if no per-destination value specified. With + * mandatory encryption at least encrypt, and with mandatory verification + * at least authenticate! + */ + switch (tls->level) { + case TLS_LEV_INVALID: + case TLS_LEV_NONE: + return; + + case TLS_LEV_MAY: + if (tls->grade == 0) + tls->grade = mystrdup(var_smtp_tls_ciph); + break; + + case TLS_LEV_ENCRYPT: + if (tls->grade == 0) + tls->grade = mystrdup(var_smtp_tls_mand_ciph); + mand_exclude = var_smtp_tls_mand_excl; + also_exclude = "eNULL"; + break; + + case TLS_LEV_HALF_DANE: + case TLS_LEV_DANE: + case TLS_LEV_DANE_ONLY: + case TLS_LEV_FPRINT: + case TLS_LEV_VERIFY: + case TLS_LEV_SECURE: + if (tls->grade == 0) + tls->grade = mystrdup(var_smtp_tls_mand_ciph); + mand_exclude = var_smtp_tls_mand_excl; + also_exclude = "aNULL"; + break; + } + +#define ADD_EXCLUDE(vstr, str) \ + do { \ + if (*(str)) \ + vstring_sprintf_append((vstr), "%s%s", \ + VSTRING_LEN(vstr) ? " " : "", (str)); \ + } while (0) + + /* + * The "exclude" policy table attribute overrides main.cf exclusion + * lists. + */ + if (tls->exclusions == 0) { + tls->exclusions = vstring_alloc(10); + ADD_EXCLUDE(tls->exclusions, var_smtp_tls_excl_ciph); + ADD_EXCLUDE(tls->exclusions, mand_exclude); + } + ADD_EXCLUDE(tls->exclusions, also_exclude); +} + +/* policy_create - create SMTP TLS policy cache object (ctable call-back) */ + +static void *policy_create(const char *unused_key, void *context) +{ + SMTP_ITERATOR *iter = (SMTP_ITERATOR *) context; + int site_level; + const char *dest = STR(iter->dest); + const char *host = STR(iter->host); + + /* + * Prepare a pristine policy object. + */ + SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) mymalloc(sizeof(*tls)); + + smtp_tls_policy_init(tls, dsb_create()); + tls->conn_reuse = var_smtp_tls_conn_reuse; + + /* + * Compute the per-site TLS enforcement level. For compatibility with the + * original TLS patch, this algorithm is gives equal precedence to host + * and next-hop policies. + */ + tls->level = global_tls_level(); + site_level = TLS_LEV_NOTFOUND; + + if (tls_policy) { + tls_policy_lookup(tls, &site_level, dest, "next-hop destination"); + } else if (tls_per_site) { + tls_site_lookup(tls, &site_level, dest, "next-hop destination"); + if (site_level != TLS_LEV_INVALID + && strcasecmp_utf8(dest, host) != 0) + tls_site_lookup(tls, &site_level, host, "server hostname"); + + /* + * Override a wild-card per-site policy with a more specific global + * policy. + * + * With the original TLS patch, 1) a per-site ENCRYPT could not override + * a global VERIFY, and 2) a combined per-site (NONE+MAY) policy + * produced inconsistent results: it changed a global VERIFY into + * NONE, while producing MAY with all weaker global policy settings. + * + * With the current implementation, a combined per-site (NONE+MAY) + * consistently overrides global policy with NONE, and global policy + * can override only a per-site MAY wildcard. That is, specific + * policies consistently override wildcard policies, and + * (non-wildcard) per-site policies consistently override global + * policies. + */ + if (site_level == TLS_LEV_MAY && tls->level > TLS_LEV_MAY) + site_level = tls->level; + } + switch (site_level) { + default: + tls->level = site_level; + /* FALLTHROUGH */ + case TLS_LEV_NOTFOUND: + break; + case TLS_LEV_INVALID: + tls->level = site_level; + return ((void *) tls); + } + + /* + * DANE initialization may change the security level to something else, + * so do this early, so that we use the right level below. Note that + * "dane-only" changes to "dane" once we obtain the requisite TLSA + * records. + */ + if (TLS_DANE_BASED(tls->level)) + dane_init(tls, iter); + if (tls->level == TLS_LEV_INVALID) + return ((void *) tls); + + /* + * Use main.cf protocols and SNI settings if not set in per-destination + * table. + */ + if (tls->level > TLS_LEV_NONE && tls->protocols == 0) + tls->protocols = + mystrdup((tls->level == TLS_LEV_MAY) ? + var_smtp_tls_proto : var_smtp_tls_mand_proto); + if (tls->level > TLS_LEV_NONE && tls->sni == 0) { + if (!*var_smtp_tls_sni || valid_hostname(var_smtp_tls_sni, DONT_GRIPE)) + tls->sni = mystrdup(var_smtp_tls_sni); + else { + msg_warn("\"%s = %s\" specifies an invalid hostname", + VAR_LMTP_SMTP(TLS_SNI), var_smtp_tls_sni); + MARK_INVALID(tls->why, &tls->level); + return ((void *) tls); + } + } + + /* + * Compute cipher grade (if set in per-destination table, else + * set_cipher() uses main.cf settings) and security level dependent + * cipher exclusion list. + */ + set_cipher_grade(tls); + + /* + * Use main.cf cert_match setting if not set in per-destination table. + */ + switch (tls->level) { + case TLS_LEV_INVALID: + case TLS_LEV_NONE: + case TLS_LEV_MAY: + case TLS_LEV_ENCRYPT: + case TLS_LEV_HALF_DANE: + case TLS_LEV_DANE: + case TLS_LEV_DANE_ONLY: + break; + case TLS_LEV_FPRINT: + if (tls->dane == 0) + tls->dane = tls_dane_alloc(); + if (tls->dane->tlsa == 0) { + tls_dane_add_fpt_digests(tls->dane, var_smtp_tls_fpt_cmatch, + CHARS_COMMA_SP, smtp_mode); + if (tls->dane->tlsa == 0) { + msg_warn("nexthop domain %s: configured at fingerprint " + "security level, but with no fingerprints to match.", + dest); + MARK_INVALID(tls->why, &tls->level); + return ((void *) tls); + } + } + break; + case TLS_LEV_VERIFY: + case TLS_LEV_SECURE: + if (tls->matchargv == 0) + tls->matchargv = + argv_split(tls->level == TLS_LEV_VERIFY ? + var_smtp_tls_vfy_cmatch : var_smtp_tls_sec_cmatch, + CHARS_COMMA_SP ":"); + if (*var_smtp_tls_tafile) { + if (tls->dane == 0) + tls->dane = tls_dane_alloc(); + if (tls->dane->tlsa == 0 + && !load_tas(tls->dane, var_smtp_tls_tafile)) { + MARK_INVALID(tls->why, &tls->level); + return ((void *) tls); + } + } + break; + default: + msg_panic("unexpected TLS security level: %d", tls->level); + } + + if (msg_verbose && tls->level != global_tls_level()) + msg_info("%s TLS level: %s", "effective", policy_name(tls->level)); + + return ((void *) tls); +} + +/* policy_delete - free no longer cached policy (ctable call-back) */ + +static void policy_delete(void *item, void *unused_context) +{ + SMTP_TLS_POLICY *tls = (SMTP_TLS_POLICY *) item; + + if (tls->protocols) + myfree(tls->protocols); + if (tls->sni) + myfree(tls->sni); + if (tls->grade) + myfree(tls->grade); + if (tls->exclusions) + vstring_free(tls->exclusions); + if (tls->matchargv) + argv_free(tls->matchargv); + if (tls->dane) + tls_dane_free(tls->dane); + dsb_free(tls->why); + + myfree((void *) tls); +} + +/* smtp_tls_policy_cache_query - cached lookup of TLS policy */ + +int smtp_tls_policy_cache_query(DSN_BUF *why, SMTP_TLS_POLICY *tls, + SMTP_ITERATOR *iter) +{ + VSTRING *key; + + /* + * Create an empty TLS Policy cache on the fly. + */ + if (policy_cache == 0) + policy_cache = + ctable_create(CACHE_SIZE, policy_create, policy_delete, (void *) 0); + + /* + * Query the TLS Policy cache, with a search key that reflects our shared + * values that also appear in other cache and table search keys. + */ + key = vstring_alloc(100); + smtp_key_prefix(key, ":", iter, SMTP_KEY_FLAG_CUR_NEXTHOP + | SMTP_KEY_FLAG_HOSTNAME + | SMTP_KEY_FLAG_PORT); + ctable_newcontext(policy_cache, (void *) iter); + *tls = *(SMTP_TLS_POLICY *) ctable_locate(policy_cache, STR(key)); + vstring_free(key); + + /* + * Report errors. Both error and non-error results are cached. We must + * therefore copy the cached DSN buffer content to the caller's buffer. + */ + if (tls->level == TLS_LEV_INVALID) { + /* XXX Simplify this by implementing a "copy" primitive. */ + dsb_update(why, + STR(tls->why->status), STR(tls->why->action), + STR(tls->why->mtype), STR(tls->why->mname), + STR(tls->why->dtype), STR(tls->why->dtext), + "%s", STR(tls->why->reason)); + return (0); + } else { + return (1); + } +} + +/* smtp_tls_policy_cache_flush - flush TLS policy cache */ + +void smtp_tls_policy_cache_flush(void) +{ + if (policy_cache != 0) { + ctable_free(policy_cache); + policy_cache = 0; + } +} + +/* global_tls_level - parse and cache var_smtp_tls_level */ + +static int global_tls_level(void) +{ + static int l = TLS_LEV_NOTFOUND; + + if (l != TLS_LEV_NOTFOUND) + return l; + + /* + * Compute the global TLS policy. This is the default policy level when + * no per-site policy exists. It also is used to override a wild-card + * per-site policy. + * + * We require that the global level is valid on startup. + */ + if (*var_smtp_tls_level) { + if ((l = tls_level_lookup(var_smtp_tls_level)) == TLS_LEV_INVALID) + msg_fatal("invalid tls security level: \"%s\"", var_smtp_tls_level); + } else if (var_smtp_enforce_tls) + l = var_smtp_tls_enforce_peername ? TLS_LEV_VERIFY : TLS_LEV_ENCRYPT; + else + l = var_smtp_use_tls ? TLS_LEV_MAY : TLS_LEV_NONE; + + if (msg_verbose) + msg_info("%s TLS level: %s", "global", policy_name(l)); + + return l; +} + +#define NONDANE_CONFIG 0 /* Administrator's fault */ +#define NONDANE_DEST 1 /* Remote server's fault */ +#define DANE_CANTAUTH 2 /* Remote server's fault */ + +static void PRINTFLIKE(4, 5) dane_incompat(SMTP_TLS_POLICY *tls, + SMTP_ITERATOR *iter, + int errtype, + const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + if (tls->level == TLS_LEV_DANE) { + tls->level = (errtype == DANE_CANTAUTH) ? TLS_LEV_ENCRYPT : TLS_LEV_MAY; + if (errtype == NONDANE_CONFIG) + vmsg_warn(fmt, ap); + else if (msg_verbose) + vmsg_info(fmt, ap); + } else { /* dane-only */ + if (errtype == NONDANE_CONFIG) { + vmsg_warn(fmt, ap); + MARK_INVALID(tls->why, &tls->level); + } else { + tls->level = TLS_LEV_INVALID; + vdsb_simple(tls->why, "4.7.5", fmt, ap); + } + } + va_end(ap); +} + +/* dane_init - special initialization for "dane" security level */ + +static void dane_init(SMTP_TLS_POLICY *tls, SMTP_ITERATOR *iter) +{ + TLS_DANE *dane; + + if (!iter->port) { + msg_warn("%s: the \"dane\" security level is invalid for delivery via" + " unix-domain sockets", STR(iter->dest)); + MARK_INVALID(tls->why, &tls->level); + return; + } + if (!tls_dane_avail()) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: %s configured, but no requisite library support", + STR(iter->dest), policy_name(tls->level)); + return; + } + if (!(smtp_host_lookup_mask & SMTP_HOST_FLAG_DNS) + || smtp_dns_support != SMTP_DNS_DNSSEC) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: %s configured with dnssec lookups disabled", + STR(iter->dest), policy_name(tls->level)); + return; + } + + /* + * If we ignore MX lookup errors, we also ignore DNSSEC security problems + * and thus avoid any reasonable expectation that we get the right DANE + * key material. + */ + if (smtp_mode && var_ign_mx_lookup_err) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: %s configured with MX lookup errors ignored", + STR(iter->dest), policy_name(tls->level)); + return; + } + + /* + * This is not optional, code in tls_dane.c assumes that the nexthop + * qname is already an fqdn. If we're using these flags to go from qname + * to rname, the assumption is invalid. Likewise we cannot add the qname + * to certificate name checks, ... + */ + if (smtp_dns_res_opt & (RES_DEFNAMES | RES_DNSRCH)) { + dane_incompat(tls, iter, NONDANE_CONFIG, + "%s: dns resolver options incompatible with %s TLS", + STR(iter->dest), policy_name(tls->level)); + return; + } + + /* + * When the MX name is present and insecure, DANE may not apply, we then + * either fail if DANE is mandatory or use regular opportunistic TLS if + * the insecure MX level is "may". + */ + if (iter->mx && !iter->mx->dnssec_valid + && (tls->level == TLS_LEV_DANE_ONLY || + smtp_tls_insecure_mx_policy <= TLS_LEV_MAY)) { + dane_incompat(tls, iter, NONDANE_DEST, "non DNSSEC destination"); + return; + } + /* When TLSA lookups fail, we defer the message */ + if ((dane = tls_dane_resolve(iter->port, "tcp", iter->rr, + var_smtp_tls_force_tlsa)) == 0) { + tls->level = TLS_LEV_INVALID; + dsb_simple(tls->why, "4.7.5", "TLSA lookup error for %s:%u", + STR(iter->host), ntohs(iter->port)); + return; + } + if (tls_dane_notfound(dane)) { + dane_incompat(tls, iter, NONDANE_DEST, "no TLSA records found"); + tls_dane_free(dane); + return; + } + + /* + * Some TLSA records found, but none usable, per + * + * https://tools.ietf.org/html/draft-ietf-dane-srv-02#section-4 + * + * we MUST use TLS, and SHALL use full PKIX certificate checks. The latter + * would be unwise for SMTP: no human present to "click ok" and risk of + * non-delivery in most cases exceeds risk of interception. + * + * We also have a form of Goedel's incompleteness theorem in play: any list + * of public root CA certs is either incomplete or inconsistent (for any + * given verifier some of the CAs are surely not trustworthy). + */ + if (tls_dane_unusable(dane)) { + dane_incompat(tls, iter, DANE_CANTAUTH, "TLSA records unusable"); + tls_dane_free(dane); + return; + } + + /* + * Perhaps downgrade to "encrypt" if MX is insecure. + */ + if (iter->mx && !iter->mx->dnssec_valid) { + if (smtp_tls_insecure_mx_policy == TLS_LEV_ENCRYPT) { + dane_incompat(tls, iter, DANE_CANTAUTH, + "Verification not possible, MX RRset is insecure"); + tls_dane_free(dane); + return; + } + if (tls->level != TLS_LEV_DANE + || smtp_tls_insecure_mx_policy != TLS_LEV_DANE) + msg_panic("wrong state for insecure MX host DANE policy"); + + /* For correct logging in tls_client_start() */ + tls->level = TLS_LEV_HALF_DANE; + } + + /* + * With DANE trust anchors, peername matching is not configurable. + */ + if (dane->tlsa != 0) { + tls->matchargv = argv_alloc(2); + argv_add(tls->matchargv, dane->base_domain, ARGV_END); + if (iter->mx) { + if (strcmp(iter->mx->qname, iter->mx->rname) == 0) + argv_add(tls->matchargv, iter->mx->qname, ARGV_END); + else + argv_add(tls->matchargv, iter->mx->rname, + iter->mx->qname, ARGV_END); + } + } else + msg_panic("empty DANE match list"); + tls->dane = dane; + return; +} + +#endif diff --git a/src/smtp/smtp_trouble.c b/src/smtp/smtp_trouble.c new file mode 100644 index 0000000..60880df --- /dev/null +++ b/src/smtp/smtp_trouble.c @@ -0,0 +1,476 @@ +/*++ +/* NAME +/* smtp_trouble 3 +/* SUMMARY +/* error handler policies +/* SYNOPSIS +/* #include "smtp.h" +/* +/* int smtp_sess_fail(state) +/* SMTP_STATE *state; +/* +/* int smtp_site_fail(state, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* +/* int smtp_mesg_fail(state, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* +/* void smtp_rcpt_fail(state, recipient, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* RECIPIENT *recipient; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* +/* int smtp_stream_except(state, exception, description) +/* SMTP_STATE *state; +/* int exception; +/* const char *description; +/* AUXILIARY FUNCTIONS +/* int smtp_misc_fail(state, throttle, mta_name, resp, format, ...) +/* SMTP_STATE *state; +/* int throttle; +/* const char *mta_name; +/* SMTP_RESP *resp; +/* const char *format; +/* DESCRIPTION +/* This module handles all non-fatal errors that can happen while +/* attempting to deliver mail via SMTP, and implements the policy +/* of how to deal with the error. Depending on the nature of +/* the problem, delivery of a single message is deferred, delivery +/* of all messages to the same domain is deferred, or one or more +/* recipients are given up as non-deliverable and a bounce log is +/* updated. In any case, the recipient is marked as either KEEP +/* (try again with a backup host) or DROP (delete recipient from +/* delivery request). +/* +/* In addition, when an unexpected response code is seen such +/* as 3xx where only 4xx or 5xx are expected, or any error code +/* that suggests a syntax error or something similar, the +/* protocol error flag is set so that the postmaster receives +/* a transcript of the session. No notification is generated for +/* what appear to be configuration errors - very likely, they +/* would suffer the same problem and just cause more trouble. +/* +/* In case of a soft error, action depends on whether the error +/* qualifies for trying the request with other mail servers (log +/* an informational record only and try a backup server) or +/* whether this is the final server (log recipient delivery status +/* records and delete the recipient from the request). +/* +/* smtp_sess_fail() takes a pre-formatted error report after +/* failure to complete some protocol handshake. The policy is +/* as with smtp_site_fail(). +/* +/* smtp_site_fail() handles the case where the program fails to +/* complete the initial handshake: the server is not reachable, +/* is not running, does not want talk to us, or we talk to ourselves. +/* The \fIcode\fR gives an error status code; the \fIformat\fR +/* argument gives a textual description. +/* The policy is: soft error, non-final server: log an informational +/* record why the host is being skipped; soft error, final server: +/* defer delivery of all remaining recipients and mark the destination +/* as problematic; hard error: bounce all remaining recipients. +/* The session is marked as "do not cache". +/* The result is non-zero. +/* +/* smtp_mesg_fail() handles the case where the smtp server +/* does not accept the sender address or the message data, +/* or when the local MTA is unable to convert the message data. +/* The policy is: soft error, non-final server: log an informational +/* record why the host is being skipped; soft error, final server: +/* defer delivery of all remaining recipients; hard error: bounce all +/* remaining recipients. +/* The result is non-zero. +/* +/* smtp_misc_fail() provides a more detailed interface than +/* smtp_site_fail() and smtp_mesg_fail(), which are convenience +/* wrappers around smtp_misc_fail(). The throttle argument +/* is either SMTP_THROTTLE or SMTP_NOTHROTTLE; it is used only +/* in the "soft error, final server" policy, and determines +/* whether a destination will be marked as problematic. +/* +/* smtp_rcpt_fail() handles the case where a recipient is not +/* accepted by the server for reasons other than that the server +/* recipient limit is reached. +/* The policy is: soft error, non-final server: log an informational +/* record why the recipient is being skipped; soft error, final server: +/* defer delivery of this recipient; hard error: bounce this +/* recipient. +/* +/* smtp_stream_except() handles the exceptions generated by +/* the smtp_stream(3) module (i.e. timeouts and I/O errors). +/* The \fIexception\fR argument specifies the type of problem. +/* The \fIdescription\fR argument describes at what stage of +/* the SMTP dialog the problem happened. +/* The policy is: non-final server: log an informational record +/* with the reason why the host is being skipped; final server: +/* defer delivery of all remaining recipients. +/* Retry plaintext delivery after TLS post-handshake session +/* failure, provided that at least one recipient was not +/* deferred or rejected during the TLS phase, and that global +/* preconditions for plaintext fallback are met. +/* The session is marked as "do not cache". +/* The result is non-zero. +/* +/* Arguments: +/* .IP state +/* SMTP client state per delivery request. +/* .IP resp +/* Server response including reply code and text. +/* .IP recipient +/* Undeliverable recipient address information. +/* .IP format +/* Human-readable description of why mail is not deliverable. +/* DIAGNOSTICS +/* Panic: unknown exception code. +/* SEE ALSO +/* smtp_proto(3) smtp high-level protocol +/* smtp_stream(3) smtp low-level protocol +/* defer(3) basic message defer interface +/* bounce(3) basic message bounce interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include /* 44BSD stdarg.h uses abort() */ +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "smtp.h" +#include "smtp_sasl.h" + +/* smtp_check_code - check response code */ + +static void smtp_check_code(SMTP_SESSION *session, int code) +{ + + /* + * The intention of this code is to alert the postmaster when the local + * Postfix SMTP client screws up, protocol wise. RFC 821 says that x0z + * replies "refer to syntax errors, syntactically correct commands that + * don't fit any functional category, and unimplemented or superfluous + * commands". Unfortunately, this also triggers postmaster notices when + * remote servers screw up, protocol wise. This is becoming a common + * problem now that response codes are configured manually as part of + * anti-UCE systems, by people who aren't aware of RFC details. + * + * Fix 20190621: don't cache an SMTP session after an SMTP protocol error. + * The protocol may be in a bad state. Disable caching here so that the + * protocol engine will send QUIT. + */ + if (code < 400 || code > 599 + || code == 555 /* RFC 1869, section 6.1. */ + || (code >= 500 && code < 510)) { + session->error_mask |= MAIL_ERROR_PROTOCOL; + DONT_CACHE_THIS_SESSION; + } +} + +/* smtp_bulk_fail - skip, defer or bounce recipients, maybe throttle queue */ + +static int smtp_bulk_fail(SMTP_STATE *state, int throttle_queue) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + RECIPIENT *rcpt; + int status; + int aggregate_status; + int soft_error = (STR(why->status)[0] == '4'); + int soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce); + int nrcpt; + + /* + * Don't defer the recipients just yet when this error qualifies them for + * delivery to a backup server. Just log something informative to show + * why we're skipping this host. + */ + if ((soft_error || soft_bounce_error) + && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) { + msg_info("%s: %s", request->queue_id, STR(why->reason)); + for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (SMTP_RCPT_ISMARKED(rcpt)) + continue; + SMTP_RCPT_KEEP(state, rcpt); + } + } + + /* + * Defer or bounce all the remaining recipients, and delete them from the + * delivery request. If a bounce fails, defer instead and do not qualify + * the recipient for delivery to a backup server. + */ + else { + + /* + * If we are still in the connection set-up phase, update the set-up + * completion time here, otherwise the time spent in set-up latency + * will be attributed as message transfer latency. + * + * All remaining recipients have failed at this point, so we update the + * delivery completion time stamp so that multiple recipient status + * records show the same delay values. + */ + if (request->msg_stats.conn_setup_done.tv_sec == 0) { + GETTIMEOFDAY(&request->msg_stats.conn_setup_done); + request->msg_stats.deliver_done = + request->msg_stats.conn_setup_done; + } else + GETTIMEOFDAY(&request->msg_stats.deliver_done); + + (void) DSN_FROM_DSN_BUF(why); + aggregate_status = 0; + for (nrcpt = 0; nrcpt < SMTP_RCPT_LEFT(state); nrcpt++) { + rcpt = request->rcpt_list.info + nrcpt; + if (SMTP_RCPT_ISMARKED(rcpt)) + continue; + status = (soft_error ? defer_append : bounce_append) + (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, + &request->msg_stats, rcpt, + session ? session->namaddrport : "none", &why->dsn); + if (status == 0) + deliver_completed(state->src, rcpt->offset); + SMTP_RCPT_DROP(state, rcpt); + aggregate_status |= status; + } + state->status |= aggregate_status; + if ((state->misc_flags & SMTP_MISC_FLAG_COMPLETE_SESSION) == 0 + && throttle_queue && aggregate_status + && request->hop_status == 0) + request->hop_status = DSN_COPY(&why->dsn); + } + + /* + * Don't cache this session. We can't talk to this server. + */ + if (throttle_queue && session) + DONT_CACHE_THROTTLED_SESSION; + + return (-1); +} + +/* smtp_sess_fail - skip site, defer or bounce all recipients */ + +int smtp_sess_fail(SMTP_STATE *state) +{ + + /* + * We can't avoid copying copying lots of strings into VSTRING buffers, + * because this error information is collected by a routine that + * terminates BEFORE the error is reported. + */ + return (smtp_bulk_fail(state, SMTP_THROTTLE)); +} + +/* vsmtp_fill_dsn - fill in temporary DSN structure */ + +static void vsmtp_fill_dsn(SMTP_STATE *state, const char *mta_name, + const char *status, const char *reply, + const char *format, va_list ap) +{ + DSN_BUF *why = state->why; + + /* + * We could avoid copying lots of strings into VSTRING buffers, because + * this error information is given to us by a routine that terminates + * AFTER the error is reported. However, this results in ugly kludges + * when informal text needs to be formatted. So we maintain consistency + * with other error reporting in the SMTP client even if we waste a few + * cycles. + * + * Fix 20190621: don't cache an SMTP session after an SMTP protocol error. + * The protocol may be in a bad state. Disable caching here so that the + * protocol engine will send QUIT. + */ + VSTRING_RESET(why->reason); + if (mta_name && status && status[0] != '4' && status[0] != '5') { + SMTP_SESSION *session = state->session; + + session->error_mask |= MAIL_ERROR_PROTOCOL; + DONT_CACHE_THIS_SESSION; + vstring_strcpy(why->reason, "Protocol error: "); + status = "5.5.0"; + } + vstring_vsprintf_append(why->reason, format, ap); + dsb_formal(why, status, DSB_DEF_ACTION, + mta_name ? DSB_MTYPE_DNS : DSB_MTYPE_NONE, mta_name, + reply ? DSB_DTYPE_SMTP : DSB_DTYPE_NONE, reply); +} + +/* smtp_misc_fail - maybe throttle queue; skip/defer/bounce all recipients */ + +int smtp_misc_fail(SMTP_STATE *state, int throttle, const char *mta_name, + SMTP_RESP *resp, const char *format,...) +{ + va_list ap; + + /* + * Initialize. + */ + va_start(ap, format); + vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap); + va_end(ap); + + if (state->session && mta_name) + smtp_check_code(state->session, resp->code); + + /* + * Skip, defer or bounce recipients, and throttle this queue. + */ + return (smtp_bulk_fail(state, throttle)); +} + +/* smtp_rcpt_fail - skip, defer, or bounce recipient */ + +void smtp_rcpt_fail(SMTP_STATE *state, RECIPIENT *rcpt, const char *mta_name, + SMTP_RESP *resp, const char *format,...) +{ + DELIVER_REQUEST *request = state->request; + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + int status; + int soft_error; + int soft_bounce_error; + va_list ap; + + /* + * Sanity check. + */ + if (SMTP_RCPT_ISMARKED(rcpt)) + msg_panic("smtp_rcpt_fail: recipient <%s> is marked", rcpt->address); + + /* + * Initialize. + */ + va_start(ap, format); + vsmtp_fill_dsn(state, mta_name, resp->dsn, resp->str, format, ap); + va_end(ap); + soft_error = STR(why->status)[0] == '4'; + soft_bounce_error = (STR(why->status)[0] == '5' && var_soft_bounce); + + if (state->session && mta_name) + smtp_check_code(state->session, resp->code); + + /* + * Don't defer this recipient record just yet when this error qualifies + * for trying other mail servers. Just log something informative to show + * why we're skipping this recipient now. + */ + if ((soft_error || soft_bounce_error) + && (state->misc_flags & SMTP_MISC_FLAG_FINAL_SERVER) == 0) { + msg_info("%s: %s", request->queue_id, STR(why->reason)); + SMTP_RCPT_KEEP(state, rcpt); + } + + /* + * Defer or bounce this recipient, and delete from the delivery request. + * If the bounce fails, defer instead and do not qualify the recipient + * for delivery to a backup server. + * + * Note: we may still make an SMTP connection to deliver other recipients + * that did qualify for delivery to a backup server. + */ + else { + (void) DSN_FROM_DSN_BUF(state->why); + status = (soft_error ? defer_append : bounce_append) + (DEL_REQ_TRACE_FLAGS(request->flags), request->queue_id, + &request->msg_stats, rcpt, + session ? session->namaddrport : "none", &why->dsn); + if (status == 0) + deliver_completed(state->src, rcpt->offset); + SMTP_RCPT_DROP(state, rcpt); + state->status |= status; + } +} + +/* smtp_stream_except - defer domain after I/O problem */ + +int smtp_stream_except(SMTP_STATE *state, int code, const char *description) +{ + SMTP_SESSION *session = state->session; + DSN_BUF *why = state->why; + + /* + * Sanity check. + */ + if (session == 0) + msg_panic("smtp_stream_except: no session"); + + /* + * Initialize. + */ + switch (code) { + default: + msg_panic("smtp_stream_except: unknown exception %d", code); + case SMTP_ERR_EOF: + dsb_simple(why, "4.4.2", "lost connection with %s while %s", + session->namaddr, description); +#ifdef USE_TLS + if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE) + RETRY_AS_PLAINTEXT; +#endif + break; + case SMTP_ERR_TIME: + dsb_simple(why, "4.4.2", "conversation with %s timed out while %s", + session->namaddr, description); +#ifdef USE_TLS + if (PLAINTEXT_FALLBACK_OK_AFTER_TLS_SESSION_FAILURE) + RETRY_AS_PLAINTEXT; +#endif + break; + case SMTP_ERR_DATA: + session->error_mask |= MAIL_ERROR_DATA; + dsb_simple(why, "4.3.0", "local data error while talking to %s", + session->namaddr); + } + + /* + * The smtp_bulk_fail() call below will not throttle the destination when + * falling back to plaintext, because RETRY_AS_PLAINTEXT clears the + * FINAL_SERVER flag. + */ + return (smtp_bulk_fail(state, SMTP_THROTTLE)); +} diff --git a/src/smtp/smtp_unalias.c b/src/smtp/smtp_unalias.c new file mode 100644 index 0000000..1c4d34d --- /dev/null +++ b/src/smtp/smtp_unalias.c @@ -0,0 +1,142 @@ +/*++ +/* NAME +/* smtp_unalias 3 +/* SUMMARY +/* replace host/domain alias by official name +/* SYNOPSIS +/* #include "smtp.h" +/* +/* const char *smtp_unalias_name(name) +/* const char *name; +/* +/* VSTRING *smtp_unalias_addr(result, addr) +/* VSTRING *result; +/* const char *addr; +/* DESCRIPTION +/* smtp_unalias_name() looks up A or MX records for the specified +/* name and returns the official name provided in the reply. When +/* no A or MX record is found, the result is a copy of the input. +/* In order to improve performance, smtp_unalias_name() remembers +/* results in a private cache, so a name is looked up only once. +/* +/* smtp_unalias_addr() returns the input address, which is in +/* RFC 821 quoted form, after replacing the domain part by the +/* official name as found by smtp_unalias_name(). When the input +/* contains no domain part, the result is a copy of the input. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* DNS library. */ + +#include + +/* Application-specific. */ + +#include "smtp.h" + +static int smtp_unalias_flags; + +/* smtp_unalias_name - look up the official host or domain name. */ + +const char *smtp_unalias_name(const char *name) +{ + static HTABLE *cache; + VSTRING *fqdn; + char *result; + + if (*name == '[') + return (name); + + /* + * Initialize the cache on the fly. The smtp client is designed to exit + * after servicing a limited number of requests, so there is no need to + * prevent the cache from growing too large, or to expire old entries. + */ + if (cache == 0) + cache = htable_create(10); + + /* + * Look up the fqdn. If none is found use the query name instead, so that + * we won't lose time looking up the same bad name again. + */ + if ((result = htable_find(cache, name)) == 0) { + fqdn = vstring_alloc(10); + if (dns_lookup_l(name, smtp_unalias_flags, (DNS_RR **) 0, fqdn, + (VSTRING *) 0, DNS_REQ_FLAG_NONE, T_MX, T_A, +#ifdef HAS_IPV6 + T_AAAA, +#endif + 0) != DNS_OK) + vstring_strcpy(fqdn, name); + htable_enter(cache, name, result = vstring_export(fqdn)); + } + return (result); +} + +/* smtp_unalias_addr - rewrite aliases in domain part of address */ + +VSTRING *smtp_unalias_addr(VSTRING *result, const char *addr) +{ + char *at; + const char *fqdn; + + if ((at = strrchr(addr, '@')) == 0 || at[1] == 0) { + vstring_strcpy(result, addr); + } else { + fqdn = smtp_unalias_name(at + 1); + vstring_strncpy(result, addr, at - addr + 1); + vstring_strcat(result, fqdn); + } + return (result); +} + +#ifdef TEST + + /* + * Test program - read address from stdin, print result on stdout. + */ + +#include + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *addr = vstring_alloc(10); + VSTRING *result = vstring_alloc(10); + + smtp_unalias_flags |= RES_DEBUG; + + while (vstring_fgets_nonl(addr, VSTREAM_IN)) { + smtp_unalias_addr(result, vstring_str(addr)); + vstream_printf("%s -> %s\n", vstring_str(addr), vstring_str(result)); + vstream_fflush(VSTREAM_OUT); + } + vstring_free(addr); + vstring_free(result); + return (0); +} + +#endif diff --git a/src/smtp/tls_policy.in b/src/smtp/tls_policy.in new file mode 100644 index 0000000..95c295b --- /dev/null +++ b/src/smtp/tls_policy.in @@ -0,0 +1,64 @@ +- - - +- - may +- - must_nopeermatch +- - must +- none - +- none may +- none must_nopeermatch +- none must +- may - +- may may +- may must_nopeermatch +- may must +- must_nopeermatch - +- must_nopeermatch may +- must_nopeermatch must_nopeermatch +- must_nopeermatch must +- must - +- must may +- must must_nopeermatch +- must must + +none none - +none none may +none none must_nopeermatch +none none must +none may - +none may may +none may must_nopeermatch +none may must +none must_nopeermatch - +none must_nopeermatch may +none must_nopeermatch must_nopeermatch +none must_nopeermatch must +none must - +none must may +none must must_nopeermatch +none must must + +may may - +may may may +may may must_nopeermatch +may may must +may must_nopeermatch - +may must_nopeermatch may +may must_nopeermatch must_nopeermatch +may must_nopeermatch must +may must - +may must may +may must must_nopeermatch +may must must + +must_nopeermatch must_nopeermatch - +must_nopeermatch must_nopeermatch may +must_nopeermatch must_nopeermatch must_nopeermatch +must_nopeermatch must_nopeermatch must +must_nopeermatch must - +must_nopeermatch must may +must_nopeermatch must must_nopeermatch +must_nopeermatch must must + +must must - +must must may +must must must_nopeermatch +must must must diff --git a/src/smtp/tls_policy.ref b/src/smtp/tls_policy.ref new file mode 100644 index 0000000..5b9f187 --- /dev/null +++ b/src/smtp/tls_policy.ref @@ -0,0 +1,65 @@ +host dest global result +- - - none +- - may may +- - must_nopeermatch must_nopeermatch +- - must must +- none - none +- none may none +- none must_nopeermatch none +- none must none +- may - may +- may may may +- may must_nopeermatch must_nopeermatch +- may must must +- must_nopeermatch - must_nopeermatch +- must_nopeermatch may must_nopeermatch +- must_nopeermatch must_nopeermatch must_nopeermatch +- must_nopeermatch must must_nopeermatch +- must - must +- must may must +- must must_nopeermatch must +- must must must + +none none - none +none none may none +none none must_nopeermatch none +none none must none +none may - none +none may may none +none may must_nopeermatch none +none may must none +none must_nopeermatch - must_nopeermatch +none must_nopeermatch may must_nopeermatch +none must_nopeermatch must_nopeermatch must_nopeermatch +none must_nopeermatch must must_nopeermatch +none must - must +none must may must +none must must_nopeermatch must +none must must must + +may may - may +may may may may +may may must_nopeermatch must_nopeermatch +may may must must +may must_nopeermatch - must_nopeermatch +may must_nopeermatch may must_nopeermatch +may must_nopeermatch must_nopeermatch must_nopeermatch +may must_nopeermatch must must_nopeermatch +may must - must +may must may must +may must must_nopeermatch must +may must must must + +must_nopeermatch must_nopeermatch - must_nopeermatch +must_nopeermatch must_nopeermatch may must_nopeermatch +must_nopeermatch must_nopeermatch must_nopeermatch must_nopeermatch +must_nopeermatch must_nopeermatch must must_nopeermatch +must_nopeermatch must - must +must_nopeermatch must may must +must_nopeermatch must must_nopeermatch must +must_nopeermatch must must must + +must must - must +must must may must +must must must_nopeermatch must +must must must must diff --git a/src/smtpd/.indent.pro b/src/smtpd/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/smtpd/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/smtpd/.printfck b/src/smtpd/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/smtpd/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/smtpd/Makefile.in b/src/smtpd/Makefile.in new file mode 100644 index 0000000..8c4132a --- /dev/null +++ b/src/smtpd/Makefile.in @@ -0,0 +1,679 @@ +SHELL = /bin/sh +SRCS = smtpd.c smtpd_token.c smtpd_check.c smtpd_chat.c smtpd_state.c \ + smtpd_peer.c smtpd_sasl_proto.c smtpd_sasl_glue.c smtpd_proxy.c \ + smtpd_xforward.c smtpd_dsn_fix.c smtpd_milter.c smtpd_resolve.c \ + smtpd_expand.c smtpd_haproxy.c +OBJS = smtpd.o smtpd_token.o smtpd_check.o smtpd_chat.o smtpd_state.o \ + smtpd_peer.o smtpd_sasl_proto.o smtpd_sasl_glue.o smtpd_proxy.o \ + smtpd_xforward.o smtpd_dsn_fix.o smtpd_milter.o smtpd_resolve.o \ + smtpd_expand.o smtpd_haproxy.o +HDRS = smtpd_token.h smtpd_check.h smtpd_chat.h smtpd_sasl_proto.h \ + smtpd_sasl_glue.h smtpd_proxy.h smtpd_dsn_fix.h smtpd_milter.h \ + smtpd_resolve.h smtpd_expand.h +TESTSRC = smtpd_token_test.c +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= smtpd_token smtpd_check +PROG = smtpd +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/libxsasl.a \ + ../../lib/libmilter.a \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +SMTPD_CHECK_OBJ = smtpd_state.o smtpd_peer.o smtpd_xforward.o smtpd_dsn_fix.o \ + smtpd_resolve.o smtpd_expand.o smtpd_proxy.o smtpd_haproxy.o + +smtpd_token: smtpd_token.c $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS) + +smtpd_check: smtpd_check.o smtpd_check.c $(SMTPD_CHECK_OBJ) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ smtpd_check.c $(SMTPD_CHECK_OBJ) \ + $(LIBS) $(SYSLIBS) + mv junk $@.o + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk *.db *.out *.tmp + rm -rf printfck + +tidy: clean + +broken-tests: smtpd_check_test smtpd_check_test2 + +tests: smtpd_acl_test smtpd_addr_valid_test smtpd_exp_test \ + smtpd_token_test smtpd_check_test4 smtpd_check_dsn_test \ + smtpd_check_backup_test smtpd_dnswl_test smtpd_error_test \ + smtpd_server_test smtpd_nullmx_test smtpd_dns_filter_test + +root_tests: + +# This requires that the DNS server can query porcupine.org. + +smtpd_check_test: smtpd_check smtpd_check.in smtpd_check.ref smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_check.tmp 2>&1 + diff smtpd_check.ref smtpd_check.tmp + rm -f smtpd_check.tmp smtpd_check_access.* + +# This requires that the DNS server can query porcupine.org. + +smtpd_check_test2: smtpd_check smtpd_check.in2 smtpd_check.ref2 smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_check.tmp 2>&1 + diff smtpd_check.ref2 smtpd_check.tmp + rm -f smtpd_check.tmp smtpd_check_access.* + +smtpd_check_test4: smtpd_check smtpd_check.in4 smtpd_check.ref4 smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_check.tmp 2>&1 + diff smtpd_check.ref4 smtpd_check.tmp + rm -f smtpd_check.tmp smtpd_check_access.* + +smtpd_acl_test: smtpd_check smtpd_acl.in smtpd_acl.ref smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_check.tmp 2>&1 + diff smtpd_acl.ref smtpd_check.tmp + rm -f smtpd_check.tmp smtpd_check_access.* + +smtpd_addr_valid_test: smtpd_check smtpd_addr_valid.in smtpd_addr_valid.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_check.tmp 2>&1 + diff smtpd_addr_valid.ref smtpd_check.tmp + rm -f smtpd_check.tmp + +# This requires that the DNS server can query porcupine.org. + +ADDRINFO_FIX = sed 's/No address associated with hostname/hostname nor servname provided, or not known/' + +smtpd_exp_test: smtpd_check smtpd_exp.in smtpd_exp.ref + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_exp.tmp 2>&1 + diff smtpd_exp.ref smtpd_exp.tmp + rm -f smtpd_exp.tmp smtpd_check_access.* + +smtpd_server_test: smtpd_check smtpd_server.in smtpd_server.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_server.tmp 2>&1 + $(ADDRINFO_FIX) smtpd_server.tmp | diff smtpd_server.ref - + rm -f smtpd_server.tmp smtpd_check_access.* + +smtpd_nullmx_test: smtpd_check smtpd_nullmx.in smtpd_nullmx.ref + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_nullmx.tmp 2>&1 + $(ADDRINFO_FIX) smtpd_nullmx.tmp | diff smtpd_nullmx.ref - + rm -f smtpd_nullmx.tmp smtpd_check_access.* + +smtpd_dns_filter_test: smtpd_check smtpd_dns_filter.in smtpd_dns_filter.ref \ + ../dns/no-mx.reg ../dns/no-a.reg ../dns/error.reg + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check &1 | \ + sed 's/\. [0-9]* IN/. TTL IN/' >smtpd_dns_filter.tmp + diff smtpd_dns_filter.ref smtpd_dns_filter.tmp + rm -f smtpd_dns_filter.tmp + +smtpd_check_dsn_test: smtpd_check smtpd_check_dsn.in smtpd_check_dsn.ref smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ../postmap/postmap hash:smtpd_check_access + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_check.tmp 2>&1 + diff smtpd_check_dsn.ref smtpd_check.tmp + rm -f smtpd_check.tmp smtpd_check_access.* + +# This requires that 168.100.3.7 is a local or virtual interface. + +smtpd_check_backup_test: smtpd_check smtpd_check_backup.in smtpd_check_backup.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_check.tmp 2>&1 + diff smtpd_check_backup.ref smtpd_check.tmp + rm -f smtpd_check.tmp + +smtpd_token_test: smtpd_token smtpd_token.in smtpd_token.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_token smtpd_token.tmp 2>&1 + diff smtpd_token.ref smtpd_token.tmp + rm -f smtpd_token.tmp + +# This requires that the DNS server can query porcupine.org and rfc-ignorant.org + +smtpd_dnswl_test: smtpd_check smtpd_dnswl.in smtpd_dnswl.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_dnswl.tmp 2>&1 + diff smtpd_dnswl.ref smtpd_dnswl.tmp + rm -f smtpd_dnswl.tmp + +smtpd_error_test: smtpd_check smtpd_error.in smtpd_error.ref + $(SHLIB_ENV) $(VALGRIND) ./smtpd_check smtpd_check.tmp 2>&1 + diff smtpd_error.ref smtpd_check.tmp + rm -f smtpd_check.tmp + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +smtpd.o: ../../include/anvil_clnt.h +smtpd.o: ../../include/argv.h +smtpd.o: ../../include/attr.h +smtpd.o: ../../include/attr_clnt.h +smtpd.o: ../../include/check_arg.h +smtpd.o: ../../include/cleanup_user.h +smtpd.o: ../../include/debug_peer.h +smtpd.o: ../../include/dict.h +smtpd.o: ../../include/dns.h +smtpd.o: ../../include/dsn_mask.h +smtpd.o: ../../include/ehlo_mask.h +smtpd.o: ../../include/events.h +smtpd.o: ../../include/flush_clnt.h +smtpd.o: ../../include/hfrom_format.h +smtpd.o: ../../include/htable.h +smtpd.o: ../../include/inet_proto.h +smtpd.o: ../../include/info_log_addr_form.h +smtpd.o: ../../include/input_transp.h +smtpd.o: ../../include/iostuff.h +smtpd.o: ../../include/is_header.h +smtpd.o: ../../include/lex_822.h +smtpd.o: ../../include/mac_expand.h +smtpd.o: ../../include/mac_parse.h +smtpd.o: ../../include/mail_conf.h +smtpd.o: ../../include/mail_date.h +smtpd.o: ../../include/mail_error.h +smtpd.o: ../../include/mail_params.h +smtpd.o: ../../include/mail_proto.h +smtpd.o: ../../include/mail_queue.h +smtpd.o: ../../include/mail_server.h +smtpd.o: ../../include/mail_stream.h +smtpd.o: ../../include/mail_version.h +smtpd.o: ../../include/maps.h +smtpd.o: ../../include/match_list.h +smtpd.o: ../../include/match_parent_style.h +smtpd.o: ../../include/milter.h +smtpd.o: ../../include/msg.h +smtpd.o: ../../include/myaddrinfo.h +smtpd.o: ../../include/myflock.h +smtpd.o: ../../include/mymalloc.h +smtpd.o: ../../include/namadr_list.h +smtpd.o: ../../include/name_code.h +smtpd.o: ../../include/name_mask.h +smtpd.o: ../../include/normalize_mailhost_addr.h +smtpd.o: ../../include/nvtable.h +smtpd.o: ../../include/off_cvt.h +smtpd.o: ../../include/quote_822_local.h +smtpd.o: ../../include/quote_flags.h +smtpd.o: ../../include/rec_type.h +smtpd.o: ../../include/recipient_list.h +smtpd.o: ../../include/record.h +smtpd.o: ../../include/resolve_clnt.h +smtpd.o: ../../include/smtp_stream.h +smtpd.o: ../../include/smtputf8.h +smtpd.o: ../../include/sock_addr.h +smtpd.o: ../../include/split_at.h +smtpd.o: ../../include/string_list.h +smtpd.o: ../../include/stringops.h +smtpd.o: ../../include/sys_defs.h +smtpd.o: ../../include/tls.h +smtpd.o: ../../include/tls_proxy.h +smtpd.o: ../../include/tok822.h +smtpd.o: ../../include/uxtext.h +smtpd.o: ../../include/valid_hostname.h +smtpd.o: ../../include/valid_mailhost_addr.h +smtpd.o: ../../include/vbuf.h +smtpd.o: ../../include/verify_sender_addr.h +smtpd.o: ../../include/verp_sender.h +smtpd.o: ../../include/vstream.h +smtpd.o: ../../include/vstring.h +smtpd.o: ../../include/vstring_vstream.h +smtpd.o: ../../include/watchdog.h +smtpd.o: ../../include/xtext.h +smtpd.o: smtpd.c +smtpd.o: smtpd.h +smtpd.o: smtpd_chat.h +smtpd.o: smtpd_check.h +smtpd.o: smtpd_expand.h +smtpd.o: smtpd_milter.h +smtpd.o: smtpd_proxy.h +smtpd.o: smtpd_sasl_glue.h +smtpd.o: smtpd_sasl_proto.h +smtpd.o: smtpd_token.h +smtpd_chat.o: ../../include/argv.h +smtpd_chat.o: ../../include/attr.h +smtpd_chat.o: ../../include/check_arg.h +smtpd_chat.o: ../../include/cleanup_user.h +smtpd_chat.o: ../../include/dict.h +smtpd_chat.o: ../../include/dns.h +smtpd_chat.o: ../../include/hfrom_format.h +smtpd_chat.o: ../../include/htable.h +smtpd_chat.o: ../../include/int_filt.h +smtpd_chat.o: ../../include/iostuff.h +smtpd_chat.o: ../../include/line_wrap.h +smtpd_chat.o: ../../include/mac_expand.h +smtpd_chat.o: ../../include/mac_parse.h +smtpd_chat.o: ../../include/mail_addr.h +smtpd_chat.o: ../../include/mail_error.h +smtpd_chat.o: ../../include/mail_params.h +smtpd_chat.o: ../../include/mail_proto.h +smtpd_chat.o: ../../include/mail_stream.h +smtpd_chat.o: ../../include/maps.h +smtpd_chat.o: ../../include/milter.h +smtpd_chat.o: ../../include/msg.h +smtpd_chat.o: ../../include/myaddrinfo.h +smtpd_chat.o: ../../include/myflock.h +smtpd_chat.o: ../../include/mymalloc.h +smtpd_chat.o: ../../include/name_code.h +smtpd_chat.o: ../../include/name_mask.h +smtpd_chat.o: ../../include/nvtable.h +smtpd_chat.o: ../../include/post_mail.h +smtpd_chat.o: ../../include/rec_type.h +smtpd_chat.o: ../../include/record.h +smtpd_chat.o: ../../include/smtp_reply_footer.h +smtpd_chat.o: ../../include/smtp_stream.h +smtpd_chat.o: ../../include/smtputf8.h +smtpd_chat.o: ../../include/sock_addr.h +smtpd_chat.o: ../../include/stringops.h +smtpd_chat.o: ../../include/sys_defs.h +smtpd_chat.o: ../../include/tls.h +smtpd_chat.o: ../../include/vbuf.h +smtpd_chat.o: ../../include/vstream.h +smtpd_chat.o: ../../include/vstring.h +smtpd_chat.o: smtpd.h +smtpd_chat.o: smtpd_chat.c +smtpd_chat.o: smtpd_chat.h +smtpd_chat.o: smtpd_expand.h +smtpd_check.o: ../../include/argv.h +smtpd_check.o: ../../include/attr.h +smtpd_check.o: ../../include/attr_clnt.h +smtpd_check.o: ../../include/attr_override.h +smtpd_check.o: ../../include/check_arg.h +smtpd_check.o: ../../include/cleanup_user.h +smtpd_check.o: ../../include/conv_time.h +smtpd_check.o: ../../include/ctable.h +smtpd_check.o: ../../include/deliver_request.h +smtpd_check.o: ../../include/dict.h +smtpd_check.o: ../../include/dns.h +smtpd_check.o: ../../include/domain_list.h +smtpd_check.o: ../../include/dsn.h +smtpd_check.o: ../../include/dsn_util.h +smtpd_check.o: ../../include/fsspace.h +smtpd_check.o: ../../include/htable.h +smtpd_check.o: ../../include/inet_addr_list.h +smtpd_check.o: ../../include/inet_proto.h +smtpd_check.o: ../../include/info_log_addr_form.h +smtpd_check.o: ../../include/input_transp.h +smtpd_check.o: ../../include/iostuff.h +smtpd_check.o: ../../include/ip_match.h +smtpd_check.o: ../../include/is_header.h +smtpd_check.o: ../../include/mac_expand.h +smtpd_check.o: ../../include/mac_parse.h +smtpd_check.o: ../../include/mail_addr.h +smtpd_check.o: ../../include/mail_addr_find.h +smtpd_check.o: ../../include/mail_addr_form.h +smtpd_check.o: ../../include/mail_conf.h +smtpd_check.o: ../../include/mail_error.h +smtpd_check.o: ../../include/mail_params.h +smtpd_check.o: ../../include/mail_proto.h +smtpd_check.o: ../../include/mail_stream.h +smtpd_check.o: ../../include/map_search.h +smtpd_check.o: ../../include/maps.h +smtpd_check.o: ../../include/match_list.h +smtpd_check.o: ../../include/match_parent_style.h +smtpd_check.o: ../../include/midna_domain.h +smtpd_check.o: ../../include/milter.h +smtpd_check.o: ../../include/msg.h +smtpd_check.o: ../../include/msg_stats.h +smtpd_check.o: ../../include/myaddrinfo.h +smtpd_check.o: ../../include/myflock.h +smtpd_check.o: ../../include/mymalloc.h +smtpd_check.o: ../../include/mynetworks.h +smtpd_check.o: ../../include/namadr_list.h +smtpd_check.o: ../../include/name_code.h +smtpd_check.o: ../../include/name_mask.h +smtpd_check.o: ../../include/nvtable.h +smtpd_check.o: ../../include/own_inet_addr.h +smtpd_check.o: ../../include/rec_type.h +smtpd_check.o: ../../include/recipient_list.h +smtpd_check.o: ../../include/record.h +smtpd_check.o: ../../include/resolve_clnt.h +smtpd_check.o: ../../include/resolve_local.h +smtpd_check.o: ../../include/smtp_stream.h +smtpd_check.o: ../../include/sock_addr.h +smtpd_check.o: ../../include/split_at.h +smtpd_check.o: ../../include/string_list.h +smtpd_check.o: ../../include/stringops.h +smtpd_check.o: ../../include/strip_addr.h +smtpd_check.o: ../../include/sys_defs.h +smtpd_check.o: ../../include/tls.h +smtpd_check.o: ../../include/valid_hostname.h +smtpd_check.o: ../../include/valid_mailhost_addr.h +smtpd_check.o: ../../include/valid_utf8_hostname.h +smtpd_check.o: ../../include/vbuf.h +smtpd_check.o: ../../include/verify_clnt.h +smtpd_check.o: ../../include/vstream.h +smtpd_check.o: ../../include/vstring.h +smtpd_check.o: ../../include/xtext.h +smtpd_check.o: smtpd.h +smtpd_check.o: smtpd_check.c +smtpd_check.o: smtpd_check.h +smtpd_check.o: smtpd_dsn_fix.h +smtpd_check.o: smtpd_expand.h +smtpd_check.o: smtpd_resolve.h +smtpd_check.o: smtpd_sasl_glue.h +smtpd_dsn_fix.o: ../../include/msg.h +smtpd_dsn_fix.o: ../../include/sys_defs.h +smtpd_dsn_fix.o: smtpd_dsn_fix.c +smtpd_dsn_fix.o: smtpd_dsn_fix.h +smtpd_expand.o: ../../include/argv.h +smtpd_expand.o: ../../include/attr.h +smtpd_expand.o: ../../include/check_arg.h +smtpd_expand.o: ../../include/dns.h +smtpd_expand.o: ../../include/htable.h +smtpd_expand.o: ../../include/iostuff.h +smtpd_expand.o: ../../include/mac_expand.h +smtpd_expand.o: ../../include/mac_parse.h +smtpd_expand.o: ../../include/mail_params.h +smtpd_expand.o: ../../include/mail_proto.h +smtpd_expand.o: ../../include/mail_stream.h +smtpd_expand.o: ../../include/milter.h +smtpd_expand.o: ../../include/msg.h +smtpd_expand.o: ../../include/myaddrinfo.h +smtpd_expand.o: ../../include/mymalloc.h +smtpd_expand.o: ../../include/name_code.h +smtpd_expand.o: ../../include/name_mask.h +smtpd_expand.o: ../../include/nvtable.h +smtpd_expand.o: ../../include/sock_addr.h +smtpd_expand.o: ../../include/stringops.h +smtpd_expand.o: ../../include/sys_defs.h +smtpd_expand.o: ../../include/tls.h +smtpd_expand.o: ../../include/vbuf.h +smtpd_expand.o: ../../include/vstream.h +smtpd_expand.o: ../../include/vstring.h +smtpd_expand.o: smtpd.h +smtpd_expand.o: smtpd_expand.c +smtpd_expand.o: smtpd_expand.h +smtpd_haproxy.o: ../../include/argv.h +smtpd_haproxy.o: ../../include/attr.h +smtpd_haproxy.o: ../../include/check_arg.h +smtpd_haproxy.o: ../../include/dns.h +smtpd_haproxy.o: ../../include/haproxy_srvr.h +smtpd_haproxy.o: ../../include/htable.h +smtpd_haproxy.o: ../../include/iostuff.h +smtpd_haproxy.o: ../../include/mail_params.h +smtpd_haproxy.o: ../../include/mail_stream.h +smtpd_haproxy.o: ../../include/milter.h +smtpd_haproxy.o: ../../include/msg.h +smtpd_haproxy.o: ../../include/myaddrinfo.h +smtpd_haproxy.o: ../../include/mymalloc.h +smtpd_haproxy.o: ../../include/name_code.h +smtpd_haproxy.o: ../../include/name_mask.h +smtpd_haproxy.o: ../../include/nvtable.h +smtpd_haproxy.o: ../../include/smtp_stream.h +smtpd_haproxy.o: ../../include/sock_addr.h +smtpd_haproxy.o: ../../include/stringops.h +smtpd_haproxy.o: ../../include/sys_defs.h +smtpd_haproxy.o: ../../include/tls.h +smtpd_haproxy.o: ../../include/valid_hostname.h +smtpd_haproxy.o: ../../include/valid_mailhost_addr.h +smtpd_haproxy.o: ../../include/vbuf.h +smtpd_haproxy.o: ../../include/vstream.h +smtpd_haproxy.o: ../../include/vstring.h +smtpd_haproxy.o: smtpd.h +smtpd_haproxy.o: smtpd_haproxy.c +smtpd_milter.o: ../../include/argv.h +smtpd_milter.o: ../../include/attr.h +smtpd_milter.o: ../../include/check_arg.h +smtpd_milter.o: ../../include/dns.h +smtpd_milter.o: ../../include/htable.h +smtpd_milter.o: ../../include/mail_params.h +smtpd_milter.o: ../../include/mail_stream.h +smtpd_milter.o: ../../include/milter.h +smtpd_milter.o: ../../include/myaddrinfo.h +smtpd_milter.o: ../../include/mymalloc.h +smtpd_milter.o: ../../include/name_code.h +smtpd_milter.o: ../../include/name_mask.h +smtpd_milter.o: ../../include/nvtable.h +smtpd_milter.o: ../../include/quote_821_local.h +smtpd_milter.o: ../../include/quote_flags.h +smtpd_milter.o: ../../include/resolve_clnt.h +smtpd_milter.o: ../../include/sock_addr.h +smtpd_milter.o: ../../include/split_at.h +smtpd_milter.o: ../../include/stringops.h +smtpd_milter.o: ../../include/sys_defs.h +smtpd_milter.o: ../../include/tls.h +smtpd_milter.o: ../../include/vbuf.h +smtpd_milter.o: ../../include/vstream.h +smtpd_milter.o: ../../include/vstring.h +smtpd_milter.o: smtpd.h +smtpd_milter.o: smtpd_milter.c +smtpd_milter.o: smtpd_milter.h +smtpd_milter.o: smtpd_resolve.h +smtpd_milter.o: smtpd_sasl_glue.h +smtpd_peer.o: ../../include/argv.h +smtpd_peer.o: ../../include/attr.h +smtpd_peer.o: ../../include/check_arg.h +smtpd_peer.o: ../../include/dns.h +smtpd_peer.o: ../../include/haproxy_srvr.h +smtpd_peer.o: ../../include/htable.h +smtpd_peer.o: ../../include/inet_proto.h +smtpd_peer.o: ../../include/iostuff.h +smtpd_peer.o: ../../include/mail_params.h +smtpd_peer.o: ../../include/mail_proto.h +smtpd_peer.o: ../../include/mail_stream.h +smtpd_peer.o: ../../include/milter.h +smtpd_peer.o: ../../include/msg.h +smtpd_peer.o: ../../include/myaddrinfo.h +smtpd_peer.o: ../../include/mymalloc.h +smtpd_peer.o: ../../include/name_code.h +smtpd_peer.o: ../../include/name_mask.h +smtpd_peer.o: ../../include/nvtable.h +smtpd_peer.o: ../../include/sock_addr.h +smtpd_peer.o: ../../include/split_at.h +smtpd_peer.o: ../../include/stringops.h +smtpd_peer.o: ../../include/sys_defs.h +smtpd_peer.o: ../../include/tls.h +smtpd_peer.o: ../../include/valid_hostname.h +smtpd_peer.o: ../../include/valid_mailhost_addr.h +smtpd_peer.o: ../../include/vbuf.h +smtpd_peer.o: ../../include/vstream.h +smtpd_peer.o: ../../include/vstring.h +smtpd_peer.o: smtpd.h +smtpd_peer.o: smtpd_peer.c +smtpd_proxy.o: ../../include/argv.h +smtpd_proxy.o: ../../include/attr.h +smtpd_proxy.o: ../../include/check_arg.h +smtpd_proxy.o: ../../include/cleanup_user.h +smtpd_proxy.o: ../../include/connect.h +smtpd_proxy.o: ../../include/dns.h +smtpd_proxy.o: ../../include/htable.h +smtpd_proxy.o: ../../include/iostuff.h +smtpd_proxy.o: ../../include/mail_error.h +smtpd_proxy.o: ../../include/mail_params.h +smtpd_proxy.o: ../../include/mail_proto.h +smtpd_proxy.o: ../../include/mail_queue.h +smtpd_proxy.o: ../../include/mail_stream.h +smtpd_proxy.o: ../../include/milter.h +smtpd_proxy.o: ../../include/msg.h +smtpd_proxy.o: ../../include/myaddrinfo.h +smtpd_proxy.o: ../../include/mymalloc.h +smtpd_proxy.o: ../../include/name_code.h +smtpd_proxy.o: ../../include/name_mask.h +smtpd_proxy.o: ../../include/nvtable.h +smtpd_proxy.o: ../../include/rec_type.h +smtpd_proxy.o: ../../include/record.h +smtpd_proxy.o: ../../include/smtp_stream.h +smtpd_proxy.o: ../../include/sock_addr.h +smtpd_proxy.o: ../../include/stringops.h +smtpd_proxy.o: ../../include/sys_defs.h +smtpd_proxy.o: ../../include/tls.h +smtpd_proxy.o: ../../include/vbuf.h +smtpd_proxy.o: ../../include/vstream.h +smtpd_proxy.o: ../../include/vstring.h +smtpd_proxy.o: ../../include/xtext.h +smtpd_proxy.o: smtpd.h +smtpd_proxy.o: smtpd_proxy.c +smtpd_proxy.o: smtpd_proxy.h +smtpd_resolve.o: ../../include/attr.h +smtpd_resolve.o: ../../include/check_arg.h +smtpd_resolve.o: ../../include/ctable.h +smtpd_resolve.o: ../../include/htable.h +smtpd_resolve.o: ../../include/iostuff.h +smtpd_resolve.o: ../../include/mail_proto.h +smtpd_resolve.o: ../../include/msg.h +smtpd_resolve.o: ../../include/mymalloc.h +smtpd_resolve.o: ../../include/nvtable.h +smtpd_resolve.o: ../../include/resolve_clnt.h +smtpd_resolve.o: ../../include/rewrite_clnt.h +smtpd_resolve.o: ../../include/split_at.h +smtpd_resolve.o: ../../include/stringops.h +smtpd_resolve.o: ../../include/sys_defs.h +smtpd_resolve.o: ../../include/vbuf.h +smtpd_resolve.o: ../../include/vstream.h +smtpd_resolve.o: ../../include/vstring.h +smtpd_resolve.o: smtpd_resolve.c +smtpd_resolve.o: smtpd_resolve.h +smtpd_sasl_glue.o: ../../include/argv.h +smtpd_sasl_glue.o: ../../include/attr.h +smtpd_sasl_glue.o: ../../include/check_arg.h +smtpd_sasl_glue.o: ../../include/dns.h +smtpd_sasl_glue.o: ../../include/htable.h +smtpd_sasl_glue.o: ../../include/mail_params.h +smtpd_sasl_glue.o: ../../include/mail_stream.h +smtpd_sasl_glue.o: ../../include/match_list.h +smtpd_sasl_glue.o: ../../include/milter.h +smtpd_sasl_glue.o: ../../include/msg.h +smtpd_sasl_glue.o: ../../include/myaddrinfo.h +smtpd_sasl_glue.o: ../../include/mymalloc.h +smtpd_sasl_glue.o: ../../include/name_code.h +smtpd_sasl_glue.o: ../../include/name_mask.h +smtpd_sasl_glue.o: ../../include/nvtable.h +smtpd_sasl_glue.o: ../../include/sasl_mech_filter.h +smtpd_sasl_glue.o: ../../include/sock_addr.h +smtpd_sasl_glue.o: ../../include/string_list.h +smtpd_sasl_glue.o: ../../include/stringops.h +smtpd_sasl_glue.o: ../../include/sys_defs.h +smtpd_sasl_glue.o: ../../include/tls.h +smtpd_sasl_glue.o: ../../include/vbuf.h +smtpd_sasl_glue.o: ../../include/vstream.h +smtpd_sasl_glue.o: ../../include/vstring.h +smtpd_sasl_glue.o: ../../include/xsasl.h +smtpd_sasl_glue.o: smtpd.h +smtpd_sasl_glue.o: smtpd_chat.h +smtpd_sasl_glue.o: smtpd_sasl_glue.c +smtpd_sasl_glue.o: smtpd_sasl_glue.h +smtpd_sasl_proto.o: ../../include/argv.h +smtpd_sasl_proto.o: ../../include/attr.h +smtpd_sasl_proto.o: ../../include/check_arg.h +smtpd_sasl_proto.o: ../../include/dns.h +smtpd_sasl_proto.o: ../../include/ehlo_mask.h +smtpd_sasl_proto.o: ../../include/htable.h +smtpd_sasl_proto.o: ../../include/iostuff.h +smtpd_sasl_proto.o: ../../include/mail_error.h +smtpd_sasl_proto.o: ../../include/mail_params.h +smtpd_sasl_proto.o: ../../include/mail_proto.h +smtpd_sasl_proto.o: ../../include/mail_stream.h +smtpd_sasl_proto.o: ../../include/milter.h +smtpd_sasl_proto.o: ../../include/msg.h +smtpd_sasl_proto.o: ../../include/myaddrinfo.h +smtpd_sasl_proto.o: ../../include/mymalloc.h +smtpd_sasl_proto.o: ../../include/name_code.h +smtpd_sasl_proto.o: ../../include/name_mask.h +smtpd_sasl_proto.o: ../../include/nvtable.h +smtpd_sasl_proto.o: ../../include/sock_addr.h +smtpd_sasl_proto.o: ../../include/stringops.h +smtpd_sasl_proto.o: ../../include/sys_defs.h +smtpd_sasl_proto.o: ../../include/tls.h +smtpd_sasl_proto.o: ../../include/vbuf.h +smtpd_sasl_proto.o: ../../include/vstream.h +smtpd_sasl_proto.o: ../../include/vstring.h +smtpd_sasl_proto.o: smtpd.h +smtpd_sasl_proto.o: smtpd_chat.h +smtpd_sasl_proto.o: smtpd_sasl_glue.h +smtpd_sasl_proto.o: smtpd_sasl_proto.c +smtpd_sasl_proto.o: smtpd_sasl_proto.h +smtpd_sasl_proto.o: smtpd_token.h +smtpd_state.o: ../../include/argv.h +smtpd_state.o: ../../include/attr.h +smtpd_state.o: ../../include/check_arg.h +smtpd_state.o: ../../include/cleanup_user.h +smtpd_state.o: ../../include/dns.h +smtpd_state.o: ../../include/events.h +smtpd_state.o: ../../include/htable.h +smtpd_state.o: ../../include/iostuff.h +smtpd_state.o: ../../include/mail_error.h +smtpd_state.o: ../../include/mail_params.h +smtpd_state.o: ../../include/mail_proto.h +smtpd_state.o: ../../include/mail_stream.h +smtpd_state.o: ../../include/milter.h +smtpd_state.o: ../../include/msg.h +smtpd_state.o: ../../include/myaddrinfo.h +smtpd_state.o: ../../include/mymalloc.h +smtpd_state.o: ../../include/name_code.h +smtpd_state.o: ../../include/name_mask.h +smtpd_state.o: ../../include/nvtable.h +smtpd_state.o: ../../include/sock_addr.h +smtpd_state.o: ../../include/sys_defs.h +smtpd_state.o: ../../include/tls.h +smtpd_state.o: ../../include/vbuf.h +smtpd_state.o: ../../include/vstream.h +smtpd_state.o: ../../include/vstring.h +smtpd_state.o: smtpd.h +smtpd_state.o: smtpd_chat.h +smtpd_state.o: smtpd_sasl_glue.h +smtpd_state.o: smtpd_state.c +smtpd_token.o: ../../include/check_arg.h +smtpd_token.o: ../../include/mvect.h +smtpd_token.o: ../../include/mymalloc.h +smtpd_token.o: ../../include/sys_defs.h +smtpd_token.o: ../../include/vbuf.h +smtpd_token.o: ../../include/vstring.h +smtpd_token.o: smtpd_token.c +smtpd_token.o: smtpd_token.h +smtpd_xforward.o: ../../include/argv.h +smtpd_xforward.o: ../../include/attr.h +smtpd_xforward.o: ../../include/check_arg.h +smtpd_xforward.o: ../../include/dns.h +smtpd_xforward.o: ../../include/htable.h +smtpd_xforward.o: ../../include/iostuff.h +smtpd_xforward.o: ../../include/mail_proto.h +smtpd_xforward.o: ../../include/mail_stream.h +smtpd_xforward.o: ../../include/milter.h +smtpd_xforward.o: ../../include/msg.h +smtpd_xforward.o: ../../include/myaddrinfo.h +smtpd_xforward.o: ../../include/mymalloc.h +smtpd_xforward.o: ../../include/name_code.h +smtpd_xforward.o: ../../include/name_mask.h +smtpd_xforward.o: ../../include/nvtable.h +smtpd_xforward.o: ../../include/sock_addr.h +smtpd_xforward.o: ../../include/sys_defs.h +smtpd_xforward.o: ../../include/tls.h +smtpd_xforward.o: ../../include/vbuf.h +smtpd_xforward.o: ../../include/vstream.h +smtpd_xforward.o: ../../include/vstring.h +smtpd_xforward.o: smtpd.h +smtpd_xforward.o: smtpd_xforward.c diff --git a/src/smtpd/smtpd.c b/src/smtpd/smtpd.c new file mode 100644 index 0000000..4f4aedd --- /dev/null +++ b/src/smtpd/smtpd.c @@ -0,0 +1,6776 @@ +/*++ +/* NAME +/* smtpd 8 +/* SUMMARY +/* Postfix SMTP server +/* SYNOPSIS +/* \fBsmtpd\fR [generic Postfix daemon options] +/* +/* \fBsendmail -bs\fR +/* DESCRIPTION +/* The SMTP server accepts network connection requests +/* and performs zero or more SMTP transactions per connection. +/* Each received message is piped through the \fBcleanup\fR(8) +/* daemon, and is placed into the \fBincoming\fR queue as one +/* single queue file. For this mode of operation, the program +/* expects to be run from the \fBmaster\fR(8) process manager. +/* +/* Alternatively, the SMTP server be can run in stand-alone +/* mode; this is traditionally obtained with "\fBsendmail +/* -bs\fR". When the SMTP server runs stand-alone with non +/* $\fBmail_owner\fR privileges, it receives mail even while +/* the mail system is not running, deposits messages directly +/* into the \fBmaildrop\fR queue, and disables the SMTP server's +/* access policies. As of Postfix version 2.3, the SMTP server +/* refuses to receive mail from the network when it runs with +/* non $\fBmail_owner\fR privileges. +/* +/* The SMTP server implements a variety of policies for connection +/* requests, and for parameters given to \fBHELO, ETRN, MAIL FROM, VRFY\fR +/* and \fBRCPT TO\fR commands. They are detailed below and in the +/* \fBmain.cf\fR configuration file. +/* SECURITY +/* .ad +/* .fi +/* The SMTP server is moderately security-sensitive. It talks to SMTP +/* clients and to DNS servers on the network. The SMTP server can be +/* run chrooted at fixed low privilege. +/* STANDARDS +/* RFC 821 (SMTP protocol) +/* RFC 1123 (Host requirements) +/* RFC 1652 (8bit-MIME transport) +/* RFC 1869 (SMTP service extensions) +/* RFC 1870 (Message size declaration) +/* RFC 1985 (ETRN command) +/* RFC 2034 (SMTP enhanced status codes) +/* RFC 2554 (AUTH command) +/* RFC 2821 (SMTP protocol) +/* RFC 2920 (SMTP pipelining) +/* RFC 3030 (CHUNKING without BINARYMIME) +/* RFC 3207 (STARTTLS command) +/* RFC 3461 (SMTP DSN extension) +/* RFC 3463 (Enhanced status codes) +/* RFC 3848 (ESMTP transmission types) +/* RFC 4409 (Message submission) +/* RFC 4954 (AUTH command) +/* RFC 5321 (SMTP protocol) +/* RFC 6531 (Internationalized SMTP) +/* RFC 6533 (Internationalized Delivery Status Notifications) +/* RFC 7505 ("Null MX" No Service Resource Record) +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces, protocol problems, +/* policy violations, and of other trouble. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically, as \fBsmtpd\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* The following parameters work around implementation errors in other +/* software, and/or allow you to override standards in order to prevent +/* undesirable use. +/* .ad +/* .fi +/* .IP "\fBbroken_sasl_auth_clients (no)\fR" +/* Enable interoperability with remote SMTP clients that implement an obsolete +/* version of the AUTH command (RFC 4954). +/* .IP "\fBdisable_vrfy_command (no)\fR" +/* Disable the SMTP VRFY command. +/* .IP "\fBsmtpd_noop_commands (empty)\fR" +/* List of commands that the Postfix SMTP server replies to with "250 +/* Ok", without doing any syntax checks and without changing state. +/* .IP "\fBstrict_rfc821_envelopes (no)\fR" +/* Require that addresses received in SMTP MAIL FROM and RCPT TO +/* commands are enclosed with <>, and that those addresses do +/* not contain RFC 822 style comments or phrases. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_reject_unlisted_sender (no)\fR" +/* Request that the Postfix SMTP server rejects mail from unknown +/* sender addresses, even when no explicit reject_unlisted_sender +/* access restriction is specified. +/* .IP "\fBsmtpd_sasl_exceptions_networks (empty)\fR" +/* What remote SMTP clients the Postfix SMTP server will not offer +/* AUTH support to. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtpd_discard_ehlo_keyword_address_maps (empty)\fR" +/* Lookup tables, indexed by the remote SMTP client address, with +/* case insensitive lists of EHLO keywords (pipelining, starttls, auth, +/* etc.) that the Postfix SMTP server will not send in the EHLO response +/* to a +/* remote SMTP client. +/* .IP "\fBsmtpd_discard_ehlo_keywords (empty)\fR" +/* A case insensitive list of EHLO keywords (pipelining, starttls, +/* auth, etc.) that the Postfix SMTP server will not send in the EHLO +/* response +/* to a remote SMTP client. +/* .IP "\fBsmtpd_delay_open_until_valid_rcpt (yes)\fR" +/* Postpone the start of an SMTP mail transaction until a valid +/* RCPT TO command is received. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_tls_always_issue_session_ids (yes)\fR" +/* Force the Postfix SMTP server to issue a TLS session id, even +/* when TLS session caching is turned off (smtpd_tls_session_cache_database +/* is empty). +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBtcp_windowsize (0)\fR" +/* An optional workaround for routers that break TCP window scaling. +/* .PP +/* Available in Postfix version 2.7 and later: +/* .IP "\fBsmtpd_command_filter (empty)\fR" +/* A mechanism to transform commands from remote SMTP clients. +/* .PP +/* Available in Postfix version 2.9 - 3.6: +/* .IP "\fBsmtpd_per_record_deadline (normal: no, overload: yes)\fR" +/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout +/* time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtpd_dns_reply_filter (empty)\fR" +/* Optional filter for Postfix SMTP server DNS lookup results. +/* .PP +/* Available in Postfix version 3.6 and later: +/* .IP "\fBsmtpd_relay_before_recipient_restrictions (see 'postconf -d' output)\fR" +/* Evaluate smtpd_relay_restrictions before smtpd_recipient_restrictions. +/* .IP "\fBknown_tcp_ports (lmtp=24, smtp=25, smtps=submissions=465, submission=587)\fR" +/* Optional setting that avoids lookups in the \fBservices\fR(5) database. +/* .PP +/* Available in Postfix version 3.7 and later: +/* .IP "\fBsmtpd_per_request_deadline (normal: no, overload: yes)\fR" +/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout +/* time limits, from a time limit per plaintext or TLS read or write +/* call, to a combined time limit for receiving a complete SMTP request +/* and for sending a complete SMTP response. +/* .IP "\fBsmtpd_min_data_rate (500)\fR" +/* The minimum plaintext data transfer rate in bytes/second for +/* DATA and BDAT requests, when deadlines are enabled with +/* smtpd_per_request_deadline. +/* ADDRESS REWRITING CONTROLS +/* .ad +/* .fi +/* See the ADDRESS_REWRITING_README document for a detailed +/* discussion of Postfix address rewriting. +/* .IP "\fBreceive_override_options (empty)\fR" +/* Enable or disable recipient validation, built-in content +/* filtering, or address mapping. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBlocal_header_rewrite_clients (permit_inet_interfaces)\fR" +/* Rewrite message header addresses in mail from these clients and +/* update incomplete addresses with the domain name in $myorigin or +/* $mydomain; either don't rewrite message headers from other clients +/* at all, or rewrite message headers and update incomplete addresses +/* with the domain specified in the remote_header_rewrite_domain +/* parameter. +/* BEFORE-SMTPD PROXY AGENT +/* .ad +/* .fi +/* Available in Postfix version 2.10 and later: +/* .IP "\fBsmtpd_upstream_proxy_protocol (empty)\fR" +/* The name of the proxy protocol used by an optional before-smtpd +/* proxy agent. +/* .IP "\fBsmtpd_upstream_proxy_timeout (5s)\fR" +/* The time limit for the proxy protocol specified with the +/* smtpd_upstream_proxy_protocol parameter. +/* AFTER QUEUE EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* As of version 1.0, Postfix can be configured to send new mail to +/* an external content filter AFTER the mail is queued. This content +/* filter is expected to inject mail back into a (Postfix or other) +/* MTA for further delivery. See the FILTER_README document for details. +/* .IP "\fBcontent_filter (empty)\fR" +/* After the message is queued, send the entire message to the +/* specified \fItransport:destination\fR. +/* BEFORE QUEUE EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* As of version 2.1, the Postfix SMTP server can be configured +/* to send incoming mail to a real-time SMTP-based content filter +/* BEFORE mail is queued. This content filter is expected to inject +/* mail back into Postfix. See the SMTPD_PROXY_README document for +/* details on how to configure and operate this feature. +/* .IP "\fBsmtpd_proxy_filter (empty)\fR" +/* The hostname and TCP port of the mail filtering proxy server. +/* .IP "\fBsmtpd_proxy_ehlo ($myhostname)\fR" +/* How the Postfix SMTP server announces itself to the proxy filter. +/* .IP "\fBsmtpd_proxy_options (empty)\fR" +/* List of options that control how the Postfix SMTP server +/* communicates with a before-queue content filter. +/* .IP "\fBsmtpd_proxy_timeout (100s)\fR" +/* The time limit for connecting to a proxy filter and for sending or +/* receiving information. +/* BEFORE QUEUE MILTER CONTROLS +/* .ad +/* .fi +/* As of version 2.3, Postfix supports the Sendmail version 8 +/* Milter (mail filter) protocol. These content filters run +/* outside Postfix. They can inspect the SMTP command stream +/* and the message content, and can request modifications before +/* mail is queued. For details see the MILTER_README document. +/* .IP "\fBsmtpd_milters (empty)\fR" +/* A list of Milter (mail filter) applications for new mail that +/* arrives via the Postfix \fBsmtpd\fR(8) server. +/* .IP "\fBmilter_protocol (6)\fR" +/* The mail filter protocol version and optional protocol extensions +/* for communication with a Milter application; prior to Postfix 2.6 +/* the default protocol is 2. +/* .IP "\fBmilter_default_action (tempfail)\fR" +/* The default action when a Milter (mail filter) response is +/* unavailable (for example, bad Postfix configuration or Milter +/* failure). +/* .IP "\fBmilter_macro_daemon_name ($myhostname)\fR" +/* The {daemon_name} macro value for Milter (mail filter) applications. +/* .IP "\fBmilter_macro_v ($mail_name $mail_version)\fR" +/* The {v} macro value for Milter (mail filter) applications. +/* .IP "\fBmilter_connect_timeout (30s)\fR" +/* The time limit for connecting to a Milter (mail filter) +/* application, and for negotiating protocol options. +/* .IP "\fBmilter_command_timeout (30s)\fR" +/* The time limit for sending an SMTP command to a Milter (mail +/* filter) application, and for receiving the response. +/* .IP "\fBmilter_content_timeout (300s)\fR" +/* The time limit for sending message content to a Milter (mail +/* filter) application, and for receiving the response. +/* .IP "\fBmilter_connect_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after completion of an SMTP connection. +/* .IP "\fBmilter_helo_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP HELO or EHLO command. +/* .IP "\fBmilter_mail_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP MAIL FROM command. +/* .IP "\fBmilter_rcpt_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the SMTP RCPT TO command. +/* .IP "\fBmilter_data_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to version 4 or higher Milter (mail +/* filter) applications after the SMTP DATA command. +/* .IP "\fBmilter_unknown_command_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to version 3 or higher Milter (mail +/* filter) applications after an unknown SMTP command. +/* .IP "\fBmilter_end_of_header_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the end of the message header. +/* .IP "\fBmilter_end_of_data_macros (see 'postconf -d' output)\fR" +/* The macros that are sent to Milter (mail filter) applications +/* after the message end-of-data. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBmilter_macro_defaults (empty)\fR" +/* Optional list of \fIname=value\fR pairs that specify default +/* values for arbitrary macros that Postfix may send to Milter +/* applications. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBsmtpd_milter_maps (empty)\fR" +/* Lookup tables with Milter settings per remote SMTP client IP +/* address. +/* GENERAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* The following parameters are applicable for both built-in +/* and external content filters. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBreceive_override_options (empty)\fR" +/* Enable or disable recipient validation, built-in content +/* filtering, or address mapping. +/* EXTERNAL CONTENT INSPECTION CONTROLS +/* .ad +/* .fi +/* The following parameters are applicable for both before-queue +/* and after-queue content filtering. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_authorized_xforward_hosts (empty)\fR" +/* What remote SMTP clients are allowed to use the XFORWARD feature. +/* SASL AUTHENTICATION CONTROLS +/* .ad +/* .fi +/* Postfix SASL support (RFC 4954) can be used to authenticate remote +/* SMTP clients to the Postfix SMTP server, and to authenticate the +/* Postfix SMTP client to a remote SMTP server. +/* See the SASL_README document for details. +/* .IP "\fBbroken_sasl_auth_clients (no)\fR" +/* Enable interoperability with remote SMTP clients that implement an obsolete +/* version of the AUTH command (RFC 4954). +/* .IP "\fBsmtpd_sasl_auth_enable (no)\fR" +/* Enable SASL authentication in the Postfix SMTP server. +/* .IP "\fBsmtpd_sasl_local_domain (empty)\fR" +/* The name of the Postfix SMTP server's local SASL authentication +/* realm. +/* .IP "\fBsmtpd_sasl_security_options (noanonymous)\fR" +/* Postfix SMTP server SASL security options; as of Postfix 2.3 +/* the list of available +/* features depends on the SASL server implementation that is selected +/* with \fBsmtpd_sasl_type\fR. +/* .IP "\fBsmtpd_sender_login_maps (empty)\fR" +/* Optional lookup table with the SASL login names that own the sender +/* (MAIL FROM) addresses. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_sasl_exceptions_networks (empty)\fR" +/* What remote SMTP clients the Postfix SMTP server will not offer +/* AUTH support to. +/* .PP +/* Available in Postfix version 2.1 and 2.2: +/* .IP "\fBsmtpd_sasl_application_name (smtpd)\fR" +/* The application name that the Postfix SMTP server uses for SASL +/* server initialization. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_sasl_authenticated_header (no)\fR" +/* Report the SASL authenticated user name in the \fBsmtpd\fR(8) Received +/* message header. +/* .IP "\fBsmtpd_sasl_path (smtpd)\fR" +/* Implementation-specific information that the Postfix SMTP server +/* passes through to +/* the SASL plug-in implementation that is selected with +/* \fBsmtpd_sasl_type\fR. +/* .IP "\fBsmtpd_sasl_type (cyrus)\fR" +/* The SASL plug-in type that the Postfix SMTP server should use +/* for authentication. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBcyrus_sasl_config_path (empty)\fR" +/* Search path for Cyrus SASL application configuration files, +/* currently used only to locate the $smtpd_sasl_path.conf file. +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBsmtpd_sasl_service (smtp)\fR" +/* The service name that is passed to the SASL plug-in that is +/* selected with \fBsmtpd_sasl_type\fR and \fBsmtpd_sasl_path\fR. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtpd_sasl_response_limit (12288)\fR" +/* The maximum length of a SASL client's response to a server challenge. +/* .PP +/* Available in Postfix 3.6 and later: +/* .IP "\fBsmtpd_sasl_mechanism_filter (!external, static:rest)\fR" +/* If non-empty, a filter for the SASL mechanism names that the +/* Postfix SMTP server will announce in the EHLO response. +/* STARTTLS SUPPORT CONTROLS +/* .ad +/* .fi +/* Detailed information about STARTTLS configuration may be +/* found in the TLS_README document. +/* .IP "\fBsmtpd_tls_security_level (empty)\fR" +/* The SMTP TLS security level for the Postfix SMTP server; when +/* a non-empty value is specified, this overrides the obsolete parameters +/* smtpd_use_tls and smtpd_enforce_tls. +/* .IP "\fBsmtpd_sasl_tls_security_options ($smtpd_sasl_security_options)\fR" +/* The SASL authentication security options that the Postfix SMTP +/* server uses for TLS encrypted SMTP sessions. +/* .IP "\fBsmtpd_starttls_timeout (see 'postconf -d' output)\fR" +/* The time limit for Postfix SMTP server write and read operations +/* during TLS startup and shutdown handshake procedures. +/* .IP "\fBsmtpd_tls_CAfile (empty)\fR" +/* A file containing (PEM format) CA certificates of root CAs trusted +/* to sign either remote SMTP client certificates or intermediate CA +/* certificates. +/* .IP "\fBsmtpd_tls_CApath (empty)\fR" +/* A directory containing (PEM format) CA certificates of root CAs +/* trusted to sign either remote SMTP client certificates or intermediate CA +/* certificates. +/* .IP "\fBsmtpd_tls_always_issue_session_ids (yes)\fR" +/* Force the Postfix SMTP server to issue a TLS session id, even +/* when TLS session caching is turned off (smtpd_tls_session_cache_database +/* is empty). +/* .IP "\fBsmtpd_tls_ask_ccert (no)\fR" +/* Ask a remote SMTP client for a client certificate. +/* .IP "\fBsmtpd_tls_auth_only (no)\fR" +/* When TLS encryption is optional in the Postfix SMTP server, do +/* not announce or accept SASL authentication over unencrypted +/* connections. +/* .IP "\fBsmtpd_tls_ccert_verifydepth (9)\fR" +/* The verification depth for remote SMTP client certificates. +/* .IP "\fBsmtpd_tls_cert_file (empty)\fR" +/* File with the Postfix SMTP server RSA certificate in PEM format. +/* .IP "\fBsmtpd_tls_exclude_ciphers (empty)\fR" +/* List of ciphers or cipher types to exclude from the SMTP server +/* cipher list at all TLS security levels. +/* .IP "\fBsmtpd_tls_dcert_file (empty)\fR" +/* File with the Postfix SMTP server DSA certificate in PEM format. +/* .IP "\fBsmtpd_tls_dh1024_param_file (empty)\fR" +/* File with DH parameters that the Postfix SMTP server should +/* use with non-export EDH ciphers. +/* .IP "\fBsmtpd_tls_dh512_param_file (empty)\fR" +/* File with DH parameters that the Postfix SMTP server should +/* use with export-grade EDH ciphers. +/* .IP "\fBsmtpd_tls_dkey_file ($smtpd_tls_dcert_file)\fR" +/* File with the Postfix SMTP server DSA private key in PEM format. +/* .IP "\fBsmtpd_tls_key_file ($smtpd_tls_cert_file)\fR" +/* File with the Postfix SMTP server RSA private key in PEM format. +/* .IP "\fBsmtpd_tls_loglevel (0)\fR" +/* Enable additional Postfix SMTP server logging of TLS activity. +/* .IP "\fBsmtpd_tls_mandatory_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP server will +/* use with mandatory TLS encryption. +/* .IP "\fBsmtpd_tls_mandatory_exclude_ciphers (empty)\fR" +/* Additional list of ciphers or cipher types to exclude from the +/* Postfix SMTP server cipher list at mandatory TLS security levels. +/* .IP "\fBsmtpd_tls_mandatory_protocols (see 'postconf -d' output)\fR" +/* TLS protocols accepted by the Postfix SMTP server with mandatory TLS +/* encryption. +/* .IP "\fBsmtpd_tls_received_header (no)\fR" +/* Request that the Postfix SMTP server produces Received: message +/* headers that include information about the protocol and cipher used, +/* as well as the remote SMTP client CommonName and client certificate issuer +/* CommonName. +/* .IP "\fBsmtpd_tls_req_ccert (no)\fR" +/* With mandatory TLS encryption, require a trusted remote SMTP client +/* certificate in order to allow TLS connections to proceed. +/* .IP "\fBsmtpd_tls_wrappermode (no)\fR" +/* Run the Postfix SMTP server in the non-standard "wrapper" mode, +/* instead of using the STARTTLS command. +/* .IP "\fBtls_daemon_random_bytes (32)\fR" +/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8) +/* process requests from the \fBtlsmgr\fR(8) server in order to seed its +/* internal pseudo random number generator (PRNG). +/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "high" grade ciphers. +/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "medium" or higher grade ciphers. +/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "low" or higher grade ciphers. +/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "export" or higher grade ciphers. +/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" +/* The OpenSSL cipherlist for "NULL" grade ciphers that provide +/* authentication without encryption. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtpd_tls_fingerprint_digest (see 'postconf -d' output)\fR" +/* The message digest algorithm to construct remote SMTP client-certificate +/* fingerprints or public key fingerprints (Postfix 2.9 and later) for +/* \fBcheck_ccert_access\fR and \fBpermit_tls_clientcerts\fR. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBsmtpd_tls_protocols (see postconf -d output)\fR" +/* TLS protocols accepted by the Postfix SMTP server with opportunistic +/* TLS encryption. +/* .IP "\fBsmtpd_tls_ciphers (medium)\fR" +/* The minimum TLS cipher grade that the Postfix SMTP server +/* will use with opportunistic TLS encryption. +/* .IP "\fBsmtpd_tls_eccert_file (empty)\fR" +/* File with the Postfix SMTP server ECDSA certificate in PEM format. +/* .IP "\fBsmtpd_tls_eckey_file ($smtpd_tls_eccert_file)\fR" +/* File with the Postfix SMTP server ECDSA private key in PEM format. +/* .IP "\fBsmtpd_tls_eecdh_grade (see 'postconf -d' output)\fR" +/* The Postfix SMTP server security grade for ephemeral elliptic-curve +/* Diffie-Hellman (EECDH) key exchange. +/* .IP "\fBtls_eecdh_strong_curve (prime256v1)\fR" +/* The elliptic curve used by the Postfix SMTP server for sensibly +/* strong +/* ephemeral ECDH key exchange. +/* .IP "\fBtls_eecdh_ultra_curve (secp384r1)\fR" +/* The elliptic curve used by the Postfix SMTP server for maximally +/* strong +/* ephemeral ECDH key exchange. +/* .PP +/* Available in Postfix version 2.8 and later: +/* .IP "\fBtls_preempt_cipherlist (no)\fR" +/* With SSLv3 and later, use the Postfix SMTP server's cipher +/* preference order instead of the remote client's cipher preference +/* order. +/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR" +/* List or bit-mask of OpenSSL bug work-arounds to disable. +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR" +/* The name of the \fBtlsmgr\fR(8) service entry in master.cf. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBtls_session_ticket_cipher (Postfix >= 3.0: aes-256-cbc, Postfix < 3.0: aes-128-cbc)\fR" +/* Algorithm used to encrypt RFC5077 TLS session tickets. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBtls_eecdh_auto_curves (see 'postconf -d' output)\fR" +/* The prioritized list of elliptic curves supported by the Postfix +/* SMTP client and server. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBsmtpd_tls_chain_files (empty)\fR" +/* List of one or more PEM files, each holding one or more private keys +/* directly followed by a corresponding certificate chain. +/* .IP "\fBtls_server_sni_maps (empty)\fR" +/* Optional lookup tables that map names received from remote SMTP +/* clients via the TLS Server Name Indication (SNI) extension to the +/* appropriate keys and certificate chains. +/* .PP +/* Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later: +/* .IP "\fBtls_fast_shutdown_enable (yes)\fR" +/* A workaround for implementations that hang Postfix while shutting +/* down a TLS session, until Postfix times out. +/* .PP +/* Available in Postfix 3.5 and later: +/* .IP "\fBinfo_log_address_format (external)\fR" +/* The email address form that will be used in non-debug logging +/* (info, warning, etc.). +/* .PP +/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later: +/* .IP "\fBtls_config_file (default)\fR" +/* Optional configuration file with baseline OpenSSL settings. +/* .IP "\fBtls_config_name (empty)\fR" +/* The application name passed by Postfix to OpenSSL library +/* initialization functions. +/* OBSOLETE STARTTLS CONTROLS +/* .ad +/* .fi +/* The following configuration parameters exist for compatibility +/* with Postfix versions before 2.3. Support for these will +/* be removed in a future release. +/* .IP "\fBsmtpd_use_tls (no)\fR" +/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients, +/* but do not require that clients use TLS encryption. +/* .IP "\fBsmtpd_enforce_tls (no)\fR" +/* Mandatory TLS: announce STARTTLS support to remote SMTP clients, +/* and require that clients use TLS encryption. +/* .IP "\fBsmtpd_tls_cipherlist (empty)\fR" +/* Obsolete Postfix < 2.3 control for the Postfix SMTP server TLS +/* cipher list. +/* SMTPUTF8 CONTROLS +/* .ad +/* .fi +/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0. +/* .IP "\fBsmtputf8_enable (yes)\fR" +/* Enable preliminary SMTPUTF8 support for the protocols described +/* in RFC 6531..6533. +/* .IP "\fBstrict_smtputf8 (no)\fR" +/* Enable stricter enforcement of the SMTPUTF8 protocol. +/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR" +/* Detect that a message requires SMTPUTF8 support for the specified +/* mail origin classes. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBenable_idna2003_compatibility (no)\fR" +/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008, +/* when converting UTF-8 domain names to/from the ASCII form that is +/* used for DNS lookups. +/* VERP SUPPORT CONTROLS +/* .ad +/* .fi +/* With VERP style delivery, each recipient of a message receives a +/* customized copy of the message with his/her own recipient address +/* encoded in the envelope sender address. The VERP_README file +/* describes configuration and operation details of Postfix support +/* for variable envelope return path addresses. VERP style delivery +/* is requested with the SMTP XVERP command or with the "sendmail +/* -V" command-line option and is available in Postfix version 1.1 +/* and later. +/* .IP "\fBdefault_verp_delimiters (+=)\fR" +/* The two default VERP delimiter characters. +/* .IP "\fBverp_delimiter_filter (-=+)\fR" +/* The characters Postfix accepts as VERP delimiter characters on the +/* Postfix \fBsendmail\fR(1) command line and in SMTP commands. +/* .PP +/* Available in Postfix version 1.1 and 2.0: +/* .IP "\fBauthorized_verp_clients ($mynetworks)\fR" +/* What remote SMTP clients are allowed to specify the XVERP command. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_authorized_verp_clients ($authorized_verp_clients)\fR" +/* What remote SMTP clients are allowed to specify the XVERP command. +/* TROUBLE SHOOTING CONTROLS +/* .ad +/* .fi +/* The DEBUG_README document describes how to debug parts of the +/* Postfix mail system. The methods vary from making the software log +/* a lot of detail, to running some daemon processes under control of +/* a call tracer or debugger. +/* .IP "\fBdebug_peer_level (2)\fR" +/* The increment in verbose logging level when a nexthop destination, +/* remote client or server name or network address matches a pattern +/* given with the debug_peer_list parameter. +/* .IP "\fBdebug_peer_list (empty)\fR" +/* Optional list of nexthop destination, remote client or server +/* name or network address patterns that, if matched, cause the verbose +/* logging level to increase by the amount specified in $debug_peer_level. +/* .IP "\fBerror_notice_recipient (postmaster)\fR" +/* The recipient of postmaster notifications about mail delivery +/* problems that are caused by policy, resource, software or protocol +/* errors. +/* .IP "\fBinternal_mail_filter_classes (empty)\fR" +/* What categories of Postfix-generated mail are subject to +/* before-queue content inspection by non_smtpd_milters, header_checks +/* and body_checks. +/* .IP "\fBnotify_classes (resource, software)\fR" +/* The list of error classes that are reported to the postmaster. +/* .IP "\fBsmtpd_reject_footer (empty)\fR" +/* Optional information that is appended after each Postfix SMTP +/* server +/* 4XX or 5XX response. +/* .IP "\fBsoft_bounce (no)\fR" +/* Safety net to keep mail queued that would otherwise be returned to +/* the sender. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_authorized_xclient_hosts (empty)\fR" +/* What remote SMTP clients are allowed to use the XCLIENT feature. +/* .PP +/* Available in Postfix version 2.10 and later: +/* .IP "\fBsmtpd_log_access_permit_actions (empty)\fR" +/* Enable logging of the named "permit" actions in SMTP server +/* access lists (by default, the SMTP server logs "reject" actions but +/* not "permit" actions). +/* KNOWN VERSUS UNKNOWN RECIPIENT CONTROLS +/* .ad +/* .fi +/* As of Postfix version 2.0, the SMTP server rejects mail for +/* unknown recipients. This prevents the mail queue from clogging up +/* with undeliverable MAILER-DAEMON messages. Additional information +/* on this topic is in the LOCAL_RECIPIENT_README and ADDRESS_CLASS_README +/* documents. +/* .IP "\fBshow_user_unknown_table_name (yes)\fR" +/* Display the name of the recipient table in the "User unknown" +/* responses. +/* .IP "\fBcanonical_maps (empty)\fR" +/* Optional address mapping lookup tables for message headers and +/* envelopes. +/* .IP "\fBrecipient_canonical_maps (empty)\fR" +/* Optional address mapping lookup tables for envelope and header +/* recipient addresses. +/* .IP "\fBsender_canonical_maps (empty)\fR" +/* Optional address mapping lookup tables for envelope and header +/* sender addresses. +/* .PP +/* Parameters concerning known/unknown local recipients: +/* .IP "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR" +/* The list of domains that are delivered via the $local_transport +/* mail delivery transport. +/* .IP "\fBinet_interfaces (all)\fR" +/* The network interface addresses that this mail system receives +/* mail on. +/* .IP "\fBproxy_interfaces (empty)\fR" +/* The network interface addresses that this mail system receives mail +/* on by way of a proxy or network address translation unit. +/* .IP "\fBinet_protocols (see 'postconf -d output')\fR" +/* The Internet protocols Postfix will attempt to use when making +/* or accepting connections. +/* .IP "\fBlocal_recipient_maps (proxy:unix:passwd.byname $alias_maps)\fR" +/* Lookup tables with all names or addresses of local recipients: +/* a recipient address is local when its domain matches $mydestination, +/* $inet_interfaces or $proxy_interfaces. +/* .IP "\fBunknown_local_recipient_reject_code (550)\fR" +/* The numerical Postfix SMTP server response code when a recipient +/* address is local, and $local_recipient_maps specifies a list of +/* lookup tables that does not match the recipient. +/* .PP +/* Parameters concerning known/unknown recipients of relay destinations: +/* .IP "\fBrelay_domains (Postfix >= 3.0: empty, Postfix < 3.0: $mydestination)\fR" +/* What destination domains (and subdomains thereof) this system +/* will relay mail to. +/* .IP "\fBrelay_recipient_maps (empty)\fR" +/* Optional lookup tables with all valid addresses in the domains +/* that match $relay_domains. +/* .IP "\fBunknown_relay_recipient_reject_code (550)\fR" +/* The numerical Postfix SMTP server reply code when a recipient +/* address matches $relay_domains, and relay_recipient_maps specifies +/* a list of lookup tables that does not match the recipient address. +/* .PP +/* Parameters concerning known/unknown recipients in virtual alias +/* domains: +/* .IP "\fBvirtual_alias_domains ($virtual_alias_maps)\fR" +/* Postfix is final destination for the specified list of virtual +/* alias domains, that is, domains for which all addresses are aliased +/* to addresses in other local or remote domains. +/* .IP "\fBvirtual_alias_maps ($virtual_maps)\fR" +/* Optional lookup tables that alias specific mail addresses or domains +/* to other local or remote address. +/* .IP "\fBunknown_virtual_alias_reject_code (550)\fR" +/* The Postfix SMTP server reply code when a recipient address matches +/* $virtual_alias_domains, and $virtual_alias_maps specifies a list +/* of lookup tables that does not match the recipient address. +/* .PP +/* Parameters concerning known/unknown recipients in virtual mailbox +/* domains: +/* .IP "\fBvirtual_mailbox_domains ($virtual_mailbox_maps)\fR" +/* Postfix is final destination for the specified list of domains; +/* mail is delivered via the $virtual_transport mail delivery transport. +/* .IP "\fBvirtual_mailbox_maps (empty)\fR" +/* Optional lookup tables with all valid addresses in the domains that +/* match $virtual_mailbox_domains. +/* .IP "\fBunknown_virtual_mailbox_reject_code (550)\fR" +/* The Postfix SMTP server reply code when a recipient address matches +/* $virtual_mailbox_domains, and $virtual_mailbox_maps specifies a list +/* of lookup tables that does not match the recipient address. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* The following parameters limit resource usage by the SMTP +/* server and/or control client request rates. +/* .IP "\fBline_length_limit (2048)\fR" +/* Upon input, long lines are chopped up into pieces of at most +/* this length; upon delivery, long lines are reconstructed. +/* .IP "\fBqueue_minfree (0)\fR" +/* The minimal amount of free space in bytes in the queue file system +/* that is needed to receive mail. +/* .IP "\fBmessage_size_limit (10240000)\fR" +/* The maximal size in bytes of a message, including envelope information. +/* .IP "\fBsmtpd_recipient_limit (1000)\fR" +/* The maximal number of recipients that the Postfix SMTP server +/* accepts per message delivery request. +/* .IP "\fBsmtpd_timeout (normal: 300s, overload: 10s)\fR" +/* When the Postfix SMTP server wants to send an SMTP server +/* response, how long the Postfix SMTP server will wait for an underlying +/* network write operation to complete; and when the Postfix SMTP +/* server Postfix wants to receive an SMTP client request, how long +/* the Postfix SMTP server will wait for an underlying network read +/* operation to complete. +/* .IP "\fBsmtpd_history_flush_threshold (100)\fR" +/* The maximal number of lines in the Postfix SMTP server command history +/* before it is flushed upon receipt of EHLO, RSET, or end of DATA. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_peername_lookup (yes)\fR" +/* Attempt to look up the remote SMTP client hostname, and verify that +/* the name matches the client IP address. +/* .PP +/* The per SMTP client connection count and request rate limits are +/* implemented in co-operation with the \fBanvil\fR(8) service, and +/* are available in Postfix version 2.2 and later. +/* .IP "\fBsmtpd_client_connection_count_limit (50)\fR" +/* How many simultaneous connections any client is allowed to +/* make to this service. +/* .IP "\fBsmtpd_client_connection_rate_limit (0)\fR" +/* The maximal number of connection attempts any client is allowed to +/* make to this service per time unit. +/* .IP "\fBsmtpd_client_message_rate_limit (0)\fR" +/* The maximal number of message delivery requests that any client is +/* allowed to make to this service per time unit, regardless of whether +/* or not Postfix actually accepts those messages. +/* .IP "\fBsmtpd_client_recipient_rate_limit (0)\fR" +/* The maximal number of recipient addresses that any client is allowed +/* to send to this service per time unit, regardless of whether or not +/* Postfix actually accepts those recipients. +/* .IP "\fBsmtpd_client_event_limit_exceptions ($mynetworks)\fR" +/* Clients that are excluded from smtpd_client_*_count/rate_limit +/* restrictions. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsmtpd_client_new_tls_session_rate_limit (0)\fR" +/* The maximal number of new (i.e., uncached) TLS sessions that a +/* remote SMTP client is allowed to negotiate with this service per +/* time unit. +/* .PP +/* Available in Postfix version 2.9 - 3.6: +/* .IP "\fBsmtpd_per_record_deadline (normal: no, overload: yes)\fR" +/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout +/* time limits, from a +/* time limit per read or write system call, to a time limit to send +/* or receive a complete record (an SMTP command line, SMTP response +/* line, SMTP message content line, or TLS protocol message). +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBsmtpd_client_auth_rate_limit (0)\fR" +/* The maximal number of AUTH commands that any client is allowed to +/* send to this service per time unit, regardless of whether or not +/* Postfix actually accepts those commands. +/* .PP +/* Available in Postfix version 3.7 and later: +/* .IP "\fBsmtpd_per_request_deadline (normal: no, overload: yes)\fR" +/* Change the behavior of the smtpd_timeout and smtpd_starttls_timeout +/* time limits, from a time limit per plaintext or TLS read or write +/* call, to a combined time limit for receiving a complete SMTP request +/* and for sending a complete SMTP response. +/* .IP "\fBsmtpd_min_data_rate (500)\fR" +/* The minimum plaintext data transfer rate in bytes/second for +/* DATA and BDAT requests, when deadlines are enabled with +/* smtpd_per_request_deadline. +/* .IP "\fBheader_from_format (standard)\fR" +/* The format of the Postfix-generated \fBFrom:\fR header. +/* .PP +/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later: +/* .IP "\fBsmtpd_forbid_unauth_pipelining (Postfix >= 3.9: yes)\fR" +/* Disconnect remote SMTP clients that violate RFC 2920 (or 5321) +/* command pipelining constraints. +/* .PP +/* Available in Postfix 3.9, 3.8.4, 3.7.9, 3.6.13, 3.5.23 and later: +/* .IP "\fBsmtpd_forbid_bare_newline (Postfix < 3.9: no)\fR" +/* Reject or restrict input lines from an SMTP client that end in +/* instead of the standard . +/* .IP "\fBsmtpd_forbid_bare_newline_exclusions ($mynetworks)\fR" +/* Exclude the specified clients from smtpd_forbid_bare_newline +/* enforcement. +/* .PP +/* Available in Postfix 3.9, 3.8.5, 3.7.10, 3.6.14, 3.5.24 and +/* later: +/* .IP "\fBsmtpd_forbid_bare_newline_reject_code (550)\fR" +/* The numerical Postfix SMTP server response code when rejecting a +/* request with "smtpd_forbid_bare_newline = reject". +/* TARPIT CONTROLS +/* .ad +/* .fi +/* When a remote SMTP client makes errors, the Postfix SMTP server +/* can insert delays before responding. This can help to slow down +/* run-away software. The behavior is controlled by an error counter +/* that counts the number of errors within an SMTP session that a +/* client makes without delivering mail. +/* .IP "\fBsmtpd_error_sleep_time (1s)\fR" +/* With Postfix version 2.1 and later: the SMTP server response delay after +/* a client has made more than $smtpd_soft_error_limit errors, and +/* fewer than $smtpd_hard_error_limit errors, without delivering mail. +/* .IP "\fBsmtpd_soft_error_limit (10)\fR" +/* The number of errors a remote SMTP client is allowed to make without +/* delivering mail before the Postfix SMTP server slows down all its +/* responses. +/* .IP "\fBsmtpd_hard_error_limit (normal: 20, overload: 1)\fR" +/* The maximal number of errors a remote SMTP client is allowed to +/* make without delivering mail. +/* .IP "\fBsmtpd_junk_command_limit (normal: 100, overload: 1)\fR" +/* The number of junk commands (NOOP, VRFY, ETRN or RSET) that a remote +/* SMTP client can send before the Postfix SMTP server starts to +/* increment the error counter with each junk command. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_recipient_overshoot_limit (1000)\fR" +/* The number of recipients that a remote SMTP client can send in +/* excess of the limit specified with $smtpd_recipient_limit, before +/* the Postfix SMTP server increments the per-session error count +/* for each excess recipient. +/* ACCESS POLICY DELEGATION CONTROLS +/* .ad +/* .fi +/* As of version 2.1, Postfix can be configured to delegate access +/* policy decisions to an external server that runs outside Postfix. +/* See the file SMTPD_POLICY_README for more information. +/* .IP "\fBsmtpd_policy_service_max_idle (300s)\fR" +/* The time after which an idle SMTPD policy service connection is +/* closed. +/* .IP "\fBsmtpd_policy_service_max_ttl (1000s)\fR" +/* The time after which an active SMTPD policy service connection is +/* closed. +/* .IP "\fBsmtpd_policy_service_timeout (100s)\fR" +/* The time limit for connecting to, writing to, or receiving from a +/* delegated SMTPD policy server. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBsmtpd_policy_service_default_action (451 4.3.5 Server configuration problem)\fR" +/* The default action when an SMTPD policy service request fails. +/* .IP "\fBsmtpd_policy_service_request_limit (0)\fR" +/* The maximal number of requests per SMTPD policy service connection, +/* or zero (no limit). +/* .IP "\fBsmtpd_policy_service_try_limit (2)\fR" +/* The maximal number of attempts to send an SMTPD policy service +/* request before giving up. +/* .IP "\fBsmtpd_policy_service_retry_delay (1s)\fR" +/* The delay between attempts to resend a failed SMTPD policy +/* service request. +/* .PP +/* Available in Postfix version 3.1 and later: +/* .IP "\fBsmtpd_policy_service_policy_context (empty)\fR" +/* Optional information that the Postfix SMTP server specifies in +/* the "policy_context" attribute of a policy service request (originally, +/* to share the same service endpoint among multiple check_policy_service +/* clients). +/* ACCESS CONTROLS +/* .ad +/* .fi +/* The SMTPD_ACCESS_README document gives an introduction to all the +/* SMTP server access control features. +/* .IP "\fBsmtpd_delay_reject (yes)\fR" +/* Wait until the RCPT TO command before evaluating +/* $smtpd_client_restrictions, $smtpd_helo_restrictions and +/* $smtpd_sender_restrictions, or wait until the ETRN command before +/* evaluating $smtpd_client_restrictions and $smtpd_helo_restrictions. +/* .IP "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR" +/* A list of Postfix features where the pattern "example.com" also +/* matches subdomains of example.com, +/* instead of requiring an explicit ".example.com" pattern. +/* .IP "\fBsmtpd_client_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client connection request. +/* .IP "\fBsmtpd_helo_required (no)\fR" +/* Require that a remote SMTP client introduces itself with the HELO +/* or EHLO command before sending the MAIL command or other commands +/* that require EHLO negotiation. +/* .IP "\fBsmtpd_helo_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client HELO command. +/* .IP "\fBsmtpd_sender_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client MAIL FROM command. +/* .IP "\fBsmtpd_recipient_restrictions (see 'postconf -d' output)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client RCPT TO command, after smtpd_relay_restrictions. +/* .IP "\fBsmtpd_etrn_restrictions (empty)\fR" +/* Optional restrictions that the Postfix SMTP server applies in the +/* context of a client ETRN command. +/* .IP "\fBallow_untrusted_routing (no)\fR" +/* Forward mail with sender-specified routing (user[@%!]remote[@%!]site) +/* from untrusted clients to destinations matching $relay_domains. +/* .IP "\fBsmtpd_restriction_classes (empty)\fR" +/* User-defined aliases for groups of access restrictions. +/* .IP "\fBsmtpd_null_access_lookup_key (<>)\fR" +/* The lookup key to be used in SMTP \fBaccess\fR(5) tables instead of the +/* null sender address. +/* .IP "\fBpermit_mx_backup_networks (empty)\fR" +/* Restrict the use of the permit_mx_backup SMTP access feature to +/* only domains whose primary MX hosts match the listed networks. +/* .PP +/* Available in Postfix version 2.0 and later: +/* .IP "\fBsmtpd_data_restrictions (empty)\fR" +/* Optional access restrictions that the Postfix SMTP server applies +/* in the context of the SMTP DATA command. +/* .IP "\fBsmtpd_expansion_filter (see 'postconf -d' output)\fR" +/* What characters are allowed in $name expansions of RBL reply +/* templates. +/* .PP +/* Available in Postfix version 2.1 and later: +/* .IP "\fBsmtpd_reject_unlisted_sender (no)\fR" +/* Request that the Postfix SMTP server rejects mail from unknown +/* sender addresses, even when no explicit reject_unlisted_sender +/* access restriction is specified. +/* .IP "\fBsmtpd_reject_unlisted_recipient (yes)\fR" +/* Request that the Postfix SMTP server rejects mail for unknown +/* recipient addresses, even when no explicit reject_unlisted_recipient +/* access restriction is specified. +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtpd_end_of_data_restrictions (empty)\fR" +/* Optional access restrictions that the Postfix SMTP server +/* applies in the context of the SMTP END-OF-DATA command. +/* .PP +/* Available in Postfix version 2.10 and later: +/* .IP "\fBsmtpd_relay_restrictions (permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination)\fR" +/* Access restrictions for mail relay control that the Postfix +/* SMTP server applies in the context of the RCPT TO command, before +/* smtpd_recipient_restrictions. +/* SENDER AND RECIPIENT ADDRESS VERIFICATION CONTROLS +/* .ad +/* .fi +/* Postfix version 2.1 introduces sender and recipient address verification. +/* This feature is implemented by sending probe email messages that +/* are not actually delivered. +/* This feature is requested via the reject_unverified_sender and +/* reject_unverified_recipient access restrictions. The status of +/* verification probes is maintained by the \fBverify\fR(8) server. +/* See the file ADDRESS_VERIFICATION_README for information +/* about how to configure and operate the Postfix sender/recipient +/* address verification service. +/* .IP "\fBaddress_verify_poll_count (normal: 3, overload: 1)\fR" +/* How many times to query the \fBverify\fR(8) service for the completion +/* of an address verification request in progress. +/* .IP "\fBaddress_verify_poll_delay (3s)\fR" +/* The delay between queries for the completion of an address +/* verification request in progress. +/* .IP "\fBaddress_verify_sender ($double_bounce_sender)\fR" +/* The sender address to use in address verification probes; prior +/* to Postfix 2.5 the default was "postmaster". +/* .IP "\fBunverified_sender_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when a recipient +/* address is rejected by the reject_unverified_sender restriction. +/* .IP "\fBunverified_recipient_reject_code (450)\fR" +/* The numerical Postfix SMTP server response when a recipient address +/* is rejected by the reject_unverified_recipient restriction. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBunverified_sender_defer_code (450)\fR" +/* The numerical Postfix SMTP server response code when a sender address +/* probe fails due to a temporary error condition. +/* .IP "\fBunverified_recipient_defer_code (450)\fR" +/* The numerical Postfix SMTP server response when a recipient address +/* probe fails due to a temporary error condition. +/* .IP "\fBunverified_sender_reject_reason (empty)\fR" +/* The Postfix SMTP server's reply when rejecting mail with +/* reject_unverified_sender. +/* .IP "\fBunverified_recipient_reject_reason (empty)\fR" +/* The Postfix SMTP server's reply when rejecting mail with +/* reject_unverified_recipient. +/* .IP "\fBunverified_sender_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unverified_sender +/* fails due to a temporary error condition. +/* .IP "\fBunverified_recipient_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unverified_recipient +/* fails due to a temporary error condition. +/* .PP +/* Available with Postfix 2.9 and later: +/* .IP "\fBaddress_verify_sender_ttl (0s)\fR" +/* The time between changes in the time-dependent portion of address +/* verification probe sender addresses. +/* ACCESS CONTROL RESPONSES +/* .ad +/* .fi +/* The following parameters control numerical SMTP reply codes +/* and/or text responses. +/* .IP "\fBaccess_map_reject_code (554)\fR" +/* The numerical Postfix SMTP server response code for +/* an \fBaccess\fR(5) map "reject" action. +/* .IP "\fBdefer_code (450)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is rejected by the "defer" restriction. +/* .IP "\fBinvalid_hostname_reject_code (501)\fR" +/* The numerical Postfix SMTP server response code when the client +/* HELO or EHLO command parameter is rejected by the reject_invalid_helo_hostname +/* restriction. +/* .IP "\fBmaps_rbl_reject_code (554)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is blocked by the reject_rbl_client, reject_rhsbl_client, +/* reject_rhsbl_reverse_client, reject_rhsbl_sender or +/* reject_rhsbl_recipient restriction. +/* .IP "\fBnon_fqdn_reject_code (504)\fR" +/* The numerical Postfix SMTP server reply code when a client request +/* is rejected by the reject_non_fqdn_helo_hostname, reject_non_fqdn_sender +/* or reject_non_fqdn_recipient restriction. +/* .IP "\fBplaintext_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when a request +/* is rejected by the \fBreject_plaintext_session\fR restriction. +/* .IP "\fBreject_code (554)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is rejected by the "reject" restriction. +/* .IP "\fBrelay_domains_reject_code (554)\fR" +/* The numerical Postfix SMTP server response code when a client +/* request is rejected by the reject_unauth_destination recipient +/* restriction. +/* .IP "\fBunknown_address_reject_code (450)\fR" +/* The numerical response code when the Postfix SMTP server rejects a +/* sender or recipient address because its domain is unknown. +/* .IP "\fBunknown_client_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when a client +/* without valid address <=> name mapping is rejected by the +/* reject_unknown_client_hostname restriction. +/* .IP "\fBunknown_hostname_reject_code (450)\fR" +/* The numerical Postfix SMTP server response code when the hostname +/* specified with the HELO or EHLO command is rejected by the +/* reject_unknown_helo_hostname restriction. +/* .PP +/* Available in Postfix version 2.0 and later: +/* .IP "\fBdefault_rbl_reply (see 'postconf -d' output)\fR" +/* The default Postfix SMTP server response template for a request that is +/* rejected by an RBL-based restriction. +/* .IP "\fBmulti_recipient_bounce_reject_code (550)\fR" +/* The numerical Postfix SMTP server response code when a remote SMTP +/* client request is blocked by the reject_multi_recipient_bounce +/* restriction. +/* .IP "\fBrbl_reply_maps (empty)\fR" +/* Optional lookup tables with RBL response templates. +/* .PP +/* Available in Postfix version 2.6 and later: +/* .IP "\fBaccess_map_defer_code (450)\fR" +/* The numerical Postfix SMTP server response code for +/* an \fBaccess\fR(5) map "defer" action, including "defer_if_permit" +/* or "defer_if_reject". +/* .IP "\fBreject_tempfail_action (defer_if_permit)\fR" +/* The Postfix SMTP server's action when a reject-type restriction +/* fails due to a temporary error condition. +/* .IP "\fBunknown_helo_hostname_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unknown_helo_hostname +/* fails due to a temporary error condition. +/* .IP "\fBunknown_address_tempfail_action ($reject_tempfail_action)\fR" +/* The Postfix SMTP server's action when reject_unknown_sender_domain +/* or reject_unknown_recipient_domain fail due to a temporary error +/* condition. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBcommand_directory (see 'postconf -d' output)\fR" +/* The location of all postfix administrative commands. +/* .IP "\fBdouble_bounce_sender (double-bounce)\fR" +/* The sender address of postmaster notifications that are generated +/* by the mail system. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmail_name (Postfix)\fR" +/* The mail system name that is displayed in Received: headers, in +/* the SMTP greeting banner, and in bounced mail. +/* .IP "\fBmail_owner (postfix)\fR" +/* The UNIX system account that owns the Postfix queue and most Postfix +/* daemon processes. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBmyhostname (see 'postconf -d' output)\fR" +/* The internet hostname of this mail system. +/* .IP "\fBmynetworks (see 'postconf -d' output)\fR" +/* The list of "trusted" remote SMTP clients that have more privileges than +/* "strangers". +/* .IP "\fBmyorigin ($myhostname)\fR" +/* The domain name that locally-posted mail appears to come +/* from, and that locally posted mail is delivered to. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBrecipient_delimiter (empty)\fR" +/* The set of characters that can separate an email address +/* localpart, user name, or a .forward file name from its extension. +/* .IP "\fBsmtpd_banner ($myhostname ESMTP $mail_name)\fR" +/* The text that follows the 220 status code in the SMTP greeting +/* banner. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 2.2 and later: +/* .IP "\fBsmtpd_forbidden_commands (CONNECT GET POST regexp:{{/^[^A-Z]/ Bogus}})\fR" +/* List of commands that cause the Postfix SMTP server to immediately +/* terminate the session with a 221 code. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBsmtpd_client_port_logging (no)\fR" +/* Enable logging of the remote SMTP client port in addition to +/* the hostname and IP address. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.4 and later: +/* .IP "\fBsmtpd_reject_footer_maps (empty)\fR" +/* Lookup tables, indexed by the complete Postfix SMTP server 4xx or +/* 5xx response, with reject footer templates. +/* SEE ALSO +/* anvil(8), connection/rate limiting +/* cleanup(8), message canonicalization +/* tlsmgr(8), TLS session and PRNG management +/* trivial-rewrite(8), address resolver +/* verify(8), address verification service +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* ADDRESS_CLASS_README, blocking unknown hosted or relay recipients +/* ADDRESS_REWRITING_README, Postfix address manipulation +/* BDAT_README, Postfix CHUNKING support +/* FILTER_README, external after-queue content filter +/* LOCAL_RECIPIENT_README, blocking unknown local recipients +/* MILTER_README, before-queue mail filter applications +/* SMTPD_ACCESS_README, built-in access policies +/* SMTPD_POLICY_README, external policy server +/* SMTPD_PROXY_README, external before-queue content filter +/* SASL_README, Postfix SASL howto +/* TLS_README, Postfix STARTTLS howto +/* VERP_README, Postfix XVERP extension +/* XCLIENT_README, Postfix XCLIENT extension +/* XFORWARD_README, Postfix XFORWARD extension +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* SASL support originally by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Revised TLS support by: +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include /* remove() */ +#include +#include +#include +#include +#include +#include /* offsetof() */ + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include /* milter_macro_v */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* ehlo filter */ +#include /* ehlo filter */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single-threaded server skeleton. */ + +#include + +/* Mail filter library. */ + +#include + +/* DNS library. */ + +#include + +/* Application-specific */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * Tunable parameters. Make sure that there is some bound on the length of + * an SMTP command, so that the mail system stays in control even when a + * malicious client sends commands of unreasonable length (qmail-dos-1). + * Make sure there is some bound on the number of recipients, so that the + * mail system stays in control even when a malicious client sends an + * unreasonable number of recipients (qmail-dos-2). + */ +int var_smtpd_rcpt_limit; +int var_smtpd_tmout; +int var_smtpd_soft_erlim; +int var_smtpd_hard_erlim; +long var_queue_minfree; /* XXX use off_t */ +char *var_smtpd_banner; +char *var_notify_classes; +char *var_client_checks; +char *var_helo_checks; +char *var_mail_checks; +char *var_relay_checks; +char *var_rcpt_checks; +char *var_etrn_checks; +char *var_data_checks; +char *var_eod_checks; +int var_unk_client_code; +int var_bad_name_code; +int var_unk_name_code; +int var_unk_addr_code; +int var_relay_code; +int var_maps_rbl_code; +int var_map_reject_code; +int var_map_defer_code; +char *var_maps_rbl_domains; +char *var_rbl_reply_maps; +int var_helo_required; +int var_reject_code; +int var_defer_code; +int var_smtpd_err_sleep; +int var_non_fqdn_code; +char *var_bounce_rcpt; +char *var_error_rcpt; +int var_smtpd_delay_reject; +char *var_rest_classes; +int var_strict_rfc821_env; +bool var_disable_vrfy_cmd; +char *var_canonical_maps; +char *var_send_canon_maps; +char *var_rcpt_canon_maps; +char *var_virt_alias_maps; +char *var_virt_mailbox_maps; +char *var_alias_maps; +char *var_local_rcpt_maps; +bool var_allow_untrust_route; +int var_smtpd_junk_cmd_limit; +int var_smtpd_rcpt_overlim; +bool var_smtpd_sasl_enable; +bool var_smtpd_sasl_auth_hdr; +char *var_smtpd_sasl_opts; +char *var_smtpd_sasl_path; +char *var_smtpd_sasl_service; +char *var_cyrus_conf_path; +char *var_smtpd_sasl_realm; +int var_smtpd_sasl_resp_limit; +char *var_smtpd_sasl_exceptions_networks; +char *var_smtpd_sasl_type; +char *var_smtpd_sasl_mech_filter; +char *var_filter_xport; +bool var_broken_auth_clients; +char *var_perm_mx_networks; +char *var_smtpd_snd_auth_maps; +char *var_smtpd_noop_cmds; +char *var_smtpd_null_key; +int var_smtpd_hist_thrsh; +char *var_smtpd_exp_filter; +char *var_def_rbl_reply; +int var_unv_from_rcode; +int var_unv_rcpt_rcode; +int var_unv_from_dcode; +int var_unv_rcpt_dcode; +char *var_unv_from_why; +char *var_unv_rcpt_why; +int var_mul_rcpt_code; +char *var_relay_rcpt_maps; +int var_local_rcpt_code; +int var_virt_alias_code; +int var_virt_mailbox_code; +int var_relay_rcpt_code; +char *var_verp_clients; +int var_show_unk_rcpt_table; +int var_verify_poll_count; +int var_verify_poll_delay; +char *var_smtpd_proxy_filt; +int var_smtpd_proxy_tmout; +char *var_smtpd_proxy_ehlo; +char *var_smtpd_proxy_opts; +char *var_input_transp; +int var_smtpd_policy_tmout; +int var_smtpd_policy_req_limit; +int var_smtpd_policy_try_limit; +int var_smtpd_policy_try_delay; +char *var_smtpd_policy_def_action; +char *var_smtpd_policy_context; +int var_smtpd_policy_idle; +int var_smtpd_policy_ttl; +char *var_xclient_hosts; +char *var_xforward_hosts; +bool var_smtpd_rej_unl_from; +bool var_smtpd_rej_unl_rcpt; +char *var_smtpd_forbid_cmds; +int var_smtpd_crate_limit; +int var_smtpd_cconn_limit; +int var_smtpd_cmail_limit; +int var_smtpd_crcpt_limit; +int var_smtpd_cntls_limit; +int var_smtpd_cauth_limit; +char *var_smtpd_hoggers; +char *var_local_rwr_clients; +char *var_smtpd_ehlo_dis_words; +char *var_smtpd_ehlo_dis_maps; + +char *var_smtpd_tls_level; +bool var_smtpd_use_tls; +bool var_smtpd_enforce_tls; +bool var_smtpd_tls_wrappermode; +bool var_smtpd_tls_auth_only; +char *var_smtpd_cmd_filter; +char *var_smtpd_rej_footer; +char *var_smtpd_rej_ftr_maps; +char *var_smtpd_acl_perm_log; +char *var_smtpd_dns_re_filter; + +#ifdef USE_TLS +char *var_smtpd_relay_ccerts; +char *var_smtpd_sasl_tls_opts; +int var_smtpd_starttls_tmout; +char *var_smtpd_tls_CAfile; +char *var_smtpd_tls_CApath; +bool var_smtpd_tls_ask_ccert; +int var_smtpd_tls_ccert_vd; +char *var_smtpd_tls_cert_file; +char *var_smtpd_tls_mand_ciph; +char *var_smtpd_tls_excl_ciph; +char *var_smtpd_tls_mand_excl; +char *var_smtpd_tls_dcert_file; +char *var_smtpd_tls_dh1024_param_file; +char *var_smtpd_tls_dh512_param_file; +char *var_smtpd_tls_dkey_file; +char *var_smtpd_tls_key_file; +char *var_smtpd_tls_loglevel; +char *var_smtpd_tls_mand_proto; +bool var_smtpd_tls_received_header; +bool var_smtpd_tls_req_ccert; +bool var_smtpd_tls_set_sessid; +char *var_smtpd_tls_fpt_dgst; +char *var_smtpd_tls_ciph; +char *var_smtpd_tls_proto; +char *var_smtpd_tls_eecdh; +char *var_smtpd_tls_eccert_file; +char *var_smtpd_tls_eckey_file; +char *var_smtpd_tls_chain_files; + +#endif + +bool var_smtpd_peername_lookup; +int var_plaintext_code; +bool var_smtpd_delay_open; +char *var_smtpd_milters; +char *var_smtpd_milter_maps; +int var_milt_conn_time; +int var_milt_cmd_time; +int var_milt_msg_time; +char *var_milt_protocol; +char *var_milt_def_action; +char *var_milt_daemon_name; +char *var_milt_v; +char *var_milt_conn_macros; +char *var_milt_helo_macros; +char *var_milt_mail_macros; +char *var_milt_rcpt_macros; +char *var_milt_data_macros; +char *var_milt_eoh_macros; +char *var_milt_eod_macros; +char *var_milt_unk_macros; +char *var_milt_macro_deflts; +bool var_smtpd_client_port_log; +bool var_smtpd_forbid_unauth_pipe; +char *var_stress; + +char *var_reject_tmpf_act; +char *var_unk_name_tf_act; +char *var_unk_addr_tf_act; +char *var_unv_rcpt_tf_act; +char *var_unv_from_tf_act; + +int smtpd_proxy_opts; + +#ifdef USE_TLSPROXY +char *var_tlsproxy_service; + +#endif + +char *var_smtpd_uproxy_proto; +int var_smtpd_uproxy_tmout; +bool var_relay_before_rcpt_checks; +bool var_smtpd_req_deadline; +int var_smtpd_min_data_rate; +char *var_hfrom_format; +char *var_smtpd_forbid_bare_lf; +char *var_smtpd_forbid_bare_lf_excl; +int var_smtpd_forbid_bare_lf_code; +static int bare_lf_mask; +static NAMADR_LIST *bare_lf_excl; + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + + /* + * EHLO keyword filter + */ +static MAPS *ehlo_discard_maps; + + /* + * Per-client Milter support. + */ +static MAPS *smtpd_milter_maps; +static void setup_milters(SMTPD_STATE *); +static void teardown_milters(SMTPD_STATE *); + + /* + * VERP command name. + */ +#define VERP_CMD "XVERP" +#define VERP_CMD_LEN 5 + +static NAMADR_LIST *verp_clients; + + /* + * XCLIENT command. Access control is cached, so that XCLIENT can't override + * its own access control. + */ +static NAMADR_LIST *xclient_hosts; +static int xclient_allowed; /* XXX should be SMTPD_STATE member */ + + /* + * XFORWARD command. Access control is cached. + */ +static NAMADR_LIST *xforward_hosts; +static int xforward_allowed; /* XXX should be SMTPD_STATE member */ + + /* + * Client connection and rate limiting. + */ +ANVIL_CLNT *anvil_clnt; +static NAMADR_LIST *hogger_list; + + /* + * Other application-specific globals. + */ +int smtpd_input_transp_mask; + + /* + * Forward declarations. + */ +static void helo_reset(SMTPD_STATE *); +static void mail_reset(SMTPD_STATE *); +static void rcpt_reset(SMTPD_STATE *); +static void chat_reset(SMTPD_STATE *, int); + +#ifdef USE_TLS +static void tls_reset(SMTPD_STATE *); + +#endif + + /* + * This filter is applied after printable(). + */ +#define NEUTER_CHARACTERS " <>()\\\";@" + + /* + * Reasons for losing the client. + */ +#define REASON_TIMEOUT "timeout" +#define REASON_LOST_CONNECTION "lost connection" +#define REASON_ERROR_LIMIT "too many errors" + +#ifdef USE_TLS + + /* + * TLS initialization status. + */ +#ifndef USE_TLSPROXY +static TLS_APPL_STATE *smtpd_tls_ctx; +static int ask_client_cert; + +#endif /* USE_TLSPROXY */ +#endif + + /* + * SMTP command mapping for broken clients. + */ +static DICT *smtpd_cmd_filter; + + /* + * Parsed header_from_format setting. + */ +int smtpd_hfrom_format; + + /* + * Bare LF and End-of-DATA controls (bare CR is handled elsewhere). + * + * At the smtp_get*() line reader level, setting any of these flags in the + * smtp_detect_bare_lf variable enables the detection of bare newlines. The + * line reader will set the same flags in the smtp_got_bare_lf variable + * after it detects a bare newline, otherwise it clears smtp_got_bare_lf. + * + * At the SMTP command level, the flags in smtp_got_bare_lf control whether + * commands ending in a bare newline are rejected. + * + * At the DATA and BDAT content level, the flags in smtp_got_bare_lf control + * whether the standard End-of-DATA sequence CRLF.CRLF is required, and + * whether lines ending in bare newlines are rejected. + * + * Postfix implements "delayed reject" after detecting a bare newline in BDAT + * or DATA content. The SMTP server delays a REJECT response until the + * command is finished, instead of replying and hanging up immediately. The + * End-of-DATA detection is secured with BARE_LF_FLAG_WANT_STD_EOD. + */ +#define BARE_LF_FLAG_WANT_STD_EOD (1<<0) /* Require CRLF.CRLF */ +#define BARE_LF_FLAG_REPLY_REJECT (1<<1) /* Reject bare newline */ + +#define IS_BARE_LF_WANT_STD_EOD(m) ((m) & BARE_LF_FLAG_WANT_STD_EOD) +#define IS_BARE_LF_REPLY_REJECT(m) ((m) & BARE_LF_FLAG_REPLY_REJECT) + +static const NAME_CODE bare_lf_mask_table[] = { + "normalize", BARE_LF_FLAG_WANT_STD_EOD, /* Default */ + "yes", BARE_LF_FLAG_WANT_STD_EOD, /* Migration aid */ + "reject", BARE_LF_FLAG_WANT_STD_EOD | BARE_LF_FLAG_REPLY_REJECT, + "no", 0, + 0, -1, /* error */ +}; + +#ifdef USE_SASL_AUTH + + /* + * SASL exceptions. + */ +static NAMADR_LIST *sasl_exceptions_networks; + +/* sasl_client_exception - can we offer AUTH for this client */ + +static int sasl_client_exception(SMTPD_STATE *state) +{ + int match; + + /* + * This is to work around a Netscape mail client bug where it tries to + * use AUTH if available, even if user has not configured it. Returns + * TRUE if AUTH should be offered in the EHLO. + */ + if (sasl_exceptions_networks == 0) + return (0); + + if ((match = namadr_list_match(sasl_exceptions_networks, + state->name, state->addr)) == 0) + match = sasl_exceptions_networks->error; + + if (msg_verbose) + msg_info("sasl_exceptions: %s, match=%d", + state->namaddr, match); + + return (match); +} + +#endif + +/* smtpd_whatsup - gather available evidence for logging */ + +static const char *smtpd_whatsup(SMTPD_STATE *state) +{ + static VSTRING *buf = 0; + + if (buf == 0) + buf = vstring_alloc(100); + else + VSTRING_RESET(buf); + if (state->sender) + vstring_sprintf_append(buf, " from=<%s>", + info_log_addr_form_sender(state->sender)); + if (state->recipient) + vstring_sprintf_append(buf, " to=<%s>", + info_log_addr_form_recipient(state->recipient)); + if (state->protocol) + vstring_sprintf_append(buf, " proto=%s", state->protocol); + if (state->helo_name) + vstring_sprintf_append(buf, " helo=<%s>", state->helo_name); +#ifdef USE_SASL_AUTH + if (state->sasl_username) + vstring_sprintf_append(buf, " sasl_username=<%s>", + state->sasl_username); +#endif + return (STR(buf)); +} + +/* collapse_args - put arguments together again */ + +static void collapse_args(int argc, SMTPD_TOKEN *argv) +{ + int i; + + for (i = 1; i < argc; i++) { + vstring_strcat(argv[0].vstrval, " "); + vstring_strcat(argv[0].vstrval, argv[i].strval); + } + argv[0].strval = STR(argv[0].vstrval); +} + +/* check_milter_reply - process reply from Milter */ + +static const char *check_milter_reply(SMTPD_STATE *state, const char *reply) +{ + const char *queue_id = state->queue_id ? state->queue_id : "NOQUEUE"; + const char *action; + const char *text; + + /* + * The syntax of user-specified SMTP replies is checked by the Milter + * module, because the replies are also used in the cleanup server. + * Automatically disconnect after 421 (shutdown) reply. The Sendmail 8 + * Milter quarantine action is not final, so it is not included in + * MILTER_SKIP_FLAGS. + */ +#define MILTER_SKIP_FLAGS (CLEANUP_FLAG_DISCARD) + + switch (reply[0]) { + case 'H': + state->saved_flags |= CLEANUP_FLAG_HOLD; + action = "milter-hold"; + reply = 0; + text = "milter triggers HOLD action"; + break; + case 'D': + state->saved_flags |= CLEANUP_FLAG_DISCARD; + action = "milter-discard"; + reply = 0; + text = "milter triggers DISCARD action"; + break; + case 'S': + state->error_mask |= MAIL_ERROR_POLICY; + action = "milter-reject"; + reply = "421 4.7.0 Server closing connection"; + text = 0; + break; + case '4': + case '5': + state->error_mask |= MAIL_ERROR_POLICY; + action = "milter-reject"; + text = 0; + break; + default: + state->error_mask |= MAIL_ERROR_SOFTWARE; + action = "reject"; + reply = "421 4.3.5 Server configuration error"; + text = 0; + break; + } + msg_info("%s: %s: %s from %s: %s;%s", queue_id, action, state->where, + state->namaddr, reply ? reply : text, smtpd_whatsup(state)); + return (reply); +} + +/* helo_cmd - process HELO command */ + +static int helo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + const char *err; + + /* + * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses other + * than the initial greeting and any response to HELO or EHLO are + * prefaced with a status code as defined in RFC 3463. + */ + if (argc < 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 Syntax: HELO hostname"); + return (-1); + } + if (argc > 2) + collapse_args(argc - 1, argv + 1); + if (SMTPD_STAND_ALONE(state) == 0 + && var_smtpd_delay_reject == 0 + && (err = smtpd_check_helo(state, argv[1].strval)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + + /* + * XXX Sendmail compatibility: if a Milter rejects CONNECT, EHLO, or + * HELO, reply with 250 except in case of 421 (disconnect). The reply + * persists so it will apply to MAIL FROM and to other commands such as + * AUTH, STARTTLS, and VRFY. + */ +#define PUSH_STRING(old, curr, new) { char *old = (curr); (curr) = (new); +#define POP_STRING(old, curr) (curr) = old; } + + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0 + && (err = milter_helo_event(state->milters, argv[1].strval, 0)) != 0) { + /* Log reject etc. with correct HELO information. */ + PUSH_STRING(saved_helo, state->helo_name, argv[1].strval); + err = check_milter_reply(state, err); + POP_STRING(saved_helo, state->helo_name); + if (err != 0 && strncmp(err, "421", 3) == 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + if (state->helo_name != 0) + helo_reset(state); + chat_reset(state, var_smtpd_hist_thrsh); + mail_reset(state); + rcpt_reset(state); + state->helo_name = mystrdup(printable(argv[1].strval, '?')); + neuter(state->helo_name, NEUTER_CHARACTERS, '?'); + /* Downgrading the protocol name breaks the unauthorized pipelining test. */ + if (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0 + && strcasecmp(state->protocol, MAIL_PROTO_SMTP) != 0) { + myfree(state->protocol); + state->protocol = mystrdup(MAIL_PROTO_SMTP); + } + smtpd_chat_reply(state, "250 %s", var_myhostname); + return (0); +} + +/* cant_announce_feature - explain and terminate this session */ + +static NORETURN cant_announce_feature(SMTPD_STATE *state, const char *feature) +{ + msg_warn("don't know if EHLO feature %s should be announced to %s", + feature, state->namaddr); + vstream_longjmp(state->client, SMTP_ERR_DATA); +} + +/* cant_permit_command - explain and terminate this session */ + +static NORETURN cant_permit_command(SMTPD_STATE *state, const char *command) +{ + msg_warn("don't know if command %s should be allowed from %s", + command, state->namaddr); + vstream_longjmp(state->client, SMTP_ERR_DATA); +} + +/* ehlo_cmd - process EHLO command */ + +static int ehlo_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + const char *err; + int discard_mask; + char **cpp; + + /* + * XXX 2821 new feature: Section 4.1.4 specifies that a server must clear + * all buffers and reset the state exactly as if a RSET command had been + * issued. + * + * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses other + * than the initial greeting and any response to HELO or EHLO are + * prefaced with a status code as defined in RFC 3463. + */ + if (argc < 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 Syntax: EHLO hostname"); + return (-1); + } + if (argc > 2) + collapse_args(argc - 1, argv + 1); + if (SMTPD_STAND_ALONE(state) == 0 + && var_smtpd_delay_reject == 0 + && (err = smtpd_check_helo(state, argv[1].strval)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + + /* + * XXX Sendmail compatibility: if a Milter 5xx rejects CONNECT, EHLO, or + * HELO, reply with ENHANCEDSTATUSCODES except in case of immediate + * disconnect. The reply persists so it will apply to MAIL FROM and to + * other commands such as AUTH, STARTTLS, and VRFY. + */ + err = 0; + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0 + && (err = milter_helo_event(state->milters, argv[1].strval, 1)) != 0) { + /* Log reject etc. with correct HELO information. */ + PUSH_STRING(saved_helo, state->helo_name, argv[1].strval); + err = check_milter_reply(state, err); + POP_STRING(saved_helo, state->helo_name); + if (err != 0 && strncmp(err, "421", 3) == 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + if (state->helo_name != 0) + helo_reset(state); + chat_reset(state, var_smtpd_hist_thrsh); + mail_reset(state); + rcpt_reset(state); + state->helo_name = mystrdup(printable(argv[1].strval, '?')); + neuter(state->helo_name, NEUTER_CHARACTERS, '?'); + + /* + * XXX reject_unauth_pipelining depends on the following. If the user + * sends EHLO then we announce PIPELINING and we can't accuse them of + * using pipelining in places where it is allowed. + * + * XXX The reject_unauth_pipelining test needs to change and also account + * for mechanisms that disable PIPELINING selectively. + */ + if (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0) { + myfree(state->protocol); + state->protocol = mystrdup(MAIL_PROTO_ESMTP); + } + + /* + * Build the EHLO response, producing no output until we know what to + * send - this simplifies exception handling. The CRLF record boundaries + * don't exist at this level in the code, so we represent multi-line + * output as an array of single-line responses. + */ +#define EHLO_APPEND(state, cmd) \ + do { \ + vstring_sprintf((state)->ehlo_buf, (cmd)); \ + argv_add((state)->ehlo_argv, STR((state)->ehlo_buf), (char *) 0); \ + } while (0) + +#define EHLO_APPEND1(state, cmd, arg) \ + do { \ + vstring_sprintf((state)->ehlo_buf, (cmd), (arg)); \ + argv_add((state)->ehlo_argv, STR((state)->ehlo_buf), (char *) 0); \ + } while (0) + + /* + * XXX Sendmail compatibility: if a Milter 5XX rejects CONNECT, EHLO, or + * HELO, reply with ENHANCEDSTATUSCODES only. The reply persists so it + * will apply to MAIL FROM, but we currently don't have a proper + * mechanism to apply Milter rejects to AUTH, STARTTLS, VRFY, and other + * commands while still allowing HELO/EHLO. + */ + discard_mask = state->ehlo_discard_mask; + if (err != 0 && err[0] == '5') + discard_mask |= ~EHLO_MASK_ENHANCEDSTATUSCODES; + if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0) + if (discard_mask && !(discard_mask & EHLO_MASK_SILENT)) + msg_info("discarding EHLO keywords: %s", str_ehlo_mask(discard_mask)); + if (ehlo_discard_maps && ehlo_discard_maps->error) { + msg_warn("don't know what EHLO features to announce to %s", + state->namaddr); + vstream_longjmp(state->client, SMTP_ERR_DATA); + } + + /* + * These may still exist after a prior exception. + */ + if (state->ehlo_argv == 0) { + state->ehlo_argv = argv_alloc(10); + state->ehlo_buf = vstring_alloc(10); + } else + argv_truncate(state->ehlo_argv, 0); + + EHLO_APPEND1(state, "%s", var_myhostname); + if ((discard_mask & EHLO_MASK_PIPELINING) == 0) + EHLO_APPEND(state, "PIPELINING"); + if ((discard_mask & EHLO_MASK_SIZE) == 0) { + if (ENFORCING_SIZE_LIMIT(var_message_limit)) + EHLO_APPEND1(state, "SIZE %lu", + (unsigned long) var_message_limit); /* XXX */ + else + EHLO_APPEND(state, "SIZE"); + } + if ((discard_mask & EHLO_MASK_VRFY) == 0) + if (var_disable_vrfy_cmd == 0) + EHLO_APPEND(state, SMTPD_CMD_VRFY); + if ((discard_mask & EHLO_MASK_ETRN) == 0) + EHLO_APPEND(state, SMTPD_CMD_ETRN); +#ifdef USE_TLS + if ((discard_mask & EHLO_MASK_STARTTLS) == 0) + if (var_smtpd_use_tls && (!state->tls_context)) + EHLO_APPEND(state, SMTPD_CMD_STARTTLS); +#endif +#ifdef USE_SASL_AUTH +#ifndef AUTH_CMD +#define AUTH_CMD "AUTH" +#endif + if ((discard_mask & EHLO_MASK_AUTH) == 0) { + if (smtpd_sasl_is_active(state) && !sasl_client_exception(state)) { + EHLO_APPEND1(state, "AUTH %s", state->sasl_mechanism_list); + if (var_broken_auth_clients) + EHLO_APPEND1(state, "AUTH=%s", state->sasl_mechanism_list); + } else if (sasl_exceptions_networks && sasl_exceptions_networks->error) + cant_announce_feature(state, AUTH_CMD); + } +#define XCLIENT_LOGIN_KLUDGE " " XCLIENT_LOGIN +#else +#define XCLIENT_LOGIN_KLUDGE "" +#endif + if ((discard_mask & EHLO_MASK_VERP) == 0) { + if (namadr_list_match(verp_clients, state->name, state->addr)) + EHLO_APPEND(state, VERP_CMD); + else if (verp_clients && verp_clients->error) + cant_announce_feature(state, VERP_CMD); + } + /* XCLIENT must not override its own access control. */ + if ((discard_mask & EHLO_MASK_XCLIENT) == 0) { + if (xclient_allowed) + EHLO_APPEND(state, XCLIENT_CMD + " " XCLIENT_NAME " " XCLIENT_ADDR + " " XCLIENT_PROTO " " XCLIENT_HELO + " " XCLIENT_REVERSE_NAME " " XCLIENT_PORT + XCLIENT_LOGIN_KLUDGE + " " XCLIENT_DESTADDR + " " XCLIENT_DESTPORT); + else if (xclient_hosts && xclient_hosts->error) + cant_announce_feature(state, XCLIENT_CMD); + } + if ((discard_mask & EHLO_MASK_XFORWARD) == 0) { + if (xforward_allowed) + EHLO_APPEND(state, XFORWARD_CMD + " " XFORWARD_NAME " " XFORWARD_ADDR + " " XFORWARD_PROTO " " XFORWARD_HELO + " " XFORWARD_DOMAIN " " XFORWARD_PORT + " " XFORWARD_IDENT); + else if (xforward_hosts && xforward_hosts->error) + cant_announce_feature(state, XFORWARD_CMD); + } + if ((discard_mask & EHLO_MASK_ENHANCEDSTATUSCODES) == 0) + EHLO_APPEND(state, "ENHANCEDSTATUSCODES"); + if ((discard_mask & EHLO_MASK_8BITMIME) == 0) + EHLO_APPEND(state, "8BITMIME"); + if ((discard_mask & EHLO_MASK_DSN) == 0) + EHLO_APPEND(state, "DSN"); + if (var_smtputf8_enable && (discard_mask & EHLO_MASK_SMTPUTF8) == 0) + EHLO_APPEND(state, "SMTPUTF8"); + if ((discard_mask & EHLO_MASK_CHUNKING) == 0) + EHLO_APPEND(state, "CHUNKING"); + + /* + * Send the reply. + */ + for (cpp = state->ehlo_argv->argv; *cpp; cpp++) + smtpd_chat_reply(state, "250%c%s", cpp[1] ? '-' : ' ', *cpp); + + /* + * Clean up. + */ + argv_free(state->ehlo_argv); + state->ehlo_argv = 0; + vstring_free(state->ehlo_buf); + state->ehlo_buf = 0; + + return (0); +} + +/* helo_reset - reset HELO/EHLO command stuff */ + +static void helo_reset(SMTPD_STATE *state) +{ + if (state->helo_name) { + myfree(state->helo_name); + state->helo_name = 0; + if (state->milters != 0) + milter_abort(state->milters); + } + if (state->ehlo_argv) { + argv_free(state->ehlo_argv); + state->ehlo_argv = 0; + } + if (state->ehlo_buf) { + vstring_free(state->ehlo_buf); + state->ehlo_buf = 0; + } +} + +#ifdef USE_SASL_AUTH + +/* smtpd_sasl_auth_cmd_wrapper - smtpd_sasl_auth_cmd front-end */ + +static int smtpd_sasl_auth_cmd_wrapper(SMTPD_STATE *state, int argc, + SMTPD_TOKEN *argv) +{ + int rate; + + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && var_smtpd_cauth_limit > 0 + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_auth(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_cauth_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("AUTH command rate limit exceeded: %d from %s for service %s", + rate, state->namaddr, state->service); + smtpd_chat_reply(state, + "450 4.7.1 Error: too many AUTH commands from %s", + state->addr); + return (-1); + } + return (smtpd_sasl_auth_cmd(state, argc, argv)); +} + +#endif + +/* mail_open_stream - open mail queue file or IPC stream */ + +static int mail_open_stream(SMTPD_STATE *state) +{ + + /* + * Connect to the before-queue filter when one is configured. The MAIL + * FROM and RCPT TO commands are forwarded as received (including DSN + * attributes), with the exception that the before-filter smtpd process + * handles all authentication, encryption, access control and relay + * control, and that the before-filter smtpd process does not forward + * blocked commands. If the after-filter smtp server does not support + * some of Postfix's ESMTP features, then they must be turned off in the + * before-filter smtpd process with the smtpd_discard_ehlo_keywords + * feature. + */ + if (state->proxy_mail) { + if (smtpd_proxy_create(state, smtpd_proxy_opts, var_smtpd_proxy_filt, + var_smtpd_proxy_tmout, var_smtpd_proxy_ehlo, + state->proxy_mail) != 0) { + smtpd_chat_reply(state, "%s", STR(state->proxy->reply)); + smtpd_proxy_free(state); + return (-1); + } + } + + /* + * If running from the master or from inetd, connect to the cleanup + * service. + * + * XXX 2821: An SMTP server is not allowed to "clean up" mail except in the + * case of original submissions. + * + * We implement this by distinguishing between mail that we are willing to + * rewrite (the local rewrite context) and mail from elsewhere. + */ + else if (SMTPD_STAND_ALONE(state) == 0) { + int cleanup_flags; + + cleanup_flags = input_transp_cleanup(CLEANUP_FLAG_MASK_EXTERNAL, + smtpd_input_transp_mask) + | CLEANUP_FLAG_SMTP_REPLY; + if (state->flags & SMTPD_FLAG_SMTPUTF8) + cleanup_flags |= CLEANUP_FLAG_SMTPUTF8; + else + cleanup_flags |= smtputf8_autodetect(MAIL_SRC_MASK_SMTPD); + state->dest = mail_stream_service(MAIL_CLASS_PUBLIC, + var_cleanup_service); + if (state->dest == 0 + || attr_print(state->dest->stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_FLAGS, cleanup_flags), + ATTR_TYPE_END) != 0) + msg_fatal("unable to connect to the %s %s service", + MAIL_CLASS_PUBLIC, var_cleanup_service); + } + + /* + * Otherwise, pipe the message through the privileged postdrop helper. + * XXX Make postdrop a manifest constant. + */ + else { + char *postdrop_command; + + postdrop_command = concatenate(var_command_dir, "/postdrop", + msg_verbose ? " -v" : (char *) 0, (char *) 0); + state->dest = mail_stream_command(postdrop_command); + if (state->dest == 0) + msg_fatal("unable to execute %s", postdrop_command); + myfree(postdrop_command); + } + + /* + * Record the time of arrival, the SASL-related stuff if applicable, the + * sender envelope address, some session information, and some additional + * attributes. + * + * XXX Send Milter information first, because this will hang when cleanup + * goes into "throw away" mode. Also, cleanup needs to know early on + * whether or not it has to do its own SMTP event emulation. + * + * XXX At this point we send only dummy information to keep the cleanup + * server from using its non_smtpd_milters settings. We have to send + * up-to-date Milter information after DATA so that the cleanup server + * knows the actual Milter state. + */ + if (state->dest) { + state->cleanup = state->dest->stream; + state->queue_id = mystrdup(state->dest->id); + if (SMTPD_STAND_ALONE(state) == 0) { + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) + /* Send place-holder smtpd_milters list. */ + (void) milter_dummy(state->milters, state->cleanup); + rec_fprintf(state->cleanup, REC_TYPE_TIME, REC_TYPE_TIME_FORMAT, + REC_TYPE_TIME_ARG(state->arrival_time)); + if (*var_filter_xport) + rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport); + if (FORWARD_IDENT(state)) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_IDENT, FORWARD_IDENT(state)); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_RWR_CONTEXT, FORWARD_DOMAIN(state)); +#ifdef USE_SASL_AUTH + /* Make external authentication painless (e.g., XCLIENT). */ + if (state->sasl_method) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_SASL_METHOD, state->sasl_method); + if (state->sasl_username) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_SASL_USERNAME, state->sasl_username); + if (state->sasl_sender) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_SASL_SENDER, state->sasl_sender); +#endif + + /* + * Record DSN related information that was received with the MAIL + * FROM command. + * + * RFC 3461 Section 5.2.1. If no ENVID parameter was included in the + * MAIL command when the message was received, the ENVID + * parameter MUST NOT be supplied when the message is relayed. + * Ditto for the RET parameter. + * + * In other words, we can't simply make up our default ENVID or RET + * values. We have to remember whether the client sent any. + * + * We store DSN information as named attribute records so that we + * don't have to pollute the queue file with records that are + * incompatible with past Postfix versions. Preferably, people + * should be able to back out from an upgrade without losing + * mail. + */ + if (state->dsn_envid) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_DSN_ENVID, state->dsn_envid); + if (state->dsn_ret) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_RET, state->dsn_ret); + } + rec_fputs(state->cleanup, REC_TYPE_FROM, state->sender); + if (state->encoding != 0) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ENCODING, state->encoding); + + /* + * Store client attributes. + */ + if (SMTPD_STAND_ALONE(state) == 0) { + + /* + * Attributes for logging, also used for XFORWARD. + * + * We store all client attributes, including ones with unknown + * values. Otherwise, an unknown client hostname would be treated + * as a non-existent hostname (i.e. local submission). + */ + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_CLIENT_NAME, FORWARD_NAME(state)); + /* XXX Note: state->rfc_addr, not state->addr. */ + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_CLIENT_ADDR, FORWARD_ADDR(state)); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_CLIENT_PORT, FORWARD_PORT(state)); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_ORIGIN, FORWARD_NAMADDR(state)); + if (FORWARD_HELO(state)) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_HELO_NAME, FORWARD_HELO(state)); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_LOG_PROTO_NAME, FORWARD_PROTO(state)); + + /* + * Attributes with actual client information. These are used by + * the smtpd Milter client for policy decisions. Mail that is + * requeued with "postsuper -r" is not subject to processing by + * the cleanup Milter client, because a) it has already been + * filtered, and b) we don't have sufficient information to + * reproduce the exact same SMTP events and Sendmail macros that + * the smtpd Milter client received when the message originally + * arrived in Postfix. + */ + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_CLIENT_NAME, state->name); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_REVERSE_CLIENT_NAME, state->reverse_name); + /* XXX Note: state->addr, not state->rfc_addr. */ + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_CLIENT_ADDR, state->addr); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_CLIENT_PORT, state->port); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_SERVER_ADDR, state->dest_addr); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_SERVER_PORT, state->dest_port); + if (state->helo_name) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_HELO_NAME, state->helo_name); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s", + MAIL_ATTR_ACT_PROTO_NAME, state->protocol); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%u", + MAIL_ATTR_ACT_CLIENT_AF, state->addr_family); + + /* + * Don't send client certificate down the pipeline unless it is + * a) verified or b) just a fingerprint. + */ + } + if (state->verp_delims) + rec_fputs(state->cleanup, REC_TYPE_VERP, state->verp_delims); + } + + /* + * Log the queue ID with the message origin. + */ +#define PRINT_OR_NULL(cond, str) \ + ((cond) ? (str) : "") +#define PRINT2_OR_NULL(cond, name, value) \ + PRINT_OR_NULL((cond), (name)), PRINT_OR_NULL((cond), (value)) + + msg_info("%s: client=%s%s%s%s%s%s%s%s%s%s%s", + (state->queue_id ? state->queue_id : "NOQUEUE"), + state->namaddr, +#ifdef USE_SASL_AUTH + PRINT2_OR_NULL(state->sasl_method, + ", sasl_method=", state->sasl_method), + PRINT2_OR_NULL(state->sasl_username, + ", sasl_username=", state->sasl_username), + PRINT2_OR_NULL(state->sasl_sender, + ", sasl_sender=", state->sasl_sender), +#else + "", "", "", "", "", "", +#endif + /* Insert transaction TLS status here. */ + PRINT2_OR_NULL(HAVE_FORWARDED_IDENT(state), + ", orig_queue_id=", FORWARD_IDENT(state)), + PRINT2_OR_NULL(HAVE_FORWARDED_CLIENT_ATTR(state), + ", orig_client=", FORWARD_NAMADDR(state))); + return (0); +} + +/* extract_addr - extract address from rubble */ + +static int extract_addr(SMTPD_STATE *state, SMTPD_TOKEN *arg, + int allow_empty_addr, int strict_rfc821, + int smtputf8) +{ + const char *myname = "extract_addr"; + TOK822 *tree; + TOK822 *tp; + TOK822 *addr = 0; + int naddr; + int non_addr; + int err = 0; + char *junk = 0; + char *text; + char *colon; + + /* + * Special case. + */ +#define PERMIT_EMPTY_ADDR 1 +#define REJECT_EMPTY_ADDR 0 + + /* + * Some mailers send RFC822-style address forms (with comments and such) + * in SMTP envelopes. We cannot blame users for this: the blame is with + * programmers violating the RFC, and with sendmail for being permissive. + * + * XXX The SMTP command tokenizer must leave the address in externalized + * (quoted) form, so that the address parser can correctly extract the + * address from surrounding junk. + * + * XXX We have only one address parser, written according to the rules of + * RFC 822. That standard differs subtly from RFC 821. + */ + if (msg_verbose) + msg_info("%s: input: %s", myname, STR(arg->vstrval)); + if (STR(arg->vstrval)[0] == '<' + && STR(arg->vstrval)[LEN(arg->vstrval) - 1] == '>') { + junk = text = mystrndup(STR(arg->vstrval) + 1, LEN(arg->vstrval) - 2); + } else + text = STR(arg->vstrval); + + /* + * Truncate deprecated route address form. + */ + if (*text == '@' && (colon = strchr(text, ':')) != 0) + text = colon + 1; + tree = tok822_parse(text); + + if (junk) + myfree(junk); + + /* + * Find trouble. + */ + for (naddr = non_addr = 0, tp = tree; tp != 0; tp = tp->next) { + if (tp->type == TOK822_ADDR) { + addr = tp; + naddr += 1; /* count address forms */ + } else if (tp->type == '<' || tp->type == '>') { + /* void */ ; /* ignore brackets */ + } else { + non_addr += 1; /* count non-address forms */ + } + } + + /* + * Report trouble. XXX Should log a warning only if we are going to + * sleep+reject so that attackers can't flood our logfiles. + * + * XXX Unfortunately, the sleep-before-reject feature had to be abandoned + * (at least for small error counts) because servers were DOS-ing + * themselves when flooded by backscatter traffic. + */ + if (naddr > 1 + || (strict_rfc821 && (non_addr || *STR(arg->vstrval) != '<'))) { + msg_warn("Illegal address syntax from %s in %s command: %s", + state->namaddr, state->where, + printable(STR(arg->vstrval), '?')); + err = 1; + } + + /* + * Don't overwrite the input with the extracted address. We need the + * original (external) form in case the client does not send ORCPT + * information; and error messages are more accurate if we log the + * unmodified form. We need the internal form for all other purposes. + */ + if (addr) + tok822_internalize(state->addr_buf, addr->head, TOK822_STR_DEFL); + else + vstring_strcpy(state->addr_buf, ""); + + /* + * Report trouble. XXX Should log a warning only if we are going to + * sleep+reject so that attackers can't flood our logfiles. Log the + * original address. + */ + if (err == 0) + if ((STR(state->addr_buf)[0] == 0 && !allow_empty_addr) + || (strict_rfc821 && STR(state->addr_buf)[0] == '@') + || (SMTPD_STAND_ALONE(state) == 0 + && smtpd_check_addr(strcmp(state->where, SMTPD_CMD_MAIL) == 0 ? + state->recipient : state->sender, + STR(state->addr_buf), smtputf8) != 0)) { + msg_warn("Illegal address syntax from %s in %s command: %s", + state->namaddr, state->where, + printable(STR(arg->vstrval), '?')); + err = 1; + } + + /* + * Cleanup. + */ + tok822_free_tree(tree); + if (msg_verbose) + msg_info("%s: in: %s, result: %s", + myname, STR(arg->vstrval), STR(state->addr_buf)); + return (err); +} + +/* milter_argv - impedance adapter */ + +static const char **milter_argv(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + int n; + ssize_t len = argc + 1; + + if (state->milter_argc < len) { + if (state->milter_argc > 0) + state->milter_argv = (const char **) + myrealloc((void *) state->milter_argv, + sizeof(const char *) * len); + else + state->milter_argv = (const char **) + mymalloc(sizeof(const char *) * len); + state->milter_argc = len; + } + for (n = 0; n < argc; n++) + state->milter_argv[n] = argv[n].strval; + state->milter_argv[n] = 0; + return (state->milter_argv); +} + +/* mail_cmd - process MAIL command */ + +static int mail_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + const char *err; + int narg; + char *arg; + char *verp_delims = 0; + int rate; + int dsn_envid = 0; + + state->flags &= ~SMTPD_FLAG_SMTPUTF8; + state->encoding = 0; + state->dsn_ret = 0; + + /* + * Sanity checks. + * + * XXX 2821 pedantism: Section 4.1.2 says that SMTP servers that receive a + * command in which invalid character codes have been employed, and for + * which there are no other reasons for rejection, MUST reject that + * command with a 501 response. Postfix attempts to be 8-bit clean. + */ + if (var_helo_required && state->helo_name == 0) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "503 5.5.1 Error: send HELO/EHLO first"); + return (-1); + } + if (SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: nested MAIL command"); + return (-1); + } + /* Don't accept MAIL after out-of-order BDAT. */ + if (SMTPD_PROCESSING_BDAT(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: MAIL after BDAT"); + return (-1); + } + if (argc < 3 + || strcasecmp(argv[1].strval, "from:") != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: MAIL FROM:
"); + return (-1); + } + + /* + * XXX The client event count/rate control must be consistent in its use + * of client address information in connect and disconnect events. For + * now we exclude xclient authorized hosts from event count/rate control. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && var_smtpd_cmail_limit > 0 + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_mail(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_cmail_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "450 4.7.1 Error: too much mail from %s", + state->addr); + msg_warn("Message delivery request rate limit exceeded: %d from %s for service %s", + rate, state->namaddr, state->service); + return (-1); + } + if (argv[2].tokval == SMTPD_TOK_ERROR) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.1.7 Bad sender address syntax"); + return (-1); + } + + /* + * XXX The sender address comes first, but the optional SMTPUTF8 + * parameter determines what address syntax is permitted. We must process + * this parameter early. + */ + if (var_smtputf8_enable + && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0) { + for (narg = 3; narg < argc; narg++) { + arg = argv[narg].strval; + if (strcasecmp(arg, "SMTPUTF8") == 0) { /* RFC 6531 */ + /* Fix 20161206: allow UTF8 in smtpd_sender_restrictions. */ + state->flags |= SMTPD_FLAG_SMTPUTF8; + break; + } + } + } + if (extract_addr(state, argv + 2, PERMIT_EMPTY_ADDR, + var_strict_rfc821_env, + state->flags & SMTPD_FLAG_SMTPUTF8) != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.1.7 Bad sender address syntax"); + return (-1); + } + for (narg = 3; narg < argc; narg++) { + arg = argv[narg].strval; + if (strcasecmp(arg, "BODY=8BITMIME") == 0) { /* RFC 1652 */ + state->encoding = MAIL_ATTR_ENC_8BIT; + } else if (strcasecmp(arg, "BODY=7BIT") == 0) { /* RFC 1652 */ + state->encoding = MAIL_ATTR_ENC_7BIT; + } else if (strncasecmp(arg, "SIZE=", 5) == 0) { /* RFC 1870 */ + /* Reject non-numeric size. */ + if (!alldig(arg + 5)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad message size syntax"); + return (-1); + } + /* Reject size overflow. */ + if ((state->msg_size = off_cvt_string(arg + 5)) < 0) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "552 5.3.4 Message size exceeds file system imposed limit"); + return (-1); + } + } else if (var_smtputf8_enable + && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0 + && strcasecmp(arg, "SMTPUTF8") == 0) { /* RFC 6531 */ + /* Already processed early. */ ; +#ifdef USE_SASL_AUTH + } else if (strncasecmp(arg, "AUTH=", 5) == 0) { + if ((err = smtpd_sasl_mail_opt(state, arg + 5)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } +#endif + } else if (namadr_list_match(verp_clients, state->name, state->addr) + && strncasecmp(arg, VERP_CMD, VERP_CMD_LEN) == 0 + && (arg[VERP_CMD_LEN] == '=' || arg[VERP_CMD_LEN] == 0)) { + if (arg[VERP_CMD_LEN] == 0) { + verp_delims = var_verp_delims; + } else { + verp_delims = arg + VERP_CMD_LEN + 1; + if (verp_delims_verify(verp_delims) != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, + "501 5.5.4 Error: %s needs two characters from %s", + VERP_CMD, var_verp_filter); + return (-1); + } + } + } else if (strncasecmp(arg, "RET=", 4) == 0) { /* RFC 3461 */ + /* Sanitized on input. */ + if (state->ehlo_discard_mask & EHLO_MASK_DSN) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled"); + return (-1); + } + if (state->dsn_ret + || (state->dsn_ret = dsn_ret_code(arg + 4)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, + "501 5.5.4 Bad RET parameter syntax"); + return (-1); + } + } else if (strncasecmp(arg, "ENVID=", 6) == 0) { /* RFC 3461 */ + /* Sanitized by bounce server. */ + if (state->ehlo_discard_mask & EHLO_MASK_DSN) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled"); + return (-1); + } + if (dsn_envid + || xtext_unquote(state->dsn_buf, arg + 6) == 0 + || !allprint(STR(state->dsn_buf))) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad ENVID parameter syntax"); + return (-1); + } + dsn_envid = 1; + } else { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "555 5.5.4 Unsupported option: %s", arg); + return (-1); + } + } + /* Fix 20161205: show the envelope sender in reject logging. */ + PUSH_STRING(saved_sender, state->sender, STR(state->addr_buf)); + err = smtpd_check_size(state, state->msg_size); + POP_STRING(saved_sender, state->sender); + if (err != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + if (verp_delims && STR(state->addr_buf)[0] == 0) { + smtpd_chat_reply(state, "503 5.5.4 Error: %s requires non-null sender", + VERP_CMD); + return (-1); + } + if (SMTPD_STAND_ALONE(state) == 0) { + const char *verify_sender; + + /* + * XXX Don't reject the address when we're probed with our own + * address verification sender address. Otherwise, some timeout or + * some UCE block may result in mutual negative caching, making it + * painful to get the mail through. Unfortunately we still have to + * send the address to the Milters otherwise they may bail out with a + * "missing recipient" protocol error. + */ + verify_sender = valid_verify_sender_addr(STR(state->addr_buf)); + if (verify_sender != 0) + vstring_strcpy(state->addr_buf, verify_sender); + } + if (SMTPD_STAND_ALONE(state) == 0 + && var_smtpd_delay_reject == 0 + && (err = smtpd_check_mail(state, STR(state->addr_buf))) != 0) { + /* XXX Reset access map side effects. */ + mail_reset(state); + smtpd_chat_reply(state, "%s", err); + return (-1); + } + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) { + state->flags |= SMTPD_FLAG_NEED_MILTER_ABORT; + PUSH_STRING(saved_sender, state->sender, STR(state->addr_buf)); + err = milter_mail_event(state->milters, + milter_argv(state, argc - 2, argv + 2)); + if (err != 0) { + /* Log reject etc. with correct sender information. */ + err = check_milter_reply(state, err); + } + POP_STRING(saved_sender, state->sender); + if (err != 0) { + /* XXX Reset access map side effects. */ + mail_reset(state); + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + if (SMTPD_STAND_ALONE(state) == 0) { + err = smtpd_check_rewrite(state); + if (err != 0) { + /* XXX Reset access map side effects. */ + mail_reset(state); + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + + /* + * Historically, Postfix does not forbid 8-bit envelope localparts. + * Changing this would be a compatibility break. That can't happen in the + * foreseeable future. + */ + if ((var_strict_smtputf8 || warn_compat_break_smtputf8_enable) + && (state->flags & SMTPD_FLAG_SMTPUTF8) == 0 + && *STR(state->addr_buf) && !allascii(STR(state->addr_buf))) { + if (var_strict_smtputf8) { + smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to " + "send unicode address"); + return (-1); + } + + /* + * Not: #ifndef NO_EAI. They must configure SMTPUTF8_ENABLE=no if a + * warning message is logged, so that they don't suddenly start to + * lose mail after Postfix is built with EAI support. + */ + if (warn_compat_break_smtputf8_enable) + msg_info("using backwards-compatible default setting " + VAR_SMTPUTF8_ENABLE "=no to accept non-ASCII sender " + "address \"%s\" from %s", STR(state->addr_buf), + state->namaddr); + } + + /* + * Check the queue file space, if applicable. The optional before-filter + * speed-adjust buffers use disk space. However, we don't know if they + * compete for storage space with the after-filter queue, so we can't + * simply bump up the free space requirement to 2.5 * message_size_limit. + */ + if (!USE_SMTPD_PROXY(state) + || (smtpd_proxy_opts & SMTPD_PROXY_FLAG_SPEED_ADJUST)) { + if (SMTPD_STAND_ALONE(state) == 0 + && (err = smtpd_check_queue(state)) != 0) { + /* XXX Reset access map side effects. */ + mail_reset(state); + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + + /* + * No more early returns. The mail transaction is in progress. + */ + GETTIMEOFDAY(&state->arrival_time); + state->sender = mystrdup(STR(state->addr_buf)); + vstring_sprintf(state->instance, "%x.%lx.%lx.%x", + var_pid, (unsigned long) state->arrival_time.tv_sec, + (unsigned long) state->arrival_time.tv_usec, state->seqno++); + if (verp_delims) + state->verp_delims = mystrdup(verp_delims); + if (dsn_envid) + state->dsn_envid = mystrdup(STR(state->dsn_buf)); + if (USE_SMTPD_PROXY(state)) + state->proxy_mail = mystrdup(STR(state->buffer)); + if (var_smtpd_delay_open == 0 && mail_open_stream(state) < 0) { + /* XXX Reset access map side effects. */ + mail_reset(state); + return (-1); + } + smtpd_chat_reply(state, "250 2.1.0 Ok"); + return (0); +} + +/* mail_reset - reset MAIL command stuff */ + +static void mail_reset(SMTPD_STATE *state) +{ + state->msg_size = 0; + state->act_size = 0; + state->flags &= SMTPD_MASK_MAIL_KEEP; + + /* + * Unceremoniously close the pipe to the cleanup service. The cleanup + * service will delete the queue file when it detects a premature + * end-of-file condition on input. + */ + if (state->cleanup != 0) { + mail_stream_cleanup(state->dest); + state->dest = 0; + state->cleanup = 0; + } + state->err = 0; + if (state->queue_id != 0) { + myfree(state->queue_id); + state->queue_id = 0; + } + if (state->sender) { + myfree(state->sender); + state->sender = 0; + } + /* WeiYu Wu: need to undo milter_mail_event() state change. */ + if (state->flags & SMTPD_FLAG_NEED_MILTER_ABORT) { + milter_abort(state->milters); + state->flags &= ~SMTPD_FLAG_NEED_MILTER_ABORT; + } + if (state->verp_delims) { + myfree(state->verp_delims); + state->verp_delims = 0; + } + if (state->proxy_mail) { + myfree(state->proxy_mail); + state->proxy_mail = 0; + } + if (state->saved_filter) { + myfree(state->saved_filter); + state->saved_filter = 0; + } + if (state->saved_redirect) { + myfree(state->saved_redirect); + state->saved_redirect = 0; + } + if (state->saved_bcc) { + argv_free(state->saved_bcc); + state->saved_bcc = 0; + } + state->saved_flags = 0; +#ifdef DELAY_ACTION + state->saved_delay = 0; +#endif +#ifdef USE_SASL_AUTH + if (state->sasl_sender) + smtpd_sasl_mail_reset(state); +#endif + state->discard = 0; + VSTRING_RESET(state->instance); + VSTRING_TERMINATE(state->instance); + + if (state->proxy) + smtpd_proxy_free(state); + if (state->xforward.flags) + smtpd_xforward_reset(state); + if (state->prepend) + state->prepend = argv_free(state->prepend); + if (state->dsn_envid) { + myfree(state->dsn_envid); + state->dsn_envid = 0; + } + if (state->milter_argv) { + myfree((void *) state->milter_argv); + state->milter_argv = 0; + state->milter_argc = 0; + } + + /* + * BDAT. + */ + state->bdat_state = SMTPD_BDAT_STAT_NONE; + if (state->bdat_get_stream) { + (void) vstream_fclose(state->bdat_get_stream); + state->bdat_get_stream = 0; + } + if (state->bdat_get_buffer) + VSTRING_RESET(state->bdat_get_buffer); +} + +/* rcpt_cmd - process RCPT TO command */ + +static int rcpt_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + SMTPD_PROXY *proxy; + const char *err; + int narg; + char *arg; + int rate; + const char *dsn_orcpt_addr = 0; + ssize_t dsn_orcpt_addr_len = 0; + const char *dsn_orcpt_type = 0; + int dsn_notify = 0; + const char *coded_addr; + const char *milter_err; + + /* + * Sanity checks. + * + * XXX 2821 pedantism: Section 4.1.2 says that SMTP servers that receive a + * command in which invalid character codes have been employed, and for + * which there are no other reasons for rejection, MUST reject that + * command with a 501 response. So much for the principle of "be liberal + * in what you accept, be strict in what you send". + */ + if (!SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: need MAIL command"); + return (-1); + } + /* Don't accept RCPT after BDAT. */ + if (SMTPD_PROCESSING_BDAT(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: RCPT after BDAT"); + return (-1); + } + if (argc < 3 + || strcasecmp(argv[1].strval, "to:") != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: RCPT TO:
"); + return (-1); + } + + /* + * XXX The client event count/rate control must be consistent in its use + * of client address information in connect and disconnect events. For + * now we exclude xclient authorized hosts from event count/rate control. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && var_smtpd_crcpt_limit > 0 + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_rcpt(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_crcpt_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("Recipient address rate limit exceeded: %d from %s for service %s", + rate, state->namaddr, state->service); + smtpd_chat_reply(state, "450 4.7.1 Error: too many recipients from %s", + state->addr); + return (-1); + } + if (argv[2].tokval == SMTPD_TOK_ERROR) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax"); + return (-1); + } + if (extract_addr(state, argv + 2, REJECT_EMPTY_ADDR, var_strict_rfc821_env, + state->flags & SMTPD_FLAG_SMTPUTF8) != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax"); + return (-1); + } + for (narg = 3; narg < argc; narg++) { + arg = argv[narg].strval; + if (strncasecmp(arg, "NOTIFY=", 7) == 0) { /* RFC 3461 */ + /* Sanitized on input. */ + if (state->ehlo_discard_mask & EHLO_MASK_DSN) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled"); + return (-1); + } + if (dsn_notify || (dsn_notify = dsn_notify_mask(arg + 7)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, + "501 5.5.4 Error: Bad NOTIFY parameter syntax"); + return (-1); + } + } else if (strncasecmp(arg, "ORCPT=", 6) == 0) { /* RFC 3461 */ + /* Sanitized by bounce server. */ + if (state->ehlo_discard_mask & EHLO_MASK_DSN) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.7.1 DSN support is disabled"); + return (-1); + } + vstring_strcpy(state->dsn_orcpt_buf, arg + 6); + if (dsn_orcpt_addr + || (coded_addr = split_at(STR(state->dsn_orcpt_buf), ';')) == 0 + || *(dsn_orcpt_type = STR(state->dsn_orcpt_buf)) == 0 + || (strcasecmp(dsn_orcpt_type, "utf-8") == 0 ? + uxtext_unquote(state->dsn_buf, coded_addr) == 0 : + xtext_unquote(state->dsn_buf, coded_addr) == 0)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, + "501 5.5.4 Error: Bad ORCPT parameter syntax"); + return (-1); + } + dsn_orcpt_addr = STR(state->dsn_buf); + dsn_orcpt_addr_len = LEN(state->dsn_buf); + } else { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "555 5.5.4 Unsupported option: %s", arg); + return (-1); + } + } + if (var_smtpd_rcpt_limit && state->rcpt_count >= var_smtpd_rcpt_limit) { + smtpd_chat_reply(state, "452 4.5.3 Error: too many recipients"); + if (state->rcpt_overshoot++ < var_smtpd_rcpt_overlim) + return (0); + state->error_mask |= MAIL_ERROR_POLICY; + return (-1); + } + + /* + * Historically, Postfix does not forbid 8-bit envelope localparts. + * Changing this would be a compatibility break. That can't happen in the + * foreseeable future. + */ + if ((var_strict_smtputf8 || warn_compat_break_smtputf8_enable) + && (state->flags & SMTPD_FLAG_SMTPUTF8) == 0 + && *STR(state->addr_buf) && !allascii(STR(state->addr_buf))) { + if (var_strict_smtputf8) { + smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to " + "send unicode address"); + return (-1); + } + + /* + * Not: #ifndef NO_EAI. They must configure SMTPUTF8_ENABLE=no if a + * warning message is logged, so that they don't suddenly start to + * lose mail after Postfix is built with EAI support. + */ + if (warn_compat_break_smtputf8_enable) + msg_info("using backwards-compatible default setting " + VAR_SMTPUTF8_ENABLE "=no to accept non-ASCII recipient " + "address \"%s\" from %s", STR(state->addr_buf), + state->namaddr); + } + if (SMTPD_STAND_ALONE(state) == 0) { + const char *verify_sender; + + /* + * XXX Don't reject the address when we're probed with our own + * address verification sender address. Otherwise, some timeout or + * some UCE block may result in mutual negative caching, making it + * painful to get the mail through. Unfortunately we still have to + * send the address to the Milters otherwise they may bail out with a + * "missing recipient" protocol error. + */ + verify_sender = valid_verify_sender_addr(STR(state->addr_buf)); + if (verify_sender != 0) { + vstring_strcpy(state->addr_buf, verify_sender); + err = 0; + } else { + err = smtpd_check_rcpt(state, STR(state->addr_buf)); + } + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) { + PUSH_STRING(saved_rcpt, state->recipient, STR(state->addr_buf)); + state->milter_reject_text = err; + milter_err = milter_rcpt_event(state->milters, + err == 0 ? MILTER_FLAG_NONE : + MILTER_FLAG_WANT_RCPT_REJ, + milter_argv(state, argc - 2, argv + 2)); + if (err == 0 && milter_err != 0) { + /* Log reject etc. with correct recipient information. */ + err = check_milter_reply(state, milter_err); + } + POP_STRING(saved_rcpt, state->recipient); + } + if (err != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + + /* + * Don't access the proxy, queue file, or queue file writer process until + * we have a valid recipient address. + */ + if (state->proxy == 0 && state->cleanup == 0 && mail_open_stream(state) < 0) + return (-1); + + /* + * Proxy the recipient. OK, so we lied. If the real-time proxy rejects + * the recipient then we can have a proxy connection without having + * accepted a recipient. + */ + proxy = state->proxy; + if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_OK, + "%s", STR(state->buffer)) != 0) { + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + return (-1); + } + + /* + * Store the recipient. Remember the first one. + * + * Flush recipients to maintain a stiffer coupling with the next stage and + * to better utilize parallelism. + * + * RFC 3461 Section 5.2.1: If the NOTIFY parameter was not supplied for a + * recipient when the message was received, the NOTIFY parameter MUST NOT + * be supplied for that recipient when the message is relayed. + * + * In other words, we can't simply make up our default NOTIFY value. We have + * to remember whether the client sent any. + * + * RFC 3461 Section 5.2.1: If no ORCPT parameter was present when the + * message was received, an ORCPT parameter MAY be added to the RCPT + * command when the message is relayed. If an ORCPT parameter is added + * by the relaying MTA, it MUST contain the recipient address from the + * RCPT command used when the message was received by that MTA. + * + * In other words, it is OK to make up our own DSN original recipient when + * the client didn't send one. Although the RFC mentions mail relaying + * only, we also make up our own original recipient for the purpose of + * final delivery. For now, we do this here, rather than on the fly. + * + * XXX We use REC_TYPE_ATTR for DSN-related recipient attributes even though + * 1) REC_TYPE_ATTR is not meant for multiple instances of the same named + * attribute, and 2) mixing REC_TYPE_ATTR with REC_TYPE_(not attr) + * requires that we map attributes with rec_attr_map() in order to + * simplify the recipient record processing loops in the cleanup and qmgr + * servers. + * + * Another possibility, yet to be explored, is to leave the additional + * recipient information in the queue file and just pass queue file + * offsets along with the delivery request. This is a trade off between + * memory allocation versus numeric conversion overhead. + * + * Since we have no record grouping mechanism, all recipient-specific + * parameters must be sent to the cleanup server before the actual + * recipient address. + */ + state->rcpt_count++; + if (state->recipient == 0) + state->recipient = mystrdup(STR(state->addr_buf)); + if (state->cleanup) { + /* Note: RFC(2)821 externalized address! */ + if (dsn_orcpt_addr == 0) { + dsn_orcpt_type = "rfc822"; + dsn_orcpt_addr = argv[2].strval; + dsn_orcpt_addr_len = strlen(argv[2].strval); + if (dsn_orcpt_addr[0] == '<' + && dsn_orcpt_addr[dsn_orcpt_addr_len - 1] == '>') { + dsn_orcpt_addr += 1; + dsn_orcpt_addr_len -= 2; + } + } + if (dsn_notify) + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_NOTIFY, dsn_notify); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%s;%.*s", + MAIL_ATTR_DSN_ORCPT, dsn_orcpt_type, + (int) dsn_orcpt_addr_len, dsn_orcpt_addr); + rec_fputs(state->cleanup, REC_TYPE_RCPT, STR(state->addr_buf)); + vstream_fflush(state->cleanup); + } + smtpd_chat_reply(state, "250 2.1.5 Ok"); + return (0); +} + +/* rcpt_reset - reset RCPT stuff */ + +static void rcpt_reset(SMTPD_STATE *state) +{ + if (state->recipient) { + myfree(state->recipient); + state->recipient = 0; + } + state->rcpt_count = 0; + /* XXX Must flush the command history. */ + state->rcpt_overshoot = 0; +} + +#if 0 + +/* rfc2047_comment_encode - encode comment string */ + +static VSTRING *rfc2047_comment_encode(const char *str, const char *charset) +{ + VSTRING *buf = vstring_alloc(30); + const unsigned char *cp; + int ch; + + /* + * XXX This is problematic code. + * + * XXX Most of the RFC 2047 "especials" are not special in RFC*822 comments, + * but we encode them anyway to avoid complaints. + * + * XXX In Received: header comments we enclose peer and issuer common names + * with "" quotes (inherited from the Lutz Jaenicke patch). This is the + * cause of several quirks. + * + * 1) We encode text that contains the " character, even though that + * character is not special for RFC*822 comments. + * + * 2) We ignore the recommended limit of 75 characters per encoded word, + * because long comments look ugly when folded in-between quotes. + * + * 3) We encode the enclosing quotes, to avoid producing invalid encoded + * words. Microsoft abuses RFC 2047 encoding with attachment names, but + * we have no information on what decoders do with malformed encoding in + * comments. This means the comments are Jaenicke-compatible only after + * decoding. + */ +#define ESPECIALS "()<>@,;:\"/[]?.=" /* Special in RFC 2047 */ +#define QSPECIALS "_" ESPECIALS /* Special in RFC 2047 'Q' */ +#define CSPECIALS "\\\"()" /* Special in our comments */ + + /* Don't encode if not needed. */ + for (cp = (unsigned char *) str; /* see below */ ; ++cp) { + if ((ch = *cp) == 0) { + vstring_sprintf(buf, "\"%s\"", str); + return (buf); + } + if (!ISPRINT(ch) || strchr(CSPECIALS, ch)) + break; + } + + /* + * Use quoted-printable (like) encoding with spaces mapped to underscore. + */ + vstring_sprintf(buf, "=?%s?Q?=%02X", charset, '"'); + for (cp = (unsigned char *) str; (ch = *cp) != 0; ++cp) { + if (!ISPRINT(ch) || strchr(QSPECIALS CSPECIALS, ch)) { + vstring_sprintf_append(buf, "=%02X", ch); + } else if (ch == ' ') { + VSTRING_ADDCH(buf, '_'); + } else { + VSTRING_ADDCH(buf, ch); + } + } + vstring_sprintf_append(buf, "=%02X?=", '"'); + return (buf); +} + +#endif + +/* comment_sanitize - clean up comment string */ + +static void comment_sanitize(VSTRING *comment_string) +{ + unsigned char *cp; + int ch; + int pc; + + /* + * Postfix Received: headers can be configured to include a comment with + * the CN (CommonName) of the peer and its issuer, or the login name of a + * SASL authenticated user. To avoid problems with RFC 822 etc. syntax, + * we limit this information to printable ASCII text, and neutralize + * characters that affect comment parsing: the backslash and unbalanced + * parentheses. + */ + for (pc = 0, cp = (unsigned char *) STR(comment_string); (ch = *cp) != 0; cp++) { + if (!ISASCII(ch) || !ISPRINT(ch) || ch == '\\') { + *cp = '?'; + } else if (ch == '(') { + pc++; + } else if (ch == ')') { + if (pc > 0) + pc--; + else + *cp = '?'; + } + } + while (pc-- > 0) + VSTRING_ADDCH(comment_string, ')'); + VSTRING_TERMINATE(comment_string); +} + +static void common_pre_message_handling(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, int out_error); +static void receive_data_message(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, int out_error); +static int common_post_message_handling(SMTPD_STATE *state); + +/* data_cmd - process DATA command */ + +static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) +{ + SMTPD_PROXY *proxy; + const char *err; + int (*out_record) (VSTREAM *, int, const char *, ssize_t); + int (*out_fprintf) (VSTREAM *, int, const char *,...); + VSTREAM *out_stream; + int out_error; + + /* + * Sanity checks. With ESMTP command pipelining the client can send DATA + * before all recipients are rejected, so don't report that as a protocol + * error. + */ + if (SMTPD_PROCESSING_BDAT(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: DATA after BDAT"); + return (-1); + } + if (state->rcpt_count == 0) { + if (!SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: need RCPT command"); + } else { + smtpd_chat_reply(state, "554 5.5.1 Error: no valid recipients"); + } + return (-1); + } + if (argc != 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: DATA"); + return (-1); + } + if (SMTPD_STAND_ALONE(state) == 0 && (err = smtpd_check_data(state)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0 + && (err = milter_data_event(state->milters)) != 0 + && (err = check_milter_reply(state, err)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + proxy = state->proxy; + if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_MORE, + "%s", STR(state->buffer)) != 0) { + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + return (-1); + } + + /* + * One level of indirection to choose between normal or proxied + * operation. We want to avoid massive code duplication within tons of + * if-else clauses. + */ + if (proxy) { + out_stream = proxy->stream; + out_record = proxy->rec_put; + out_fprintf = proxy->rec_fprintf; + out_error = CLEANUP_STAT_PROXY; + } else { + out_stream = state->cleanup; + out_record = rec_put; + out_fprintf = rec_fprintf; + out_error = CLEANUP_STAT_WRITE; + } + common_pre_message_handling(state, out_record, out_fprintf, + out_stream, out_error); + smtpd_chat_reply(state, "354 End data with ."); + state->where = SMTPD_AFTER_DATA; + receive_data_message(state, out_record, out_fprintf, out_stream, out_error); + return common_post_message_handling(state); +} + +/* common_pre_message_handling - finish envelope and open message segment */ + +static void common_pre_message_handling(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, + int out_error) +{ + SMTPD_PROXY *proxy = state->proxy; + char **cpp; + const char *rfc3848_sess; + const char *rfc3848_auth; + const char *with_protocol = (state->flags & SMTPD_FLAG_SMTPUTF8) ? + "UTF8SMTP" : state->protocol; + +#ifdef USE_TLS + VSTRING *peer_CN; + VSTRING *issuer_CN; + +#endif +#ifdef USE_SASL_AUTH + VSTRING *username; + +#endif + + /* + * Flush out a first batch of access table actions that are delegated to + * the cleanup server, and that may trigger before we accept the first + * valid recipient. There will be more after end-of-data. + * + * Terminate the message envelope segment. Start the message content + * segment, and prepend our own Received: header. If there is only one + * recipient, list the recipient address. + */ + if (state->cleanup) { + if (SMTPD_STAND_ALONE(state) == 0) { + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0) + /* Send actual smtpd_milters list. */ + (void) milter_send(state->milters, state->cleanup); + if (state->saved_flags) + rec_fprintf(state->cleanup, REC_TYPE_FLGS, "%d", + state->saved_flags); + } + rec_fputs(state->cleanup, REC_TYPE_MESG, ""); + } + + /* + * PREPEND message headers above our own Received: header. + */ + if (state->prepend) + for (cpp = state->prepend->argv; *cpp; cpp++) + out_fprintf(out_stream, REC_TYPE_NORM, "%s", *cpp); + + /* + * Suppress our own Received: header in the unlikely case that we are an + * intermediate proxy. + */ + if (!proxy || state->xforward.flags == 0) { + out_fprintf(out_stream, REC_TYPE_NORM, + "Received: from %s (%s [%s])", + state->helo_name ? state->helo_name : state->name, + state->name, state->rfc_addr); + +#define VSTRING_STRDUP(s) vstring_strcpy(vstring_alloc(strlen(s) + 1), (s)) + +#ifdef USE_TLS + if (var_smtpd_tls_received_header && state->tls_context) { + int cont = 0; + + vstring_sprintf(state->buffer, + "\t(using %s with cipher %s (%d/%d bits)", + state->tls_context->protocol, + state->tls_context->cipher_name, + state->tls_context->cipher_usebits, + state->tls_context->cipher_algbits); + if (state->tls_context->kex_name && *state->tls_context->kex_name) { + out_record(out_stream, REC_TYPE_NORM, STR(state->buffer), + LEN(state->buffer)); + vstring_sprintf(state->buffer, "\t key-exchange %s", + state->tls_context->kex_name); + if (state->tls_context->kex_curve + && *state->tls_context->kex_curve) + vstring_sprintf_append(state->buffer, " (%s)", + state->tls_context->kex_curve); + else if (state->tls_context->kex_bits > 0) + vstring_sprintf_append(state->buffer, " (%d bits)", + state->tls_context->kex_bits); + cont = 1; + } + if (state->tls_context->srvr_sig_name + && *state->tls_context->srvr_sig_name) { + if (cont) { + vstring_sprintf_append(state->buffer, " server-signature %s", + state->tls_context->srvr_sig_name); + } else { + out_record(out_stream, REC_TYPE_NORM, STR(state->buffer), + LEN(state->buffer)); + vstring_sprintf(state->buffer, "\t server-signature %s", + state->tls_context->srvr_sig_name); + } + if (state->tls_context->srvr_sig_curve + && *state->tls_context->srvr_sig_curve) + vstring_sprintf_append(state->buffer, " (%s)", + state->tls_context->srvr_sig_curve); + else if (state->tls_context->srvr_sig_bits > 0) + vstring_sprintf_append(state->buffer, " (%d bits)", + state->tls_context->srvr_sig_bits); + if (state->tls_context->srvr_sig_dgst + && *state->tls_context->srvr_sig_dgst) + vstring_sprintf_append(state->buffer, " server-digest %s", + state->tls_context->srvr_sig_dgst); + } + if (state->tls_context->clnt_sig_name + && *state->tls_context->clnt_sig_name) { + out_record(out_stream, REC_TYPE_NORM, STR(state->buffer), + LEN(state->buffer)); + vstring_sprintf(state->buffer, "\t client-signature %s", + state->tls_context->clnt_sig_name); + if (state->tls_context->clnt_sig_curve + && *state->tls_context->clnt_sig_curve) + vstring_sprintf_append(state->buffer, " (%s)", + state->tls_context->clnt_sig_curve); + else if (state->tls_context->clnt_sig_bits > 0) + vstring_sprintf_append(state->buffer, " (%d bits)", + state->tls_context->clnt_sig_bits); + if (state->tls_context->clnt_sig_dgst + && *state->tls_context->clnt_sig_dgst) + vstring_sprintf_append(state->buffer, " client-digest %s", + state->tls_context->clnt_sig_dgst); + } + out_fprintf(out_stream, REC_TYPE_NORM, "%s)", STR(state->buffer)); + if (TLS_CERT_IS_PRESENT(state->tls_context)) { + peer_CN = VSTRING_STRDUP(state->tls_context->peer_CN); + comment_sanitize(peer_CN); + issuer_CN = VSTRING_STRDUP(state->tls_context->issuer_CN ? + state->tls_context->issuer_CN : ""); + comment_sanitize(issuer_CN); + out_fprintf(out_stream, REC_TYPE_NORM, + "\t(Client CN \"%s\", Issuer \"%s\" (%s))", + STR(peer_CN), STR(issuer_CN), + TLS_CERT_IS_TRUSTED(state->tls_context) ? + "verified OK" : "not verified"); + vstring_free(issuer_CN); + vstring_free(peer_CN); + } else if (var_smtpd_tls_ask_ccert) + out_fprintf(out_stream, REC_TYPE_NORM, + "\t(Client did not present a certificate)"); + else + out_fprintf(out_stream, REC_TYPE_NORM, + "\t(No client certificate requested)"); + } + /* RFC 3848 is defined for ESMTP only. */ + if (state->tls_context != 0 + && strcmp(state->protocol, MAIL_PROTO_ESMTP) == 0) + rfc3848_sess = "S"; + else +#endif + rfc3848_sess = ""; +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_auth_hdr && state->sasl_username) { + username = VSTRING_STRDUP(state->sasl_username); + comment_sanitize(username); + out_fprintf(out_stream, REC_TYPE_NORM, + "\t(Authenticated sender: %s)", STR(username)); + vstring_free(username); + } + /* RFC 3848 is defined for ESMTP only. */ + if (state->sasl_username + && strcmp(state->protocol, MAIL_PROTO_ESMTP) == 0) + rfc3848_auth = "A"; + else +#endif + rfc3848_auth = ""; + if (state->rcpt_count == 1 && state->recipient) { + out_fprintf(out_stream, REC_TYPE_NORM, + state->cleanup ? "\tby %s (%s) with %s%s%s id %s" : + "\tby %s (%s) with %s%s%s", + var_myhostname, var_mail_name, + with_protocol, rfc3848_sess, + rfc3848_auth, state->queue_id); + quote_822_local(state->buffer, state->recipient); + out_fprintf(out_stream, REC_TYPE_NORM, + "\tfor <%s>; %s", STR(state->buffer), + mail_date(state->arrival_time.tv_sec)); + } else { + out_fprintf(out_stream, REC_TYPE_NORM, + state->cleanup ? "\tby %s (%s) with %s%s%s id %s;" : + "\tby %s (%s) with %s%s%s;", + var_myhostname, var_mail_name, + with_protocol, rfc3848_sess, + rfc3848_auth, state->queue_id); + out_fprintf(out_stream, REC_TYPE_NORM, + "\t%s", mail_date(state->arrival_time.tv_sec)); + } +#ifdef RECEIVED_ENVELOPE_FROM + quote_822_local(state->buffer, state->sender); + out_fprintf(out_stream, REC_TYPE_NORM, + "\t(envelope-from %s)", STR(state->buffer)); +#endif + } +} + +/* receive_data_message - finish envelope and open message segment */ + +static void receive_data_message(SMTPD_STATE *state, + int (*out_record) (VSTREAM *, int, const char *, ssize_t), + int (*out_fprintf) (VSTREAM *, int, const char *,...), + VSTREAM *out_stream, + int out_error) +{ + SMTPD_PROXY *proxy = state->proxy; + char *start; + int len; + int curr_rec_type; + int prev_rec_type; + int first = 1; + int prev_got_bare_lf = 0; + + /* + * If deadlines are enabled, increase the time budget as message content + * arrives. + */ + smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_req_deadline, + var_smtpd_min_data_rate); + + /* + * Copy the message content. If the cleanup process has a problem, keep + * reading until the remote stops sending, then complain. Produce typed + * records from the SMTP stream so we can handle data that spans buffers. + * + * XXX Force an empty record when the queue file content begins with + * whitespace, so that it won't be considered as being part of our own + * Received: header. What an ugly Kluge. + * + * XXX Deal with UNIX-style From_ lines at the start of message content + * because sendmail permits it. + */ + for (prev_rec_type = 0; /* void */ ; prev_rec_type = curr_rec_type, + prev_got_bare_lf = smtp_got_bare_lf) { + if (smtp_get(state->buffer, state->client, var_line_limit, + SMTP_GET_FLAG_NONE) == '\n') + curr_rec_type = REC_TYPE_NORM; + else + curr_rec_type = REC_TYPE_CONT; + if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf)) + state->err |= CLEANUP_STAT_BARE_LF; + start = vstring_str(state->buffer); + len = VSTRING_LEN(state->buffer); + if (first) { + if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) { + out_fprintf(out_stream, curr_rec_type, + "X-Mailbox-Line: %s", start); + continue; + } + first = 0; + if (len > 0 && IS_SPACE_TAB(start[0])) + out_record(out_stream, REC_TYPE_NORM, "", 0); + } + if (prev_rec_type != REC_TYPE_CONT && *start == '.') { + if (len == 1 && IS_BARE_LF_WANT_STD_EOD(smtp_detect_bare_lf) + && (smtp_got_bare_lf || prev_got_bare_lf)) + /* Do not store or send to proxy filter. */ + continue; + if (proxy == 0 ? (++start, --len) == 0 : len == 1) + break; + } + if (state->err == CLEANUP_STAT_OK) { + if (ENFORCING_SIZE_LIMIT(var_message_limit) + && var_message_limit - state->act_size < len + 2) { + state->err = CLEANUP_STAT_SIZE; + msg_warn("%s: queue file size limit exceeded", + state->queue_id ? state->queue_id : "NOQUEUE"); + } else { + state->act_size += len + 2; + if (out_record(out_stream, curr_rec_type, start, len) < 0) + state->err = out_error; + } + } + } + state->where = SMTPD_AFTER_EOM; +} + +/* common_post_message_handling - commit message or report error */ + +static int common_post_message_handling(SMTPD_STATE *state) +{ + SMTPD_PROXY *proxy = state->proxy; + const char *err; + VSTRING *why = 0; + int saved_err; + const CLEANUP_STAT_DETAIL *detail; + +#define IS_SMTP_REJECT(s) \ + (((s)[0] == '4' || (s)[0] == '5') \ + && ISDIGIT((s)[1]) && ISDIGIT((s)[2]) \ + && ((s)[3] == '\0' || (s)[3] == ' ' || (s)[3] == '-')) + + if (state->err == CLEANUP_STAT_OK + && SMTPD_STAND_ALONE(state) == 0 + && (err = smtpd_check_eod(state)) != 0) { + smtpd_chat_reply(state, "%s", err); + if (proxy) { + smtpd_proxy_close(state); + } else { + mail_stream_cleanup(state->dest); + state->dest = 0; + state->cleanup = 0; + } + return (-1); + } + + /* + * Send the end of DATA and finish the proxy connection. Set the + * CLEANUP_STAT_PROXY error flag in case of trouble. + */ + if (proxy) { + if (state->err == CLEANUP_STAT_OK) { + (void) proxy->cmd(state, SMTPD_PROX_WANT_ANY, "."); + if (state->err == CLEANUP_STAT_OK && + *STR(proxy->reply) != '2') + state->err = CLEANUP_STAT_CONT; + } + } + + /* + * Flush out access table actions that are delegated to the cleanup + * server. There is similar code at the beginning of the DATA command. + * + * Send the end-of-segment markers and finish the queue file record stream. + */ + else { + if (state->err == CLEANUP_STAT_OK) { + rec_fputs(state->cleanup, REC_TYPE_XTRA, ""); + if (state->saved_filter) + rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", + state->saved_filter); + if (state->saved_redirect) + rec_fprintf(state->cleanup, REC_TYPE_RDR, "%s", + state->saved_redirect); + if (state->saved_bcc) { + char **cpp; + + for (cpp = state->saved_bcc->argv; *cpp; cpp++) { + rec_fprintf(state->cleanup, REC_TYPE_RCPT, "%s", + *cpp); + rec_fprintf(state->cleanup, REC_TYPE_ATTR, "%s=%d", + MAIL_ATTR_DSN_NOTIFY, DSN_NOTIFY_NEVER); + } + } + if (state->saved_flags) + rec_fprintf(state->cleanup, REC_TYPE_FLGS, "%d", + state->saved_flags); +#ifdef DELAY_ACTION + if (state->saved_delay) + rec_fprintf(state->cleanup, REC_TYPE_DELAY, "%d", + state->saved_delay); +#endif + if (vstream_ferror(state->cleanup)) + state->err = CLEANUP_STAT_WRITE; + } + if (state->err == CLEANUP_STAT_OK) + if (rec_fputs(state->cleanup, REC_TYPE_END, "") < 0 + || vstream_fflush(state->cleanup)) + state->err = CLEANUP_STAT_WRITE; + if (state->err == 0) { + why = vstring_alloc(10); + state->err = mail_stream_finish(state->dest, why); + if (IS_SMTP_REJECT(STR(why))) + printable_except(STR(why), ' ', "\r\n"); + else + printable(STR(why), ' '); + } else + mail_stream_cleanup(state->dest); + state->dest = 0; + state->cleanup = 0; + } + + /* + * XXX If we lose the cleanup server while it is editing a queue file, + * the Postfix SMTP server will be out of sync with Milter applications. + * Sending an ABORT to the Milters is not sufficient to restore + * synchronization, because there may be any number of Milter replies + * already in flight. Destroying and recreating the Milters (and faking + * the connect and ehlo events) is too much trouble for testing and + * maintenance. Workaround: force the Postfix SMTP server to hang up with + * a 421 response in the rare case that the cleanup server breaks AND + * that the remote SMTP client continues the session after end-of-data. + * + * XXX Should use something other than CLEANUP_STAT_WRITE when we lose + * contact with the cleanup server. This requires changes to the + * mail_stream module and its users (smtpd, qmqpd, perhaps sendmail). + * + * XXX See exception below in code that overrides state->access_denied for + * compliance with RFC 2821 Sec 3.1. + */ + if (state->milters != 0 && (state->err & CLEANUP_STAT_WRITE) != 0) + state->access_denied = mystrdup("421 4.3.0 Mail system error"); + + /* + * Handle any errors. One message may suffer from multiple errors, so + * complain only about the most severe error. Forgive any previous client + * errors when a message was received successfully. + * + * See also: qmqpd.c + */ + if (state->err == CLEANUP_STAT_OK) { + state->error_count = 0; + state->error_mask = 0; + state->junk_cmds = 0; + if (proxy) + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + else if (SMTPD_PROCESSING_BDAT(state)) + smtpd_chat_reply(state, + "250 2.0.0 Ok: %ld bytes queued as %s", + (long) state->act_size, state->queue_id); + else + smtpd_chat_reply(state, + "250 2.0.0 Ok: queued as %s", state->queue_id); + } else if ((state->err & CLEANUP_STAT_BARE_LF) != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + log_whatsup(state, "reject", "bare received"); + smtpd_chat_reply(state, "%d 5.5.2 %s Error: bare received", + var_smtpd_forbid_bare_lf_code, var_myhostname); + } else if (why && IS_SMTP_REJECT(STR(why))) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "%s", STR(why)); + } else if ((state->err & CLEANUP_STAT_DEFER) != 0) { + state->error_mask |= MAIL_ERROR_POLICY; + detail = cleanup_stat_detail(CLEANUP_STAT_DEFER); + if (why && LEN(why) > 0) { + /* Allow address-specific DSN status in header/body_checks. */ + smtpd_chat_reply(state, "%d %s", detail->smtp, STR(why)); + } else { + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } + } else if ((state->err & CLEANUP_STAT_BAD) != 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + detail = cleanup_stat_detail(CLEANUP_STAT_BAD); + smtpd_chat_reply(state, "%d %s Error: internal error %d", + detail->smtp, detail->dsn, state->err); + } else if ((state->err & CLEANUP_STAT_SIZE) != 0) { + state->error_mask |= MAIL_ERROR_BOUNCE; + detail = cleanup_stat_detail(CLEANUP_STAT_SIZE); + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } else if ((state->err & CLEANUP_STAT_HOPS) != 0) { + state->error_mask |= MAIL_ERROR_BOUNCE; + detail = cleanup_stat_detail(CLEANUP_STAT_HOPS); + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } else if ((state->err & CLEANUP_STAT_CONT) != 0) { + state->error_mask |= MAIL_ERROR_POLICY; + detail = cleanup_stat_detail(CLEANUP_STAT_CONT); + if (proxy) { + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + } else if (why && LEN(why) > 0) { + /* Allow address-specific DSN status in header/body_checks. */ + smtpd_chat_reply(state, "%d %s", detail->smtp, STR(why)); + } else { + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } + } else if ((state->err & CLEANUP_STAT_WRITE) != 0) { + state->error_mask |= MAIL_ERROR_RESOURCE; + detail = cleanup_stat_detail(CLEANUP_STAT_WRITE); + smtpd_chat_reply(state, "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); + } else if ((state->err & CLEANUP_STAT_PROXY) != 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + smtpd_chat_reply(state, "%s", STR(proxy->reply)); + } else { + state->error_mask |= MAIL_ERROR_SOFTWARE; + detail = cleanup_stat_detail(CLEANUP_STAT_BAD); + smtpd_chat_reply(state, "%d %s Error: internal error %d", + detail->smtp, detail->dsn, state->err); + } + + /* + * By popular command: the proxy's end-of-data reply. + */ + if (proxy) + msg_info("proxy-%s: %s: %s;%s", + (state->err == CLEANUP_STAT_OK) ? "accept" : "reject", + state->where, STR(proxy->reply), smtpd_whatsup(state)); + + /* + * Cleanup. The client may send another MAIL command. + */ + saved_err = state->err; + chat_reset(state, var_smtpd_hist_thrsh); + mail_reset(state); + rcpt_reset(state); + if (why) + vstring_free(why); + return (saved_err); +} + +/* skip_bdat - skip content and respond to BDAT error */ + +static int skip_bdat(SMTPD_STATE *state, off_t chunk_size, + bool final_chunk, const char *format,...) +{ + va_list ap; + off_t done; + off_t len; + + /* + * Read and discard content from the remote SMTP client. TODO: drop the + * connection in case of overload. + */ + for (done = 0; done < chunk_size; done += len) { + if ((len = chunk_size - done) > VSTREAM_BUFSIZE) + len = VSTREAM_BUFSIZE; + smtp_fread_buf(state->buffer, len, state->client); + } + + /* + * Send the response to the remote SMTP client. + */ + va_start(ap, format); + vsmtpd_chat_reply(state, format, ap); + va_end(ap); + + /* + * Reset state, or drop subsequent BDAT payloads until BDAT LAST or RSET. + */ + if (final_chunk) + mail_reset(state); + else + state->bdat_state = SMTPD_BDAT_STAT_ERROR; + return (-1); +} + +/* bdat_cmd - process BDAT command */ + +static int bdat_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + SMTPD_PROXY *proxy; + const char *err; + off_t chunk_size; + bool final_chunk; + off_t done; + off_t read_len; + char *start; + int len; + int curr_rec_type; + int (*out_record) (VSTREAM *, int, const char *, ssize_t); + int (*out_fprintf) (VSTREAM *, int, const char *,...); + VSTREAM *out_stream; + int out_error; + + /* + * Hang up if the BDAT command is disabled. The next input would be raw + * message content and that would trigger lots of command errors. + */ + if (state->ehlo_discard_mask & EHLO_MASK_CHUNKING) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "521 5.5.1 Error: command not implemented"); + return (-1); + } + + /* + * Hang up if the BDAT command is malformed. The next input would be raw + * message content and that would trigger lots of command errors. + */ + if (argc < 2 || argc > 3 || !alldig(argv[1].strval) + || (chunk_size = off_cvt_string(argv[1].strval)) < 0 + || ((final_chunk = (argc == 3)) + && strcasecmp(argv[2].strval, "LAST") != 0)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + msg_warn("%s: malformed BDAT command syntax from %s: %.100s", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr, printable(vstring_str(state->buffer), '?')); + smtpd_chat_reply(state, "521 5.5.4 Syntax: BDAT count [LAST]"); + return (-1); + } + + /* + * If deadlines are enabled, increase the time budget as message content + * arrives. + */ + smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_req_deadline, + var_smtpd_min_data_rate); + + /* + * Block abuse involving empty chunks (alternatively, we could count + * "BDAT 0" as a "NOOP", but then we would have to refactor the code that + * enforces the junk command limit). Clients that send a message as a + * sequence of "BDAT 1" should not be a problem: the Postfix BDAT + * implementation should be efficient enough to handle that. + */ + if (chunk_size == 0 && !final_chunk) { + msg_warn("%s: null BDAT request from %s", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr); + return skip_bdat(state, chunk_size, final_chunk, + "551 5.7.1 Null BDAT request"); + } + + /* + * BDAT commands may be pipelined within a MAIL transaction. After a BDAT + * request fails, keep accepting BDAT requests and skipping BDAT payloads + * to maintain synchronization with the remote SMTP client, until the + * client sends BDAT LAST or RSET. + */ + if (state->bdat_state == SMTPD_BDAT_STAT_ERROR) + return skip_bdat(state, chunk_size, final_chunk, + "551 5.0.0 Discarded %ld bytes after earlier error", + (long) chunk_size); + + /* + * Special handling for the first BDAT command in a MAIL transaction, + * treating it as a kind of "DATA" command for the purpose of policy + * evaluation. + */ + if (!SMTPD_PROCESSING_BDAT(state)) { + + /* + * With ESMTP command pipelining a client may send BDAT before the + * server has replied to all RCPT commands. For this reason we cannot + * treat BDAT without valid recipients as a protocol error. Worse, + * RFC 3030 does not discuss the role of BDAT commands in RFC 2920 + * command groups (batches of commands that may be sent without + * waiting for a response to each individual command). Therefore we + * have to allow for clients that pipeline the entire SMTP session + * after EHLO, including multiple MAIL transactions. + */ + if (state->rcpt_count == 0) { + if (!SMTPD_IN_MAIL_TRANSACTION(state)) { + /* TODO: maybe remove this from the DATA and BDAT handlers. */ + state->error_mask |= MAIL_ERROR_PROTOCOL; + return skip_bdat(state, chunk_size, final_chunk, + "503 5.5.1 Error: need RCPT command"); + } else { + return skip_bdat(state, chunk_size, final_chunk, + "554 5.5.1 Error: no valid recipients"); + } + } + if (SMTPD_STAND_ALONE(state) == 0 + && (err = smtpd_check_data(state)) != 0) { + return skip_bdat(state, chunk_size, final_chunk, "%s", err); + } + if (state->milters != 0 + && (state->saved_flags & MILTER_SKIP_FLAGS) == 0 + && (err = milter_data_event(state->milters)) != 0 + && (err = check_milter_reply(state, err)) != 0) { + return skip_bdat(state, chunk_size, final_chunk, "%s", err); + } + proxy = state->proxy; + if (proxy != 0 && proxy->cmd(state, SMTPD_PROX_WANT_MORE, + SMTPD_CMD_DATA) != 0) { + return skip_bdat(state, chunk_size, final_chunk, + "%s", STR(proxy->reply)); + } + } + /* Block too large chunks. */ + if (ENFORCING_SIZE_LIMIT(var_message_limit) + && state->act_size > var_message_limit - chunk_size) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("%s: BDAT request from %s exceeds message size limit", + state->queue_id ? state->queue_id : "NOQUEUE", + state->namaddr); + return skip_bdat(state, chunk_size, final_chunk, + "552 5.3.4 Chunk exceeds message size limit"); + } + + /* + * One level of indirection to choose between normal or proxied + * operation. We want to avoid massive code duplication within tons of + * if-else clauses. TODO: store this in its own data structure, or in + * SMTPD_STATE. + */ + proxy = state->proxy; + if (proxy) { + out_stream = proxy->stream; + out_record = proxy->rec_put; + out_fprintf = proxy->rec_fprintf; + out_error = CLEANUP_STAT_PROXY; + } else { + out_stream = state->cleanup; + out_record = rec_put; + out_fprintf = rec_fprintf; + out_error = CLEANUP_STAT_WRITE; + } + if (!SMTPD_PROCESSING_BDAT(state)) { + common_pre_message_handling(state, out_record, out_fprintf, + out_stream, out_error); + if (state->bdat_get_buffer == 0) + state->bdat_get_buffer = vstring_alloc(VSTREAM_BUFSIZE); + else + VSTRING_RESET(state->bdat_get_buffer); + state->bdat_prev_rec_type = 0; + } + state->bdat_state = SMTPD_BDAT_STAT_OK; + state->where = SMTPD_AFTER_BDAT; + + /* + * Copy the message content. If the cleanup process has a problem, keep + * reading until the remote stops sending, then complain. Produce typed + * records from the SMTP stream so we can handle data that spans buffers. + */ + + /* + * Instead of reading the entire BDAT chunk into memory, read the chunk + * one fragment at a time. The loops below always make one iteration, to + * avoid code duplication for the "BDAT 0 LAST" case (empty chunk). + */ + done = 0; + do { + + /* + * Do not skip the smtp_fread_buf() call if read_len == 0. We still + * need the side effects which include resetting the buffer write + * position. Skipping the call would invalidate the buffer state. + * + * Caution: smtp_fread_buf() will long jump after EOF or timeout. + */ + if ((read_len = chunk_size - done) > VSTREAM_BUFSIZE) + read_len = VSTREAM_BUFSIZE; + smtp_fread_buf(state->buffer, read_len, state->client); + state->bdat_get_stream = vstream_memreopen( + state->bdat_get_stream, state->buffer, O_RDONLY); + + /* + * Read lines from the fragment. The last line may continue in the + * next fragment, or in the next chunk. + */ + do { + if (smtp_get_noexcept(state->bdat_get_buffer, + state->bdat_get_stream, + var_line_limit, + SMTP_GET_FLAG_APPEND) == '\n') { + /* Stopped at end-of-line. */ + curr_rec_type = REC_TYPE_NORM; + } else if (!vstream_feof(state->bdat_get_stream)) { + /* Stopped at var_line_limit. */ + curr_rec_type = REC_TYPE_CONT; + } else if (VSTRING_LEN(state->bdat_get_buffer) > 0 + && final_chunk && read_len == chunk_size - done) { + /* Stopped at final chunk end; handle missing end-of-line. */ + curr_rec_type = REC_TYPE_NORM; + } else { + /* Stopped at fragment end; empty buffer or not at chunk end. */ + /* Skip the out_record() and VSTRING_RESET() calls below. */ + break; + } + if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf)) + state->err |= CLEANUP_STAT_BARE_LF; + start = vstring_str(state->bdat_get_buffer); + len = VSTRING_LEN(state->bdat_get_buffer); + if (state->err == CLEANUP_STAT_OK) { + if (ENFORCING_SIZE_LIMIT(var_message_limit) + && var_message_limit - state->act_size < len + 2) { + state->err = CLEANUP_STAT_SIZE; + msg_warn("%s: queue file size limit exceeded", + state->queue_id ? state->queue_id : "NOQUEUE"); + } else { + state->act_size += len + 2; + if (*start == '.' && proxy != 0 + && state->bdat_prev_rec_type != REC_TYPE_CONT) + if (out_record(out_stream, REC_TYPE_CONT, ".", 1) < 0) + state->err = out_error; + if (state->err == CLEANUP_STAT_OK + && out_record(out_stream, curr_rec_type, + vstring_str(state->bdat_get_buffer), + VSTRING_LEN(state->bdat_get_buffer)) < 0) + state->err = out_error; + } + } + VSTRING_RESET(state->bdat_get_buffer); + state->bdat_prev_rec_type = curr_rec_type; + } while (!vstream_feof(state->bdat_get_stream)); + done += read_len; + } while (done < chunk_size); + + /* + * Special handling for BDAT LAST (successful or unsuccessful). + */ + if (final_chunk) { + state->where = SMTPD_AFTER_EOM; + return common_post_message_handling(state); + } + + /* + * Unsuccessful non-final BDAT command. common_post_message_handling() + * resets all MAIL transaction state including BDAT state. To avoid + * useless error messages due to pipelined BDAT commands, enter the + * SMTPD_BDAT_STAT_ERROR state to accept BDAT commands and skip BDAT + * payloads. + */ + else if (state->err != CLEANUP_STAT_OK) { + /* NOT: state->where = SMTPD_AFTER_EOM; */ + (void) common_post_message_handling(state); + state->bdat_state = SMTPD_BDAT_STAT_ERROR; + return (-1); + } + + /* + * Successful non-final BDAT command. + */ + else { + smtpd_chat_reply(state, "250 2.0.0 Ok: %ld bytes", (long) chunk_size); + return (0); + } +} + +/* rset_cmd - process RSET */ + +static int rset_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) +{ + + /* + * Sanity checks. + */ + if (argc != 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: RSET"); + return (-1); + } + + /* + * Restore state to right after HELO/EHLO command. + */ + chat_reset(state, var_smtpd_hist_thrsh); + mail_reset(state); + rcpt_reset(state); + smtpd_chat_reply(state, "250 2.0.0 Ok"); + return (0); +} + +/* noop_cmd - process NOOP */ + +static int noop_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) +{ + + /* + * XXX 2821 incompatibility: Section 4.1.1.9 says that NOOP can have a + * parameter string which is to be ignored. NOOP instructions with + * parameters? Go figure. + * + * RFC 2821 violates RFC 821, which says that NOOP takes no parameters. + */ +#ifdef RFC821_SYNTAX + + /* + * Sanity checks. + */ + if (argc != 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: NOOP"); + return (-1); + } +#endif + smtpd_chat_reply(state, "250 2.0.0 Ok"); + return (0); +} + +/* vrfy_cmd - process VRFY */ + +static int vrfy_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + const char *err = 0; + int rate; + int smtputf8 = 0; + int saved_flags; + + /* + * The SMTP standard (RFC 821) disallows unquoted special characters in + * the VRFY argument. Common practice violates the standard, however. + * Postfix accommodates common practice where it violates the standard. + * + * XXX Impedance mismatch! The SMTP command tokenizer preserves quoting, + * whereas the recipient restrictions checks expect unquoted (internal) + * address forms. Therefore we must parse out the address, or we must + * stop doing recipient restriction checks and lose the opportunity to + * say "user unknown" at the SMTP port. + * + * XXX 2821 incompatibility and brain damage: Section 4.5.1 requires that + * VRFY is implemented. RFC 821 specifies that VRFY is optional. It gets + * even worse: section 3.5.3 says that a 502 (command recognized but not + * implemented) reply is not fully compliant. + * + * Thus, an RFC 2821 compliant implementation cannot refuse to supply + * information in reply to VRFY queries. That is simply bogus. The only + * reply we could supply is a generic 252 reply. This causes spammers to + * add tons of bogus addresses to their mailing lists (spam harvesting by + * trying out large lists of potential recipient names with VRFY). + */ +#define SLOPPY 0 + + if (var_disable_vrfy_cmd) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "502 5.5.1 VRFY command is disabled"); + return (-1); + } + /* Fix 20140707: handle missing address. */ + if (var_smtputf8_enable + && (state->ehlo_discard_mask & EHLO_MASK_SMTPUTF8) == 0 + && argc > 1 && strcasecmp(argv[argc - 1].strval, "SMTPUTF8") == 0) { + argc--; /* RFC 6531 */ + smtputf8 = 1; + } + if (argc < 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: VRFY address%s", + var_smtputf8_enable ? " [SMTPUTF8]" : ""); + return (-1); + } + + /* + * XXX The client event count/rate control must be consistent in its use + * of client address information in connect and disconnect events. For + * now we exclude xclient authorized hosts from event count/rate control. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && var_smtpd_crcpt_limit > 0 + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_rcpt(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_crcpt_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("Recipient address rate limit exceeded: %d from %s for service %s", + rate, state->namaddr, state->service); + smtpd_chat_reply(state, "450 4.7.1 Error: too many recipients from %s", + state->addr); + return (-1); + } + if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0 + && (err[0] == '5' || err[0] == '4')) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "%s", err); + return (-1); + } + if (argc > 2) + collapse_args(argc - 1, argv + 1); + if (extract_addr(state, argv + 1, REJECT_EMPTY_ADDR, SLOPPY, smtputf8) != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.1.3 Bad recipient address syntax"); + return (-1); + } + /* Fix 20140707: Check the VRFY command. */ + if (smtputf8 == 0 && var_strict_smtputf8) { + if (*STR(state->addr_buf) && !allascii(STR(state->addr_buf))) { + mail_reset(state); + smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to send unicode address"); + return (-1); + } + } + /* Use state->addr_buf, with the unquoted result from extract_addr() */ + if (SMTPD_STAND_ALONE(state) == 0) { + /* Fix 20161206: allow UTF8 in smtpd_recipient_restrictions. */ + saved_flags = state->flags; + if (smtputf8) + state->flags |= SMTPD_FLAG_SMTPUTF8; + err = smtpd_check_rcpt(state, STR(state->addr_buf)); + state->flags = saved_flags; + if (err != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + } + + /* + * XXX 2821 new feature: Section 3.5.1 requires that the VRFY response is + * either "full name " or "user@domain". Postfix replies + * with the string that was provided by the client, whether or not it is + * in fully qualified domain form and the address is in <>. + * + * Reply code 250 is reserved for the case where the address is verified; + * reply code 252 should be used when no definitive certainty exists. + */ + smtpd_chat_reply(state, "252 2.0.0 %s", argv[1].strval); + return (0); +} + +/* etrn_cmd - process ETRN command */ + +static int etrn_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + const char *err; + + /* + * Sanity checks. + */ + if (var_helo_required && state->helo_name == 0) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "503 Error: send HELO/EHLO first"); + return (-1); + } + if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0 + && (err[0] == '5' || err[0] == '4')) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "%s", err); + return (-1); + } + if (SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 Error: MAIL transaction in progress"); + return (-1); + } + if (argc != 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "500 Syntax: ETRN domain"); + return (-1); + } + if (argv[1].strval[0] == '@' || argv[1].strval[0] == '#') + argv[1].strval++; + + /* + * As an extension to RFC 1985 we also allow an RFC 2821 address literal + * enclosed in []. + * + * XXX There does not appear to be an ETRN parameter to indicate that the + * domain name is UTF-8. + */ + if (!valid_hostname(argv[1].strval, DONT_GRIPE) + && !valid_mailhost_literal(argv[1].strval, DONT_GRIPE)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 Error: invalid parameter syntax"); + return (-1); + } + + /* + * XXX The implementation borrows heavily from the code that implements + * UCE restrictions. These typically return 450 or 550 when a request is + * rejected. RFC 1985 requires that 459 be sent when the server refuses + * to perform the request. + */ + if (SMTPD_STAND_ALONE(state)) { + msg_warn("do not use ETRN in \"sendmail -bs\" mode"); + smtpd_chat_reply(state, "458 Unable to queue messages"); + return (-1); + } + if ((err = smtpd_check_etrn(state, argv[1].strval)) != 0) { + smtpd_chat_reply(state, "%s", err); + return (-1); + } + switch (flush_send_site(argv[1].strval)) { + case FLUSH_STAT_OK: + smtpd_chat_reply(state, "250 Queuing started"); + return (0); + case FLUSH_STAT_DENY: + msg_warn("reject: ETRN %.100s... from %s", + argv[1].strval, state->namaddr); + smtpd_chat_reply(state, "459 <%s>: service unavailable", + argv[1].strval); + return (-1); + case FLUSH_STAT_BAD: + msg_warn("bad ETRN %.100s... from %s", argv[1].strval, state->namaddr); + smtpd_chat_reply(state, "458 Unable to queue messages"); + return (-1); + default: + msg_warn("unable to talk to fast flush service"); + smtpd_chat_reply(state, "458 Unable to queue messages"); + return (-1); + } +} + +/* quit_cmd - process QUIT command */ + +static int quit_cmd(SMTPD_STATE *state, int unused_argc, SMTPD_TOKEN *unused_argv) +{ + int out_pending = vstream_bufstat(state->client, VSTREAM_BST_OUT_PEND); + + /* + * Don't bother checking the syntax. + */ + smtpd_chat_reply(state, "221 2.0.0 Bye"); + + /* + * When the "." and quit replies are pipelined, make sure they are + * flushed now, to avoid repeated mail deliveries in case of a crash in + * the "clean up before disconnect" code. + * + * XXX When this was added in Postfix 2.1 we used vstream_fflush(). As of + * Postfix 2.3 we use smtp_flush() for better error reporting. + */ + if (out_pending > 0) + smtp_flush(state->client); + return (0); +} + +/* xclient_cmd - override SMTP client attributes */ + +static int xclient_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + SMTPD_TOKEN *argp; + char *raw_value; + char *attr_value; + char *attr_name; + int update_namaddr = 0; + int name_status; + static const NAME_CODE peer_codes[] = { + XCLIENT_UNAVAILABLE, SMTPD_PEER_CODE_PERM, + XCLIENT_TEMPORARY, SMTPD_PEER_CODE_TEMP, + 0, SMTPD_PEER_CODE_OK, + }; + static const NAME_CODE proto_names[] = { + MAIL_PROTO_SMTP, 1, + MAIL_PROTO_ESMTP, 2, + 0, -1, + }; + int got_helo = 0; + int got_proto = 0; + +#ifdef USE_SASL_AUTH + int got_login = 0; + char *saved_username; + +#endif + + /* + * Sanity checks. + * + * XXX The XCLIENT command will override its own access control, so that + * connection count/rate restrictions can be correctly simulated. + */ + if (SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress"); + return (-1); + } + if (argc < 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: %s attribute=value...", + XCLIENT_CMD); + return (-1); + } + if (xclient_hosts && xclient_hosts->error) + cant_permit_command(state, XCLIENT_CMD); + if (!xclient_allowed) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "550 5.7.0 Error: insufficient authorization"); + return (-1); + } +#define STREQ(x,y) (strcasecmp((x), (y)) == 0) + + /* + * Initialize. + */ + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(100); + + /* + * Iterate over all attribute=value elements. + */ + for (argp = argv + 1; argp < argv + argc; argp++) { + attr_name = argp->strval; + + if ((raw_value = split_at(attr_name, '=')) == 0 || *raw_value == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Error: attribute=value expected"); + return (-1); + } + if (strlen(raw_value) > 255) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Error: attribute value too long"); + return (-1); + } + + /* + * Backwards compatibility: Postfix prior to version 2.3 does not + * xtext encode attribute values. + */ + attr_value = xtext_unquote(state->expand_buf, raw_value) ? + STR(state->expand_buf) : raw_value; + + /* + * For safety's sake mask non-printable characters. We'll do more + * specific censoring later. + */ + printable(attr_value, '?'); + +#define UPDATE_STR(s, v) do { \ + const char *_v = (v); \ + if (s) myfree(s); \ + (s) = (_v) ? mystrdup(_v) : 0; \ + } while(0) + + /* + * NAME=substitute SMTP client hostname (and reverse/forward name, in + * case of success). Also updates the client hostname lookup status + * code. + */ + if (STREQ(attr_name, XCLIENT_NAME)) { + name_status = name_code(peer_codes, NAME_CODE_FLAG_NONE, attr_value); + if (name_status != SMTPD_PEER_CODE_OK) { + attr_value = CLIENT_NAME_UNKNOWN; + } else { + /* XXX EAI */ + if (!valid_hostname(attr_value, DONT_GRIPE)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_NAME, attr_value); + return (-1); + } + } + state->name_status = name_status; + UPDATE_STR(state->name, attr_value); + update_namaddr = 1; + if (name_status == SMTPD_PEER_CODE_OK) { + UPDATE_STR(state->reverse_name, attr_value); + state->reverse_name_status = name_status; + } + } + + /* + * REVERSE_NAME=substitute SMTP client reverse hostname. Also updates + * the client reverse hostname lookup status code. + */ + else if (STREQ(attr_name, XCLIENT_REVERSE_NAME)) { + name_status = name_code(peer_codes, NAME_CODE_FLAG_NONE, attr_value); + if (name_status != SMTPD_PEER_CODE_OK) { + attr_value = CLIENT_NAME_UNKNOWN; + } else { + /* XXX EAI */ + if (!valid_hostname(attr_value, DONT_GRIPE)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_REVERSE_NAME, attr_value); + return (-1); + } + } + state->reverse_name_status = name_status; + UPDATE_STR(state->reverse_name, attr_value); + } + + /* + * ADDR=substitute SMTP client network address. + */ + else if (STREQ(attr_name, XCLIENT_ADDR)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) { + attr_value = CLIENT_ADDR_UNKNOWN; + UPDATE_STR(state->addr, attr_value); + UPDATE_STR(state->rfc_addr, attr_value); + } else { + neuter(attr_value, NEUTER_CHARACTERS, '?'); + if (normalize_mailhost_addr(attr_value, &state->rfc_addr, + &state->addr, + &state->addr_family) < 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_ADDR, attr_value); + return (-1); + } + } + update_namaddr = 1; + } + + /* + * PORT=substitute SMTP client port number. + */ + else if (STREQ(attr_name, XCLIENT_PORT)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) { + attr_value = CLIENT_PORT_UNKNOWN; + } else { + if (!alldig(attr_value) + || strlen(attr_value) > sizeof("65535") - 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_PORT, attr_value); + return (-1); + } + } + UPDATE_STR(state->port, attr_value); + update_namaddr = 1; + } + + /* + * HELO=substitute SMTP client HELO parameter. Censor special + * characters that could mess up message headers. + */ + else if (STREQ(attr_name, XCLIENT_HELO)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) { + attr_value = CLIENT_HELO_UNKNOWN; + } else { + if (strlen(attr_value) > VALID_HOSTNAME_LEN) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_HELO, attr_value); + return (-1); + } + neuter(attr_value, NEUTER_CHARACTERS, '?'); + } + UPDATE_STR(state->helo_name, attr_value); + got_helo = 1; + } + + /* + * PROTO=SMTP protocol name. + */ + else if (STREQ(attr_name, XCLIENT_PROTO)) { + if (name_code(proto_names, NAME_CODE_FLAG_NONE, attr_value) < 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_PROTO, attr_value); + return (-1); + } + UPDATE_STR(state->protocol, uppercase(attr_value)); + got_proto = 1; + } + + /* + * LOGIN=sasl_username. Sets the authentication method as XCLIENT. + * This can be used even if SASL authentication is turned off in + * main.cf. We can't make it easier than that. + */ +#ifdef USE_SASL_AUTH + else if (STREQ(attr_name, XCLIENT_LOGIN)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE) == 0) { + smtpd_sasl_auth_extern(state, attr_value, XCLIENT_CMD); + got_login = 1; + } + } +#endif + + /* + * DESTADDR=substitute SMTP server network address. + */ + else if (STREQ(attr_name, XCLIENT_DESTADDR)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) { + attr_value = SERVER_ADDR_UNKNOWN; + UPDATE_STR(state->dest_addr, attr_value); + } else { +#define NO_NORM_RFC_ADDR ((char **) 0) +#define NO_NORM_ADDR_FAMILY ((int *) 0) + neuter(attr_value, NEUTER_CHARACTERS, '?'); + if (normalize_mailhost_addr(attr_value, NO_NORM_RFC_ADDR, + &state->dest_addr, + NO_NORM_ADDR_FAMILY) < 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_DESTADDR, attr_value); + return (-1); + } + } + /* XXX Require same address family as client address. */ + } + + /* + * DESTPORT=substitute SMTP server port number. + */ + else if (STREQ(attr_name, XCLIENT_DESTPORT)) { + if (STREQ(attr_value, XCLIENT_UNAVAILABLE)) { + attr_value = SERVER_PORT_UNKNOWN; + } else { + if (!alldig(attr_value) + || strlen(attr_value) > sizeof("65535") - 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XCLIENT_DESTPORT, attr_value); + return (-1); + } + } + UPDATE_STR(state->dest_port, attr_value); + } + + /* + * Unknown attribute name. Complain. + */ + else { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s attribute name: %s", + XCLIENT_CMD, attr_name); + return (-1); + } + } + + /* + * Update the combined name and address when either has changed. + */ + if (update_namaddr) { + if (state->namaddr) + myfree(state->namaddr); + state->namaddr = + SMTPD_BUILD_NAMADDRPORT(state->name, state->addr, state->port); + } + + /* + * XXX Compatibility: when the client issues XCLIENT then we have to go + * back to initial server greeting stage, otherwise we can't correctly + * simulate smtpd_client_restrictions (with smtpd_delay_reject=0) and + * Milter connect restrictions. + * + * XXX Compatibility: for accurate simulation we must also reset the HELO + * information. We keep the information if it was specified in the + * XCLIENT command. + * + * XXX The client connection count/rate control must be consistent in its + * use of client address information in connect and disconnect events. We + * re-evaluate xclient so that we correctly simulate connection + * concurrency and connection rate restrictions. + * + * XXX Duplicated from smtpd_proto(). + */ + xclient_allowed = + namadr_list_match(xclient_hosts, state->name, state->addr); + smtp_detect_bare_lf = (SMTPD_STAND_ALONE((state)) == 0 && bare_lf_mask + && !namadr_list_match(bare_lf_excl, state->name, state->addr)) ? + bare_lf_mask : 0; + /* NOT: tls_reset() */ + if (got_helo == 0) + helo_reset(state); + if (got_proto == 0 && strcasecmp(state->protocol, MAIL_PROTO_SMTP) != 0) { + myfree(state->protocol); + state->protocol = mystrdup(MAIL_PROTO_SMTP); + } +#ifdef USE_SASL_AUTH + /* XXX What if they send the parameters via multiple commands? */ + if (got_login == 0) + smtpd_sasl_auth_reset(state); + if (smtpd_sasl_is_active(state)) { + if (got_login) + saved_username = mystrdup(state->sasl_username); + smtpd_sasl_deactivate(state); +#ifdef USE_TLS + if (state->tls_context != 0) /* TLS from XCLIENT proxy? */ + smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS, + var_smtpd_sasl_tls_opts); + else +#endif + smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS, + var_smtpd_sasl_opts); + if (got_login) { + smtpd_sasl_auth_extern(state, saved_username, XCLIENT_CMD); + myfree(saved_username); + } + } +#endif + chat_reset(state, 0); + mail_reset(state); + rcpt_reset(state); + if (state->milters) + milter_disc_event(state->milters); + /* Following duplicates the top-level connect/disconnect handler. */ + teardown_milters(state); + setup_milters(state); + vstream_longjmp(state->client, SMTP_ERR_NONE); + return (0); +} + +/* xforward_cmd - forward logging attributes */ + +static int xforward_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + SMTPD_TOKEN *argp; + char *raw_value; + char *attr_value; + char *attr_name; + int updated = 0; + static const NAME_CODE xforward_flags[] = { + XFORWARD_NAME, SMTPD_STATE_XFORWARD_NAME, + XFORWARD_ADDR, SMTPD_STATE_XFORWARD_ADDR, + XFORWARD_PORT, SMTPD_STATE_XFORWARD_PORT, + XFORWARD_PROTO, SMTPD_STATE_XFORWARD_PROTO, + XFORWARD_HELO, SMTPD_STATE_XFORWARD_HELO, + XFORWARD_IDENT, SMTPD_STATE_XFORWARD_IDENT, + XFORWARD_DOMAIN, SMTPD_STATE_XFORWARD_DOMAIN, + 0, 0, + }; + static const char *context_name[] = { + MAIL_ATTR_RWR_LOCAL, /* Postfix internal form */ + MAIL_ATTR_RWR_REMOTE, /* Postfix internal form */ + }; + static const NAME_CODE xforward_to_context[] = { + XFORWARD_DOM_LOCAL, 0, /* XFORWARD representation */ + XFORWARD_DOM_REMOTE, 1, /* XFORWARD representation */ + 0, -1, + }; + int flag; + int context_code; + + /* + * Sanity checks. + */ + if (SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress"); + return (-1); + } + if (argc < 2) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: %s attribute=value...", + XFORWARD_CMD); + return (-1); + } + if (xforward_hosts && xforward_hosts->error) + cant_permit_command(state, XFORWARD_CMD); + if (!xforward_allowed) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "550 5.7.0 Error: insufficient authorization"); + return (-1); + } + + /* + * Initialize. + */ + if (state->xforward.flags == 0) + smtpd_xforward_preset(state); + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(100); + + /* + * Iterate over all attribute=value elements. + */ + for (argp = argv + 1; argp < argv + argc; argp++) { + attr_name = argp->strval; + + if ((raw_value = split_at(attr_name, '=')) == 0 || *raw_value == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Error: attribute=value expected"); + return (-1); + } + if (strlen(raw_value) > 255) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Error: attribute value too long"); + return (-1); + } + + /* + * Backwards compatibility: Postfix prior to version 2.3 does not + * xtext encode attribute values. + */ + attr_value = xtext_unquote(state->expand_buf, raw_value) ? + STR(state->expand_buf) : raw_value; + + /* + * For safety's sake mask non-printable characters. We'll do more + * specific censoring later. + */ + printable(attr_value, '?'); + + flag = name_code(xforward_flags, NAME_CODE_FLAG_NONE, attr_name); + switch (flag) { + + /* + * NAME=up-stream host name, not necessarily in the DNS. Censor + * special characters that could mess up message headers. + */ + case SMTPD_STATE_XFORWARD_NAME: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_NAME_UNKNOWN; + } else { + /* XXX EAI */ + neuter(attr_value, NEUTER_CHARACTERS, '?'); + if (!valid_hostname(attr_value, DONT_GRIPE)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XFORWARD_NAME, attr_value); + return (-1); + } + } + UPDATE_STR(state->xforward.name, attr_value); + break; + + /* + * ADDR=up-stream host network address, not necessarily on the + * Internet. Censor special characters that could mess up message + * headers. + */ + case SMTPD_STATE_XFORWARD_ADDR: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_ADDR_UNKNOWN; + UPDATE_STR(state->xforward.addr, attr_value); + } else { + neuter(attr_value, NEUTER_CHARACTERS, '?'); + if (normalize_mailhost_addr(attr_value, + &state->xforward.rfc_addr, + &state->xforward.addr, + NO_NORM_ADDR_FAMILY) < 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XFORWARD_ADDR, attr_value); + return (-1); + } + } + break; + + /* + * PORT=up-stream port number. + */ + case SMTPD_STATE_XFORWARD_PORT: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_PORT_UNKNOWN; + } else { + if (!alldig(attr_value) + || strlen(attr_value) > sizeof("65535") - 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XFORWARD_PORT, attr_value); + return (-1); + } + } + UPDATE_STR(state->xforward.port, attr_value); + break; + + /* + * HELO=hostname that the up-stream MTA introduced itself with + * (not necessarily SMTP HELO). Censor special characters that + * could mess up message headers. + */ + case SMTPD_STATE_XFORWARD_HELO: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_HELO_UNKNOWN; + } else { + neuter(attr_value, NEUTER_CHARACTERS, '?'); + } + UPDATE_STR(state->xforward.helo_name, attr_value); + break; + + /* + * PROTO=up-stream protocol, not necessarily SMTP or ESMTP. + * Censor special characters that could mess up message headers. + */ + case SMTPD_STATE_XFORWARD_PROTO: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_PROTO_UNKNOWN; + } else { + if (strlen(attr_value) > 64) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XFORWARD_PROTO, attr_value); + return (-1); + } + neuter(attr_value, NEUTER_CHARACTERS, '?'); + } + UPDATE_STR(state->xforward.protocol, attr_value); + break; + + /* + * IDENT=local message identifier on the up-stream MTA. Censor + * special characters that could mess up logging or macro + * expansions. + */ + case SMTPD_STATE_XFORWARD_IDENT: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) { + attr_value = CLIENT_IDENT_UNKNOWN; + } else { + neuter(attr_value, NEUTER_CHARACTERS, '?'); + } + UPDATE_STR(state->xforward.ident, attr_value); + break; + + /* + * DOMAIN=local or remote. + */ + case SMTPD_STATE_XFORWARD_DOMAIN: + if (STREQ(attr_value, XFORWARD_UNAVAILABLE)) + attr_value = XFORWARD_DOM_LOCAL; + if ((context_code = name_code(xforward_to_context, + NAME_CODE_FLAG_NONE, + attr_value)) < 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s syntax: %s", + XFORWARD_DOMAIN, attr_value); + return (-1); + } + UPDATE_STR(state->xforward.domain, context_name[context_code]); + break; + + /* + * Unknown attribute name. Complain. + */ + default: + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Bad %s attribute name: %s", + XFORWARD_CMD, attr_name); + return (-1); + } + updated |= flag; + } + state->xforward.flags |= updated; + + /* + * Update the combined name and address when either has changed. Use only + * the name when no address is available. + */ + if (updated & (SMTPD_STATE_XFORWARD_NAME | SMTPD_STATE_XFORWARD_ADDR + | SMTPD_STATE_XFORWARD_PORT)) { + if (state->xforward.namaddr) + myfree(state->xforward.namaddr); + state->xforward.namaddr = + IS_AVAIL_CLIENT_ADDR(state->xforward.addr) ? + SMTPD_BUILD_NAMADDRPORT(state->xforward.name, + state->xforward.addr, + state->xforward.port) : + mystrdup(state->xforward.name); + } + smtpd_chat_reply(state, "250 2.0.0 Ok"); + return (0); +} + +/* chat_reset - notify postmaster and reset conversation log */ + +static void chat_reset(SMTPD_STATE *state, int threshold) +{ + + /* + * Notify the postmaster if there were errors. This usually indicates a + * client configuration problem, or that someone is trying nasty things. + * Either is significant enough to bother the postmaster. XXX Can't + * report problems when running in stand-alone mode: postmaster notices + * require availability of the cleanup service. + */ + if (state->history != 0 && state->history->argc > threshold) { + if (SMTPD_STAND_ALONE(state) == 0 + && (state->error_mask & state->notify_mask)) + smtpd_chat_notify(state); + state->error_mask = 0; + smtpd_chat_reset(state); + } +} + +#ifdef USE_TLS + +/* smtpd_start_tls - turn on TLS or force disconnect */ + +static void smtpd_start_tls(SMTPD_STATE *state) +{ + int rate; + int cert_present; + int requirecert; + +#ifdef USE_TLSPROXY + + /* + * This is non-production code, for tlsproxy(8) load testing only. It + * implements enough to enable some Postfix features that depend on TLS + * encryption. + * + * To insert tlsproxy(8) between this process and the SMTP client, we swap + * the file descriptors between the state->tlsproxy and state->client + * VSTREAMS, so that we don't lose all the user-configurable + * state->client attributes (such as longjump buffers or timeouts). + * + * As we implement tlsproxy support in the Postfix SMTP client we should + * develop a usable abstraction that encapsulates this stream plumbing in + * a library module. + */ + vstream_control(state->tlsproxy, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END); + vstream_control(state->client, CA_VSTREAM_CTL_SWAP_FD(state->tlsproxy), + CA_VSTREAM_CTL_END); + (void) vstream_fclose(state->tlsproxy); /* direct-to-client stream! */ + state->tlsproxy = 0; + + /* + * After plumbing the plaintext stream, receive the TLS context object. + * For this we must use the same VSTREAM buffer that we also use to + * receive subsequent SMTP commands. The attribute protocol is robust + * enough that an adversary cannot inject their own bogus TLS context + * attributes into the stream. + */ + state->tls_context = tls_proxy_context_receive(state->client); + + /* + * XXX Maybe it is better to send this information to tlsproxy(8) when + * requesting service, effectively making a remote tls_server_start() + * call. + */ + requirecert = (var_smtpd_tls_req_ccert && var_smtpd_enforce_tls); + +#else /* USE_TLSPROXY */ + TLS_SERVER_START_PROPS props; + static char *cipher_grade; + static VSTRING *cipher_exclusions; + + /* + * Wrapper mode uses a dedicated port and always requires TLS. + * + * XXX In non-wrapper mode, it is possible to require client certificate + * verification without requiring TLS. Since certificates can be verified + * only while TLS is turned on, this means that Postfix will happily + * perform SMTP transactions when the client does not use the STARTTLS + * command. For this reason, Postfix does not require client certificate + * verification unless TLS is required. + * + * The cipher grade and exclusions don't change between sessions. Compute + * just once and cache. + */ +#define ADD_EXCLUDE(vstr, str) \ + do { \ + if (*(str)) \ + vstring_sprintf_append((vstr), "%s%s", \ + VSTRING_LEN(vstr) ? " " : "", (str)); \ + } while (0) + + if (cipher_grade == 0) { + cipher_grade = var_smtpd_enforce_tls ? + var_smtpd_tls_mand_ciph : var_smtpd_tls_ciph; + cipher_exclusions = vstring_alloc(10); + ADD_EXCLUDE(cipher_exclusions, var_smtpd_tls_excl_ciph); + if (var_smtpd_enforce_tls) + ADD_EXCLUDE(cipher_exclusions, var_smtpd_tls_mand_excl); + if (ask_client_cert) + ADD_EXCLUDE(cipher_exclusions, "aNULL"); + } + + /* + * Perform the TLS handshake now. Check the client certificate + * requirements later, if necessary. + */ + requirecert = (var_smtpd_tls_req_ccert && var_smtpd_enforce_tls); + + state->tls_context = + TLS_SERVER_START(&props, + ctx = smtpd_tls_ctx, + stream = state->client, + fd = -1, + timeout = var_smtpd_starttls_tmout, + requirecert = requirecert, + serverid = state->service, + namaddr = state->namaddr, + cipher_grade = cipher_grade, + cipher_exclusions = STR(cipher_exclusions), + mdalg = var_smtpd_tls_fpt_dgst); + +#endif /* USE_TLSPROXY */ + + /* + * For new (i.e. not re-used) TLS sessions, increment the client's new + * TLS session rate counter. We enforce the limit here only for human + * factors reasons (reduce the WTF factor), even though it is too late to + * save the CPU that was already burnt on PKI ops. The real safety + * mechanism applies with future STARTTLS commands (or wrappermode + * connections), prior to the SSL handshake. + * + * XXX The client event count/rate control must be consistent in its use of + * client address information in connect and disconnect events. For now + * we exclude xclient authorized hosts from event count/rate control. + */ + if (var_smtpd_cntls_limit > 0 + && (state->tls_context == 0 || state->tls_context->session_reused == 0) + && SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_newtls(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_cntls_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("New TLS session rate limit exceeded: %d from %s for service %s", + rate, state->namaddr, state->service); + if (state->tls_context) + smtpd_chat_reply(state, + "421 4.7.0 %s Error: too many new TLS sessions from %s", + var_myhostname, state->namaddr); + /* XXX Use regular return to signal end of session. */ + vstream_longjmp(state->client, SMTP_ERR_QUIET); + } + + /* + * When the TLS handshake fails, the conversation is in an unknown state. + * There is nothing we can do except to disconnect from the client. + */ + if (state->tls_context == 0) + vstream_longjmp(state->client, SMTP_ERR_EOF); + + /* + * If we are requiring verified client certs, enforce the constraint + * here. We have a usable TLS session with the client, so no need to + * disable I/O, ... we can even be polite and send "421 ...". + */ + if (requirecert && TLS_CERT_IS_TRUSTED(state->tls_context) == 0) { + + /* + * In non-wrappermode, fetch the next command (should be EHLO). Reply + * with 421, then disconnect (as a side-effect of replying with 421). + */ + cert_present = TLS_CERT_IS_PRESENT(state->tls_context); + msg_info("NOQUEUE: abort: TLS from %s: %s", + state->namaddr, cert_present ? + "Client certificate not trusted" : + "No client certificate presented"); + if (var_smtpd_tls_wrappermode == 0) + smtpd_chat_query(state); + smtpd_chat_reply(state, "421 4.7.1 %s Error: %s", + var_myhostname, cert_present ? + "Client certificate not trusted" : + "No client certificate presented"); + state->error_mask |= MAIL_ERROR_POLICY; + return; + } + + /* + * When TLS is turned on, we may offer AUTH methods that would not be + * offered within a plain-text session. + * + * XXX Always refresh SASL the mechanism list after STARTTLS. Dovecot + * responses may depend on whether the SMTP connection is encrypted. + */ +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable) { + /* Non-wrappermode, presumably. */ + if (smtpd_sasl_is_active(state)) { + smtpd_sasl_auth_reset(state); + smtpd_sasl_deactivate(state); + } + /* Wrappermode and non-wrappermode. */ + if (smtpd_sasl_is_active(state) == 0) + smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS, + var_smtpd_sasl_tls_opts); + } +#endif +} + +/* starttls_cmd - respond to STARTTLS */ + +static int starttls_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) +{ + const char *err; + int rate; + + if (argc != 1) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: STARTTLS"); + return (-1); + } + if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0) { + if (err[0] == '5') { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "%s", err); + return (-1); + } + /* Sendmail compatibility: map 4xx into 454. */ + else if (err[0] == '4') { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "454 4.3.0 Try again later"); + return (-1); + } + } + if (state->tls_context != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "554 5.5.1 Error: TLS already active"); + return (-1); + } + if (var_smtpd_use_tls == 0 + || (state->ehlo_discard_mask & EHLO_MASK_STARTTLS)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "502 5.5.1 Error: command not implemented"); + return (-1); + } +#ifdef USE_TLSPROXY + + /* + * Note: state->tlsproxy is left open when smtp_flush() calls longjmp(), + * so we garbage-collect the VSTREAM in smtpd_state_reset(). + */ +#define PROXY_OPEN_FLAGS \ + (TLS_PROXY_FLAG_ROLE_SERVER | TLS_PROXY_FLAG_SEND_CONTEXT) + + state->tlsproxy = + tls_proxy_legacy_open(var_tlsproxy_service, PROXY_OPEN_FLAGS, + state->client, state->addr, + state->port, var_smtpd_tmout, + state->service); + if (state->tlsproxy == 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + /* RFC 3207 Section 4. */ + smtpd_chat_reply(state, "454 4.7.0 TLS not available due to local problem"); + return (-1); + } +#else /* USE_TLSPROXY */ + if (smtpd_tls_ctx == 0) { + state->error_mask |= MAIL_ERROR_SOFTWARE; + /* RFC 3207 Section 4. */ + smtpd_chat_reply(state, "454 4.7.0 TLS not available due to local problem"); + return (-1); + } +#endif /* USE_TLSPROXY */ + + /* + * Enforce TLS handshake rate limit when this client negotiated too many + * new TLS sessions in the recent past. + * + * XXX The client event count/rate control must be consistent in its use of + * client address information in connect and disconnect events. For now + * we exclude xclient authorized hosts from event count/rate control. + */ + if (var_smtpd_cntls_limit > 0 + && SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_newtls_stat(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_cntls_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("Refusing STARTTLS request from %s for service %s", + state->namaddr, state->service); + smtpd_chat_reply(state, + "454 4.7.0 Error: too many new TLS sessions from %s", + state->namaddr); +#ifdef USE_TLSPROXY + (void) vstream_fclose(state->tlsproxy); + state->tlsproxy = 0; +#endif + return (-1); + } + smtpd_chat_reply(state, "220 2.0.0 Ready to start TLS"); + /* Flush before we switch read/write routines or file descriptors. */ + smtp_flush(state->client); + /* At this point there must not be any pending plaintext. */ + vstream_fpurge(state->client, VSTREAM_PURGE_BOTH); + + /* + * Reset all inputs to the initial state. + * + * XXX RFC 2487 does not forbid the use of STARTTLS while mail transfer is + * in progress, so we have to allow it even when it makes no sense. + */ + helo_reset(state); + mail_reset(state); + rcpt_reset(state); + + /* + * Turn on TLS, using code that is shared with TLS wrapper mode. This + * code does not return when the handshake fails. + */ + smtpd_start_tls(state); + return (0); +} + +/* tls_reset - undo STARTTLS */ + +static void tls_reset(SMTPD_STATE *state) +{ + int failure = 0; + + /* + * Don't waste time when we lost contact. + */ + if (state->tls_context) { + if (vstream_feof(state->client) || vstream_ferror(state->client)) + failure = 1; + vstream_fflush(state->client); /* NOT: smtp_flush() */ +#ifdef USE_TLSPROXY + tls_proxy_context_free(state->tls_context); +#else + tls_server_stop(smtpd_tls_ctx, state->client, var_smtpd_starttls_tmout, + failure, state->tls_context); +#endif + state->tls_context = 0; + } +} + +#endif + +#if !defined(USE_TLS) || !defined(USE_SASL_AUTH) + +/* unimpl_cmd - dummy for functionality that is not compiled in */ + +static int unimpl_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) +{ + + /* + * When a connection is closed we want to log the request counts for + * unimplemented STARTTLS or AUTH commands separately, instead of logging + * those commands as "unknown". By handling unimplemented commands with + * this dummy function, we avoid messing up the command processing loop. + */ + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "502 5.5.1 Error: command not implemented"); + return (-1); +} + +#endif + + /* + * The table of all SMTP commands that we know. Set the junk limit flag on + * any command that can be repeated an arbitrary number of times without + * triggering a tarpit delay of some sort. + */ +typedef struct SMTPD_CMD { + char *name; + int (*action) (SMTPD_STATE *, int, SMTPD_TOKEN *); + int flags; + int success_count; + int total_count; +} SMTPD_CMD; + + /* + * Per RFC 2920: "In particular, the commands RSET, MAIL FROM, SEND FROM, + * SOML FROM, SAML FROM, and RCPT TO can all appear anywhere in a pipelined + * command group. The EHLO, DATA, VRFY, EXPN, TURN, QUIT, and NOOP commands + * can only appear as the last command in a group". RFC 3030 allows BDAT + * commands to be pipelined as well. + */ +#define SMTPD_CMD_FLAG_LIMIT (1<<0) /* limit usage */ +#define SMTPD_CMD_FLAG_PRE_TLS (1<<1) /* allow before STARTTLS */ +#define SMTPD_CMD_FLAG_LAST (1<<2) /* last in PIPELINING command group */ + +static SMTPD_CMD smtpd_cmd_table[] = { + {SMTPD_CMD_HELO, helo_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,}, + {SMTPD_CMD_EHLO, ehlo_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,}, + {SMTPD_CMD_XCLIENT, xclient_cmd, SMTPD_CMD_FLAG_PRE_TLS}, + {SMTPD_CMD_XFORWARD, xforward_cmd,}, +#ifdef USE_TLS + {SMTPD_CMD_STARTTLS, starttls_cmd, SMTPD_CMD_FLAG_PRE_TLS,}, +#else + {SMTPD_CMD_STARTTLS, unimpl_cmd, SMTPD_CMD_FLAG_PRE_TLS,}, +#endif +#ifdef USE_SASL_AUTH + {SMTPD_CMD_AUTH, smtpd_sasl_auth_cmd_wrapper,}, +#else + {SMTPD_CMD_AUTH, unimpl_cmd,}, +#endif + {SMTPD_CMD_MAIL, mail_cmd,}, + {SMTPD_CMD_RCPT, rcpt_cmd,}, + {SMTPD_CMD_DATA, data_cmd, SMTPD_CMD_FLAG_LAST,}, + {SMTPD_CMD_BDAT, bdat_cmd,}, + {SMTPD_CMD_RSET, rset_cmd, SMTPD_CMD_FLAG_LIMIT,}, + {SMTPD_CMD_NOOP, noop_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_PRE_TLS | SMTPD_CMD_FLAG_LAST,}, + {SMTPD_CMD_VRFY, vrfy_cmd, SMTPD_CMD_FLAG_LIMIT | SMTPD_CMD_FLAG_LAST,}, + {SMTPD_CMD_ETRN, etrn_cmd, SMTPD_CMD_FLAG_LIMIT,}, + {SMTPD_CMD_QUIT, quit_cmd, SMTPD_CMD_FLAG_PRE_TLS,}, + {0,}, +}; + +static STRING_LIST *smtpd_noop_cmds; +static STRING_LIST *smtpd_forbid_cmds; + +/* smtpd_flag_ill_pipelining - flag pipelining protocol violation */ + +static int smtpd_flag_ill_pipelining(SMTPD_STATE *state) +{ + + /* + * This code will not return after I/O error, timeout, or EOF. VSTREAM + * exceptions must be enabled in advance with smtp_stream_setup(). + */ + if (vstream_peek(state->client) == 0 + && peekfd(vstream_fileno(state->client)) > 0) + (void) vstream_ungetc(state->client, smtp_fgetc(state->client)); + if (vstream_peek(state->client) > 0) { + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(100); + escape(state->expand_buf, vstream_peek_data(state->client), + vstream_peek(state->client) < 100 ? + vstream_peek(state->client) : 100); + msg_info("improper command pipelining after %s from %s: %s", + state->where, state->namaddr, STR(state->expand_buf)); + state->flags |= SMTPD_FLAG_ILL_PIPELINING; + return (1); + } + return (0); +} + +/* smtpd_proto - talk the SMTP protocol */ + +static void smtpd_proto(SMTPD_STATE *state) +{ + int argc; + SMTPD_TOKEN *argv; + SMTPD_CMD *cmdp; + const char *ehlo_words; + const char *err; + int status; + const char *cp; + +#ifdef USE_TLS + int tls_rate; + +#endif + + /* + * Print a greeting banner and run the state machine. Read SMTP commands + * one line at a time. According to the standard, a sender or recipient + * address could contain an escaped newline. I think this is perverse, + * and anyone depending on this is really asking for trouble. + * + * In case of mail protocol trouble, the program jumps back to this place, + * so that it can perform the necessary cleanup before talking to the + * next client. The setjmp/longjmp primitives are like a sharp tool: use + * with care. I would certainly recommend against the use of + * setjmp/longjmp in programs that change privilege levels. + * + * In case of file system trouble the program terminates after logging the + * error and after informing the client. In all other cases (out of + * memory, panic) the error is logged, and the msg_cleanup() exit handler + * cleans up, but no attempt is made to inform the client of the nature + * of the problem. + * + * With deadlines enabled, do not increase the time budget while receiving a + * command, because that would give an attacker too much time. + */ + vstream_control(state->client, VSTREAM_CTL_EXCEPT, VSTREAM_CTL_END); + while ((status = vstream_setjmp(state->client)) == SMTP_ERR_NONE) + /* void */ ; + smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_req_deadline, 0); + switch (status) { + + default: + msg_panic("smtpd_proto: unknown error reading from %s", + state->namaddr); + break; + + case SMTP_ERR_TIME: + state->reason = REASON_TIMEOUT; + if (vstream_setjmp(state->client) == 0) + smtpd_chat_reply(state, "421 4.4.2 %s Error: timeout exceeded", + var_myhostname); + break; + + case SMTP_ERR_EOF: + state->reason = REASON_LOST_CONNECTION; + break; + + case SMTP_ERR_QUIET: + break; + + case SMTP_ERR_DATA: + msg_info("%s: reject: %s from %s: " + "421 4.3.0 %s Server local data error", + (state->queue_id ? state->queue_id : "NOQUEUE"), + state->where, state->namaddr, var_myhostname); + state->error_mask |= MAIL_ERROR_DATA; + if (vstream_setjmp(state->client) == 0) + smtpd_chat_reply(state, "421 4.3.0 %s Server local data error", + var_myhostname); + break; + + case 0: + + /* + * Don't bother doing anything if some pre-SMTP handshake (haproxy) + * did not work out. + */ + if (state->flags & SMTPD_FLAG_HANGUP) { + smtpd_chat_reply(state, "421 4.3.0 %s Server local error", + var_myhostname); + break; + } + + /* + * In TLS wrapper mode, turn on TLS using code that is shared with + * the STARTTLS command. This code does not return when the handshake + * fails. + * + * Enforce TLS handshake rate limit when this client negotiated too many + * new TLS sessions in the recent past. + * + * XXX This means we don't complete a TLS handshake just to tell the + * client that we don't provide service. TLS wrapper mode is + * obsolete, so we don't have to provide perfect support. + */ +#ifdef USE_TLS + if (SMTPD_STAND_ALONE(state) == 0 && var_smtpd_tls_wrappermode + && state->tls_context == 0) { +#ifdef USE_TLSPROXY + /* We garbage-collect the VSTREAM in smtpd_state_reset() */ + state->tlsproxy = + tls_proxy_legacy_open(var_tlsproxy_service, + PROXY_OPEN_FLAGS, + state->client, state->addr, + state->port, var_smtpd_tmout, + state->service); + if (state->tlsproxy == 0) { + msg_warn("Wrapper-mode request dropped from %s for service %s." + " TLS context initialization failed. For details see" + " earlier warnings in your logs.", + state->namaddr, state->service); + break; + } +#else /* USE_TLSPROXY */ + if (smtpd_tls_ctx == 0) { + msg_warn("Wrapper-mode request dropped from %s for service %s." + " TLS context initialization failed. For details see" + " earlier warnings in your logs.", + state->namaddr, state->service); + break; + } +#endif /* USE_TLSPROXY */ + if (var_smtpd_cntls_limit > 0 + && !xclient_allowed + && anvil_clnt + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_newtls_stat(anvil_clnt, state->service, + state->addr, &tls_rate) == ANVIL_STAT_OK + && tls_rate > var_smtpd_cntls_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("Refusing TLS service request from %s for service %s", + state->namaddr, state->service); + break; + } + smtpd_start_tls(state); + } +#endif + + /* + * If the client spoke before the server sends the initial greeting, + * raise a flag and log the content of the protocol violation. This + * check MUST NOT apply to TLS wrappermode connections. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && vstream_context(state->client) == 0 /* not postscreen */ + && (state->flags & SMTPD_FLAG_ILL_PIPELINING) == 0 + && smtpd_flag_ill_pipelining(state) + && var_smtpd_forbid_unauth_pipe) { + smtpd_chat_reply(state, + "554 5.5.0 Error: SMTP protocol synchronization"); + break; + } + + /* + * XXX The client connection count/rate control must be consistent in + * its use of client address information in connect and disconnect + * events. For now we exclude xclient authorized hosts from + * connection count/rate control. + * + * XXX Must send connect/disconnect events to the anvil server even when + * this service is not connection count or rate limited, otherwise it + * will discard client message or recipient rate information too + * early or too late. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_connect(anvil_clnt, state->service, state->addr, + &state->conn_count, &state->conn_rate) + == ANVIL_STAT_OK) { + if (var_smtpd_cconn_limit > 0 + && state->conn_count > var_smtpd_cconn_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("Connection concurrency limit exceeded: %d from %s for service %s", + state->conn_count, state->namaddr, state->service); + smtpd_chat_reply(state, "421 4.7.0 %s Error: too many connections from %s", + var_myhostname, state->addr); + break; + } + if (var_smtpd_crate_limit > 0 + && state->conn_rate > var_smtpd_crate_limit) { + msg_warn("Connection rate limit exceeded: %d from %s for service %s", + state->conn_rate, state->namaddr, state->service); + smtpd_chat_reply(state, "421 4.7.0 %s Error: too many connections from %s", + var_myhostname, state->addr); + break; + } + } + + /* + * Determine what server ESMTP features to suppress, typically to + * avoid inter-operability problems. Moved up so we don't send 421 + * immediately after sending the initial server response. + */ + if (ehlo_discard_maps == 0 + || (ehlo_words = maps_find(ehlo_discard_maps, state->addr, 0)) == 0) + ehlo_words = var_smtpd_ehlo_dis_words; + state->ehlo_discard_mask = ehlo_mask(ehlo_words); + + /* XXX We use the real client for connect access control. */ + if (SMTPD_STAND_ALONE(state) == 0 + && var_smtpd_delay_reject == 0 + && (err = smtpd_check_client(state)) != 0) { + state->error_mask |= MAIL_ERROR_POLICY; + state->access_denied = mystrdup(err); + smtpd_chat_reply(state, "%s", state->access_denied); + state->error_count++; + } + + /* + * RFC 2034: the text part of all 2xx, 4xx, and 5xx SMTP responses + * other than the initial greeting and any response to HELO or EHLO + * are prefaced with a status code as defined in RFC 3463. + */ + + /* + * XXX If a Milter rejects CONNECT, reply with 220 except in case of + * hard reject or 421 (disconnect). The reply persists so it will + * apply to MAIL FROM and to other commands such as AUTH, STARTTLS, + * and VRFY. Note: after a Milter CONNECT reject, we must not reject + * HELO or EHLO, but we do change the feature list that is announced + * in the EHLO response. + */ + else { + err = 0; + if (state->milters != 0) { + milter_macro_callback(state->milters, smtpd_milter_eval, + (void *) state); + if ((err = milter_conn_event(state->milters, state->name, + state->addr, + strcmp(state->port, CLIENT_PORT_UNKNOWN) ? + state->port : "0", + state->addr_family)) != 0) + err = check_milter_reply(state, err); + } + if (err && err[0] == '5') { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "554 %s ESMTP not accepting connections", + var_myhostname); + state->error_count++; + } else if (err && strncmp(err, "421", 3) == 0) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "421 %s Service unavailable - try again later", + var_myhostname); + /* Not: state->error_count++; */ + } else { + smtpd_chat_reply(state, "220 %s", var_smtpd_banner); + } + } + + /* + * SASL initialization for plaintext mode. + * + * XXX Backwards compatibility: allow AUTH commands when the AUTH + * announcement is suppressed via smtpd_sasl_exceptions_networks. + * + * XXX Safety: don't enable SASL with "smtpd_tls_auth_only = yes" and + * non-TLS build. + */ +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable && smtpd_sasl_is_active(state) == 0 +#ifdef USE_TLS + && state->tls_context == 0 && !var_smtpd_tls_auth_only +#else + && var_smtpd_tls_auth_only == 0 +#endif + ) + smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS, + var_smtpd_sasl_opts); +#endif + + /* + * The command read/execute loop. + */ + for (;;) { + if (state->flags & SMTPD_FLAG_HANGUP) + break; + smtp_stream_setup(state->client, var_smtpd_tmout, + var_smtpd_req_deadline, 0); + if (state->error_count >= var_smtpd_hard_erlim) { + state->reason = REASON_ERROR_LIMIT; + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "421 4.7.0 %s Error: too many errors", + var_myhostname); + break; + } + watchdog_pat(); + smtpd_chat_query(state); + if (IS_BARE_LF_REPLY_REJECT(smtp_got_bare_lf)) { + log_whatsup(state, "reject", "bare received"); + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "%d 5.5.2 %s Error: bare received", + var_smtpd_forbid_bare_lf_code, var_myhostname); + break; + } + /* Safety: protect internal interfaces against malformed UTF-8. */ + if (var_smtputf8_enable && valid_utf8_string(STR(state->buffer), + LEN(state->buffer)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "500 5.5.2 Error: bad UTF-8 syntax"); + state->error_count++; + continue; + } + /* Move into smtpd_chat_query() and update session transcript. */ + if (smtpd_cmd_filter != 0) { + for (cp = STR(state->buffer); *cp && IS_SPACE_TAB(*cp); cp++) + /* void */ ; + if ((cp = dict_get(smtpd_cmd_filter, cp)) != 0) { + msg_info("%s: replacing command \"%.100s\" with \"%.100s\"", + state->namaddr, STR(state->buffer), cp); + vstring_strcpy(state->buffer, cp); + } else if (smtpd_cmd_filter->error != 0) { + msg_warn("%s:%s lookup error for \"%.100s\"", + smtpd_cmd_filter->type, smtpd_cmd_filter->name, + printable(STR(state->buffer), '?')); + vstream_longjmp(state->client, SMTP_ERR_DATA); + } + } + if ((argc = smtpd_token(vstring_str(state->buffer), &argv)) == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "500 5.5.2 Error: bad syntax"); + state->error_count++; + continue; + } + /* Ignore smtpd_noop_cmds lookup errors. Non-critical feature. */ + if (*var_smtpd_noop_cmds + && string_list_match(smtpd_noop_cmds, argv[0].strval)) { + smtpd_chat_reply(state, "250 2.0.0 Ok"); + if (state->junk_cmds++ > var_smtpd_junk_cmd_limit) + state->error_count++; + continue; + } + for (cmdp = smtpd_cmd_table; cmdp->name != 0; cmdp++) + if (strcasecmp(argv[0].strval, cmdp->name) == 0) + break; + cmdp->total_count += 1; + /* Ignore smtpd_forbid_cmds lookup errors. Non-critical feature. */ + if (cmdp->name == 0) { + state->where = SMTPD_CMD_UNKNOWN; + if (is_header(argv[0].strval) + || (*var_smtpd_forbid_cmds + && string_list_match(smtpd_forbid_cmds, argv[0].strval))) { + VSTRING *escape_buf = vstring_alloc(100); + + msg_warn("non-SMTP command from %s: %.100s", + state->namaddr, + vstring_str(escape(escape_buf, + vstring_str(state->buffer), + VSTRING_LEN(state->buffer)))); + smtpd_chat_reply(state, "221 2.7.0 Error: I can break rules, too. Goodbye."); + vstring_free(escape_buf); + break; + } + } + /* XXX We use the real client for connect access control. */ + if (state->access_denied && cmdp->action != quit_cmd) { + /* XXX Exception for Milter override. */ + if (strncmp(state->access_denied + 1, "21", 2) == 0) { + smtpd_chat_reply(state, "%s", state->access_denied); + continue; + } + smtpd_chat_reply(state, "503 5.7.0 Error: access denied for %s", + state->namaddr); /* RFC 2821 Sec 3.1 */ + state->error_count++; + continue; + } + /* state->access_denied == 0 || cmdp->action == quit_cmd */ + if (cmdp->name == 0) { + if (state->milters != 0 + && (err = milter_unknown_event(state->milters, + argv[0].strval)) != 0 + && (err = check_milter_reply(state, err)) != 0) { + smtpd_chat_reply(state, "%s", err); + } else + smtpd_chat_reply(state, "500 5.5.2 Error: command not recognized"); + state->error_mask |= MAIL_ERROR_PROTOCOL; + state->error_count++; + continue; + } +#ifdef USE_TLS + if (var_smtpd_enforce_tls && + !state->tls_context && + (cmdp->flags & SMTPD_CMD_FLAG_PRE_TLS) == 0) { + smtpd_chat_reply(state, + "530 5.7.0 Must issue a STARTTLS command first"); + state->error_count++; + continue; + } +#endif + state->where = cmdp->name; + if (SMTPD_STAND_ALONE(state) == 0 + && (strcasecmp(state->protocol, MAIL_PROTO_ESMTP) != 0 + || (cmdp->flags & SMTPD_CMD_FLAG_LAST)) + && (state->flags & SMTPD_FLAG_ILL_PIPELINING) == 0 + && smtpd_flag_ill_pipelining(state) + && var_smtpd_forbid_unauth_pipe) { + smtpd_chat_reply(state, + "554 5.5.0 Error: SMTP protocol synchronization"); + break; + } + if (cmdp->action(state, argc, argv) != 0) + state->error_count++; + else + cmdp->success_count += 1; + if ((cmdp->flags & SMTPD_CMD_FLAG_LIMIT) + && state->junk_cmds++ > var_smtpd_junk_cmd_limit) + state->error_count++; + if (cmdp->action == quit_cmd) + break; + } + break; + } + + /* + * XXX The client connection count/rate control must be consistent in its + * use of client address information in connect and disconnect events. + * For now we exclude xclient authorized hosts from connection count/rate + * control. + * + * XXX Must send connect/disconnect events to the anvil server even when + * this service is not connection count or rate limited, otherwise it + * will discard client message or recipient rate information too early or + * too late. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && !namadr_list_match(hogger_list, state->name, state->addr)) + anvil_clnt_disconnect(anvil_clnt, state->service, state->addr); + + /* + * Log abnormal session termination, in case postmaster notification has + * been turned off. In the log, indicate the last recognized state before + * things went wrong. Don't complain about clients that go away without + * sending QUIT. Log the byte count after DATA to help diagnose MTU + * troubles. + */ + if (state->reason && state->where) { + if (strcmp(state->where, SMTPD_AFTER_DATA) == 0) { + msg_info("%s after %s (%lu bytes) from %s", /* 2.5 compat */ + state->reason, SMTPD_CMD_DATA, /* 2.5 compat */ + (long) (state->act_size + vstream_peek(state->client)), + state->namaddr); + } else if (strcmp(state->where, SMTPD_AFTER_BDAT) == 0) { + msg_info("%s after %s (%lu bytes) from %s", + state->reason, SMTPD_CMD_BDAT, + (long) (state->act_size + VSTRING_LEN(state->buffer) + + VSTRING_LEN(state->bdat_get_buffer)), + state->namaddr); + } else if (strcmp(state->where, SMTPD_AFTER_EOM) + || strcmp(state->reason, REASON_LOST_CONNECTION)) { + msg_info("%s after %s from %s", + state->reason, state->where, state->namaddr); + } + } + + /* + * Cleanup whatever information the client gave us during the SMTP + * dialog. + * + * XXX Duplicated in xclient_cmd(). + */ +#ifdef USE_TLS + tls_reset(state); +#endif + helo_reset(state); +#ifdef USE_SASL_AUTH + smtpd_sasl_auth_reset(state); + if (smtpd_sasl_is_active(state)) { + smtpd_sasl_deactivate(state); + } +#endif + chat_reset(state, 0); + mail_reset(state); + rcpt_reset(state); + if (state->milters) + milter_disc_event(state->milters); +} + +/* smtpd_format_cmd_stats - format per-command statistics */ + +static char *smtpd_format_cmd_stats(VSTRING *buf) +{ + SMTPD_CMD *cmdp; + int all_success = 0; + int all_total = 0; + + /* + * Log the statistics. Note that this loop produces no output when no + * command was received. We address that after the loop. + */ + VSTRING_RESET(buf); + for (cmdp = smtpd_cmd_table; /* see below */ ; cmdp++) { + if (cmdp->total_count > 0) { + vstring_sprintf_append(buf, " %s=%d", + cmdp->name ? cmdp->name : "unknown", + cmdp->success_count); + if (cmdp->success_count != cmdp->total_count) + vstring_sprintf_append(buf, "/%d", cmdp->total_count); + all_success += cmdp->success_count; + all_total += cmdp->total_count; + } + if (cmdp->name == 0) + break; + } + + /* + * Reset the per-command counters. + * + * Fix 20190621: the command counter resetting code was moved from the SMTP + * protocol handler to this place, because the protocol handler was never + * called after HaProxy handshake error, causing stale numbers to be + * logged. + */ + for (cmdp = smtpd_cmd_table; /* see below */ ; cmdp++) { + cmdp->success_count = cmdp->total_count = 0; + if (cmdp->name == 0) + break; + } + + /* + * Log total numbers, so that logfile analyzers will see something even + * if the above loop produced no output. When no commands were received + * log "0/0" to simplify the identification of abnormal sessions: any + * statistics with [0-9]/ indicate that there was a problem. + */ + vstring_sprintf_append(buf, " commands=%d", all_success); + if (all_success != all_total || all_total == 0) + vstring_sprintf_append(buf, "/%d", all_total); + return (lowercase(STR(buf))); +} + +/* setup_milters - set up Milters after a connection is established */ + +static void setup_milters(SMTPD_STATE *state) +{ + const char *milter_string; + + /* + * Postcondition: either state->milters is set, or the + * INPUT_TRANSP_MILTER flag is passed down-stream. + */ + if (SMTPD_STAND_ALONE(state) == 0 + && (smtpd_input_transp_mask & INPUT_TRANSP_MILTER) == 0 + && ((smtpd_milter_maps + && (milter_string = + maps_find(smtpd_milter_maps, state->addr, 0)) != 0) + || *(milter_string = var_smtpd_milters) != 0) + && strcasecmp(milter_string, SMTPD_MILTERS_DISABLE) != 0) { + state->milters = milter_create(milter_string, + var_milt_conn_time, + var_milt_cmd_time, + var_milt_msg_time, + var_milt_protocol, + var_milt_def_action, + var_milt_conn_macros, + var_milt_helo_macros, + var_milt_mail_macros, + var_milt_rcpt_macros, + var_milt_data_macros, + var_milt_eoh_macros, + var_milt_eod_macros, + var_milt_unk_macros, + var_milt_macro_deflts); + } + + /* + * Safety: disable non_smtpd_milters when not sending our own mail filter + * list. Otherwise the next stage could handle this message as a local + * submission. + */ + if (state->milters == 0) + smtpd_input_transp_mask |= INPUT_TRANSP_MILTER; +} + +/* teardown_milters - release resources */ + +static void teardown_milters(SMTPD_STATE *state) +{ + if (state->milters) { + milter_free(state->milters); + state->milters = 0; + } + smtpd_input_transp_mask = + input_transp_mask(VAR_INPUT_TRANSP, var_input_transp); +} + + +/* smtpd_service - service one client */ + +static void smtpd_service(VSTREAM *stream, char *service, char **argv) +{ + SMTPD_STATE state; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * For sanity, require that at least one of INET or INET6 is enabled. + * Otherwise, we can't look up interface information, and we can't + * convert names or addresses. + */ + if (SMTPD_STAND_ALONE_STREAM(stream) == 0 + && inet_proto_info()->ai_family_list[0] == 0) + msg_fatal("all network protocols are disabled (%s = %s)", + VAR_INET_PROTOCOLS, var_inet_protocols); + + /* + * This routine runs when a client has connected to our network port, or + * when the smtp server is run in stand-alone mode (input from pipe). + * + * Look up and sanitize the peer name, then initialize some connection- + * specific state. When the name service is hosed, hostname lookup will + * take a while. This is why I always run a local name server on critical + * machines. + */ + smtpd_state_init(&state, stream, service); + msg_info("connect from %s", state.namaddr); + + /* + * Disable TLS when running in stand-alone mode via "sendmail -bs". + */ + if (SMTPD_STAND_ALONE((&state))) { + var_smtpd_use_tls = 0; + var_smtpd_enforce_tls = 0; + var_smtpd_tls_auth_only = 0; + } + + /* + * XCLIENT must not override its own access control. + */ + xclient_allowed = SMTPD_STAND_ALONE((&state)) == 0 && + namadr_list_match(xclient_hosts, state.name, state.addr); + + /* + * Overriding XFORWARD access control makes no sense, either. + */ + xforward_allowed = SMTPD_STAND_ALONE((&state)) == 0 && + namadr_list_match(xforward_hosts, state.name, state.addr); + + /* + * Reject or normalize bare LF, with compatibility exclusions. + */ + smtp_detect_bare_lf = (SMTPD_STAND_ALONE((&state)) == 0 && bare_lf_mask + && !namadr_list_match(bare_lf_excl, state.name, state.addr)) ? + bare_lf_mask : 0; + + /* + * See if we need to turn on verbose logging for this client. + */ + debug_peer_check(state.name, state.addr); + + /* + * Set up Milters, or disable Milters down-stream. + */ + setup_milters(&state); /* duplicates xclient_cmd */ + + /* + * Provide the SMTP service. + */ + smtpd_proto(&state); + + /* + * After the client has gone away, clean up whatever we have set up at + * connection time. + */ + msg_info("disconnect from %s%s", state.namaddr, + smtpd_format_cmd_stats(state.buffer)); + teardown_milters(&state); /* duplicates xclient_cmd */ + smtpd_state_reset(&state); + debug_peer_restore(); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Initialize denylist/etc. patterns before entering the chroot jail, in + * case they specify a filename pattern. + */ + smtpd_noop_cmds = string_list_init(VAR_SMTPD_NOOP_CMDS, MATCH_FLAG_RETURN, + var_smtpd_noop_cmds); + smtpd_forbid_cmds = string_list_init(VAR_SMTPD_FORBID_CMDS, + MATCH_FLAG_RETURN, + var_smtpd_forbid_cmds); + verp_clients = namadr_list_init(VAR_VERP_CLIENTS, MATCH_FLAG_RETURN, + var_verp_clients); + xclient_hosts = namadr_list_init(VAR_XCLIENT_HOSTS, MATCH_FLAG_RETURN, + var_xclient_hosts); + xforward_hosts = namadr_list_init(VAR_XFORWARD_HOSTS, MATCH_FLAG_RETURN, + var_xforward_hosts); + hogger_list = namadr_list_init(VAR_SMTPD_HOGGERS, MATCH_FLAG_RETURN + | match_parent_style(VAR_SMTPD_HOGGERS), + var_smtpd_hoggers); + bare_lf_excl = namadr_list_init(VAR_SMTPD_FORBID_BARE_LF_EXCL, + MATCH_FLAG_RETURN + | match_parent_style(VAR_MYNETWORKS), + var_smtpd_forbid_bare_lf_excl); + if ((bare_lf_mask = name_code(bare_lf_mask_table, NAME_CODE_FLAG_NONE, + var_smtpd_forbid_bare_lf)) < 0) + msg_fatal("bad parameter value: '%s = %s'", + VAR_SMTPD_FORBID_BARE_LF, var_smtpd_forbid_bare_lf); + + /* + * Open maps before dropping privileges so we can read passwords etc. + * + * XXX We should not do this in stand-alone (sendmail -bs) mode, but we + * can't use SMTPD_STAND_ALONE(state) here. This means "sendmail -bs" + * will try to connect to proxymap when invoked by root for mail + * submission. To fix, we would have to pass stand-alone mode information + * via different means. For now we have to tell people not to run mail + * clients as root. + */ + if (getuid() == 0 || getuid() == var_owner_uid) + smtpd_check_init(); + smtpd_expand_init(); + debug_peer_init(); + + if (var_smtpd_sasl_enable) +#ifdef USE_SASL_AUTH + smtpd_sasl_initialize(); + + if (*var_smtpd_sasl_exceptions_networks) + sasl_exceptions_networks = + namadr_list_init(VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS, + MATCH_FLAG_RETURN, + var_smtpd_sasl_exceptions_networks); +#else + msg_warn("%s is true, but SASL support is not compiled in", + VAR_SMTPD_SASL_ENABLE); +#endif + + if (*var_smtpd_cmd_filter) + smtpd_cmd_filter = dict_open(var_smtpd_cmd_filter, O_RDONLY, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); + + /* + * XXX Temporary fix to pretend that we consistently implement TLS + * security levels. We implement only a subset for now. If we implement + * more levels, wrappermode should override only weaker TLS security + * levels. + * + * Note: tls_level_lookup() logs no warning. + */ + if (!var_smtpd_tls_wrappermode && *var_smtpd_tls_level) { + switch (tls_level_lookup(var_smtpd_tls_level)) { + default: + msg_fatal("Invalid TLS level \"%s\"", var_smtpd_tls_level); + /* NOTREACHED */ + break; + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_FPRINT: + msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"", + VAR_SMTPD_TLS_LEVEL, var_smtpd_tls_level); + /* FALLTHROUGH */ + case TLS_LEV_ENCRYPT: + var_smtpd_enforce_tls = var_smtpd_use_tls = 1; + break; + case TLS_LEV_MAY: + var_smtpd_enforce_tls = 0; + var_smtpd_use_tls = 1; + break; + case TLS_LEV_NONE: + var_smtpd_enforce_tls = var_smtpd_use_tls = 0; + break; + } + } + + /* + * With TLS wrapper mode, we run on a dedicated port and turn on TLS + * before actually speaking the SMTP protocol. This implies TLS enforce + * mode. + * + * With non-wrapper mode, TLS enforce mode implies that we don't advertise + * AUTH before the client issues STARTTLS. + */ + var_smtpd_enforce_tls = var_smtpd_tls_wrappermode || var_smtpd_enforce_tls; + var_smtpd_tls_auth_only = var_smtpd_tls_auth_only || var_smtpd_enforce_tls; + var_smtpd_use_tls = var_smtpd_use_tls || var_smtpd_enforce_tls; + + /* + * Keys can only be loaded when running with suitable permissions. When + * called from "sendmail -bs" this is not the case, so we must not + * announce STARTTLS support. + */ + if (getuid() == 0 || getuid() == var_owner_uid) { + if (var_smtpd_use_tls) { +#ifdef USE_TLS +#ifndef USE_TLSPROXY + TLS_SERVER_INIT_PROPS props; + const char *cert_file; + int have_server_cert; + int no_server_cert_ok; + int require_server_cert; + + /* + * Can't use anonymous ciphers if we want client certificates. + * Must use anonymous ciphers if we have no certificates. + * + * XXX: Ugh! Too many booleans! + */ + ask_client_cert = require_server_cert = + (var_smtpd_tls_ask_ccert + || (var_smtpd_enforce_tls && var_smtpd_tls_req_ccert)); + if (strcasecmp(var_smtpd_tls_cert_file, "none") == 0) { + no_server_cert_ok = 1; + cert_file = ""; + } else { + no_server_cert_ok = 0; + cert_file = var_smtpd_tls_cert_file; + } + + have_server_cert = *cert_file != 0; + have_server_cert |= *var_smtpd_tls_eccert_file != 0; + have_server_cert |= *var_smtpd_tls_dcert_file != 0; + + if (*var_smtpd_tls_chain_files != 0) { + if (!have_server_cert) + have_server_cert = 1; + else + msg_warn("Both %s and one or more of the legacy " + " %s, %s or %s are non-empty; the legacy " + " parameters will be ignored", + VAR_SMTPD_TLS_CHAIN_FILES, + VAR_SMTPD_TLS_CERT_FILE, + VAR_SMTPD_TLS_ECCERT_FILE, + VAR_SMTPD_TLS_DCERT_FILE); + } + /* Some TLS configuration errors are not show stoppers. */ + if (!have_server_cert && require_server_cert) + msg_warn("Need a server cert to request client certs"); + if (!var_smtpd_enforce_tls && var_smtpd_tls_req_ccert) + msg_warn("Can't require client certs unless TLS is required"); + /* After a show-stopper error, reply with 454 to STARTTLS. */ + if (have_server_cert + || (no_server_cert_ok && !require_server_cert)) { + + tls_pre_jail_init(TLS_ROLE_SERVER); + + /* + * Large parameter lists are error-prone, so we emulate a + * language feature that C does not have natively: named + * parameter lists. + */ + smtpd_tls_ctx = + TLS_SERVER_INIT(&props, + log_param = VAR_SMTPD_TLS_LOGLEVEL, + log_level = var_smtpd_tls_loglevel, + verifydepth = var_smtpd_tls_ccert_vd, + cache_type = TLS_MGR_SCACHE_SMTPD, + set_sessid = var_smtpd_tls_set_sessid, + chain_files = var_smtpd_tls_chain_files, + cert_file = cert_file, + key_file = var_smtpd_tls_key_file, + dcert_file = var_smtpd_tls_dcert_file, + dkey_file = var_smtpd_tls_dkey_file, + eccert_file = var_smtpd_tls_eccert_file, + eckey_file = var_smtpd_tls_eckey_file, + CAfile = var_smtpd_tls_CAfile, + CApath = var_smtpd_tls_CApath, + dh1024_param_file + = var_smtpd_tls_dh1024_param_file, + dh512_param_file + = var_smtpd_tls_dh512_param_file, + eecdh_grade = var_smtpd_tls_eecdh, + protocols = var_smtpd_enforce_tls ? + var_smtpd_tls_mand_proto : + var_smtpd_tls_proto, + ask_ccert = ask_client_cert, + mdalg = var_smtpd_tls_fpt_dgst); + } else { + msg_warn("No server certs available. TLS won't be enabled"); + } +#endif /* USE_TLSPROXY */ +#else + msg_warn("TLS has been selected, but TLS support is not compiled in"); +#endif + } + } + + /* + * flush client. + */ + flush_init(); + + /* + * EHLO keyword filter. + */ + if (*var_smtpd_ehlo_dis_maps) + ehlo_discard_maps = maps_create(VAR_SMTPD_EHLO_DIS_MAPS, + var_smtpd_ehlo_dis_maps, + DICT_FLAG_LOCK); + + /* + * Per-client Milter support. + */ + if (*var_smtpd_milter_maps) + smtpd_milter_maps = maps_create(VAR_SMTPD_MILTER_MAPS, + var_smtpd_milter_maps, + DICT_FLAG_LOCK); + + /* + * DNS reply filter. + */ + if (*var_smtpd_dns_re_filter) + dns_rr_filter_compile(VAR_SMTPD_DNS_RE_FILTER, + var_smtpd_dns_re_filter); + + /* + * Reject footer. + */ + if (*var_smtpd_rej_ftr_maps) + smtpd_chat_pre_jail_init(); +} + +/* post_jail_init - post-jail initialization */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Initialize the receive transparency options: do we want unknown + * recipient checks, address mapping, header_body_checks?. + */ + smtpd_input_transp_mask = + input_transp_mask(VAR_INPUT_TRANSP, var_input_transp); + + /* + * Initialize before-queue filter options: do we want speed-matching + * support so that the entire message is received before we contact a + * before-queue content filter? + */ + if (*var_smtpd_proxy_filt) + smtpd_proxy_opts = + smtpd_proxy_parse_opts(VAR_SMTPD_PROXY_OPTS, var_smtpd_proxy_opts); + + /* + * Sanity checks. The queue_minfree value should be at least as large as + * (process_limit * message_size_limit) but that is unpractical, so we + * arbitrarily pick a small multiple of the per-message size limit. This + * helps to avoid many unneeded (re)transmissions. + */ + if (ENFORCING_SIZE_LIMIT(var_queue_minfree) + && ENFORCING_SIZE_LIMIT(var_message_limit) + && var_queue_minfree / 1.5 < var_message_limit) + msg_warn("%s(%lu) should be at least 1.5*%s(%lu)", + VAR_QUEUE_MINFREE, (unsigned long) var_queue_minfree, + VAR_MESSAGE_LIMIT, (unsigned long) var_message_limit); + + /* + * Connection rate management. + */ + if (var_smtpd_crate_limit || var_smtpd_cconn_limit + || var_smtpd_cmail_limit || var_smtpd_crcpt_limit + || var_smtpd_cntls_limit || var_smtpd_cauth_limit) + anvil_clnt = anvil_clnt_create(); + + /* + * header_from_format support, for postmaster notifications. + */ + smtpd_hfrom_format = hfrom_format_parse(VAR_HFROM_FORMAT, var_hfrom_format); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static const CONFIG_NINT_TABLE nint_table[] = { + VAR_SMTPD_SOFT_ERLIM, DEF_SMTPD_SOFT_ERLIM, &var_smtpd_soft_erlim, 1, 0, + VAR_SMTPD_HARD_ERLIM, DEF_SMTPD_HARD_ERLIM, &var_smtpd_hard_erlim, 1, 0, + VAR_SMTPD_JUNK_CMD, DEF_SMTPD_JUNK_CMD, &var_smtpd_junk_cmd_limit, 1, 0, + VAR_VERIFY_POLL_COUNT, DEF_VERIFY_POLL_COUNT, &var_verify_poll_count, 1, 0, + 0, + }; + static const CONFIG_INT_TABLE int_table[] = { + VAR_SMTPD_RCPT_LIMIT, DEF_SMTPD_RCPT_LIMIT, &var_smtpd_rcpt_limit, 1, 0, + VAR_UNK_CLIENT_CODE, DEF_UNK_CLIENT_CODE, &var_unk_client_code, 0, 0, + VAR_BAD_NAME_CODE, DEF_BAD_NAME_CODE, &var_bad_name_code, 0, 0, + VAR_UNK_NAME_CODE, DEF_UNK_NAME_CODE, &var_unk_name_code, 0, 0, + VAR_UNK_ADDR_CODE, DEF_UNK_ADDR_CODE, &var_unk_addr_code, 0, 0, + VAR_RELAY_CODE, DEF_RELAY_CODE, &var_relay_code, 0, 0, + VAR_MAPS_RBL_CODE, DEF_MAPS_RBL_CODE, &var_maps_rbl_code, 0, 0, + VAR_MAP_REJECT_CODE, DEF_MAP_REJECT_CODE, &var_map_reject_code, 0, 0, + VAR_MAP_DEFER_CODE, DEF_MAP_DEFER_CODE, &var_map_defer_code, 0, 0, + VAR_REJECT_CODE, DEF_REJECT_CODE, &var_reject_code, 0, 0, + VAR_DEFER_CODE, DEF_DEFER_CODE, &var_defer_code, 0, 0, + VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code, 0, 0, + VAR_SMTPD_RCPT_OVERLIM, DEF_SMTPD_RCPT_OVERLIM, &var_smtpd_rcpt_overlim, 1, 0, + VAR_SMTPD_HIST_THRSH, DEF_SMTPD_HIST_THRSH, &var_smtpd_hist_thrsh, 1, 0, + VAR_UNV_FROM_RCODE, DEF_UNV_FROM_RCODE, &var_unv_from_rcode, 200, 599, + VAR_UNV_RCPT_RCODE, DEF_UNV_RCPT_RCODE, &var_unv_rcpt_rcode, 200, 599, + VAR_UNV_FROM_DCODE, DEF_UNV_FROM_DCODE, &var_unv_from_dcode, 200, 499, + VAR_UNV_RCPT_DCODE, DEF_UNV_RCPT_DCODE, &var_unv_rcpt_dcode, 200, 499, + VAR_MUL_RCPT_CODE, DEF_MUL_RCPT_CODE, &var_mul_rcpt_code, 0, 0, + VAR_LOCAL_RCPT_CODE, DEF_LOCAL_RCPT_CODE, &var_local_rcpt_code, 0, 0, + VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code, 0, 0, + VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code, 0, 0, + VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code, 0, 0, + VAR_PLAINTEXT_CODE, DEF_PLAINTEXT_CODE, &var_plaintext_code, 0, 0, + VAR_SMTPD_FORBID_BARE_LF_CODE, DEF_SMTPD_FORBID_BARE_LF_CODE, &var_smtpd_forbid_bare_lf_code, 500, 599, + VAR_SMTPD_CRATE_LIMIT, DEF_SMTPD_CRATE_LIMIT, &var_smtpd_crate_limit, 0, 0, + VAR_SMTPD_CCONN_LIMIT, DEF_SMTPD_CCONN_LIMIT, &var_smtpd_cconn_limit, 0, 0, + VAR_SMTPD_CMAIL_LIMIT, DEF_SMTPD_CMAIL_LIMIT, &var_smtpd_cmail_limit, 0, 0, + VAR_SMTPD_CRCPT_LIMIT, DEF_SMTPD_CRCPT_LIMIT, &var_smtpd_crcpt_limit, 0, 0, + VAR_SMTPD_CNTLS_LIMIT, DEF_SMTPD_CNTLS_LIMIT, &var_smtpd_cntls_limit, 0, 0, + VAR_SMTPD_CAUTH_LIMIT, DEF_SMTPD_CAUTH_LIMIT, &var_smtpd_cauth_limit, 0, 0, +#ifdef USE_TLS + VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, +#endif + VAR_SMTPD_SASL_RESP_LIMIT, DEF_SMTPD_SASL_RESP_LIMIT, &var_smtpd_sasl_resp_limit, DEF_SMTPD_SASL_RESP_LIMIT, 0, + VAR_SMTPD_POLICY_REQ_LIMIT, DEF_SMTPD_POLICY_REQ_LIMIT, &var_smtpd_policy_req_limit, 0, 0, + VAR_SMTPD_POLICY_TRY_LIMIT, DEF_SMTPD_POLICY_TRY_LIMIT, &var_smtpd_policy_try_limit, 1, 0, + VAR_SMTPD_MIN_DATA_RATE, DEF_SMTPD_MIN_DATA_RATE, &var_smtpd_min_data_rate, 1, 0, + 0, + }; + static const CONFIG_LONG_TABLE long_table[] = { + VAR_QUEUE_MINFREE, DEF_QUEUE_MINFREE, &var_queue_minfree, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_SMTPD_TMOUT, DEF_SMTPD_TMOUT, &var_smtpd_tmout, 1, 0, + VAR_SMTPD_ERR_SLEEP, DEF_SMTPD_ERR_SLEEP, &var_smtpd_err_sleep, 0, 0, + VAR_SMTPD_PROXY_TMOUT, DEF_SMTPD_PROXY_TMOUT, &var_smtpd_proxy_tmout, 1, 0, + VAR_VERIFY_POLL_DELAY, DEF_VERIFY_POLL_DELAY, &var_verify_poll_delay, 1, 0, + VAR_SMTPD_POLICY_TMOUT, DEF_SMTPD_POLICY_TMOUT, &var_smtpd_policy_tmout, 1, 0, + VAR_SMTPD_POLICY_IDLE, DEF_SMTPD_POLICY_IDLE, &var_smtpd_policy_idle, 1, 0, + VAR_SMTPD_POLICY_TTL, DEF_SMTPD_POLICY_TTL, &var_smtpd_policy_ttl, 1, 0, +#ifdef USE_TLS + VAR_SMTPD_STARTTLS_TMOUT, DEF_SMTPD_STARTTLS_TMOUT, &var_smtpd_starttls_tmout, 1, 0, +#endif + VAR_MILT_CONN_TIME, DEF_MILT_CONN_TIME, &var_milt_conn_time, 1, 0, + VAR_MILT_CMD_TIME, DEF_MILT_CMD_TIME, &var_milt_cmd_time, 1, 0, + VAR_MILT_MSG_TIME, DEF_MILT_MSG_TIME, &var_milt_msg_time, 1, 0, + VAR_VERIFY_SENDER_TTL, DEF_VERIFY_SENDER_TTL, &var_verify_sender_ttl, 0, 0, + VAR_SMTPD_UPROXY_TMOUT, DEF_SMTPD_UPROXY_TMOUT, &var_smtpd_uproxy_tmout, 1, 0, + VAR_SMTPD_POLICY_TRY_DELAY, DEF_SMTPD_POLICY_TRY_DELAY, &var_smtpd_policy_try_delay, 1, 0, + 0, + }; + static const CONFIG_BOOL_TABLE bool_table[] = { + VAR_HELO_REQUIRED, DEF_HELO_REQUIRED, &var_helo_required, + VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject, + VAR_STRICT_RFC821_ENV, DEF_STRICT_RFC821_ENV, &var_strict_rfc821_env, + VAR_DISABLE_VRFY_CMD, DEF_DISABLE_VRFY_CMD, &var_disable_vrfy_cmd, + VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route, + VAR_SMTPD_SASL_ENABLE, DEF_SMTPD_SASL_ENABLE, &var_smtpd_sasl_enable, + VAR_SMTPD_SASL_AUTH_HDR, DEF_SMTPD_SASL_AUTH_HDR, &var_smtpd_sasl_auth_hdr, + VAR_BROKEN_AUTH_CLNTS, DEF_BROKEN_AUTH_CLNTS, &var_broken_auth_clients, + VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table, + VAR_SMTPD_REJ_UNL_FROM, DEF_SMTPD_REJ_UNL_FROM, &var_smtpd_rej_unl_from, + VAR_SMTPD_REJ_UNL_RCPT, DEF_SMTPD_REJ_UNL_RCPT, &var_smtpd_rej_unl_rcpt, + VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls, + VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls, + VAR_SMTPD_TLS_WRAPPER, DEF_SMTPD_TLS_WRAPPER, &var_smtpd_tls_wrappermode, + VAR_SMTPD_TLS_AUTH_ONLY, DEF_SMTPD_TLS_AUTH_ONLY, &var_smtpd_tls_auth_only, +#ifdef USE_TLS + VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert, + VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert, + VAR_SMTPD_TLS_RECHEAD, DEF_SMTPD_TLS_RECHEAD, &var_smtpd_tls_received_header, + VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid, +#endif + VAR_SMTPD_PEERNAME_LOOKUP, DEF_SMTPD_PEERNAME_LOOKUP, &var_smtpd_peername_lookup, + VAR_SMTPD_DELAY_OPEN, DEF_SMTPD_DELAY_OPEN, &var_smtpd_delay_open, + VAR_SMTPD_CLIENT_PORT_LOG, DEF_SMTPD_CLIENT_PORT_LOG, &var_smtpd_client_port_log, + VAR_SMTPD_FORBID_UNAUTH_PIPE, DEF_SMTPD_FORBID_UNAUTH_PIPE, &var_smtpd_forbid_unauth_pipe, + 0, + }; + static const CONFIG_NBOOL_TABLE nbool_table[] = { + VAR_RELAY_BEFORE_RCPT_CHECKS, DEF_RELAY_BEFORE_RCPT_CHECKS, &var_relay_before_rcpt_checks, + VAR_SMTPD_REQ_DEADLINE, DEF_SMTPD_REQ_DEADLINE, &var_smtpd_req_deadline, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_SMTPD_BANNER, DEF_SMTPD_BANNER, &var_smtpd_banner, 1, 0, + VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, + VAR_CLIENT_CHECKS, DEF_CLIENT_CHECKS, &var_client_checks, 0, 0, + VAR_HELO_CHECKS, DEF_HELO_CHECKS, &var_helo_checks, 0, 0, + VAR_MAIL_CHECKS, DEF_MAIL_CHECKS, &var_mail_checks, 0, 0, + VAR_RELAY_CHECKS, DEF_RELAY_CHECKS, &var_relay_checks, 0, 0, + VAR_RCPT_CHECKS, DEF_RCPT_CHECKS, &var_rcpt_checks, 0, 0, + VAR_ETRN_CHECKS, DEF_ETRN_CHECKS, &var_etrn_checks, 0, 0, + VAR_DATA_CHECKS, DEF_DATA_CHECKS, &var_data_checks, 0, 0, + VAR_EOD_CHECKS, DEF_EOD_CHECKS, &var_eod_checks, 0, 0, + VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains, 0, 0, + VAR_RBL_REPLY_MAPS, DEF_RBL_REPLY_MAPS, &var_rbl_reply_maps, 0, 0, + VAR_BOUNCE_RCPT, DEF_BOUNCE_RCPT, &var_bounce_rcpt, 1, 0, + VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, + VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes, 0, 0, + VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0, + VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0, + VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0, + VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0, + VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0, + VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0, + VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0, + VAR_SMTPD_SASL_OPTS, DEF_SMTPD_SASL_OPTS, &var_smtpd_sasl_opts, 0, 0, + VAR_SMTPD_SASL_PATH, DEF_SMTPD_SASL_PATH, &var_smtpd_sasl_path, 1, 0, + VAR_SMTPD_SASL_SERVICE, DEF_SMTPD_SASL_SERVICE, &var_smtpd_sasl_service, 1, 0, + VAR_CYRUS_CONF_PATH, DEF_CYRUS_CONF_PATH, &var_cyrus_conf_path, 0, 0, + VAR_SMTPD_SASL_REALM, DEF_SMTPD_SASL_REALM, &var_smtpd_sasl_realm, 0, 0, + VAR_SMTPD_SASL_EXCEPTIONS_NETWORKS, DEF_SMTPD_SASL_EXCEPTIONS_NETWORKS, &var_smtpd_sasl_exceptions_networks, 0, 0, + VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0, + VAR_PERM_MX_NETWORKS, DEF_PERM_MX_NETWORKS, &var_perm_mx_networks, 0, 0, + VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0, + VAR_SMTPD_NOOP_CMDS, DEF_SMTPD_NOOP_CMDS, &var_smtpd_noop_cmds, 0, 0, + VAR_SMTPD_FORBID_CMDS, DEF_SMTPD_FORBID_CMDS, &var_smtpd_forbid_cmds, 0, 0, + VAR_SMTPD_NULL_KEY, DEF_SMTPD_NULL_KEY, &var_smtpd_null_key, 0, 0, + VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0, + VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender, 0, 0, + VAR_VERP_CLIENTS, DEF_VERP_CLIENTS, &var_verp_clients, 0, 0, + VAR_SMTPD_PROXY_FILT, DEF_SMTPD_PROXY_FILT, &var_smtpd_proxy_filt, 0, 0, + VAR_SMTPD_PROXY_EHLO, DEF_SMTPD_PROXY_EHLO, &var_smtpd_proxy_ehlo, 0, 0, + VAR_SMTPD_PROXY_OPTS, DEF_SMTPD_PROXY_OPTS, &var_smtpd_proxy_opts, 0, 0, + VAR_INPUT_TRANSP, DEF_INPUT_TRANSP, &var_input_transp, 0, 0, + VAR_XCLIENT_HOSTS, DEF_XCLIENT_HOSTS, &var_xclient_hosts, 0, 0, + VAR_XFORWARD_HOSTS, DEF_XFORWARD_HOSTS, &var_xforward_hosts, 0, 0, + VAR_SMTPD_HOGGERS, DEF_SMTPD_HOGGERS, &var_smtpd_hoggers, 0, 0, + VAR_LOC_RWR_CLIENTS, DEF_LOC_RWR_CLIENTS, &var_local_rwr_clients, 0, 0, + VAR_SMTPD_EHLO_DIS_WORDS, DEF_SMTPD_EHLO_DIS_WORDS, &var_smtpd_ehlo_dis_words, 0, 0, + VAR_SMTPD_EHLO_DIS_MAPS, DEF_SMTPD_EHLO_DIS_MAPS, &var_smtpd_ehlo_dis_maps, 0, 0, +#ifdef USE_TLS + VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_smtpd_relay_ccerts, 0, 0, + VAR_SMTPD_SASL_TLS_OPTS, DEF_SMTPD_SASL_TLS_OPTS, &var_smtpd_sasl_tls_opts, 0, 0, + VAR_SMTPD_TLS_CHAIN_FILES, DEF_SMTPD_TLS_CHAIN_FILES, &var_smtpd_tls_chain_files, 0, 0, + VAR_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CERT_FILE, &var_smtpd_tls_cert_file, 0, 0, + VAR_SMTPD_TLS_KEY_FILE, DEF_SMTPD_TLS_KEY_FILE, &var_smtpd_tls_key_file, 0, 0, + VAR_SMTPD_TLS_DCERT_FILE, DEF_SMTPD_TLS_DCERT_FILE, &var_smtpd_tls_dcert_file, 0, 0, + VAR_SMTPD_TLS_DKEY_FILE, DEF_SMTPD_TLS_DKEY_FILE, &var_smtpd_tls_dkey_file, 0, 0, + VAR_SMTPD_TLS_ECCERT_FILE, DEF_SMTPD_TLS_ECCERT_FILE, &var_smtpd_tls_eccert_file, 0, 0, + VAR_SMTPD_TLS_ECKEY_FILE, DEF_SMTPD_TLS_ECKEY_FILE, &var_smtpd_tls_eckey_file, 0, 0, + VAR_SMTPD_TLS_CA_FILE, DEF_SMTPD_TLS_CA_FILE, &var_smtpd_tls_CAfile, 0, 0, + VAR_SMTPD_TLS_CA_PATH, DEF_SMTPD_TLS_CA_PATH, &var_smtpd_tls_CApath, 0, 0, + VAR_SMTPD_TLS_CIPH, DEF_SMTPD_TLS_CIPH, &var_smtpd_tls_ciph, 1, 0, + VAR_SMTPD_TLS_MAND_CIPH, DEF_SMTPD_TLS_MAND_CIPH, &var_smtpd_tls_mand_ciph, 1, 0, + VAR_SMTPD_TLS_EXCL_CIPH, DEF_SMTPD_TLS_EXCL_CIPH, &var_smtpd_tls_excl_ciph, 0, 0, + VAR_SMTPD_TLS_MAND_EXCL, DEF_SMTPD_TLS_MAND_EXCL, &var_smtpd_tls_mand_excl, 0, 0, + VAR_SMTPD_TLS_PROTO, DEF_SMTPD_TLS_PROTO, &var_smtpd_tls_proto, 0, 0, + VAR_SMTPD_TLS_MAND_PROTO, DEF_SMTPD_TLS_MAND_PROTO, &var_smtpd_tls_mand_proto, 0, 0, + VAR_SMTPD_TLS_512_FILE, DEF_SMTPD_TLS_512_FILE, &var_smtpd_tls_dh512_param_file, 0, 0, + VAR_SMTPD_TLS_1024_FILE, DEF_SMTPD_TLS_1024_FILE, &var_smtpd_tls_dh1024_param_file, 0, 0, + VAR_SMTPD_TLS_EECDH, DEF_SMTPD_TLS_EECDH, &var_smtpd_tls_eecdh, 1, 0, + VAR_SMTPD_TLS_FPT_DGST, DEF_SMTPD_TLS_FPT_DGST, &var_smtpd_tls_fpt_dgst, 1, 0, + VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0, +#endif + VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0, + VAR_SMTPD_SASL_TYPE, DEF_SMTPD_SASL_TYPE, &var_smtpd_sasl_type, 1, 0, + VAR_SMTPD_SASL_MECH_FILTER, DEF_SMTPD_SASL_MECH_FILTER, &var_smtpd_sasl_mech_filter, 0, 0, + VAR_SMTPD_MILTERS, DEF_SMTPD_MILTERS, &var_smtpd_milters, 0, 0, + VAR_MILT_CONN_MACROS, DEF_MILT_CONN_MACROS, &var_milt_conn_macros, 0, 0, + VAR_MILT_HELO_MACROS, DEF_MILT_HELO_MACROS, &var_milt_helo_macros, 0, 0, + VAR_MILT_MAIL_MACROS, DEF_MILT_MAIL_MACROS, &var_milt_mail_macros, 0, 0, + VAR_MILT_RCPT_MACROS, DEF_MILT_RCPT_MACROS, &var_milt_rcpt_macros, 0, 0, + VAR_MILT_DATA_MACROS, DEF_MILT_DATA_MACROS, &var_milt_data_macros, 0, 0, + VAR_MILT_EOH_MACROS, DEF_MILT_EOH_MACROS, &var_milt_eoh_macros, 0, 0, + VAR_MILT_EOD_MACROS, DEF_MILT_EOD_MACROS, &var_milt_eod_macros, 0, 0, + VAR_MILT_UNK_MACROS, DEF_MILT_UNK_MACROS, &var_milt_unk_macros, 0, 0, + VAR_MILT_PROTOCOL, DEF_MILT_PROTOCOL, &var_milt_protocol, 1, 0, + VAR_MILT_DEF_ACTION, DEF_MILT_DEF_ACTION, &var_milt_def_action, 1, 0, + VAR_MILT_DAEMON_NAME, DEF_MILT_DAEMON_NAME, &var_milt_daemon_name, 1, 0, + VAR_MILT_V, DEF_MILT_V, &var_milt_v, 1, 0, + VAR_MILT_MACRO_DEFLTS, DEF_MILT_MACRO_DEFLTS, &var_milt_macro_deflts, 0, 0, + VAR_SMTPD_MILTER_MAPS, DEF_SMTPD_MILTER_MAPS, &var_smtpd_milter_maps, 0, 0, + VAR_STRESS, DEF_STRESS, &var_stress, 0, 0, + VAR_UNV_FROM_WHY, DEF_UNV_FROM_WHY, &var_unv_from_why, 0, 0, + VAR_UNV_RCPT_WHY, DEF_UNV_RCPT_WHY, &var_unv_rcpt_why, 0, 0, + VAR_REJECT_TMPF_ACT, DEF_REJECT_TMPF_ACT, &var_reject_tmpf_act, 1, 0, + VAR_UNK_NAME_TF_ACT, DEF_UNK_NAME_TF_ACT, &var_unk_name_tf_act, 1, 0, + VAR_UNK_ADDR_TF_ACT, DEF_UNK_ADDR_TF_ACT, &var_unk_addr_tf_act, 1, 0, + VAR_UNV_RCPT_TF_ACT, DEF_UNV_RCPT_TF_ACT, &var_unv_rcpt_tf_act, 1, 0, + VAR_UNV_FROM_TF_ACT, DEF_UNV_FROM_TF_ACT, &var_unv_from_tf_act, 1, 0, + VAR_SMTPD_CMD_FILTER, DEF_SMTPD_CMD_FILTER, &var_smtpd_cmd_filter, 0, 0, +#ifdef USE_TLSPROXY + VAR_TLSPROXY_SERVICE, DEF_TLSPROXY_SERVICE, &var_tlsproxy_service, 1, 0, +#endif + VAR_SMTPD_ACL_PERM_LOG, DEF_SMTPD_ACL_PERM_LOG, &var_smtpd_acl_perm_log, 0, 0, + VAR_SMTPD_UPROXY_PROTO, DEF_SMTPD_UPROXY_PROTO, &var_smtpd_uproxy_proto, 0, 0, + VAR_SMTPD_POLICY_DEF_ACTION, DEF_SMTPD_POLICY_DEF_ACTION, &var_smtpd_policy_def_action, 1, 0, + VAR_SMTPD_POLICY_CONTEXT, DEF_SMTPD_POLICY_CONTEXT, &var_smtpd_policy_context, 0, 0, + VAR_SMTPD_DNS_RE_FILTER, DEF_SMTPD_DNS_RE_FILTER, &var_smtpd_dns_re_filter, 0, 0, + VAR_SMTPD_REJ_FTR_MAPS, DEF_SMTPD_REJ_FTR_MAPS, &var_smtpd_rej_ftr_maps, 0, 0, + VAR_HFROM_FORMAT, DEF_HFROM_FORMAT, &var_hfrom_format, 1, 0, + VAR_SMTPD_FORBID_BARE_LF_EXCL, DEF_SMTPD_FORBID_BARE_LF_EXCL, &var_smtpd_forbid_bare_lf_excl, 0, 0, + VAR_SMTPD_FORBID_BARE_LF, DEF_SMTPD_FORBID_BARE_LF, &var_smtpd_forbid_bare_lf, 1, 0, + 0, + }; + static const CONFIG_RAW_TABLE raw_table[] = { + VAR_SMTPD_EXP_FILTER, DEF_SMTPD_EXP_FILTER, &var_smtpd_exp_filter, 1, 0, + VAR_DEF_RBL_REPLY, DEF_DEF_RBL_REPLY, &var_def_rbl_reply, 1, 0, + VAR_SMTPD_REJ_FOOTER, DEF_SMTPD_REJ_FOOTER, &var_smtpd_rej_footer, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Pass control to the single-threaded service skeleton. + */ + single_server_main(argc, argv, smtpd_service, + CA_MAIL_SERVER_NINT_TABLE(nint_table), + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_LONG_TABLE(long_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_RAW_TABLE(raw_table), + CA_MAIL_SERVER_BOOL_TABLE(bool_table), + CA_MAIL_SERVER_NBOOL_TABLE(nbool_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + 0); +} diff --git a/src/smtpd/smtpd.h b/src/smtpd/smtpd.h new file mode 100644 index 0000000..0bb5bbb --- /dev/null +++ b/src/smtpd/smtpd.h @@ -0,0 +1,446 @@ +/*++ +/* NAME +/* smtpd 3h +/* SUMMARY +/* smtp server +/* SYNOPSIS +/* include "smtpd.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * Global library. + */ +#include + + /* + * Postfix TLS library. + */ +#include + + /* + * Milter library. + */ +#include + + /* + * Variables that keep track of conversation state. There is only one SMTP + * conversation at a time, so the state variables can be made global. And + * some of this has to be global anyway, so that the run-time error handler + * can clean up in case of a fatal error deep down in some library routine. + */ +typedef struct SMTPD_DEFER { + int active; /* is this active */ + VSTRING *reason; /* reason for deferral */ + VSTRING *dsn; /* DSN detail */ + int code; /* SMTP reply code */ + int class; /* error notification class */ +} SMTPD_DEFER; + +typedef struct { + int flags; /* XFORWARD server state */ + char *name; /* name for access control */ + char *addr; /* address for access control */ + char *port; /* port for logging */ + char *namaddr; /* name[address]:port */ + char *rfc_addr; /* address for RFC 2821 */ + char *protocol; /* email protocol */ + char *helo_name; /* helo/ehlo parameter */ + char *ident; /* local message identifier */ + char *domain; /* rewrite context */ +} SMTPD_XFORWARD_ATTR; + +typedef struct { + int flags; /* see below */ + int err; /* cleanup server/queue file errors */ + VSTREAM *client; /* SMTP client handle */ + VSTRING *buffer; /* SMTP client buffer */ + VSTRING *addr_buf; /* internalized address buffer */ + char *service; /* for event rate control */ + struct timeval arrival_time; /* start of MAIL FROM transaction */ + char *name; /* verified client hostname */ + char *reverse_name; /* unverified client hostname */ + char *addr; /* client host address string */ + char *port; /* port for logging */ + char *namaddr; /* name[address]:port */ + char *rfc_addr; /* address for RFC 2821 */ + int addr_family; /* address family */ + char *dest_addr; /* Dovecot AUTH, Milter {daemon_addr} */ + char *dest_port; /* Milter {daemon_port} */ + struct sockaddr_storage sockaddr; /* binary client endpoint */ + SOCKADDR_SIZE sockaddr_len; /* binary client endpoint */ + struct sockaddr_storage dest_sockaddr; /* binary local endpoint */ + SOCKADDR_SIZE dest_sockaddr_len; /* binary local endpoint */ + int name_status; /* 2=ok 4=soft 5=hard 6=forged */ + int reverse_name_status; /* 2=ok 4=soft 5=hard */ + int conn_count; /* connections from this client */ + int conn_rate; /* connection rate for this client */ + int error_count; /* reset after DOT */ + int error_mask; /* client errors */ + int notify_mask; /* what to report to postmaster */ + char *helo_name; /* client HELO/EHLO argument */ + char *queue_id; /* from cleanup server/queue file */ + VSTREAM *cleanup; /* cleanup server/queue file handle */ + MAIL_STREAM *dest; /* another server/file handle */ + int rcpt_count; /* number of accepted recipients */ + char *access_denied; /* fixme */ + ARGV *history; /* protocol transcript */ + char *reason; /* cause of connection loss */ + char *sender; /* sender address */ + char *encoding; /* owned by mail_cmd() */ + char *verp_delims; /* owned by mail_cmd() */ + char *recipient; /* recipient address */ + char *etrn_name; /* client ETRN argument */ + char *protocol; /* SMTP or ESMTP */ + char *where; /* protocol stage */ + int recursion; /* Kellerspeicherpegelanzeiger */ + off_t msg_size; /* MAIL FROM message size */ + off_t act_size; /* END-OF-DATA message size */ + int junk_cmds; /* counter */ + int rcpt_overshoot; /* counter */ + char *rewrite_context; /* address rewriting context */ + + /* + * SASL specific. + */ +#ifdef USE_SASL_AUTH + struct XSASL_SERVER *sasl_server; + VSTRING *sasl_reply; + char *sasl_mechanism_list; + char *sasl_method; + char *sasl_username; + char *sasl_sender; +#endif + + /* + * Specific to smtpd access checks. + */ + int sender_rcptmap_checked; /* sender validated against maps */ + int recipient_rcptmap_checked; /* recipient validated against maps */ + int warn_if_reject; /* force reject into warning */ + SMTPD_DEFER defer_if_reject; /* force reject into deferral */ + SMTPD_DEFER defer_if_permit; /* force permit into deferral */ + int defer_if_permit_client; /* force permit into warning */ + int defer_if_permit_helo; /* force permit into warning */ + int defer_if_permit_sender; /* force permit into warning */ + int discard; /* discard message */ + char *saved_filter; /* postponed filter action */ + char *saved_redirect; /* postponed redirect action */ + ARGV *saved_bcc; /* postponed bcc action */ + int saved_flags; /* postponed hold/discard */ +#ifdef DELAY_ACTION + int saved_delay; /* postponed deferred delay */ +#endif + VSTRING *expand_buf; /* scratch space for $name expansion */ + ARGV *prepend; /* prepended headers */ + VSTRING *instance; /* policy query correlation */ + int seqno; /* policy query correlation */ + int ehlo_discard_mask; /* suppressed EHLO features */ + char *dsn_envid; /* temporary MAIL FROM state */ + int dsn_ret; /* temporary MAIL FROM state */ + VSTRING *dsn_buf; /* scratch space for xtext expansion */ + VSTRING *dsn_orcpt_buf; /* scratch space for ORCPT parsing */ + + /* + * Pass-through proxy client. + */ + struct SMTPD_PROXY *proxy; + char *proxy_mail; /* owned by mail_cmd() */ + + /* + * XFORWARD server state. + */ + SMTPD_XFORWARD_ATTR xforward; /* up-stream logging info */ + + /* + * TLS related state. + */ +#ifdef USE_TLS +#ifdef USE_TLSPROXY + VSTREAM *tlsproxy; /* tlsproxy(8) temp. handle */ +#endif + TLS_SESS_STATE *tls_context; /* TLS session state */ +#endif + + /* + * Milter support. + */ + const char **milter_argv; /* SMTP command vector */ + ssize_t milter_argc; /* SMTP command vector */ + const char *milter_reject_text; /* input to call-back from Milter */ + MILTERS *milters; /* Milter initialization status. */ + + /* + * EHLO temporary space. + */ + VSTRING *ehlo_buf; + ARGV *ehlo_argv; + + /* + * BDAT processing state. + */ +#define SMTPD_BDAT_STAT_NONE 0 /* not processing BDAT */ +#define SMTPD_BDAT_STAT_OK 1 /* accepting BDAT chunks */ +#define SMTPD_BDAT_STAT_ERROR 2 /* skipping BDAT chunks */ + int bdat_state; /* see above */ + VSTREAM *bdat_get_stream; /* memory stream from BDAT chunk */ + VSTRING *bdat_get_buffer; /* read from memory stream */ + int bdat_prev_rec_type; +} SMTPD_STATE; + +#define SMTPD_FLAG_HANGUP (1<<0) /* 421/521 disconnect */ +#define SMTPD_FLAG_ILL_PIPELINING (1<<1) /* inappropriate pipelining */ +#define SMTPD_FLAG_AUTH_USED (1<<2) /* don't reuse SASL state */ +#define SMTPD_FLAG_SMTPUTF8 (1<<3) /* RFC 6531/2 transaction */ +#define SMTPD_FLAG_NEED_MILTER_ABORT (1<<4) /* undo milter_mail_event() */ + + /* Security: don't reset SMTPD_FLAG_AUTH_USED. */ +#define SMTPD_MASK_MAIL_KEEP \ + ~(SMTPD_FLAG_SMTPUTF8) /* Fix 20140706 */ + +#define SMTPD_STATE_XFORWARD_INIT (1<<0) /* xforward preset done */ +#define SMTPD_STATE_XFORWARD_NAME (1<<1) /* client name received */ +#define SMTPD_STATE_XFORWARD_ADDR (1<<2) /* client address received */ +#define SMTPD_STATE_XFORWARD_PROTO (1<<3) /* protocol received */ +#define SMTPD_STATE_XFORWARD_HELO (1<<4) /* client helo received */ +#define SMTPD_STATE_XFORWARD_IDENT (1<<5) /* message identifier */ +#define SMTPD_STATE_XFORWARD_DOMAIN (1<<6) /* address context */ +#define SMTPD_STATE_XFORWARD_PORT (1<<7) /* client port received */ + +#define SMTPD_STATE_XFORWARD_CLIENT_MASK \ + (SMTPD_STATE_XFORWARD_NAME | SMTPD_STATE_XFORWARD_ADDR \ + | SMTPD_STATE_XFORWARD_PROTO | SMTPD_STATE_XFORWARD_HELO \ + | SMTPD_STATE_XFORWARD_PORT) + +extern void smtpd_state_init(SMTPD_STATE *, VSTREAM *, const char *); +extern void smtpd_state_reset(SMTPD_STATE *); + + /* + * Conversation stages. This is used for "lost connection after XXX" + * diagnostics. + */ +#define SMTPD_AFTER_CONNECT "CONNECT" +#define SMTPD_AFTER_DATA "DATA content" +#define SMTPD_AFTER_BDAT "BDAT content" +#define SMTPD_AFTER_EOM "END-OF-MESSAGE" + + /* + * Other stages. These are sometimes used to change the way information is + * logged or what information will be available for access control. + */ +#define SMTPD_CMD_HELO "HELO" +#define SMTPD_CMD_EHLO "EHLO" +#define SMTPD_CMD_STARTTLS "STARTTLS" +#define SMTPD_CMD_AUTH "AUTH" +#define SMTPD_CMD_MAIL "MAIL" +#define SMTPD_CMD_RCPT "RCPT" +#define SMTPD_CMD_DATA "DATA" +#define SMTPD_CMD_BDAT "BDAT" +#define SMTPD_CMD_EOD SMTPD_AFTER_EOM /* XXX Was: END-OF-DATA */ +#define SMTPD_CMD_RSET "RSET" +#define SMTPD_CMD_NOOP "NOOP" +#define SMTPD_CMD_VRFY "VRFY" +#define SMTPD_CMD_ETRN "ETRN" +#define SMTPD_CMD_QUIT "QUIT" +#define SMTPD_CMD_XCLIENT "XCLIENT" +#define SMTPD_CMD_XFORWARD "XFORWARD" +#define SMTPD_CMD_UNKNOWN "UNKNOWN" + + /* + * Representation of unknown and non-existent client information. Throughout + * Postfix, we use the "unknown" string value for unknown client information + * (e.g., unknown remote client hostname), and we use the empty string, null + * pointer or "no queue file record" for non-existent client information + * (e.g., no HELO command, or local submission). + * + * Inside the SMTP server, unknown real client attributes are represented by + * the string "unknown", and non-existent HELO is represented as a null + * pointer. The SMTP server uses this same representation internally for + * forwarded client attributes; the XFORWARD syntax makes no distinction + * between unknown (remote submission) and non-existent (local submission). + * + * The SMTP client sends forwarded client attributes only when upstream client + * attributes exist (i.e. remote submission). Thus, local submissions will + * appear to come from an SMTP-based content filter, which is acceptable. + * + * Known/unknown client attribute values use the SMTP server's internal + * representation in queue files, in queue manager delivery requests, and in + * delivery agent $name expansions. + * + * Non-existent attribute values are never present in queue files. Non-existent + * information is represented as empty strings in queue manager delivery + * requests and in delivery agent $name expansions. + */ +#define CLIENT_ATTR_UNKNOWN "unknown" + +#define CLIENT_NAME_UNKNOWN CLIENT_ATTR_UNKNOWN +#define CLIENT_ADDR_UNKNOWN CLIENT_ATTR_UNKNOWN +#define CLIENT_PORT_UNKNOWN CLIENT_ATTR_UNKNOWN +#define CLIENT_NAMADDR_UNKNOWN CLIENT_ATTR_UNKNOWN +#define CLIENT_HELO_UNKNOWN 0 +#define CLIENT_PROTO_UNKNOWN CLIENT_ATTR_UNKNOWN +#define CLIENT_IDENT_UNKNOWN 0 +#define CLIENT_DOMAIN_UNKNOWN 0 +#define CLIENT_LOGIN_UNKNOWN 0 + +#define SERVER_ATTR_UNKNOWN "unknown" + +#define SERVER_ADDR_UNKNOWN SERVER_ATTR_UNKNOWN +#define SERVER_PORT_UNKNOWN SERVER_ATTR_UNKNOWN + +#define IS_AVAIL_CLIENT_ATTR(v) ((v) && strcmp((v), CLIENT_ATTR_UNKNOWN)) + +#define IS_AVAIL_CLIENT_NAME(v) IS_AVAIL_CLIENT_ATTR(v) +#define IS_AVAIL_CLIENT_ADDR(v) IS_AVAIL_CLIENT_ATTR(v) +#define IS_AVAIL_CLIENT_PORT(v) IS_AVAIL_CLIENT_ATTR(v) +#define IS_AVAIL_CLIENT_NAMADDR(v) IS_AVAIL_CLIENT_ATTR(v) +#define IS_AVAIL_CLIENT_HELO(v) ((v) != 0) +#define IS_AVAIL_CLIENT_PROTO(v) IS_AVAIL_CLIENT_ATTR(v) +#define IS_AVAIL_CLIENT_IDENT(v) ((v) != 0) +#define IS_AVAIL_CLIENT_DOMAIN(v) ((v) != 0) + + /* + * If running in stand-alone mode, do not try to talk to Postfix daemons but + * write to queue file instead. + */ +#define SMTPD_STAND_ALONE_STREAM(stream) \ + (stream == VSTREAM_IN && getuid() != var_owner_uid) + +#define SMTPD_STAND_ALONE(state) \ + (state->client == VSTREAM_IN && getuid() != var_owner_uid) + + /* + * If running as proxy front-end, disable actions that require communication + * with the cleanup server. + */ +#define USE_SMTPD_PROXY(state) \ + (SMTPD_STAND_ALONE(state) == 0 && *var_smtpd_proxy_filt) + + /* + * Are we in a MAIL transaction? + */ +#define SMTPD_IN_MAIL_TRANSACTION(state) ((state)->sender != 0) + + /* + * Are we processing BDAT requests? + */ +#define SMTPD_PROCESSING_BDAT(state) \ + ((state)->bdat_state != SMTPD_BDAT_STAT_NONE) + + /* + * SMTPD peer information lookup. + */ +extern void smtpd_peer_init(SMTPD_STATE *state); +extern void smtpd_peer_reset(SMTPD_STATE *state); +extern void smtpd_peer_from_default(SMTPD_STATE *); +extern int smtpd_peer_from_haproxy(SMTPD_STATE *); + +#define SMTPD_PEER_CODE_OK 2 +#define SMTPD_PEER_CODE_TEMP 4 +#define SMTPD_PEER_CODE_PERM 5 +#define SMTPD_PEER_CODE_FORGED 6 + + /* + * Construct name[addr] or name[addr]:port as appropriate + */ +#define SMTPD_BUILD_NAMADDRPORT(name, addr, port) \ + concatenate((name), "[", (addr), "]", \ + var_smtpd_client_port_log ? ":" : (char *) 0, \ + (port), (char *) 0) + + /* + * Don't mix information from the current SMTP session with forwarded + * information from an up-stream session. + */ +#define HAVE_FORWARDED_CLIENT_ATTR(s) \ + ((s)->xforward.flags & SMTPD_STATE_XFORWARD_CLIENT_MASK) + +#define FORWARD_CLIENT_ATTR(s, a) \ + (HAVE_FORWARDED_CLIENT_ATTR(s) ? \ + (s)->xforward.a : (s)->a) + +#define FORWARD_ADDR(s) FORWARD_CLIENT_ATTR((s), rfc_addr) +#define FORWARD_NAME(s) FORWARD_CLIENT_ATTR((s), name) +#define FORWARD_NAMADDR(s) FORWARD_CLIENT_ATTR((s), namaddr) +#define FORWARD_PROTO(s) FORWARD_CLIENT_ATTR((s), protocol) +#define FORWARD_HELO(s) FORWARD_CLIENT_ATTR((s), helo_name) +#define FORWARD_PORT(s) FORWARD_CLIENT_ATTR((s), port) + + /* + * Mixing is not a problem with forwarded local message identifiers. + */ +#define HAVE_FORWARDED_IDENT(s) \ + ((s)->xforward.ident != 0) + +#define FORWARD_IDENT(s) \ + (HAVE_FORWARDED_IDENT(s) ? \ + (s)->xforward.ident : (s)->queue_id) + + /* + * Mixing is not a problem with forwarded address rewriting contexts. + */ +#define FORWARD_DOMAIN(s) \ + (((s)->xforward.flags & SMTPD_STATE_XFORWARD_DOMAIN) ? \ + (s)->xforward.domain : (s)->rewrite_context) + +extern void smtpd_xforward_init(SMTPD_STATE *); +extern void smtpd_xforward_preset(SMTPD_STATE *); +extern void smtpd_xforward_reset(SMTPD_STATE *); + + /* + * Transparency: before mail is queued, do we check for unknown recipients, + * do we allow address mapping, automatic bcc, header/body checks? + */ +extern int smtpd_input_transp_mask; + + /* + * More Milter support. + */ +extern MILTERS *smtpd_milters; + + /* + * Message size multiplication factor for free space check. + */ +extern double smtpd_space_multf; + + /* + * header_from_format support. + */ +extern int smtpd_hfrom_format; + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ diff --git a/src/smtpd/smtpd_acl.in b/src/smtpd/smtpd_acl.in new file mode 100644 index 0000000..daed26c --- /dev/null +++ b/src/smtpd/smtpd_acl.in @@ -0,0 +1,120 @@ +# +# Initialize +# +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.3.0/28 +relay_domains porcupine.org +smtpd_null_access_lookup_key <> +# +# Test check_domain_access() +# +helo_restrictions hash:./smtpd_check_access +# Expect: REJECT +helo foo.dunno.com +# Expect: OK +helo bar.dunno.com +# Expect: OK +helo foo.duuno.com +# +# Test check_namadr_access(), domain part +# +client_restrictions hash:./smtpd_check_access +# Expect: REJECT +client foo.dunno.com 131.155.210.17 +# Expect: OK +client bar.dunno.com 131.155.210.17 +# Expect: OK +client bar.dunno.com 131.155.210.19 +# +# Test check_namadr_access(), address part +# +# Expect: OK +client bar.duno.com 131.155.210.17 +# Expect: REJECT +client bar.duno.com 131.155.210.19 +# Expect: REJECT +client bar.duno.com 44.33.22.11 +# Expect: OK +client bar.duno.com 44.33.22.55 +# Expect: REJECT +client bar.duno.com 44.33.44.33 +# +# Test check_mail_access() +# +sender_restrictions hash:./smtpd_check_access +# Expect: REJECT +mail reject@dunno.domain +# Expect: OK +mail ok@dunno.domain +# Expect: OK +mail anyone@dunno.domain +# Expect: OK +mail bad-sender@dunno.domain +# +# Again, with a domain that rejects by default +# +# Expect: REJECT +mail reject@reject.domain +# Expect: OK +mail ok@reject.domain +# Expect: REJECT +mail anyone@reject.domain +# Expect: REJECT +mail good-sender@reject.domain +# +# Again, with a domain that accepts by default +# +# Expect: REJECT +mail reject@ok.domain +# Expect: OK +mail ok@ok.domain +# Expect: OK +mail anyone@ok.domain +# Expect: OK +mail bad-sender@ok.domain +# +# Test check_mail_access() +# +recipient_restrictions hash:./smtpd_check_access +# Expect: REJECT +rcpt reject@dunno.domain +# Expect: REJECT +recipient_delimiter + +rcpt reject+ext@dunno.domain +recipient_delimiter | +# Expect: OK +rcpt ok@dunno.domain +# Expect: OK +recipient_delimiter + +rcpt ok+ext@dunno.domain +recipient_delimiter | +# Expect: OK +rcpt anyone@dunno.domain +# Expect: OK +rcpt bad-sender@dunno.domain +# +# Again, with a domain that rejects by default +# +# Expect: REJECT +rcpt reject@reject.domain +# Expect: OK +rcpt ok@reject.domain +# Expect: REJECT +rcpt anyone@reject.domain +# Expect: REJECT +rcpt good-sender@reject.domain +# +# Again, with a domain that accepts by default +# +# Expect: REJECT +rcpt reject@ok.domain +# Expect: OK +rcpt ok@ok.domain +# Expect: OK +rcpt anyone@ok.domain +# Expect: OK +rcpt bad-sender@ok.domain +# +# check_sender_access specific +# +mail <> diff --git a/src/smtpd/smtpd_acl.ref b/src/smtpd/smtpd_acl.ref new file mode 100644 index 0000000..4b7f6e9 --- /dev/null +++ b/src/smtpd/smtpd_acl.ref @@ -0,0 +1,187 @@ +>>> # +>>> # Initialize +>>> # +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.3.0/28 +OK +>>> relay_domains porcupine.org +OK +>>> smtpd_null_access_lookup_key <> +OK +>>> # +>>> # Test check_domain_access() +>>> # +>>> helo_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> helo foo.dunno.com +./smtpd_check: : reject: HELO from localhost[127.0.0.1]: 554 5.7.1 : Helo command rejected: Access denied; proto=SMTP helo= +554 5.7.1 : Helo command rejected: Access denied +>>> # Expect: OK +>>> helo bar.dunno.com +OK +>>> # Expect: OK +>>> helo foo.duuno.com +OK +>>> # +>>> # Test check_namadr_access(), domain part +>>> # +>>> client_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> client foo.dunno.com 131.155.210.17 +./smtpd_check: : reject: CONNECT from foo.dunno.com[131.155.210.17]: 554 5.7.1 : Client host rejected: Access denied; proto=SMTP helo= +554 5.7.1 : Client host rejected: Access denied +>>> # Expect: OK +>>> client bar.dunno.com 131.155.210.17 +OK +>>> # Expect: OK +>>> client bar.dunno.com 131.155.210.19 +OK +>>> # +>>> # Test check_namadr_access(), address part +>>> # +>>> # Expect: OK +>>> client bar.duno.com 131.155.210.17 +OK +>>> # Expect: REJECT +>>> client bar.duno.com 131.155.210.19 +./smtpd_check: : reject: CONNECT from bar.duno.com[131.155.210.19]: 554 5.7.1 : Client host rejected: Access denied; proto=SMTP helo= +554 5.7.1 : Client host rejected: Access denied +>>> # Expect: REJECT +>>> client bar.duno.com 44.33.22.11 +./smtpd_check: : reject: CONNECT from bar.duno.com[44.33.22.11]: 554 5.7.1 : Client host rejected: Access denied; proto=SMTP helo= +554 5.7.1 : Client host rejected: Access denied +>>> # Expect: OK +>>> client bar.duno.com 44.33.22.55 +OK +>>> # Expect: REJECT +>>> client bar.duno.com 44.33.44.33 +./smtpd_check: : reject: CONNECT from bar.duno.com[44.33.44.33]: 554 5.7.1 : Client host rejected: Access denied; proto=SMTP helo= +554 5.7.1 : Client host rejected: Access denied +>>> # +>>> # Test check_mail_access() +>>> # +>>> sender_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> mail reject@dunno.domain +./smtpd_check: : reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 : Sender address rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: Access denied +>>> # Expect: OK +>>> mail ok@dunno.domain +OK +>>> # Expect: OK +>>> mail anyone@dunno.domain +OK +>>> # Expect: OK +>>> mail bad-sender@dunno.domain +OK +>>> # +>>> # Again, with a domain that rejects by default +>>> # +>>> # Expect: REJECT +>>> mail reject@reject.domain +./smtpd_check: : reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 : Sender address rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: Access denied +>>> # Expect: OK +>>> mail ok@reject.domain +OK +>>> # Expect: REJECT +>>> mail anyone@reject.domain +./smtpd_check: : reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 : Sender address rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: Access denied +>>> # Expect: REJECT +>>> mail good-sender@reject.domain +./smtpd_check: : reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 : Sender address rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: Access denied +>>> # +>>> # Again, with a domain that accepts by default +>>> # +>>> # Expect: REJECT +>>> mail reject@ok.domain +./smtpd_check: : reject: MAIL from bar.duno.com[44.33.44.33]: 554 5.7.1 : Sender address rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: Access denied +>>> # Expect: OK +>>> mail ok@ok.domain +OK +>>> # Expect: OK +>>> mail anyone@ok.domain +OK +>>> # Expect: OK +>>> mail bad-sender@ok.domain +OK +>>> # +>>> # Test check_mail_access() +>>> # +>>> recipient_restrictions hash:./smtpd_check_access +OK +>>> # Expect: REJECT +>>> rcpt reject@dunno.domain +./smtpd_check: : reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Access denied +>>> # Expect: REJECT +>>> recipient_delimiter + +OK +>>> rcpt reject+ext@dunno.domain +./smtpd_check: : reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Access denied +>>> recipient_delimiter | +OK +>>> # Expect: OK +>>> rcpt ok@dunno.domain +OK +>>> # Expect: OK +>>> recipient_delimiter + +OK +>>> rcpt ok+ext@dunno.domain +OK +>>> recipient_delimiter | +OK +>>> # Expect: OK +>>> rcpt anyone@dunno.domain +OK +>>> # Expect: OK +>>> rcpt bad-sender@dunno.domain +OK +>>> # +>>> # Again, with a domain that rejects by default +>>> # +>>> # Expect: REJECT +>>> rcpt reject@reject.domain +./smtpd_check: : reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Access denied +>>> # Expect: OK +>>> rcpt ok@reject.domain +OK +>>> # Expect: REJECT +>>> rcpt anyone@reject.domain +./smtpd_check: : reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Access denied +>>> # Expect: REJECT +>>> rcpt good-sender@reject.domain +./smtpd_check: : reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Access denied +>>> # +>>> # Again, with a domain that accepts by default +>>> # +>>> # Expect: REJECT +>>> rcpt reject@ok.domain +./smtpd_check: : reject: RCPT from bar.duno.com[44.33.44.33]: 554 5.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Access denied +>>> # Expect: OK +>>> rcpt ok@ok.domain +OK +>>> # Expect: OK +>>> rcpt anyone@ok.domain +OK +>>> # Expect: OK +>>> rcpt bad-sender@ok.domain +OK +>>> # +>>> # check_sender_access specific +>>> # +>>> mail <> +./smtpd_check: : reject: MAIL from bar.duno.com[44.33.44.33]: 550 5.7.1 <>: Sender address rejected: Go away postmaster; from=<> proto=SMTP helo= +550 5.7.1 <>: Sender address rejected: Go away postmaster diff --git a/src/smtpd/smtpd_addr_valid.in b/src/smtpd/smtpd_addr_valid.in new file mode 100644 index 0000000..10b5f01 --- /dev/null +++ b/src/smtpd/smtpd_addr_valid.in @@ -0,0 +1,35 @@ +# +# Initialize +# +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.3.0/28 +local_recipient_maps inline:{foo_canon=whatever,bar_canon=whatever} +mydestination example.com +myorigin example.com + +sender_canonical_maps inline:{foo@example.com=foo_canon@example.com} +recipient_canonical_maps inline:{bar@example.com=bar_canon@example.com} + +sender_restrictions reject_unlisted_sender +# Expect accept +mail bar_canon@example.com +# Expect accept +mail bar@example.com +# Expect accept +mail foo_canon@example.com +# Expect accept +mail foo@example.com +# Expect reject +mail baz@example.com + +recipient_restrictions reject_unlisted_recipient +# Expect accept +rcpt bar_canon@example.com +# Expect accept +rcpt bar@example.com +# Expect accept +rcpt foo_canon@example.com +# Expect reject +rcpt foo@example.com +# Expect reject +mail baz@example.com diff --git a/src/smtpd/smtpd_addr_valid.ref b/src/smtpd/smtpd_addr_valid.ref new file mode 100644 index 0000000..3bf610c --- /dev/null +++ b/src/smtpd/smtpd_addr_valid.ref @@ -0,0 +1,57 @@ +>>> # +>>> # Initialize +>>> # +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.3.0/28 +OK +>>> local_recipient_maps inline:{foo_canon=whatever,bar_canon=whatever} +OK +>>> mydestination example.com +OK +>>> myorigin example.com +OK +>>> +>>> sender_canonical_maps inline:{foo@example.com=foo_canon@example.com} +OK +>>> recipient_canonical_maps inline:{bar@example.com=bar_canon@example.com} +OK +>>> +>>> sender_restrictions reject_unlisted_sender +OK +>>> # Expect accept +>>> mail bar_canon@example.com +OK +>>> # Expect accept +>>> mail bar@example.com +OK +>>> # Expect accept +>>> mail foo_canon@example.com +OK +>>> # Expect accept +>>> mail foo@example.com +OK +>>> # Expect reject +>>> mail baz@example.com +./smtpd_check: : reject: MAIL from localhost[127.0.0.1]: 550 5.1.0 : Sender address rejected: User unknown in local recipient table; from= proto=SMTP +550 5.1.0 : Sender address rejected: User unknown in local recipient table +>>> +>>> recipient_restrictions reject_unlisted_recipient +OK +>>> # Expect accept +>>> rcpt bar_canon@example.com +OK +>>> # Expect accept +>>> rcpt bar@example.com +OK +>>> # Expect accept +>>> rcpt foo_canon@example.com +OK +>>> # Expect reject +>>> rcpt foo@example.com +./smtpd_check: : reject: RCPT from localhost[127.0.0.1]: 550 5.1.1 : Recipient address rejected: User unknown in local recipient table; from= to= proto=SMTP +550 5.1.1 : Recipient address rejected: User unknown in local recipient table +>>> # Expect reject +>>> mail baz@example.com +./smtpd_check: : reject: MAIL from localhost[127.0.0.1]: 550 5.1.0 : Sender address rejected: User unknown in local recipient table; from= proto=SMTP +550 5.1.0 : Sender address rejected: User unknown in local recipient table diff --git a/src/smtpd/smtpd_chat.c b/src/smtpd/smtpd_chat.c new file mode 100644 index 0000000..278e536 --- /dev/null +++ b/src/smtpd/smtpd_chat.c @@ -0,0 +1,352 @@ +/*++ +/* NAME +/* smtpd_chat 3 +/* SUMMARY +/* SMTP server request/response support +/* SYNOPSIS +/* #include +/* #include +/* +/* void smtpd_chat_pre_jail_init(void) +/* +/* int smtpd_chat_query_limit(state, limit) +/* SMTPD_STATE *state; +/* int limit; +/* +/* void smtpd_chat_query(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_chat_reply(state, format, ...) +/* SMTPD_STATE *state; +/* char *format; +/* +/* void smtpd_chat_notify(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_chat_reset(state) +/* SMTPD_STATE *state; +/* DESCRIPTION +/* This module implements SMTP server support for request/reply +/* conversations, and maintains a limited SMTP transaction log. +/* +/* smtpd_chat_pre_jail_init() performs one-time initialization. +/* +/* smtpd_chat_query_limit() reads a line from the client that is +/* at most "limit" bytes long. A copy is appended to the SMTP +/* transaction log. The return value is non-zero for a complete +/* line or else zero if the length limit was exceeded. +/* +/* smtpd_chat_query() receives a client request and appends a copy +/* to the SMTP transaction log. +/* +/* smtpd_chat_reply() formats a server reply, sends it to the +/* client, and appends a copy to the SMTP transaction log. +/* When soft_bounce is enabled, all 5xx (reject) responses are +/* replaced by 4xx (try again). In case of a 421 reply the +/* SMTPD_FLAG_HANGUP flag is set for orderly disconnect. +/* +/* smtpd_chat_notify() sends a copy of the SMTP transaction log +/* to the postmaster for review. The postmaster notice is sent only +/* when delivery is possible immediately. It is an error to call +/* smtpd_chat_notify() when no SMTP transaction log exists. +/* +/* smtpd_chat_reset() resets the transaction log. This is +/* typically done at the beginning of an SMTP session, or +/* within a session to discard non-error information. +/* DIAGNOSTICS +/* Panic: interface violations. Fatal errors: out of memory. +/* internal protocol errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include /* 44BSD stdarg.h uses abort() */ +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "smtpd.h" +#include "smtpd_expand.h" +#include "smtpd_chat.h" + + /* + * Reject footer. + */ +static MAPS *smtpd_rej_ftr_maps; + +#define STR vstring_str +#define LEN VSTRING_LEN + +/* smtpd_chat_pre_jail_init - initialize */ + +void smtpd_chat_pre_jail_init(void) +{ + static int init_count = 0; + + if (init_count++ != 0) + msg_panic("smtpd_chat_pre_jail_init: multiple calls"); + + /* + * SMTP server reject footer. + */ + if (*var_smtpd_rej_ftr_maps) + smtpd_rej_ftr_maps = maps_create(VAR_SMTPD_REJ_FTR_MAPS, + var_smtpd_rej_ftr_maps, + DICT_FLAG_LOCK); +} + +/* smtp_chat_reset - reset SMTP transaction log */ + +void smtpd_chat_reset(SMTPD_STATE *state) +{ + if (state->history) { + argv_free(state->history); + state->history = 0; + } +} + +/* smtp_chat_append - append record to SMTP transaction log */ + +static void smtp_chat_append(SMTPD_STATE *state, char *direction, + const char *text) +{ + char *line; + + if (state->notify_mask == 0) + return; + + if (state->history == 0) + state->history = argv_alloc(10); + line = concatenate(direction, text, (char *) 0); + argv_add(state->history, line, (char *) 0); + myfree(line); +} + +/* smtpd_chat_query - receive and record an SMTP request */ + +int smtpd_chat_query_limit(SMTPD_STATE *state, int limit) +{ + int last_char; + + /* + * We can't parse or store input that exceeds var_line_limit, so we skip + * over it to avoid loss of synchronization. + */ + last_char = smtp_get(state->buffer, state->client, limit, + SMTP_GET_FLAG_SKIP); + smtp_chat_append(state, "In: ", STR(state->buffer)); + if (last_char != '\n') + msg_warn("%s: request longer than %d: %.30s...", + state->namaddr, limit, + printable(STR(state->buffer), '?')); + + if (msg_verbose) + msg_info("< %s: %s", state->namaddr, STR(state->buffer)); + return (last_char == '\n'); +} + +/* smtpd_chat_reply - format, send and record an SMTP response */ + +void smtpd_chat_reply(SMTPD_STATE *state, const char *format,...) +{ + va_list ap; + + va_start(ap, format); + vsmtpd_chat_reply(state, format, ap); + va_end(ap); +} + +/* vsmtpd_chat_reply - format, send and record an SMTP response */ + +void vsmtpd_chat_reply(SMTPD_STATE *state, const char *format, va_list ap) +{ + int delay = 0; + char *cp; + char *next; + char *end; + const char *footer; + + /* + * Slow down clients that make errors. Sleep-on-anything slows down + * clients that make an excessive number of errors within a session. + */ + if (state->error_count >= var_smtpd_soft_erlim) + sleep(delay = var_smtpd_err_sleep); + + vstring_vsprintf(state->buffer, format, ap); + + if ((*(cp = STR(state->buffer)) == '4' || *cp == '5') + && ((smtpd_rej_ftr_maps != 0 + && (footer = maps_find(smtpd_rej_ftr_maps, cp, 0)) != 0) + || *(footer = var_smtpd_rej_footer) != 0)) + smtp_reply_footer(state->buffer, 0, footer, STR(smtpd_expand_filter), + smtpd_expand_lookup, (void *) state); + + /* All 5xx replies must have a 5.xx.xx detail code. */ + for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;) { + if (var_soft_bounce) { + if (cp[0] == '5') { + cp[0] = '4'; + if (cp[4] == '5') + cp[4] = '4'; + } + } + /* This is why we use strlen() above instead of VSTRING_LEN(). */ + if ((next = strstr(cp, "\r\n")) != 0) { + *next = 0; + if (next[2] != 0) + cp[3] = '-'; /* contact footer kludge */ + else + next = end; /* strip trailing \r\n */ + } else { + next = end; + } + smtp_chat_append(state, "Out: ", cp); + + if (msg_verbose) + msg_info("> %s: %s", state->namaddr, cp); + + smtp_fputs(cp, next - cp, state->client); + if (next < end) + cp = next + 2; + else + break; + } + + /* + * Flush unsent output if no I/O happened for a while. This avoids + * timeouts with pipelined SMTP sessions that have lots of server-side + * delays (tarpit delays or DNS lookups for UCE restrictions). + */ + if (delay || time((time_t *) 0) - vstream_ftime(state->client) > 10) + vstream_fflush(state->client); + + /* + * Abort immediately if the connection is broken. + */ + if (vstream_ftimeout(state->client)) + vstream_longjmp(state->client, SMTP_ERR_TIME); + if (vstream_ferror(state->client)) + vstream_longjmp(state->client, SMTP_ERR_EOF); + + /* + * Orderly disconnect in case of 421 or 521 reply. + */ + if (strncmp(STR(state->buffer), "421", 3) == 0 + || strncmp(STR(state->buffer), "521", 3) == 0) + state->flags |= SMTPD_FLAG_HANGUP; +} + +/* print_line - line_wrap callback */ + +static void print_line(const char *str, int len, int indent, void *context) +{ + VSTREAM *notice = (VSTREAM *) context; + + post_mail_fprintf(notice, " %*s%.*s", indent, "", len, str); +} + +/* smtpd_chat_notify - notify postmaster */ + +void smtpd_chat_notify(SMTPD_STATE *state) +{ + const char *myname = "smtpd_chat_notify"; + VSTREAM *notice; + char **cpp; + + /* + * Sanity checks. + */ + if (state->history == 0) + msg_panic("%s: no conversation history", myname); + if (msg_verbose) + msg_info("%s: notify postmaster", myname); + + /* + * Construct a message for the postmaster, explaining what this is all + * about. This is junk mail: don't send it when the mail posting service + * is unavailable, and use the double bounce sender address to prevent + * mail bounce wars. Always prepend one space to message content that we + * generate from untrusted data. + */ +#define NULL_TRACE_FLAGS 0 +#define NO_QUEUE_ID ((VSTRING *) 0) +#define LENGTH 78 +#define INDENT 4 + + notice = post_mail_fopen_nowait(mail_addr_double_bounce(), + (state->error_mask & MAIL_ERROR_BOUNCE) ? + var_bounce_rcpt : var_error_rcpt, + MAIL_SRC_MASK_NOTIFY, NULL_TRACE_FLAGS, + SMTPUTF8_FLAG_NONE, NO_QUEUE_ID); + if (notice == 0) { + msg_warn("postmaster notify: %m"); + return; + } + if (smtpd_hfrom_format == HFROM_FORMAT_CODE_STD) { + post_mail_fprintf(notice, "From: Mail Delivery System <%s>", + mail_addr_mail_daemon()); + post_mail_fprintf(notice, "To: Postmaster <%s>", var_error_rcpt); + } else { + post_mail_fprintf(notice, "From: %s (Mail Delivery System)", + mail_addr_mail_daemon()); + post_mail_fprintf(notice, "To: %s (Postmaster)", var_error_rcpt); + } + post_mail_fprintf(notice, "Subject: %s SMTP server: errors from %s", + var_mail_name, state->namaddr); + post_mail_fputs(notice, ""); + post_mail_fputs(notice, "Transcript of session follows."); + post_mail_fputs(notice, ""); + argv_terminate(state->history); + for (cpp = state->history->argv; *cpp; cpp++) + line_wrap(printable(*cpp, '?'), LENGTH, INDENT, print_line, + (void *) notice); + post_mail_fputs(notice, ""); + if (state->reason) + post_mail_fprintf(notice, "Session aborted, reason: %s", state->reason); + post_mail_fputs(notice, ""); + post_mail_fprintf(notice, "For other details, see the local mail logfile"); + (void) post_mail_fclose(notice); +} diff --git a/src/smtpd/smtpd_chat.h b/src/smtpd/smtpd_chat.h new file mode 100644 index 0000000..9fbe178 --- /dev/null +++ b/src/smtpd/smtpd_chat.h @@ -0,0 +1,45 @@ +/*++ +/* NAME +/* smtpd_chat 3h +/* SUMMARY +/* SMTP server request/response support +/* SYNOPSIS +/* #include +/* #include +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include + + /* + * External interface. + */ +extern void smtpd_chat_pre_jail_init(void); +extern void smtpd_chat_reset(SMTPD_STATE *); +extern int smtpd_chat_query_limit(SMTPD_STATE *, int); +extern void smtpd_chat_query(SMTPD_STATE *); +extern void PRINTFLIKE(2, 3) smtpd_chat_reply(SMTPD_STATE *, const char *,...); +extern void vsmtpd_chat_reply(SMTPD_STATE *, const char *, va_list); +extern void smtpd_chat_notify(SMTPD_STATE *); + +#define smtpd_chat_query(state) \ + ((void) smtpd_chat_query_limit((state), var_line_limit)) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/smtpd/smtpd_check.c b/src/smtpd/smtpd_check.c new file mode 100644 index 0000000..996a19e --- /dev/null +++ b/src/smtpd/smtpd_check.c @@ -0,0 +1,6445 @@ +/*++ +/* NAME +/* smtpd_check 3 +/* SUMMARY +/* SMTP client request filtering +/* SYNOPSIS +/* #include "smtpd.h" +/* #include "smtpd_check.h" +/* +/* void smtpd_check_init() +/* +/* int smtpd_check_addr(sender, address, smtputf8) +/* const char *sender; +/* const char *address; +/* int smtputf8; +/* +/* char *smtpd_check_rewrite(state) +/* SMTPD_STATE *state; +/* +/* char *smtpd_check_client(state) +/* SMTPD_STATE *state; +/* +/* char *smtpd_check_helo(state, helohost) +/* SMTPD_STATE *state; +/* char *helohost; +/* +/* char *smtpd_check_mail(state, sender) +/* SMTPD_STATE *state; +/* char *sender; +/* +/* char *smtpd_check_rcpt(state, recipient) +/* SMTPD_STATE *state; +/* char *recipient; +/* +/* char *smtpd_check_etrn(state, destination) +/* SMTPD_STATE *state; +/* char *destination; +/* +/* char *smtpd_check_data(state) +/* SMTPD_STATE *state; +/* +/* char *smtpd_check_eod(state) +/* SMTPD_STATE *state; +/* +/* char *smtpd_check_size(state, size) +/* SMTPD_STATE *state; +/* off_t size; +/* +/* char *smtpd_check_queue(state) +/* SMTPD_STATE *state; +/* AUXILIARY FUNCTIONS +/* void log_whatsup(state, action, text) +/* SMTPD_STATE *state; +/* const char *action; +/* const char *text; +/* DESCRIPTION +/* This module implements additional checks on SMTP client requests. +/* A client request is validated in the context of the session state. +/* The result is either an error response (including the numerical +/* code) or the result is a null pointer in case of success. +/* +/* smtpd_check_init() initializes. This function should be called +/* once during the process life time. +/* +/* smtpd_check_addr() sanity checks an email address and returns +/* non-zero in case of badness. The sender argument provides sender +/* context for address resolution and caching, or a null pointer +/* if information is unavailable. +/* +/* smtpd_check_rewrite() should be called before opening a queue +/* file or proxy connection, in order to establish the proper +/* header address rewriting context. +/* +/* Each of the following routines scrutinizes the argument passed to +/* an SMTP command such as HELO, MAIL FROM, RCPT TO, or scrutinizes +/* the initial client connection request. The administrator can +/* specify what restrictions apply. +/* +/* Restrictions are specified via configuration parameters named +/* \fIsmtpd_{client,helo,sender,recipient}_restrictions.\fR Each +/* configuration parameter specifies a list of zero or more +/* restrictions that are applied in the order as specified. +/* .PP +/* smtpd_check_client() validates the client host name or address. +/* Relevant configuration parameters: +/* .IP smtpd_client_restrictions +/* Restrictions on the names or addresses of clients that may connect +/* to this SMTP server. +/* .PP +/* smtpd_check_helo() validates the hostname provided with the +/* HELO/EHLO commands. Relevant configuration parameters: +/* .IP smtpd_helo_restrictions +/* Restrictions on the hostname that is sent with the HELO/EHLO +/* command. +/* .PP +/* smtpd_check_mail() validates the sender address provided with +/* a MAIL FROM request. Relevant configuration parameters: +/* .IP smtpd_sender_restrictions +/* Restrictions on the sender address that is sent with the MAIL FROM +/* command. +/* .PP +/* smtpd_check_rcpt() validates the recipient address provided +/* with an RCPT TO request. Relevant configuration parameters: +/* .IP smtpd_recipient_restrictions +/* Restrictions on the recipient address that is sent with the RCPT +/* TO command. +/* .IP local_recipient_maps +/* Tables of user names (not addresses) that exist in $mydestination. +/* Mail for local users not in these tables is rejected. +/* .PP +/* smtpd_check_etrn() validates the domain name provided with the +/* ETRN command, and other client-provided information. Relevant +/* configuration parameters: +/* .IP smtpd_etrn_restrictions +/* Restrictions on the hostname that is sent with the HELO/EHLO +/* command. +/* .PP +/* smtpd_check_size() checks if a message with the given size can +/* be received (zero means that the message size is unknown). The +/* message is rejected when +/* the message size exceeds the non-zero bound specified with the +/* \fImessage_size_limit\fR configuration parameter. This is a +/* permanent error. +/* +/* smtpd_check_queue() checks the available queue file system +/* space. The message is rejected when: +/* .IP \(bu +/* The available queue file system space is less than the amount +/* specified with the \fImin_queue_free\fR configuration parameter. +/* This is a temporary error. +/* .IP \(bu +/* The available queue file system space is less than twice the +/* message size limit. This is a temporary error. +/* .PP +/* smtpd_check_data() enforces generic restrictions after the +/* client has sent the DATA command. +/* +/* smtpd_check_eod() enforces generic restrictions after the +/* client has sent the END-OF-DATA command. +/* +/* Arguments: +/* .IP name +/* The client hostname, or \fIunknown\fR. +/* .IP addr +/* The client address. +/* .IP helohost +/* The hostname given with the HELO command. +/* .IP sender +/* The sender address given with the MAIL FROM command. +/* .IP recipient +/* The recipient address given with the RCPT TO or VRFY command. +/* .IP size +/* The message size given with the MAIL FROM command (zero if unknown). +/* .PP +/* log_whatsup() logs ": : +/* from: : " plus the protocol +/* (SMTP or ESMTP), and if available, EHLO, MAIL FROM, or RCPT +/* TO. +/* BUGS +/* Policies like these should not be hard-coded in C, but should +/* be user-programmable instead. +/* SEE ALSO +/* namadr_list(3) host access control +/* domain_list(3) domain access control +/* fsspace(3) free file system space +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* DNS library. */ + +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "smtpd.h" +#include "smtpd_sasl_glue.h" +#include "smtpd_check.h" +#include "smtpd_dsn_fix.h" +#include "smtpd_resolve.h" +#include "smtpd_expand.h" + + /* + * Eject seat in case of parsing problems. + */ +static jmp_buf smtpd_check_buf; + + /* + * Results of restrictions. Errors are negative; see dict.h. + */ +#define SMTPD_CHECK_DUNNO 0 /* indifferent */ +#define SMTPD_CHECK_OK 1 /* explicitly permit */ +#define SMTPD_CHECK_REJECT 2 /* explicitly reject */ + + /* + * Intermediate results. These are static to avoid unnecessary stress on the + * memory manager routines. + */ +static VSTRING *error_text; +static CTABLE *smtpd_rbl_cache; +static CTABLE *smtpd_rbl_byte_cache; + + /* + * Pre-opened SMTP recipient maps so we can reject mail for unknown users. + * XXX This does not belong here and will eventually become part of the + * trivial-rewrite resolver. + */ +static MAPS *local_rcpt_maps; +static MAPS *send_canon_maps; +static MAPS *rcpt_canon_maps; +static MAPS *canonical_maps; +static MAPS *virt_alias_maps; +static MAPS *virt_mailbox_maps; +static MAPS *relay_rcpt_maps; + +#ifdef TEST + +static STRING_LIST *virt_alias_doms; +static STRING_LIST *virt_mailbox_doms; + +#endif + + /* + * Response templates for various rbl domains. + */ +static MAPS *rbl_reply_maps; + + /* + * Pre-opened sender to login name mapping. + */ +static MAPS *smtpd_sender_login_maps; + + /* + * Pre-opened access control lists. + */ +static DOMAIN_LIST *relay_domains; +static NAMADR_LIST *mynetworks_curr; +static NAMADR_LIST *mynetworks_new; +static NAMADR_LIST *perm_mx_networks; + +#ifdef USE_TLS +static MAPS *relay_ccerts; + +#endif + + /* + * How to do parent domain wildcard matching, if any. + */ +static int access_parent_style; + + /* + * Pre-parsed restriction lists. + */ +static ARGV *client_restrctions; +static ARGV *helo_restrctions; +static ARGV *mail_restrctions; +static ARGV *relay_restrctions; +static ARGV *fake_relay_restrctions; +static ARGV *rcpt_restrctions; +static ARGV *etrn_restrctions; +static ARGV *data_restrctions; +static ARGV *eod_restrictions; + +static HTABLE *smtpd_rest_classes; +static HTABLE *policy_clnt_table; +static HTABLE *map_command_table; + +static ARGV *local_rewrite_clients; + + /* + * The routine that recursively applies restrictions. + */ +static int generic_checks(SMTPD_STATE *, ARGV *, const char *, const char *, const char *); + + /* + * Recipient table check. + */ +static int check_sender_rcpt_maps(SMTPD_STATE *, const char *); +static int check_recipient_rcpt_maps(SMTPD_STATE *, const char *); +static int check_rcpt_maps(SMTPD_STATE *, const char *, const char *, + const char *); + + /* + * Tempfail actions; + */ +static int unk_name_tf_act; +static int unk_addr_tf_act; +static int unv_rcpt_tf_act; +static int unv_from_tf_act; + + /* + * Optional permit logging. + */ +static STRING_LIST *smtpd_acl_perm_log; + + /* + * YASLM. + */ +#define STR vstring_str +#define CONST_STR(x) ((const char *) vstring_str(x)) +#define UPDATE_STRING(ptr,val) { if (ptr) myfree(ptr); ptr = mystrdup(val); } + + /* + * If some decision can't be made due to a temporary error, then change + * other decisions into deferrals. + * + * XXX Deferrals can be postponed only with restrictions that are based on + * client-specified information: this restricts their use to parameters + * given in HELO, MAIL FROM, RCPT TO commands. + * + * XXX Deferrals must not be postponed after client hostname lookup failure. + * The reason is that the effect of access tables may depend on whether a + * client hostname is available or not. Thus, the reject_unknown_client + * restriction must defer immediately when lookup fails, otherwise incorrect + * results happen with: + * + * reject_unknown_client, hostname-based allow-list, reject + * + * XXX With warn_if_reject, don't raise the defer_if_permit flag when a + * reject-style restriction fails. Instead, log the warning for the + * resulting defer message. + * + * XXX With warn_if_reject, do raise the defer_if_reject flag when a + * permit-style restriction fails. Otherwise, we could reject legitimate + * mail. + */ +static int PRINTFLIKE(5, 6) defer_if(SMTPD_DEFER *, int, int, const char *, const char *,...); +static int PRINTFLIKE(5, 6) smtpd_check_reject(SMTPD_STATE *, int, int, const char *, const char *,...); + +#define DEFER_IF_REJECT2(state, class, code, dsn, fmt, a1, a2) \ + defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2)) +#define DEFER_IF_REJECT3(state, class, code, dsn, fmt, a1, a2, a3) \ + defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2), (a3)) +#define DEFER_IF_REJECT4(state, class, code, dsn, fmt, a1, a2, a3, a4) \ + defer_if(&(state)->defer_if_reject, (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4)) + + /* + * The following choose between DEFER_IF_PERMIT (only if warn_if_reject is + * turned off) and plain DEFER. See tempfail_actions[] below for the mapping + * from names to numeric action code. + */ +#define DEFER_ALL_ACT 0 +#define DEFER_IF_PERMIT_ACT 1 + +#define DEFER_IF_PERMIT2(type, state, class, code, dsn, fmt, a1, a2) \ + (((state)->warn_if_reject == 0 && (type) != 0) ? \ + defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2)) \ + : \ + smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2))) +#define DEFER_IF_PERMIT3(type, state, class, code, dsn, fmt, a1, a2, a3) \ + (((state)->warn_if_reject == 0 && (type) != 0) ? \ + defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2), (a3)) \ + : \ + smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2), (a3))) +#define DEFER_IF_PERMIT4(type, state, class, code, dsn, fmt, a1, a2, a3, a4) \ + (((state)->warn_if_reject == 0 && (type) != 0) ? \ + defer_if(&(state)->defer_if_permit, (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4)) \ + : \ + smtpd_check_reject((state), (class), (code), (dsn), (fmt), (a1), (a2), (a3), (a4))) + + /* + * Cached RBL lookup state. + */ +typedef struct { + char *txt; /* TXT content or NULL */ + DNS_RR *a; /* A records */ +} SMTPD_RBL_STATE; + +static void *rbl_pagein(const char *, void *); +static void rbl_pageout(void *, void *); +static void *rbl_byte_pagein(const char *, void *); +static void rbl_byte_pageout(void *, void *); + + /* + * Context for RBL $name expansion. + */ +typedef struct { + SMTPD_STATE *state; /* general state */ + char *domain; /* query domain */ + const char *what; /* rejected value */ + const char *class; /* name of rejected value */ + const char *txt; /* randomly selected trimmed TXT rr */ +} SMTPD_RBL_EXPAND_CONTEXT; + + /* + * Multiplication factor for free space check. Free space must be at least + * smtpd_space_multf * message_size_limit. + */ +double smtpd_space_multf = 1.5; + + /* + * SMTPD policy client. Most attributes are ATTR_CLNT attributes. + */ +typedef struct { + ATTR_CLNT *client; /* client handle */ + char *def_action; /* default action */ + char *policy_context; /* context of policy request */ +} SMTPD_POLICY_CLNT; + + /* + * Table-driven parsing of main.cf parameter overrides for specific policy + * clients. We derive the override names from the corresponding main.cf + * parameter names by skipping the redundant "smtpd_policy_service_" prefix. + */ +static ATTR_OVER_TIME time_table[] = { + 21 + (const char *) VAR_SMTPD_POLICY_TMOUT, DEF_SMTPD_POLICY_TMOUT, 0, 1, 0, + 21 + (const char *) VAR_SMTPD_POLICY_IDLE, DEF_SMTPD_POLICY_IDLE, 0, 1, 0, + 21 + (const char *) VAR_SMTPD_POLICY_TTL, DEF_SMTPD_POLICY_TTL, 0, 1, 0, + 21 + (const char *) VAR_SMTPD_POLICY_TRY_DELAY, DEF_SMTPD_POLICY_TRY_DELAY, 0, 1, 0, + 0, +}; +static ATTR_OVER_INT int_table[] = { + 21 + (const char *) VAR_SMTPD_POLICY_REQ_LIMIT, 0, 0, 0, + 21 + (const char *) VAR_SMTPD_POLICY_TRY_LIMIT, 0, 1, 0, + 0, +}; +static ATTR_OVER_STR str_table[] = { + 21 + (const char *) VAR_SMTPD_POLICY_DEF_ACTION, 0, 1, 0, + 21 + (const char *) VAR_SMTPD_POLICY_CONTEXT, 0, 1, 0, + 0, +}; + +#define link_override_table_to_variable(table, var) \ + do { table[var##_offset].target = &var; } while (0) + +#define smtpd_policy_tmout_offset 0 +#define smtpd_policy_idle_offset 1 +#define smtpd_policy_ttl_offset 2 +#define smtpd_policy_try_delay_offset 3 + +#define smtpd_policy_req_limit_offset 0 +#define smtpd_policy_try_limit_offset 1 + +#define smtpd_policy_def_action_offset 0 +#define smtpd_policy_context_offset 1 + + /* + * Search order names must be distinct, non-empty, and non-null. + */ +#define SMTPD_ACL_SEARCH_NAME_CERT_FPRINT "cert_fingerprint" +#define SMTPD_ACL_SEARCH_NAME_PKEY_FPRINT "pubkey_fingerprint" +#define SMTPD_ACL_SEARCH_NAME_CERT_ISSUER_CN "issuer_cn" +#define SMTPD_ACL_SEARCH_NAME_CERT_SUBJECT_CN "subject_cn" + + /* + * Search order tokens must be distinct, and 1..126 inclusive, so that they + * can be stored in a character string without concerns about signed versus + * unsigned. Code 127 is reserved by map_search(3). + */ +#define SMTPD_ACL_SEARCH_CODE_CERT_FPRINT 1 +#define SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT 2 +#define SMTPD_ACL_SEARCH_CODE_CERT_ISSUER_CN 3 +#define SMTPD_ACL_SEARCH_CODE_CERT_SUBJECT_CN 4 + + /* + * Mapping from search-list names and to search-list codes. + */ +static const NAME_CODE search_actions[] = { + SMTPD_ACL_SEARCH_NAME_CERT_FPRINT, SMTPD_ACL_SEARCH_CODE_CERT_FPRINT, + SMTPD_ACL_SEARCH_NAME_PKEY_FPRINT, SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT, + SMTPD_ACL_SEARCH_NAME_CERT_ISSUER_CN, SMTPD_ACL_SEARCH_CODE_CERT_ISSUER_CN, + SMTPD_ACL_SEARCH_NAME_CERT_SUBJECT_CN, SMTPD_ACL_SEARCH_CODE_CERT_SUBJECT_CN, + 0, MAP_SEARCH_CODE_UNKNOWN, +}; + +/* policy_client_register - register policy service endpoint */ + +static void policy_client_register(const char *name) +{ + static const char myname[] = "policy_client_register"; + SMTPD_POLICY_CLNT *policy_client; + char *saved_name = 0; + const char *policy_name = 0; + char *cp; + const char *sep = CHARS_COMMA_SP; + const char *parens = CHARS_BRACE; + char *err; + + if (policy_clnt_table == 0) + policy_clnt_table = htable_create(1); + + if (htable_find(policy_clnt_table, name) == 0) { + + /* + * Allow per-service overrides for main.cf global settings. + */ + int smtpd_policy_tmout = var_smtpd_policy_tmout; + int smtpd_policy_idle = var_smtpd_policy_idle; + int smtpd_policy_ttl = var_smtpd_policy_ttl; + int smtpd_policy_try_delay = var_smtpd_policy_try_delay; + int smtpd_policy_req_limit = var_smtpd_policy_req_limit; + int smtpd_policy_try_limit = var_smtpd_policy_try_limit; + const char *smtpd_policy_def_action = var_smtpd_policy_def_action; + const char *smtpd_policy_context = var_smtpd_policy_context; + + link_override_table_to_variable(time_table, smtpd_policy_tmout); + link_override_table_to_variable(time_table, smtpd_policy_idle); + link_override_table_to_variable(time_table, smtpd_policy_ttl); + link_override_table_to_variable(time_table, smtpd_policy_try_delay); + link_override_table_to_variable(int_table, smtpd_policy_req_limit); + link_override_table_to_variable(int_table, smtpd_policy_try_limit); + link_override_table_to_variable(str_table, smtpd_policy_def_action); + link_override_table_to_variable(str_table, smtpd_policy_context); + + if (*name == parens[0]) { + cp = saved_name = mystrdup(name); + if ((err = extpar(&cp, parens, EXTPAR_FLAG_NONE)) != 0) + msg_fatal("policy service syntax error: %s", cp); + if ((policy_name = mystrtok(&cp, sep)) == 0) + msg_fatal("empty policy service: \"%s\"", name); + attr_override(cp, sep, parens, + CA_ATTR_OVER_TIME_TABLE(time_table), + CA_ATTR_OVER_INT_TABLE(int_table), + CA_ATTR_OVER_STR_TABLE(str_table), + CA_ATTR_OVER_END); + } else { + policy_name = name; + } + if (msg_verbose) + msg_info("%s: name=\"%s\" default_action=\"%s\" max_idle=%d " + "max_ttl=%d request_limit=%d retry_delay=%d " + "timeout=%d try_limit=%d policy_context=\"%s\"", + myname, policy_name, smtpd_policy_def_action, + smtpd_policy_idle, smtpd_policy_ttl, + smtpd_policy_req_limit, smtpd_policy_try_delay, + smtpd_policy_tmout, smtpd_policy_try_limit, + smtpd_policy_context); + + /* + * Create the client. + */ + policy_client = (SMTPD_POLICY_CLNT *) mymalloc(sizeof(*policy_client)); + policy_client->client = attr_clnt_create(policy_name, + smtpd_policy_tmout, + smtpd_policy_idle, + smtpd_policy_ttl); + + attr_clnt_control(policy_client->client, + ATTR_CLNT_CTL_REQ_LIMIT, smtpd_policy_req_limit, + ATTR_CLNT_CTL_TRY_LIMIT, smtpd_policy_try_limit, + ATTR_CLNT_CTL_TRY_DELAY, smtpd_policy_try_delay, + ATTR_CLNT_CTL_END); + policy_client->def_action = mystrdup(smtpd_policy_def_action); + policy_client->policy_context = mystrdup(smtpd_policy_context); + htable_enter(policy_clnt_table, name, (void *) policy_client); + if (saved_name) + myfree(saved_name); + } +} + +/* command_map_register - register access table for maps lookup */ + +static void command_map_register(const char *name) +{ + MAPS *maps; + + if (map_command_table == 0) + map_command_table = htable_create(1); + + if (htable_find(map_command_table, name) == 0) { + maps = maps_create(name, name, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + (void) htable_enter(map_command_table, name, (void *) maps); + } +} + +/* smtpd_check_parse - pre-parse restrictions */ + +static ARGV *smtpd_check_parse(int flags, const char *checks) +{ + char *saved_checks = mystrdup(checks); + ARGV *argv = argv_alloc(1); + char *bp = saved_checks; + char *name; + char *last = 0; + const MAP_SEARCH *map_search; + + /* + * Pre-parse the restriction list, and open any dictionaries that we + * encounter. Dictionaries must be opened before entering the chroot + * jail. + */ +#define SMTPD_CHECK_PARSE_POLICY (1<<0) +#define SMTPD_CHECK_PARSE_MAPS (1<<1) +#define SMTPD_CHECK_PARSE_ALL (~0) + + while ((name = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) { + argv_add(argv, name, (char *) 0); + if ((flags & SMTPD_CHECK_PARSE_POLICY) + && last && strcasecmp(last, CHECK_POLICY_SERVICE) == 0) { + policy_client_register(name); + } else if ((flags & SMTPD_CHECK_PARSE_MAPS) + && (*name == *CHARS_BRACE || strchr(name, ':') != 0)) { + if ((map_search = map_search_create(name)) != 0) + command_map_register(map_search->map_type_name); + } + last = name; + } + argv_terminate(argv); + + /* + * Cleanup. + */ + myfree(saved_checks); + return (argv); +} + +#ifndef TEST + +/* has_required - make sure required restriction is present */ + +static int has_required(ARGV *restrictions, const char **required) +{ + char **rest; + const char **reqd; + ARGV *expansion; + + /* + * Recursively check list membership. + */ + for (rest = restrictions->argv; *rest; rest++) { + if (strcasecmp(*rest, WARN_IF_REJECT) == 0 && rest[1] != 0) { + rest += 1; + continue; + } + if (strcasecmp(*rest, PERMIT_ALL) == 0) { + if (rest[1] != 0) + msg_warn("restriction `%s' after `%s' is ignored", + rest[1], rest[0]); + return (0); + } + for (reqd = required; *reqd; reqd++) + if (strcasecmp(*rest, *reqd) == 0) + return (1); + /* XXX This lookup operation should not be case-sensitive. */ + if ((expansion = (ARGV *) htable_find(smtpd_rest_classes, *rest)) != 0) + if (has_required(expansion, required)) + return (1); + } + return (0); +} + +/* fail_required - handle failure to use required restriction */ + +static void fail_required(const char *name, const char **required) +{ + const char *myname = "fail_required"; + const char **reqd; + VSTRING *example; + + /* + * Sanity check. + */ + if (required[0] == 0) + msg_panic("%s: null required list", myname); + + /* + * Go bust. + */ + example = vstring_alloc(10); + for (reqd = required; *reqd; reqd++) + vstring_sprintf_append(example, "%s%s", *reqd, + reqd[1] == 0 ? "" : reqd[2] == 0 ? " or " : ", "); + msg_fatal("in parameter %s, specify at least one working instance of: %s", + name, STR(example)); +} + +#endif + +/* smtpd_check_init - initialize once during process lifetime */ + +void smtpd_check_init(void) +{ + char *saved_classes; + const char *name; + const char *value; + char *cp; + +#ifndef TEST + static const char *rcpt_required[] = { + REJECT_UNAUTH_DEST, + DEFER_UNAUTH_DEST, + REJECT_ALL, + DEFER_ALL, + DEFER_IF_PERMIT, + CHECK_RELAY_DOMAINS, + 0, + }; + +#endif + static NAME_CODE tempfail_actions[] = { + DEFER_ALL, DEFER_ALL_ACT, + DEFER_IF_PERMIT, DEFER_IF_PERMIT_ACT, + 0, -1, + }; + + /* + * Pre-open access control lists before going to jail. + */ + mynetworks_curr = + namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN + | match_parent_style(VAR_MYNETWORKS), var_mynetworks); + mynetworks_new = + namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN + | match_parent_style(VAR_MYNETWORKS), mynetworks_host()); + relay_domains = + domain_list_init(VAR_RELAY_DOMAINS, + match_parent_style(VAR_RELAY_DOMAINS), + var_relay_domains); + perm_mx_networks = + namadr_list_init(VAR_PERM_MX_NETWORKS, MATCH_FLAG_RETURN + | match_parent_style(VAR_PERM_MX_NETWORKS), + var_perm_mx_networks); +#ifdef USE_TLS + relay_ccerts = maps_create(VAR_RELAY_CCERTS, var_smtpd_relay_ccerts, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX); +#endif + + /* + * Pre-parse and pre-open the recipient maps. + */ + local_rcpt_maps = maps_create(VAR_LOCAL_RCPT_MAPS, var_local_rcpt_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + send_canon_maps = maps_create(VAR_SEND_CANON_MAPS, var_send_canon_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + rcpt_canon_maps = maps_create(VAR_RCPT_CANON_MAPS, var_rcpt_canon_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + canonical_maps = maps_create(VAR_CANONICAL_MAPS, var_canonical_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + virt_alias_maps = maps_create(VAR_VIRT_ALIAS_MAPS, var_virt_alias_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + virt_mailbox_maps = maps_create(VAR_VIRT_MAILBOX_MAPS, + var_virt_mailbox_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + relay_rcpt_maps = maps_create(VAR_RELAY_RCPT_MAPS, var_relay_rcpt_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + +#ifdef TEST + virt_alias_doms = string_list_init(VAR_VIRT_ALIAS_DOMS, MATCH_FLAG_NONE, + var_virt_alias_doms); + virt_mailbox_doms = string_list_init(VAR_VIRT_MAILBOX_DOMS, MATCH_FLAG_NONE, + var_virt_mailbox_doms); +#endif + + access_parent_style = match_parent_style(SMTPD_ACCESS_MAPS); + + /* + * Templates for RBL rejection replies. + */ + rbl_reply_maps = maps_create(VAR_RBL_REPLY_MAPS, var_rbl_reply_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + + /* + * Sender to login name mapping. + */ + smtpd_sender_login_maps = maps_create(VAR_SMTPD_SND_AUTH_MAPS, + var_smtpd_snd_auth_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); + + /* + * error_text is used for returning error responses. + */ + error_text = vstring_alloc(10); + + /* + * Initialize the resolved address cache. Note: the cache persists across + * SMTP sessions so we cannot make it dependent on session state. + */ + smtpd_resolve_init(100); + + /* + * Initialize the RBL lookup cache. Note: the cache persists across SMTP + * sessions so we cannot make it dependent on session state. + */ + smtpd_rbl_cache = ctable_create(100, rbl_pagein, rbl_pageout, (void *) 0); + smtpd_rbl_byte_cache = ctable_create(1000, rbl_byte_pagein, + rbl_byte_pageout, (void *) 0); + + /* + * Initialize access map search list support before parsing restriction + * lists. + */ + map_search_init(search_actions); + + /* + * Pre-parse the restriction lists. At the same time, pre-open tables + * before going to jail. + */ + client_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + var_client_checks); + helo_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + var_helo_checks); + mail_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + var_mail_checks); + relay_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + var_relay_checks); + if (warn_compat_break_relay_restrictions) + fake_relay_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + FAKE_RELAY_CHECKS); + rcpt_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + var_rcpt_checks); + etrn_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + var_etrn_checks); + data_restrctions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + var_data_checks); + eod_restrictions = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + var_eod_checks); + + /* + * Parse the pre-defined restriction classes. + */ + smtpd_rest_classes = htable_create(1); + if (*var_rest_classes) { + cp = saved_classes = mystrdup(var_rest_classes); + while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) { + if ((value = mail_conf_lookup_eval(name)) == 0 || *value == 0) + msg_fatal("restriction class `%s' needs a definition", name); + /* XXX This store operation should not be case-sensitive. */ + htable_enter(smtpd_rest_classes, name, + (void *) smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + value)); + } + myfree(saved_classes); + } + + /* + * This is the place to specify definitions for complex restrictions such + * as check_relay_domains in terms of more elementary restrictions. + */ +#if 0 + htable_enter(smtpd_rest_classes, "check_relay_domains", + smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + "permit_mydomain reject_unauth_destination")); +#endif + htable_enter(smtpd_rest_classes, REJECT_SENDER_LOGIN_MISMATCH, + (void *) smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, + REJECT_AUTH_SENDER_LOGIN_MISMATCH + " " REJECT_UNAUTH_SENDER_LOGIN_MISMATCH)); + + /* + * People screw up the relay restrictions too often. Require that they + * list at least one restriction that rejects mail by default. We allow + * relay restrictions to be empty for sites that require backwards + * compatibility. + */ +#ifndef TEST + if (!has_required(rcpt_restrctions, rcpt_required) + && !has_required(relay_restrctions, rcpt_required)) + fail_required(VAR_RELAY_CHECKS " or " VAR_RCPT_CHECKS, rcpt_required); +#endif + + /* + * Local rewrite policy. + */ + local_rewrite_clients = smtpd_check_parse(SMTPD_CHECK_PARSE_MAPS, + var_local_rwr_clients); + + /* + * Tempfail_actions. + * + * XXX This name-to-number mapping should be encapsulated in a separate + * mail_conf_name_code.c module. + */ + if ((unk_name_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE, + var_unk_name_tf_act)) < 0) + msg_fatal("bad configuration: %s = %s", + VAR_UNK_NAME_TF_ACT, var_unk_name_tf_act); + if ((unk_addr_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE, + var_unk_addr_tf_act)) < 0) + msg_fatal("bad configuration: %s = %s", + VAR_UNK_ADDR_TF_ACT, var_unk_addr_tf_act); + if ((unv_rcpt_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE, + var_unv_rcpt_tf_act)) < 0) + msg_fatal("bad configuration: %s = %s", + VAR_UNV_RCPT_TF_ACT, var_unv_rcpt_tf_act); + if ((unv_from_tf_act = name_code(tempfail_actions, NAME_CODE_FLAG_NONE, + var_unv_from_tf_act)) < 0) + msg_fatal("bad configuration: %s = %s", + VAR_UNV_FROM_TF_ACT, var_unv_from_tf_act); + if (msg_verbose) { + msg_info("%s = %s", VAR_UNK_NAME_TF_ACT, tempfail_actions[unk_name_tf_act].name); + msg_info("%s = %s", VAR_UNK_ADDR_TF_ACT, tempfail_actions[unk_addr_tf_act].name); + msg_info("%s = %s", VAR_UNV_RCPT_TF_ACT, tempfail_actions[unv_rcpt_tf_act].name); + msg_info("%s = %s", VAR_UNV_FROM_TF_ACT, tempfail_actions[unv_from_tf_act].name); + } + + /* + * Optional permit logging. + */ + smtpd_acl_perm_log = string_list_init(VAR_SMTPD_ACL_PERM_LOG, + MATCH_FLAG_RETURN, + var_smtpd_acl_perm_log); +} + +/* log_whatsup - log as much context as we have */ + +void log_whatsup(SMTPD_STATE *state, const char *whatsup, + const char *text) +{ + VSTRING *buf = vstring_alloc(100); + + vstring_sprintf(buf, "%s: %s: %s from %s: %s;", + state->queue_id ? state->queue_id : "NOQUEUE", + whatsup, state->where, state->namaddr, text); + if (state->sender) + vstring_sprintf_append(buf, " from=<%s>", + info_log_addr_form_sender(state->sender)); + if (state->recipient) + vstring_sprintf_append(buf, " to=<%s>", + info_log_addr_form_recipient(state->recipient)); + if (state->protocol) + vstring_sprintf_append(buf, " proto=%s", state->protocol); + if (state->helo_name) + vstring_sprintf_append(buf, " helo=<%s>", state->helo_name); + msg_info("%s", STR(buf)); + vstring_free(buf); +} + +/* smtpd_acl_permit - permit request with optional logging */ + +static int PRINTFLIKE(5, 6) smtpd_acl_permit(SMTPD_STATE *state, + const char *action, + const char *reply_class, + const char *reply_name, + const char *format,...) +{ + const char myname[] = "smtpd_acl_permit"; + va_list ap; + const char *whatsup; + +#ifdef notdef +#define NO_PRINT_ARGS "" +#else +#define NO_PRINT_ARGS "%s", "" +#endif + + /* + * First, find out if (and how) this permit action should be logged. + */ + if (msg_verbose) + msg_info("%s: checking %s settings", myname, VAR_SMTPD_ACL_PERM_LOG); + + if (state->defer_if_permit.active) { + /* This action is overruled. Do not log. */ + whatsup = 0; + } else if (string_list_match(smtpd_acl_perm_log, action) != 0) { + /* This is not a test. Logging is enabled. */ + whatsup = "permit"; + } else { + /* This is not a test. Logging is disabled. */ + whatsup = 0; + } + if (whatsup != 0) { + vstring_sprintf(error_text, "action=%s for %s=%s", + action, reply_class, reply_name); + if (format && *format) { + vstring_strcat(error_text, " "); + va_start(ap, format); + vstring_vsprintf_append(error_text, format, ap); + va_end(ap); + } + log_whatsup(state, whatsup, STR(error_text)); + } else { + if (msg_verbose) + msg_info("%s: %s: no match", myname, VAR_SMTPD_ACL_PERM_LOG); + } + return (SMTPD_CHECK_OK); +} + +/* smtpd_check_reject - do the boring things that must be done */ + +static int smtpd_check_reject(SMTPD_STATE *state, int error_class, + int code, const char *dsn, + const char *format,...) +{ + va_list ap; + int warn_if_reject; + const char *whatsup; + + /* + * Do not reject mail if we were asked to warn only. However, + * configuration/software/data errors cannot be converted into warnings. + */ + if (state->warn_if_reject && error_class != MAIL_ERROR_SOFTWARE + && error_class != MAIL_ERROR_RESOURCE + && error_class != MAIL_ERROR_DATA) { + warn_if_reject = 1; + whatsup = "reject_warning"; + } else { + warn_if_reject = 0; + whatsup = "reject"; + } + + /* + * Update the error class mask, and format the response. XXX What about + * multi-line responses? For now we cheat and send whitespace. + * + * Format the response before complaining about configuration errors, so + * that we can show the error in context. + */ + state->error_mask |= error_class; + vstring_sprintf(error_text, "%d %s ", code, dsn); + va_start(ap, format); + vstring_vsprintf_append(error_text, format, ap); + va_end(ap); + + /* + * Validate the response, that is, the response must begin with a + * three-digit status code, and the first digit must be 4 or 5. If the + * response is bad, log a warning and send a generic response instead. + */ + if (code < 400 || code > 599) { + msg_warn("SMTP reply code configuration error: %s", STR(error_text)); + vstring_strcpy(error_text, "450 4.7.1 Service unavailable"); + } + if (!dsn_valid(STR(error_text) + 4)) { + msg_warn("DSN detail code configuration error: %s", STR(error_text)); + vstring_strcpy(error_text, "450 4.7.1 Service unavailable"); + } + + /* + * Ensure RFC compliance. We could do this inside smtpd_chat_reply() and + * switch to multi-line for long replies. + */ + vstring_truncate(error_text, 510); + printable(STR(error_text), ' '); + + /* + * Force this rejection into deferral because of some earlier temporary + * error that may have prevented us from accepting mail, and report the + * earlier problem instead. + */ + if (!warn_if_reject && state->defer_if_reject.active && STR(error_text)[0] == '5') { + state->warn_if_reject = state->defer_if_reject.active = 0; + return (smtpd_check_reject(state, state->defer_if_reject.class, + state->defer_if_reject.code, + STR(state->defer_if_reject.dsn), + "%s", STR(state->defer_if_reject.reason))); + } + + /* + * Soft bounce safety net. + * + * XXX The code below also appears in the Postfix SMTP server reply output + * routine. It is duplicated here in order to avoid discrepancies between + * the reply codes that are shown in "reject" logging and the reply codes + * that are actually sent to the SMTP client. + * + * Implementing the soft_bounce safety net in the SMTP server reply output + * routine has the advantage that it covers all 5xx replies, including + * SMTP protocol or syntax errors, which makes soft_bounce great for + * non-destructive tests (especially by people who are paranoid about + * losing mail). + * + * We could eliminate the code duplication and implement the soft_bounce + * safety net only in the code below. But then the safety net would cover + * the UCE restrictions only. This would be at odds with documentation + * which says soft_bounce changes all 5xx replies into 4xx ones. + */ + if (var_soft_bounce && STR(error_text)[0] == '5') + STR(error_text)[0] = '4'; + + /* + * In any case, enforce consistency between the SMTP code and DSN code. + * SMTP has the higher precedence since it came here first. + */ + STR(error_text)[4] = STR(error_text)[0]; + + /* + * Log what is happening. When the sysadmin discards policy violation + * postmaster notices, this may be the only trace left that service was + * rejected. Print the request, client name/address, and response. + */ + log_whatsup(state, whatsup, STR(error_text)); + + return (warn_if_reject ? 0 : SMTPD_CHECK_REJECT); +} + +/* defer_if - prepare to change our mind */ + +static int defer_if(SMTPD_DEFER *defer, int error_class, + int code, const char *dsn, + const char *fmt,...) +{ + va_list ap; + + /* + * Keep the first reason for this type of deferral, to minimize + * confusion. + */ + if (defer->active == 0) { + defer->active = 1; + defer->class = error_class; + defer->code = code; + if (defer->dsn == 0) + defer->dsn = vstring_alloc(10); + vstring_strcpy(defer->dsn, dsn); + if (defer->reason == 0) + defer->reason = vstring_alloc(10); + va_start(ap, fmt); + vstring_vsprintf(defer->reason, fmt, ap); + va_end(ap); + } + return (SMTPD_CHECK_DUNNO); +} + +/* reject_dict_retry - reject with temporary failure if dict lookup fails */ + +static NORETURN reject_dict_retry(SMTPD_STATE *state, const char *reply_name) +{ + longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_DATA, + 451, "4.3.0", + "<%s>: Temporary lookup failure", + reply_name)); +} + +/* reject_server_error - reject with temporary failure after non-dict error */ + +static NORETURN reject_server_error(SMTPD_STATE *state) +{ + longjmp(smtpd_check_buf, smtpd_check_reject(state, MAIL_ERROR_SOFTWARE, + 451, "4.3.5", + "Server configuration error")); +} + +/* check_mail_addr_find - reject with temporary failure if dict lookup fails */ + +static const char *check_mail_addr_find(SMTPD_STATE *state, + const char *reply_name, + MAPS *maps, const char *key, + char **ext) +{ + const char *result; + + if ((result = mail_addr_find(maps, key, ext)) != 0 || maps->error == 0) + return (result); + if (maps->error == DICT_ERR_RETRY) + /* Warning is already logged. */ + reject_dict_retry(state, reply_name); + else + reject_server_error(state); +} + +/* reject_unknown_reverse_name - fail if reverse client hostname is unknown */ + +static int reject_unknown_reverse_name(SMTPD_STATE *state) +{ + const char *myname = "reject_unknown_reverse_name"; + + if (msg_verbose) + msg_info("%s: %s", myname, state->reverse_name); + + if (state->reverse_name_status != SMTPD_PEER_CODE_OK) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + state->reverse_name_status == SMTPD_PEER_CODE_PERM ? + var_unk_client_code : 450, "4.7.1", + "Client host rejected: cannot find your reverse hostname, [%s]", + state->addr)); + return (SMTPD_CHECK_DUNNO); +} + +/* reject_unknown_client - fail if client hostname is unknown */ + +static int reject_unknown_client(SMTPD_STATE *state) +{ + const char *myname = "reject_unknown_client"; + + if (msg_verbose) + msg_info("%s: %s %s", myname, state->name, state->addr); + + /* RFC 7372: Email Authentication Status Codes. */ + if (state->name_status != SMTPD_PEER_CODE_OK) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + state->name_status >= SMTPD_PEER_CODE_PERM ? + var_unk_client_code : 450, "4.7.25", + "Client host rejected: cannot find your hostname, [%s]", + state->addr)); + return (SMTPD_CHECK_DUNNO); +} + +/* reject_plaintext_session - fail if session is not encrypted */ + +static int reject_plaintext_session(SMTPD_STATE *state) +{ + const char *myname = "reject_plaintext_session"; + + if (msg_verbose) + msg_info("%s: %s %s", myname, state->name, state->addr); + +#ifdef USE_TLS + if (state->tls_context == 0) +#endif + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_plaintext_code, "4.7.1", + "Session encryption is required")); + return (SMTPD_CHECK_DUNNO); +} + +/* permit_inet_interfaces - succeed if client my own address */ + +static int permit_inet_interfaces(SMTPD_STATE *state) +{ + const char *myname = "permit_inet_interfaces"; + + if (msg_verbose) + msg_info("%s: %s %s", myname, state->name, state->addr); + + if (own_inet_addr((struct sockaddr *) &(state->sockaddr))) + /* Permit logging in generic_checks() only. */ + return (SMTPD_CHECK_OK); + return (SMTPD_CHECK_DUNNO); +} + +/* permit_mynetworks - succeed if client is in a trusted network */ + +static int permit_mynetworks(SMTPD_STATE *state) +{ + const char *myname = "permit_mynetworks"; + + if (msg_verbose) + msg_info("%s: %s %s", myname, state->name, state->addr); + + if (namadr_list_match(mynetworks_curr, state->name, state->addr)) { + if (warn_compat_break_mynetworks_style + && !namadr_list_match(mynetworks_new, state->name, state->addr)) + msg_info("using backwards-compatible default setting " + VAR_MYNETWORKS_STYLE "=%s to permit request from " + "client \"%s\"", var_mynetworks_style, state->namaddr); + /* Permit logging in generic_checks() only. */ + return (SMTPD_CHECK_OK); + } else if (mynetworks_curr->error == 0) + return (SMTPD_CHECK_DUNNO); + else + return (mynetworks_curr->error); +} + +/* dup_if_truncate - save hostname and truncate if it ends in dot */ + +static char *dup_if_truncate(char *name) +{ + ssize_t len; + char *result; + + /* + * Truncate hostnames ending in dot but not dot-dot. + * + * XXX This should not be distributed all over the code. Problem is, + * addresses can enter the system via multiple paths: networks, local + * forward/alias/include files, even as the result of address rewriting. + */ + if ((len = strlen(name)) > 1 + && name[len - 1] == '.' + && name[len - 2] != '.') { + result = mystrndup(name, len - 1); + } else + result = name; + return (result); +} + +/* reject_invalid_hostaddr - fail if host address is incorrect */ + +static int reject_invalid_hostaddr(SMTPD_STATE *state, char *addr, + char *reply_name, char *reply_class) +{ + const char *myname = "reject_invalid_hostaddr"; + ssize_t len; + char *test_addr; + int stat; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + if (addr[0] == '[' && (len = strlen(addr)) > 2 && addr[len - 1] == ']') { + test_addr = mystrndup(addr + 1, len - 2); + } else + test_addr = addr; + + /* + * Validate the address. + */ + if (!valid_mailhost_addr(test_addr, DONT_GRIPE)) + stat = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_bad_name_code, "5.5.2", + "<%s>: %s rejected: invalid ip address", + reply_name, reply_class); + else + stat = SMTPD_CHECK_DUNNO; + + /* + * Cleanup. + */ + if (test_addr != addr) + myfree(test_addr); + + return (stat); +} + +/* reject_invalid_hostname - fail if host/domain syntax is incorrect */ + +static int reject_invalid_hostname(SMTPD_STATE *state, char *name, + char *reply_name, char *reply_class) +{ + const char *myname = "reject_invalid_hostname"; + char *test_name; + int stat; + + if (msg_verbose) + msg_info("%s: %s", myname, name); + + /* + * Truncate hostnames ending in dot but not dot-dot. + */ + test_name = dup_if_truncate(name); + + /* + * Validate the HELO/EHLO hostname. Fix 20140706: EAI not allowed here. + */ + if (!valid_hostname(test_name, DONT_GRIPE) + && !valid_hostaddr(test_name, DONT_GRIPE)) /* XXX back compat */ + stat = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_bad_name_code, "5.5.2", + "<%s>: %s rejected: Invalid name", + reply_name, reply_class); + else + stat = SMTPD_CHECK_DUNNO; + + /* + * Cleanup. + */ + if (test_name != name) + myfree(test_name); + + return (stat); +} + +/* reject_non_fqdn_hostname - fail if host name is not in fqdn form */ + +static int reject_non_fqdn_hostname(SMTPD_STATE *state, char *name, + char *reply_name, char *reply_class) +{ + const char *myname = "reject_non_fqdn_hostname"; + char *test_name; + int stat; + + if (msg_verbose) + msg_info("%s: %s", myname, name); + + /* + * Truncate hostnames ending in dot but not dot-dot. + */ + test_name = dup_if_truncate(name); + + /* + * Validate the hostname. For backwards compatibility, permit non-ASCII + * names only when the client requested SMTPUTF8 support. + */ + if (valid_utf8_hostname(state->flags & SMTPD_FLAG_SMTPUTF8, + test_name, DONT_GRIPE) == 0 || strchr(test_name, '.') == 0) + stat = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_non_fqdn_code, "5.5.2", + "<%s>: %s rejected: need fully-qualified hostname", + reply_name, reply_class); + else + stat = SMTPD_CHECK_DUNNO; + + /* + * Cleanup. + */ + if (test_name != name) + myfree(test_name); + + return (stat); +} + +/* reject_unknown_hostname - fail if name has no A, AAAA or MX record */ + +static int reject_unknown_hostname(SMTPD_STATE *state, char *name, + char *reply_name, char *reply_class) +{ + const char *myname = "reject_unknown_hostname"; + int dns_status; + DNS_RR *dummy; + + if (msg_verbose) + msg_info("%s: %s", myname, name); + +#ifdef T_AAAA +#define RR_ADDR_TYPES T_A, T_AAAA +#else +#define RR_ADDR_TYPES T_A +#endif + + dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0, + (VSTRING *) 0, DNS_REQ_FLAG_STOP_OK, + RR_ADDR_TYPES, T_MX, 0); + if (dummy) + dns_rr_free(dummy); + /* Allow MTA names to have nullMX records. */ + if (dns_status != DNS_OK && dns_status != DNS_NULLMX) { + if (dns_status == DNS_POLICY) { + msg_warn("%s: address or MX lookup error: %s", + name, "DNS reply filter drops all results"); + return (SMTPD_CHECK_DUNNO); + } + if (dns_status != DNS_RETRY) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_unk_name_code, "4.7.1", + "<%s>: %s rejected: %s", + reply_name, reply_class, + dns_status == DNS_INVAL ? + "Malformed DNS server reply" : + "Host not found")); + else + return (DEFER_IF_PERMIT2(unk_name_tf_act, state, MAIL_ERROR_POLICY, + 450, "4.7.1", + "<%s>: %s rejected: Host not found", + reply_name, reply_class)); + } + return (SMTPD_CHECK_DUNNO); +} + +/* reject_unknown_mailhost - fail if name has no A, AAAA or MX record */ + +static int reject_unknown_mailhost(SMTPD_STATE *state, const char *name, + const char *reply_name, const char *reply_class) +{ + const char *myname = "reject_unknown_mailhost"; + int dns_status; + DNS_RR *dummy; + const char *aname; + + if (msg_verbose) + msg_info("%s: %s", myname, name); + + /* + * Fix 20140924: convert domain to ASCII. + */ +#ifndef NO_EAI + if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", name, aname); + name = aname; + } +#endif + +#define MAILHOST_LOOKUP_FLAGS \ + (DNS_REQ_FLAG_STOP_OK | DNS_REQ_FLAG_STOP_INVAL | \ + DNS_REQ_FLAG_STOP_NULLMX | DNS_REQ_FLAG_STOP_MX_POLICY) + + dns_status = dns_lookup_l(name, 0, &dummy, (VSTRING *) 0, + (VSTRING *) 0, MAILHOST_LOOKUP_FLAGS, + T_MX, RR_ADDR_TYPES, 0); + if (dummy) + dns_rr_free(dummy); + if (dns_status != DNS_OK) { /* incl. DNS_INVAL */ + if (dns_status == DNS_POLICY) { + msg_warn("%s: MX or address lookup error: %s", + name, "DNS reply filter drops all results"); + return (SMTPD_CHECK_DUNNO); + } + if (dns_status == DNS_NULLMX) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + 550 : 556, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "4.7.27" : "4.1.10", + "<%s>: %s rejected: Domain %s " + "does not accept mail (nullMX)", + reply_name, reply_class, name)); + if (dns_status != DNS_RETRY) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_unk_addr_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "4.1.8" : "4.1.2", + "<%s>: %s rejected: %s", + reply_name, reply_class, + dns_status == DNS_INVAL ? + "Malformed DNS server reply" : + "Domain not found")); + else + return (DEFER_IF_PERMIT2(unk_addr_tf_act, state, MAIL_ERROR_POLICY, + 450, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "4.1.8" : "4.1.2", + "<%s>: %s rejected: Domain not found", + reply_name, reply_class)); + } + return (SMTPD_CHECK_DUNNO); +} + +static int permit_auth_destination(SMTPD_STATE *state, char *recipient); + +/* permit_tls_clientcerts - OK/DUNNO for message relaying, or set dict_errno */ + +static int permit_tls_clientcerts(SMTPD_STATE *state, int permit_all_certs) +{ +#ifdef USE_TLS + const char *found = 0; + + if (!state->tls_context) + return SMTPD_CHECK_DUNNO; + + if (TLS_CERT_IS_TRUSTED(state->tls_context) && permit_all_certs) { + if (msg_verbose) + msg_info("Relaying allowed for all verified client certificates"); + /* Permit logging in generic_checks() only. */ + return (SMTPD_CHECK_OK); + } + + /* + * When directly checking the fingerprint, it is OK if the issuing CA is + * not trusted. + */ + if (TLS_CERT_IS_PRESENT(state->tls_context)) { + int i; + char *prints[2]; + + if (warn_compat_break_smtpd_tls_fpt_dgst) + msg_info("using backwards-compatible default setting " + VAR_SMTPD_TLS_FPT_DGST "=md5 to compute certificate " + "fingerprints"); + + prints[0] = state->tls_context->peer_cert_fprint; + prints[1] = state->tls_context->peer_pkey_fprint; + + /* After lookup error, leave relay_ccerts->error at non-zero value. */ + for (i = 0; i < 2; ++i) { + found = maps_find(relay_ccerts, prints[i], DICT_FLAG_NONE); + if (found != 0) { + if (msg_verbose) + msg_info("Relaying allowed for certified client: %s", found); + /* Permit logging in generic_checks() only. */ + return (SMTPD_CHECK_OK); + } else if (relay_ccerts->error != 0) { + msg_warn("relay_clientcerts: lookup error for fingerprint '%s', " + "pkey fingerprint %s", prints[0], prints[1]); + return (relay_ccerts->error); + } + } + if (msg_verbose) + msg_info("relay_clientcerts: No match for fingerprint '%s', " + "pkey fingerprint %s", prints[0], prints[1]); + } else if (!var_smtpd_tls_ask_ccert) { + msg_warn("%s is requested, but \"%s = no\"", permit_all_certs ? + PERMIT_TLS_ALL_CLIENTCERTS : PERMIT_TLS_CLIENTCERTS, + VAR_SMTPD_TLS_ACERT); + } +#endif + return (SMTPD_CHECK_DUNNO); +} + +/* check_relay_domains - OK/FAIL for message relaying */ + +static int check_relay_domains(SMTPD_STATE *state, char *recipient, + char *reply_name, char *reply_class) +{ + const char *myname = "check_relay_domains"; + +#if 1 + static int once; + + if (once == 0) { + once = 1; + msg_warn("support for restriction \"%s\" will be removed from %s; " + "use \"%s\" instead", + CHECK_RELAY_DOMAINS, var_mail_name, REJECT_UNAUTH_DEST); + } +#endif + + if (msg_verbose) + msg_info("%s: %s", myname, recipient); + + /* + * Permit if the client matches the relay_domains list. + */ + if (domain_list_match(relay_domains, state->name)) { + if (warn_compat_break_relay_domains) + msg_info("using backwards-compatible default setting " + VAR_RELAY_DOMAINS "=$mydestination to permit " + "request from client \"%s\"", state->name); + return (SMTPD_CHECK_OK); + } + + /* + * Permit authorized destinations. + */ + if (permit_auth_destination(state, recipient) == SMTPD_CHECK_OK) + return (SMTPD_CHECK_OK); + + /* + * Deny relaying between sites that both are not in relay_domains. + */ + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_relay_code, "5.7.1", + "<%s>: %s rejected: Relay access denied", + reply_name, reply_class)); +} + +/* permit_auth_destination - OK for message relaying */ + +static int permit_auth_destination(SMTPD_STATE *state, char *recipient) +{ + const char *myname = "permit_auth_destination"; + const RESOLVE_REPLY *reply; + const char *domain; + + if (msg_verbose) + msg_info("%s: %s", myname, recipient); + + /* + * Resolve the address. + */ + reply = smtpd_resolve_addr(state->sender, recipient); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, recipient); + + /* + * Handle special case that is not supposed to happen. + */ + if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) + return (SMTPD_CHECK_OK); + domain += 1; + + /* + * Skip source-routed non-local or virtual mail (uncertain destination). + */ + if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED)) + return (SMTPD_CHECK_DUNNO); + + /* + * Permit final delivery: the destination matches mydestination, + * virtual_alias_domains, or virtual_mailbox_domains. + */ + if (reply->flags & RESOLVE_CLASS_FINAL) + return (SMTPD_CHECK_OK); + + /* + * Permit if the destination matches the relay_domains list. + */ + if (reply->flags & RESOLVE_CLASS_RELAY) { + if (warn_compat_break_relay_domains) + msg_info("using backwards-compatible default setting " + VAR_RELAY_DOMAINS "=$mydestination to accept mail " + "for domain \"%s\"", domain); + return (SMTPD_CHECK_OK); + } + + /* + * Skip when not matched + */ + return (SMTPD_CHECK_DUNNO); +} + +/* reject_unauth_destination - FAIL for message relaying */ + +static int reject_unauth_destination(SMTPD_STATE *state, char *recipient, + int reply_code, const char *reply_dsn) +{ + const char *myname = "reject_unauth_destination"; + + if (msg_verbose) + msg_info("%s: %s", myname, recipient); + + /* + * Skip authorized destination. + */ + if (permit_auth_destination(state, recipient) == SMTPD_CHECK_OK) + return (SMTPD_CHECK_DUNNO); + + /* + * Reject relaying to sites that are not listed in relay_domains. + */ + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + reply_code, reply_dsn, + "<%s>: Relay access denied", + recipient)); +} + +/* reject_unauth_pipelining - reject improper use of SMTP command pipelining */ + +static int reject_unauth_pipelining(SMTPD_STATE *state, + const char *reply_name, const char *reply_class) +{ + const char *myname = "reject_unauth_pipelining"; + + if (msg_verbose) + msg_info("%s: %s", myname, state->where); + + if (state->flags & SMTPD_FLAG_ILL_PIPELINING) + return (smtpd_check_reject(state, MAIL_ERROR_PROTOCOL, + 503, "5.5.0", + "<%s>: %s rejected: Improper use of SMTP command pipelining", + reply_name, reply_class)); + + return (SMTPD_CHECK_DUNNO); +} + +/* all_auth_mx_addr - match host addresses against permit_mx_backup_networks */ + +static int all_auth_mx_addr(SMTPD_STATE *state, char *host, + const char *reply_name, const char *reply_class) +{ + const char *myname = "all_auth_mx_addr"; + MAI_HOSTADDR_STR hostaddr; + DNS_RR *rr; + DNS_RR *addr_list; + int dns_status; + + if (msg_verbose) + msg_info("%s: host %s", myname, host); + + /* + * If we can't lookup the host, defer. + */ +#define NOPE 0 +#define YUP 1 + + /* + * Verify that all host addresses are within permit_mx_backup_networks. + */ + dns_status = dns_lookup_v(host, 0, &addr_list, (VSTRING *) 0, (VSTRING *) 0, + DNS_REQ_FLAG_NONE, inet_proto_info()->dns_atype_list); + /* DNS_NULLMX is not applicable here. */ + if (dns_status != DNS_OK) { /* incl. DNS_INVAL */ + DEFER_IF_REJECT4(state, MAIL_ERROR_POLICY, + 450, "4.4.4", + "<%s>: %s rejected: Unable to look up host " + "%s as mail exchanger: %s", + reply_name, reply_class, host, + dns_status == DNS_POLICY ? + "DNS reply filter policy" : + dns_strerror(dns_get_h_errno())); + return (NOPE); + } + for (rr = addr_list; rr != 0; rr = rr->next) { + if (dns_rr_to_pa(rr, &hostaddr) == 0) { + msg_warn("%s: skipping record type %s for host %s: %m", + myname, dns_strtype(rr->type), host); + continue; + } + if (msg_verbose) + msg_info("%s: checking: %s", myname, hostaddr.buf); + + if (!namadr_list_match(perm_mx_networks, host, hostaddr.buf)) { + if (perm_mx_networks->error == 0) { + + /* + * Reject: at least one IP address is not listed in + * permit_mx_backup_networks. + */ + if (msg_verbose) + msg_info("%s: address %s for %s does not match %s", + myname, hostaddr.buf, host, VAR_PERM_MX_NETWORKS); + } else { + msg_warn("%s: %s lookup error for address %s for %s", + myname, VAR_PERM_MX_NETWORKS, hostaddr.buf, host); + DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY, + 450, "4.4.4", + "<%s>: %s rejected: Unable to verify host %s as mail exchanger", + reply_name, reply_class, host); + } + dns_rr_free(addr_list); + return (NOPE); + } + } + dns_rr_free(addr_list); + return (YUP); +} + +/* has_my_addr - see if this host name lists one of my network addresses */ + +static int has_my_addr(SMTPD_STATE *state, const char *host, + const char *reply_name, const char *reply_class) +{ + const char *myname = "has_my_addr"; + struct addrinfo *res; + struct addrinfo *res0; + int aierr; + MAI_HOSTADDR_STR hostaddr; + const INET_PROTO_INFO *proto_info = inet_proto_info(); + + if (msg_verbose) + msg_info("%s: host %s", myname, host); + + /* + * If we can't lookup the host, defer rather than reject. + */ +#define YUP 1 +#define NOPE 0 + + aierr = hostname_to_sockaddr(host, (char *) 0, 0, &res0); + if (aierr) { + DEFER_IF_REJECT4(state, MAIL_ERROR_POLICY, + 450, "4.4.4", + "<%s>: %s rejected: Unable to look up mail exchanger host %s: %s", + reply_name, reply_class, host, MAI_STRERROR(aierr)); + return (NOPE); + } +#define HAS_MY_ADDR_RETURN(x) { freeaddrinfo(res0); return (x); } + + for (res = res0; res != 0; res = res->ai_next) { + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + if (msg_verbose) + msg_info("skipping address family %d for host %s", + res->ai_family, host); + continue; + } + if (msg_verbose) { + SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen, + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_info("%s: addr %s", myname, hostaddr.buf); + } + if (own_inet_addr(res->ai_addr)) + HAS_MY_ADDR_RETURN(YUP); + if (proxy_inet_addr(res->ai_addr)) + HAS_MY_ADDR_RETURN(YUP); + } + if (msg_verbose) + msg_info("%s: host %s: no match", myname, host); + + HAS_MY_ADDR_RETURN(NOPE); +} + +/* i_am_mx - is this machine listed as MX relay */ + +static int i_am_mx(SMTPD_STATE *state, DNS_RR *mx_list, + const char *reply_name, const char *reply_class) +{ + const char *myname = "i_am_mx"; + DNS_RR *mx; + + /* + * Compare hostnames first. Only if no name match is found, go through + * the trouble of host address lookups. + */ + for (mx = mx_list; mx != 0; mx = mx->next) { + if (msg_verbose) + msg_info("%s: resolve hostname: %s", myname, (char *) mx->data); + if (resolve_local((char *) mx->data) > 0) + return (YUP); + /* if no match or error, match interface addresses instead. */ + } + + /* + * Argh. Do further DNS lookups and match interface addresses. + */ + for (mx = mx_list; mx != 0; mx = mx->next) { + if (msg_verbose) + msg_info("%s: address lookup: %s", myname, (char *) mx->data); + if (has_my_addr(state, (char *) mx->data, reply_name, reply_class)) + return (YUP); + } + + /* + * This machine is not listed as MX relay. + */ + if (msg_verbose) + msg_info("%s: I am not listed as MX relay", myname); + return (NOPE); +} + +/* permit_mx_primary - authorize primary MX relays */ + +static int permit_mx_primary(SMTPD_STATE *state, DNS_RR *mx_list, + const char *reply_name, const char *reply_class) +{ + const char *myname = "permit_mx_primary"; + DNS_RR *mx; + + if (msg_verbose) + msg_info("%s", myname); + + /* + * See if each best MX host has all IP addresses in + * permit_mx_backup_networks. + */ + for (mx = mx_list; mx != 0; mx = mx->next) { + if (!all_auth_mx_addr(state, (char *) mx->data, reply_name, reply_class)) + return (NOPE); + } + + /* + * All IP addresses of the best MX hosts are within + * permit_mx_backup_networks. + */ + return (YUP); +} + +/* permit_mx_backup - permit use of me as MX backup for recipient domain */ + +static int permit_mx_backup(SMTPD_STATE *state, const char *recipient, + const char *reply_name, const char *reply_class) +{ + const char *myname = "permit_mx_backup"; + const RESOLVE_REPLY *reply; + const char *domain; + const char *adomain; + DNS_RR *mx_list; + DNS_RR *middle; + DNS_RR *rest; + int dns_status; + + if (msg_verbose) + msg_info("%s: %s", myname, recipient); + + /* + * Resolve the address. + */ + reply = smtpd_resolve_addr(state->sender, recipient); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, recipient); + + /* + * For backwards compatibility, emulate permit_auth_destination. However, + * old permit_mx_backup implementations allow source routing with local + * address class. + */ + if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) + return (SMTPD_CHECK_OK); + domain += 1; +#if 0 + if (reply->flags & RESOLVE_CLASS_LOCAL) + return (SMTPD_CHECK_OK); +#endif + if (var_allow_untrust_route == 0 && (reply->flags & RESOLVE_FLAG_ROUTED)) + return (SMTPD_CHECK_DUNNO); + if (reply->flags & RESOLVE_CLASS_FINAL) + return (SMTPD_CHECK_OK); + if (reply->flags & RESOLVE_CLASS_RELAY) { + if (warn_compat_break_relay_domains) + msg_info("using backwards-compatible default setting " + VAR_RELAY_DOMAINS "=$mydestination to accept mail " + "for domain \"%s\"", domain); + return (SMTPD_CHECK_OK); + } + if (msg_verbose) + msg_info("%s: not local: %s", myname, recipient); + + /* + * Skip numerical forms that didn't match the local system. + */ + if (domain[0] == '[' && domain[strlen(domain) - 1] == ']') + return (SMTPD_CHECK_DUNNO); + + /* + * Fix 20140924: convert domain to ASCII. + */ +#ifndef NO_EAI + if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", domain, adomain); + domain = adomain; + } +#endif + + /* + * Look up the list of MX host names for this domain. If no MX host is + * found, perhaps it is a CNAME for the local machine. Clients aren't + * supposed to send CNAMEs in SMTP commands, but it happens anyway. If we + * can't look up the destination, play safe and turn reject into defer. + */ + dns_status = dns_lookup(domain, T_MX, 0, &mx_list, + (VSTRING *) 0, (VSTRING *) 0); +#if 0 + if (dns_status == DNS_NOTFOUND) + return (has_my_addr(state, domain, reply_name, reply_class) ? + SMTPD_CHECK_OK : SMTPD_CHECK_DUNNO); +#endif + if (dns_status != DNS_OK) { /* incl. DNS_INVAL */ + /* We don't special-case DNS_NULLMX. */ + if (dns_status == DNS_RETRY || dns_status == DNS_POLICY) + DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY, + 450, "4.4.4", + "<%s>: %s rejected: Unable to look up mail " + "exchanger information: %s", + reply_name, reply_class, + dns_status == DNS_POLICY ? + "DNS reply filter policy" : + dns_strerror(dns_get_h_errno())); + return (SMTPD_CHECK_DUNNO); + } + + /* + * Separate MX list into primaries and backups. + */ + mx_list = dns_rr_sort(mx_list, dns_rr_compare_pref_any); + for (middle = mx_list; /* see below */ ; middle = rest) { + rest = middle->next; + if (rest == 0) + break; + if (rest->pref != mx_list->pref) { + middle->next = 0; + break; + } + } + /* postcondition: middle->next = 0, rest may be 0. */ + +#define PERMIT_MX_BACKUP_RETURN(x) do { \ + middle->next = rest; \ + dns_rr_free(mx_list); \ + return (x); \ + } while (0) + + /* + * First, see if we match any of the primary MX servers. + */ + if (i_am_mx(state, mx_list, reply_name, reply_class)) + PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO); + + /* + * Then, see if we match any of the backup MX servers. + */ + if (rest == 0 || !i_am_mx(state, rest, reply_name, reply_class)) + PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO); + + /* + * Optionally, see if the primary MX hosts are in a restricted list of + * networks. + */ + if (*var_perm_mx_networks + && !permit_mx_primary(state, mx_list, reply_name, reply_class)) + PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_DUNNO); + + /* + * The destination passed all requirements. + */ + PERMIT_MX_BACKUP_RETURN(SMTPD_CHECK_OK); +} + +/* reject_non_fqdn_address - fail if address is not in fqdn form */ + +static int reject_non_fqdn_address(SMTPD_STATE *state, char *addr, + char *reply_name, char *reply_class) +{ + const char *myname = "reject_non_fqdn_address"; + char *domain; + char *test_dom; + int stat; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + /* + * Locate the domain information. + */ + if ((domain = strrchr(addr, '@')) != 0) + domain++; + else + domain = ""; + + /* + * Skip forms that we can't handle yet. + */ + if (domain[0] == '[' && domain[strlen(domain) - 1] == ']') + return (SMTPD_CHECK_DUNNO); + + /* + * Truncate names ending in dot but not dot-dot. + */ + test_dom = dup_if_truncate(domain); + + /* + * Validate the domain. For backwards compatibility, permit non-ASCII + * names only when the client requested SMTPUTF8 support. + */ + if (!*test_dom || !valid_utf8_hostname(state->flags & SMTPD_FLAG_SMTPUTF8, + test_dom, DONT_GRIPE) || !strchr(test_dom, '.')) + stat = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_non_fqdn_code, "4.5.2", + "<%s>: %s rejected: need fully-qualified address", + reply_name, reply_class); + else + stat = SMTPD_CHECK_DUNNO; + + /* + * Cleanup. + */ + if (test_dom != domain) + myfree(test_dom); + + return (stat); +} + +/* reject_unknown_address - fail if address does not resolve */ + +static int reject_unknown_address(SMTPD_STATE *state, const char *addr, + const char *reply_name, const char *reply_class) +{ + const char *myname = "reject_unknown_address"; + const RESOLVE_REPLY *reply; + const char *domain; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + /* + * Resolve the address. + */ + reply = smtpd_resolve_addr(strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + state->recipient : state->sender, addr); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, addr); + + /* + * Skip local destinations and non-DNS forms. + */ + if ((domain = strrchr(CONST_STR(reply->recipient), '@')) == 0) + return (SMTPD_CHECK_DUNNO); + domain += 1; + if (reply->flags & RESOLVE_CLASS_FINAL) + return (SMTPD_CHECK_DUNNO); + if (domain[0] == '[' && domain[strlen(domain) - 1] == ']') + return (SMTPD_CHECK_DUNNO); + + /* + * Look up the name in the DNS. + */ + return (reject_unknown_mailhost(state, domain, reply_name, reply_class)); +} + +/* reject_unverified_address - fail if address bounces */ + +static int reject_unverified_address(SMTPD_STATE *state, const char *addr, + const char *reply_name, const char *reply_class, + int unv_addr_dcode, int unv_addr_rcode, + int unv_addr_tf_act, + const char *alt_reply) +{ + const char *myname = "reject_unverified_address"; + VSTRING *why = vstring_alloc(10); + int rqst_status = SMTPD_CHECK_DUNNO; + int rcpt_status; + int verify_status; + int count; + int reject_code = 0; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + /* + * Verify the address. Don't waste too much of their or our time. + */ + for (count = 0; /* see below */ ; /* see below */ ) { + verify_status = verify_clnt_query(addr, &rcpt_status, why); + if (verify_status != VRFY_STAT_OK || rcpt_status != DEL_RCPT_STAT_TODO) + break; + if (++count >= var_verify_poll_count) + break; + sleep(var_verify_poll_delay); + } + if (verify_status != VRFY_STAT_OK) { + msg_warn("%s service failure", var_verify_service); + rqst_status = + DEFER_IF_PERMIT2(unv_addr_tf_act, state, MAIL_ERROR_POLICY, + 450, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + SND_DSN : "4.1.1", + "<%s>: %s rejected: address verification problem", + reply_name, reply_class); + } else { + switch (rcpt_status) { + default: + msg_warn("unknown address verification status %d", rcpt_status); + break; + case DEL_RCPT_STAT_TODO: + case DEL_RCPT_STAT_DEFER: + reject_code = unv_addr_dcode; + break; + case DEL_RCPT_STAT_OK: + break; + case DEL_RCPT_STAT_BOUNCE: + reject_code = unv_addr_rcode; + break; + } + if (reject_code >= 400 && *alt_reply) + vstring_strcpy(why, alt_reply); + switch (reject_code / 100) { + case 2: + break; + case 4: + rqst_status = + DEFER_IF_PERMIT3(unv_addr_tf_act, state, MAIL_ERROR_POLICY, + reject_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + SND_DSN : "4.1.1", + "<%s>: %s rejected: unverified address: %.250s", + reply_name, reply_class, STR(why)); + break; + default: + if (reject_code != 0) + rqst_status = + smtpd_check_reject(state, MAIL_ERROR_POLICY, + reject_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + SND_DSN : "4.1.1", + "<%s>: %s rejected: undeliverable address: %s", + reply_name, reply_class, STR(why)); + break; + } + } + vstring_free(why); + return (rqst_status); +} + +/* can_delegate_action - can we delegate this to the cleanup server */ + +#ifndef TEST + +static int not_in_client_helo(SMTPD_STATE *, const char *, const char *, const char *); + +static int can_delegate_action(SMTPD_STATE *state, const char *table, + const char *action, const char *reply_class) +{ + + /* + * If we're not using the cleanup server, then there is no way that we + * can support actions such as FILTER or HOLD that are delegated to the + * cleanup server. + */ + if (USE_SMTPD_PROXY(state)) { + msg_warn("access table %s: with %s specified, action %s is unavailable", + table, VAR_SMTPD_PROXY_FILT, action); + return (0); + } + + /* + * ETRN does not receive mail so we can't store queue file records. + */ + if (strcmp(state->where, SMTPD_CMD_ETRN) == 0) { + msg_warn("access table %s: action %s is unavailable in %s", + table, action, VAR_ETRN_CHECKS); + return (0); + } + return (not_in_client_helo(state, table, action, reply_class)); +} + +/* not_in_client_helo - not in client or helo restriction context */ + +static int not_in_client_helo(SMTPD_STATE *state, const char *table, + const char *action, + const char *unused_reply_class) +{ + + /* + * If delay_reject=no, then client and helo restrictions take effect + * immediately, outside any particular mail transaction context. For + * example, rejecting HELO does not affect subsequent mail deliveries. + * Thus, if delay_reject=no, client and helo actions such as FILTER or + * HOLD also should not affect subsequent mail deliveries. Hmm... + * + * XXX If the MAIL FROM command is rejected then we have to reset access map + * side effects such as FILTER. + */ + if (state->sender == 0) { + msg_warn("access table %s: with %s=%s, " + "action %s is always skipped in %s or %s restrictions", + table, VAR_SMTPD_DELAY_REJECT, CONFIG_BOOL_NO, + action, SMTPD_NAME_CLIENT, SMTPD_NAME_HELO); + /* XXX What about ETRN? */ + return (0); + } + return (1); +} + +#endif + +/* check_table_result - translate table lookup result into pass/reject */ + +static int check_table_result(SMTPD_STATE *state, const char *table, + const char *value, const char *datum, + const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_table_result"; + int code; + ARGV *restrictions; + jmp_buf savebuf; + int status; + const char *cmd_text; + int cmd_len; + static char def_dsn[] = "5.7.1"; + DSN_SPLIT dp; + static VSTRING *buf; + +#ifdef DELAY_ACTION + int defer_delay; + +#endif + + if (buf == 0) + buf = vstring_alloc(10); + + /* + * Parse into command and text. Do not change the input. + */ + cmd_text = value + strcspn(value, " \t"); + cmd_len = cmd_text - value; + vstring_strncpy(buf, value, cmd_len); + while (*cmd_text && ISSPACE(*cmd_text)) + cmd_text++; + + if (msg_verbose) + msg_info("%s: %s %s %s", myname, table, value, datum); + +#define STREQUAL(x,y,l) (strncasecmp((x), (y), (l)) == 0 && (y)[l] == 0) + + /* + * DUNNO means skip this table. Silently ignore optional text. + */ + if (STREQUAL(value, "DUNNO", cmd_len)) + return (SMTPD_CHECK_DUNNO); + + /* + * REJECT means NO. Use optional text or generate a generic error + * response. + */ + if (STREQUAL(value, "REJECT", cmd_len)) { + dsn_split(&dp, "5.7.1", cmd_text); + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_map_reject_code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "<%s>: %s rejected: %s", + reply_name, reply_class, + *dp.text ? dp.text : "Access denied")); + } + + /* + * DEFER means "try again". Use optional text or generate a generic error + * response. + */ + if (STREQUAL(value, "DEFER", cmd_len)) { + dsn_split(&dp, "4.7.1", cmd_text); + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_map_defer_code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "<%s>: %s rejected: %s", + reply_name, reply_class, + *dp.text ? dp.text : "Access denied")); + } +#ifndef SHUT_RDWR +#define SHUT_RDWR 2 +#endif + + /* + * HANGUP. Text is optional. Drop the connection without sending any + * reply. + * + * Note: this is an unsupported test feature. No attempt is made to maintain + * compatibility between successive versions. + */ + if (STREQUAL(value, "HANGUP", cmd_len)) { + shutdown(vstream_fileno(state->client), SHUT_RDWR); + log_whatsup(state, "hangup", cmd_text); + vstream_longjmp(state->client, SMTP_ERR_QUIET); + } + + /* + * INFO. Text is optional. + */ + if (STREQUAL(value, "INFO", cmd_len)) { + log_whatsup(state, "info", cmd_text); + return (SMTPD_CHECK_DUNNO); + } + + /* + * WARN. Text is optional. + */ + if (STREQUAL(value, "WARN", cmd_len)) { + log_whatsup(state, "warn", cmd_text); + return (SMTPD_CHECK_DUNNO); + } + + /* + * FILTER means deliver to content filter. But we may still change our + * mind, and reject/discard the message for other reasons. + */ + if (STREQUAL(value, "FILTER", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "FILTER", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + if (*cmd_text == 0) { + msg_warn("access table %s entry \"%s\" has FILTER entry without value", + table, datum); + return (SMTPD_CHECK_DUNNO); + } else if (strchr(cmd_text, ':') == 0) { + msg_warn("access table %s entry \"%s\" requires transport:destination", + table, datum); + return (SMTPD_CHECK_DUNNO); + } else { + vstring_sprintf(error_text, "<%s>: %s triggers FILTER %s", + reply_name, reply_class, cmd_text); + log_whatsup(state, "filter", STR(error_text)); +#ifndef TEST + UPDATE_STRING(state->saved_filter, cmd_text); +#endif + return (SMTPD_CHECK_DUNNO); + } + } + + /* + * HOLD means deliver later. But we may still change our mind, and + * reject/discard the message for other reasons. + */ + if (STREQUAL(value, "HOLD", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "HOLD", reply_class) == 0 + || (state->saved_flags & CLEANUP_FLAG_HOLD)) + return (SMTPD_CHECK_DUNNO); +#endif + vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class, + *cmd_text ? cmd_text : "triggers HOLD action"); + log_whatsup(state, "hold", STR(error_text)); +#ifndef TEST + state->saved_flags |= CLEANUP_FLAG_HOLD; +#endif + return (SMTPD_CHECK_DUNNO); + } + + /* + * DELAY means deliver later. But we may still change our mind, and + * reject/discard the message for other reasons. + * + * This feature is deleted because it has too many problems. 1) It does not + * work on some remote file systems; 2) mail will be delivered anyway + * with "sendmail -q" etc.; 3) while the mail is queued it bogs down the + * deferred queue scan with huge amounts of useless disk I/O operations. + */ +#ifdef DELAY_ACTION + if (STREQUAL(value, "DELAY", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "DELAY", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + if (*cmd_text == 0) { + msg_warn("access table %s entry \"%s\" has DELAY entry without value", + table, datum); + return (SMTPD_CHECK_DUNNO); + } + if (conv_time(cmd_text, &defer_delay, 's') == 0) { + msg_warn("access table %s entry \"%s\" has invalid DELAY argument \"%s\"", + table, datum, cmd_text); + return (SMTPD_CHECK_DUNNO); + } + vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class, + *cmd_text ? cmd_text : "triggers DELAY action"); + log_whatsup(state, "delay", STR(error_text)); +#ifndef TEST + state->saved_delay = defer_delay; +#endif + return (SMTPD_CHECK_DUNNO); + } +#endif + + /* + * DISCARD means silently discard and claim successful delivery. + */ + if (STREQUAL(value, "DISCARD", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "DISCARD", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + vstring_sprintf(error_text, "<%s>: %s %s", reply_name, reply_class, + *cmd_text ? cmd_text : "triggers DISCARD action"); + log_whatsup(state, "discard", STR(error_text)); +#ifndef TEST + state->saved_flags |= CLEANUP_FLAG_DISCARD; + state->discard = 1; +#endif + return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name, + "from %s", table)); + } + + /* + * REDIRECT means deliver to designated recipient. But we may still + * change our mind, and reject/discard the message for other reasons. + */ + if (STREQUAL(value, "REDIRECT", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "REDIRECT", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + if (strchr(cmd_text, '@') == 0) { + msg_warn("access table %s entry \"%s\" requires user@domain target", + table, datum); + return (SMTPD_CHECK_DUNNO); + } else { + vstring_sprintf(error_text, "<%s>: %s triggers REDIRECT %s", + reply_name, reply_class, cmd_text); + log_whatsup(state, "redirect", STR(error_text)); +#ifndef TEST + UPDATE_STRING(state->saved_redirect, cmd_text); +#endif + return (SMTPD_CHECK_DUNNO); + } + } + + /* + * BCC means deliver to designated recipient. But we may still change our + * mind, and reject/discard the message for other reasons. + */ + if (STREQUAL(value, "BCC", cmd_len)) { +#ifndef TEST + if (can_delegate_action(state, table, "BCC", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + if (strchr(cmd_text, '@') == 0) { + msg_warn("access table %s entry \"%s\" requires user@domain target", + table, datum); + return (SMTPD_CHECK_DUNNO); + } else { + vstring_sprintf(error_text, "<%s>: %s triggers BCC %s", + reply_name, reply_class, cmd_text); + log_whatsup(state, "bcc", STR(error_text)); +#ifndef TEST + if (state->saved_bcc == 0) + state->saved_bcc = argv_alloc(1); + argv_add(state->saved_bcc, cmd_text, (char *) 0); +#endif + return (SMTPD_CHECK_DUNNO); + } + } + + /* + * DEFER_IF_PERMIT changes "permit" into "maybe". Use optional text or + * generate a generic error response. + */ + if (STREQUAL(value, DEFER_IF_PERMIT, cmd_len)) { + dsn_split(&dp, "4.7.1", cmd_text); + return (DEFER_IF_PERMIT3(DEFER_IF_PERMIT_ACT, state, MAIL_ERROR_POLICY, + var_map_defer_code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), reply_class), + "<%s>: %s rejected: %s", + reply_name, reply_class, + *dp.text ? dp.text : "Service unavailable")); + } + + /* + * DEFER_IF_REJECT changes "reject" into "maybe". Use optional text or + * generate a generic error response. + */ + if (STREQUAL(value, DEFER_IF_REJECT, cmd_len)) { + dsn_split(&dp, "4.7.1", cmd_text); + DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY, + var_map_defer_code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), reply_class), + "<%s>: %s rejected: %s", + reply_name, reply_class, + *dp.text ? dp.text : "Service unavailable"); + return (SMTPD_CHECK_DUNNO); + } + + /* + * PREPEND prepends the specified message header text. + */ + if (STREQUAL(value, "PREPEND", cmd_len)) { +#ifndef TEST + /* XXX what about ETRN. */ + if (not_in_client_helo(state, table, "PREPEND", reply_class) == 0) + return (SMTPD_CHECK_DUNNO); +#endif + if (strcmp(state->where, SMTPD_AFTER_EOM) == 0) { + msg_warn("access table %s: action PREPEND must be used before %s", + table, VAR_EOD_CHECKS); + return (SMTPD_CHECK_DUNNO); + } + if (*cmd_text == 0 || is_header(cmd_text) == 0) { + msg_warn("access table %s entry \"%s\" requires header: text", + table, datum); + return (SMTPD_CHECK_DUNNO); + } else { + if (state->prepend == 0) + state->prepend = argv_alloc(1); + argv_add(state->prepend, cmd_text, (char *) 0); + return (SMTPD_CHECK_DUNNO); + } + } + + /* + * All-numeric result probably means OK - some out-of-band authentication + * mechanism uses this as time stamp. + */ + if (alldig(value)) + return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name, + "from %s", table)); + + /* + * 4xx or 5xx means NO as well. smtpd_check_reject() will validate the + * response status code. + * + * If the caller specifies an RFC 3463 enhanced status code, put it + * immediately after the SMTP status code as described in RFC 2034. + */ + if (cmd_len == 3 && *cmd_text + && (value[0] == '4' || value[0] == '5') + && ISDIGIT(value[1]) && ISDIGIT(value[2])) { + code = atoi(value); + def_dsn[0] = value[0]; + dsn_split(&dp, def_dsn, cmd_text); + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, + code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "<%s>: %s rejected: %s", + reply_name, reply_class, + *dp.text ? dp.text : "Access denied")); + } + + /* + * OK or RELAY means YES. Ignore trailing text. + */ + if (STREQUAL(value, "OK", cmd_len) || STREQUAL(value, "RELAY", cmd_len)) + return (smtpd_acl_permit(state, STR(buf), reply_class, reply_name, + "from %s", table)); + + /* + * Unfortunately, maps must be declared ahead of time so they can be + * opened before we go to jail. We could insist that the RHS can only + * contain a pre-defined restriction class name, but that would be too + * restrictive. Instead we warn if an access table references any map. + * + * XXX Don't use passwd files or address rewriting maps as access tables. + */ + if (strchr(value, ':') != 0) { + msg_warn("access table %s has entry with lookup table: %s", + table, value); + msg_warn("do not specify lookup tables inside SMTPD access maps"); + msg_warn("define a restriction class and specify its name instead."); + reject_server_error(state); + } + + /* + * Don't get carried away with recursion. + */ + if (state->recursion > 100) { + msg_warn("access table %s entry %s causes unreasonable recursion", + table, value); + reject_server_error(state); + } + + /* + * Recursively evaluate the restrictions given in the right-hand side. In + * the dark ages, an empty right-hand side meant OK. Make some + * discouraging comments. + * + * XXX Jump some hoops to avoid a minute memory leak in case of a file + * configuration error. + */ +#define ADDROF(x) ((char *) &(x)) + + restrictions = argv_splitq(value, CHARS_COMMA_SP, CHARS_BRACE); + memcpy(ADDROF(savebuf), ADDROF(smtpd_check_buf), sizeof(savebuf)); + status = setjmp(smtpd_check_buf); + if (status != 0) { + argv_free(restrictions); + memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf), + sizeof(smtpd_check_buf)); + longjmp(smtpd_check_buf, status); + } + if (restrictions->argc == 0) { + msg_warn("access table %s entry %s has empty value", + table, value); + status = SMTPD_CHECK_OK; + } else { + status = generic_checks(state, restrictions, reply_name, + reply_class, def_acl); + } + argv_free(restrictions); + memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf), sizeof(smtpd_check_buf)); + return (status); +} + +/* check_access - table lookup without substring magic */ + +static int check_access(SMTPD_STATE *state, const char *table, const char *name, + int flags, int *found, const char *reply_name, + const char *reply_class, const char *def_acl) +{ + const char *myname = "check_access"; + const char *value; + MAPS *maps; + +#define CHK_ACCESS_RETURN(x,y) \ + { *found = y; return(x); } +#define FULL 0 +#define PARTIAL DICT_FLAG_FIXED +#define FOUND 1 +#define MISSED 0 + + if (msg_verbose) + msg_info("%s: %s", myname, name); + + if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) { + msg_warn("%s: unexpected dictionary: %s", myname, table); + value = "451 4.3.5 Server configuration error"; + CHK_ACCESS_RETURN(check_table_result(state, table, value, name, + reply_name, reply_class, + def_acl), FOUND); + } + if ((value = maps_find(maps, name, flags)) != 0) + CHK_ACCESS_RETURN(check_table_result(state, table, value, name, + reply_name, reply_class, + def_acl), FOUND); + if (maps->error != 0) { + /* Warning is already logged. */ + value = "451 4.3.5 Server configuration error"; + CHK_ACCESS_RETURN(check_table_result(state, table, value, name, + reply_name, reply_class, + def_acl), FOUND); + } + CHK_ACCESS_RETURN(SMTPD_CHECK_DUNNO, MISSED); +} + +/* check_domain_access - domainname-based table lookup */ + +static int check_domain_access(SMTPD_STATE *state, const char *table, + const char *domain, int flags, + int *found, const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_domain_access"; + const char *name; + const char *next; + const char *value; + MAPS *maps; + int maybe_numerical = 1; + + if (msg_verbose) + msg_info("%s: %s", myname, domain); + + /* + * Try the name and its parent domains. Including top-level domains. + * + * Helo names can end in ".". The test below avoids lookups of the empty + * key, because Berkeley DB cannot deal with it. [Victor Duchovni, Morgan + * Stanley]. + * + * TODO(wietse) move to mail_domain_find library module. + */ +#define CHK_DOMAIN_RETURN(x,y) { *found = y; return(x); } + + if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) { + msg_warn("%s: unexpected dictionary: %s", myname, table); + value = "451 4.3.5 Server configuration error"; + CHK_DOMAIN_RETURN(check_table_result(state, table, value, + domain, reply_name, reply_class, + def_acl), FOUND); + } + for (name = domain; *name != 0; name = next) { + if ((value = maps_find(maps, name, flags)) != 0) + CHK_DOMAIN_RETURN(check_table_result(state, table, value, + domain, reply_name, reply_class, + def_acl), FOUND); + if (maps->error != 0) { + /* Warning is already logged. */ + value = "451 4.3.5 Server configuration error"; + CHK_DOMAIN_RETURN(check_table_result(state, table, value, + domain, reply_name, reply_class, + def_acl), FOUND); + } + /* Don't apply subdomain magic to numerical hostnames. */ + if (maybe_numerical + && (maybe_numerical = valid_hostaddr(domain, DONT_GRIPE)) != 0) + break; + if ((next = strchr(name + 1, '.')) == 0) + break; + if (access_parent_style == MATCH_FLAG_PARENT) + next += 1; + flags = PARTIAL; + } + CHK_DOMAIN_RETURN(SMTPD_CHECK_DUNNO, MISSED); +} + +/* check_addr_access - address-based table lookup */ + +static int check_addr_access(SMTPD_STATE *state, const char *table, + const char *address, int flags, + int *found, const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_addr_access"; + char *addr; + const char *value; + MAPS *maps; + int delim; + + if (msg_verbose) + msg_info("%s: %s", myname, address); + + /* + * Try the address and its parent networks. + * + * TODO(wietse) move to mail_ipaddr_find library module. + */ +#define CHK_ADDR_RETURN(x,y) { *found = y; return(x); } + + addr = STR(vstring_strcpy(error_text, address)); +#ifdef HAS_IPV6 + if (strchr(addr, ':') != 0) + delim = ':'; + else +#endif + delim = '.'; + + if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) { + msg_warn("%s: unexpected dictionary: %s", myname, table); + value = "451 4.3.5 Server configuration error"; + CHK_ADDR_RETURN(check_table_result(state, table, value, address, + reply_name, reply_class, + def_acl), FOUND); + } + do { + if ((value = maps_find(maps, addr, flags)) != 0) + CHK_ADDR_RETURN(check_table_result(state, table, value, address, + reply_name, reply_class, + def_acl), FOUND); + if (maps->error != 0) { + /* Warning is already logged. */ + value = "451 4.3.5 Server configuration error"; + CHK_ADDR_RETURN(check_table_result(state, table, value, address, + reply_name, reply_class, + def_acl), FOUND); + } + flags = PARTIAL; + } while (split_at_right(addr, delim)); + + CHK_ADDR_RETURN(SMTPD_CHECK_DUNNO, MISSED); +} + +/* check_namadr_access - OK/FAIL based on host name/address lookup */ + +static int check_namadr_access(SMTPD_STATE *state, const char *table, + const char *name, const char *addr, + int flags, int *found, + const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_namadr_access"; + int status; + + if (msg_verbose) + msg_info("%s: name %s addr %s", myname, name, addr); + + /* + * Look up the host name, or parent domains thereof. XXX A domain + * wildcard may pre-empt a more specific address table entry. + */ + if ((status = check_domain_access(state, table, name, flags, + found, reply_name, reply_class, + def_acl)) != 0 || *found) + return (status); + + /* + * Look up the network address, or parent networks thereof. + */ + if ((status = check_addr_access(state, table, addr, flags, + found, reply_name, reply_class, + def_acl)) != 0 || *found) + return (status); + + /* + * Undecided when the host was not found. + */ + return (SMTPD_CHECK_DUNNO); +} + +/* check_server_access - access control by server host name or address */ + +static int check_server_access(SMTPD_STATE *state, const char *table, + const char *name, + int type, + const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_server_access"; + const char *domain; + const char *adomain; + int dns_status; + DNS_RR *server_list; + DNS_RR *server; + int found = 0; + MAI_HOSTADDR_STR addr_string; + int aierr; + struct addrinfo *res0; + struct addrinfo *res; + int status; + const INET_PROTO_INFO *proto_info; + + /* + * Sanity check. + */ + if (type != T_MX && type != T_NS && type != T_A +#ifdef HAS_IPV6 + && type != T_AAAA +#endif + ) + msg_panic("%s: unexpected resource type \"%s\" in request", + myname, dns_strtype(type)); + + if (msg_verbose) + msg_info("%s: %s %s", myname, dns_strtype(type), name); + + /* + * Skip over local-part. + */ + if ((domain = strrchr(name, '@')) != 0) + domain += 1; + else + domain = name; + + /* + * Treat an address literal as its own MX server, just like we treat a + * name without MX record as its own MX server. There is, however, no + * applicable NS server equivalent. + */ + if (*domain == '[') { + char *saved_addr; + const char *bare_addr; + ssize_t len; + + if (type != T_A && type != T_MX) + return (SMTPD_CHECK_DUNNO); + len = strlen(domain); + if (domain[len - 1] != ']') + return (SMTPD_CHECK_DUNNO); + /* Memory leak alert: no early returns after this point. */ + saved_addr = mystrndup(domain + 1, len - 2); + if ((bare_addr = valid_mailhost_addr(saved_addr, DONT_GRIPE)) == 0) + status = SMTPD_CHECK_DUNNO; + else + status = check_addr_access(state, table, bare_addr, FULL, + &found, reply_name, reply_class, + def_acl); + myfree(saved_addr); + return (status); + } + + /* + * Fix 20140924: convert domain to ASCII. + */ +#ifndef NO_EAI + if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", domain, adomain); + domain = adomain; + } +#endif + + /* + * If the request is type A or AAAA, fabricate an MX record that points + * to the domain name itself, and skip name-based access control. + * + * If the domain name does not exist then we apply no restriction. + * + * If the domain name exists but no MX record exists, fabricate an MX record + * that points to the domain name itself. + * + * If the domain name exists but no NS record exists, look up parent domain + * NS records. + * + * XXX 20150707 Work around broken DNS servers that reply with NXDOMAIN + * instead of "no data". + */ + if (type == T_A +#ifdef HAS_IPV6 + || type == T_AAAA +#endif + ) { + server_list = dns_rr_create(domain, domain, T_MX, C_IN, 0, 0, + domain, strlen(domain) + 1); + } else { + dns_status = dns_lookup(domain, type, 0, &server_list, + (VSTRING *) 0, (VSTRING *) 0); + if (dns_status == DNS_NULLMX) + return (SMTPD_CHECK_DUNNO); + if (dns_status == DNS_NOTFOUND /* Not: h_errno == NO_DATA */ ) { + if (type == T_MX) { + server_list = dns_rr_create(domain, domain, type, C_IN, 0, 0, + domain, strlen(domain) + 1); + dns_status = DNS_OK; + } else if (type == T_NS /* && h_errno == NO_DATA */ ) { + while ((domain = strchr(domain, '.')) != 0 && domain[1]) { + domain += 1; + dns_status = dns_lookup(domain, type, 0, &server_list, + (VSTRING *) 0, (VSTRING *) 0); + if (dns_status != DNS_NOTFOUND /* || h_errno != NO_DATA */ ) + break; + } + } + } + if (dns_status != DNS_OK) { + msg_warn("Unable to look up %s host for %s: %s", dns_strtype(type), + domain && domain[1] ? domain : name, + dns_status == DNS_POLICY ? + "DNS reply filter policy" : + dns_strerror(dns_get_h_errno())); + return (SMTPD_CHECK_DUNNO); + } + } + + /* + * No bare returns after this point or we have a memory leak. + */ +#define CHECK_SERVER_RETURN(x) { dns_rr_free(server_list); return(x); } + + /* + * Check the hostnames first, then the addresses. + */ + proto_info = inet_proto_info(); + for (server = server_list; server != 0; server = server->next) { + if (msg_verbose) + msg_info("%s: %s hostname check: %s", + myname, dns_strtype(type), (char *) server->data); + if (valid_hostaddr((char *) server->data, DONT_GRIPE)) { + if ((status = check_addr_access(state, table, (char *) server->data, + FULL, &found, reply_name, reply_class, + def_acl)) != 0 || found) + CHECK_SERVER_RETURN(status); + continue; + } + if (type != T_A && type != T_AAAA + && ((status = check_domain_access(state, table, (char *) server->data, + FULL, &found, reply_name, reply_class, + def_acl)) != 0 || found)) + CHECK_SERVER_RETURN(status); + if ((aierr = hostname_to_sockaddr((char *) server->data, + (char *) 0, 0, &res0)) != 0) { + if (type != T_A && type != T_AAAA) + msg_warn("Unable to look up %s host %s for %s %s: %s", + dns_strtype(type), (char *) server->data, + reply_class, reply_name, MAI_STRERROR(aierr)); + continue; + } + /* Now we must also free the addrinfo result. */ + if (msg_verbose) + msg_info("%s: %s host address check: %s", + myname, dns_strtype(type), (char *) server->data); + for (res = res0; res != 0; res = res->ai_next) { + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + if (msg_verbose) + msg_info("skipping address family %d for host %s", + res->ai_family, server->data); + continue; + } + SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen, + &addr_string, (MAI_SERVPORT_STR *) 0, 0); + status = check_addr_access(state, table, addr_string.buf, FULL, + &found, reply_name, reply_class, + def_acl); + if (status != 0 || found) { + freeaddrinfo(res0); /* 200412 */ + CHECK_SERVER_RETURN(status); + } + } + freeaddrinfo(res0); /* 200412 */ + } + CHECK_SERVER_RETURN(SMTPD_CHECK_DUNNO); +} + +/* check_ccert_access - access for TLS clients by certificate fingerprint */ + +static int check_ccert_access(SMTPD_STATE *state, const char *acl_spec, + const char *def_acl) +{ + int result = SMTPD_CHECK_DUNNO; + +#ifdef USE_TLS + const char *myname = "check_ccert_access"; + int found; + const MAP_SEARCH *acl; + const char default_search[] = { + SMTPD_ACL_SEARCH_CODE_CERT_FPRINT, + SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT, + 0, + }; + const char *search_order; + + /* + * Look up the acl search list. If there is no ACL then we don't have a + * table to check. + */ + if ((acl = map_search_lookup(acl_spec)) == 0) { + msg_warn("See earlier parsing error messages for '%s", acl_spec); + return (smtpd_check_reject(state, MAIL_ERROR_SOFTWARE, 451, "4.3.5", + "Server configuration error")); + } + if ((search_order = acl->search_order) == 0) + search_order = default_search; + if (msg_verbose) + msg_info("%s: search_order length=%ld", + myname, (long) strlen(search_order)); + + /* + * When directly checking the fingerprint, it is OK if the issuing CA is + * not trusted. + */ + if (TLS_CERT_IS_PRESENT(state->tls_context)) { + const char *action; + const char *match_this; + const char *known_action; + + for (action = search_order; *action; action++) { + switch (*action) { + case SMTPD_ACL_SEARCH_CODE_CERT_FPRINT: + match_this = state->tls_context->peer_cert_fprint; + if (warn_compat_break_smtpd_tls_fpt_dgst) + msg_info("using backwards-compatible default setting " + VAR_SMTPD_TLS_FPT_DGST "=md5 to compute " + "certificate fingerprints"); + break; + case SMTPD_ACL_SEARCH_CODE_PKEY_FPRINT: + match_this = state->tls_context->peer_pkey_fprint; + if (warn_compat_break_smtpd_tls_fpt_dgst) + msg_info("using backwards-compatible default setting " + VAR_SMTPD_TLS_FPT_DGST "=md5 to compute " + "certificate fingerprints"); + break; + default: + known_action = str_name_code(search_actions, *action); + if (known_action == 0) + msg_panic("%s: unknown action #%d in '%s'", + myname, *action, acl_spec); + msg_warn("%s: unexpected action '%s' in '%s'", + myname, known_action, acl_spec); + return (smtpd_check_reject(state, MAIL_ERROR_SOFTWARE, + 451, "4.3.5", + "Server configuration error")); + } + if (msg_verbose) + msg_info("%s: look up %s %s", + myname, str_name_code(search_actions, *action), + match_this); + + /* + * Log the peer CommonName when access is denied. Non-printable + * characters will be neutered by smtpd_check_reject(). The SMTP + * client name and address are always syslogged as part of a + * "reject" event. XXX Should log the thing that is rejected + * (fingerprint etc.) or would that give away too much? + */ + result = check_access(state, acl->map_type_name, match_this, + DICT_FLAG_NONE, &found, + state->tls_context->peer_CN, + SMTPD_NAME_CCERT, def_acl); + if (result != SMTPD_CHECK_DUNNO) + break; + } + } else if (!var_smtpd_tls_ask_ccert) { + msg_warn("%s is requested, but \"%s = no\"", + CHECK_CCERT_ACL, VAR_SMTPD_TLS_ACERT); + } else { + if (msg_verbose) + msg_info("%s: no client certificate", myname); + } +#endif + return (result); +} + +/* check_sasl_access - access by SASL user name */ + +#ifdef USE_SASL_AUTH + +static int check_sasl_access(SMTPD_STATE *state, const char *table, + const char *def_acl) +{ + int result; + int unused_found; + char *sane_username = printable(mystrdup(state->sasl_username), '_'); + + result = check_access(state, table, state->sasl_username, + DICT_FLAG_NONE, &unused_found, sane_username, + SMTPD_NAME_SASL_USER, def_acl); + myfree(sane_username); + return (result); +} + +#endif + +/* check_mail_access - OK/FAIL based on mail address lookup */ + +static int check_mail_access(SMTPD_STATE *state, const char *table, + const char *addr, int *found, + const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "check_mail_access"; + const RESOLVE_REPLY *reply; + const char *value; + int lookup_strategy; + int status; + MAPS *maps; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + /* + * Resolve the address. + */ + reply = smtpd_resolve_addr(strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + state->recipient : state->sender, addr); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, addr); + + /* + * Garbage in, garbage out. Every address from rewrite_clnt_internal() + * and from resolve_clnt_query() must be fully qualified. + */ + if (strrchr(CONST_STR(reply->recipient), '@') == 0) { + msg_warn("%s: no @domain in address: %s", myname, + CONST_STR(reply->recipient)); + return (0); + } + + /* + * Source-routed (non-local or virtual) recipient addresses are too + * suspicious for returning an "OK" result. The complicated expression + * below was brought to you by the keyboard of Victor Duchovni, Morgan + * Stanley and hacked up a bit by Wietse. + */ +#define SUSPICIOUS(reply, reply_class) \ + (var_allow_untrust_route == 0 \ + && (reply->flags & RESOLVE_FLAG_ROUTED) \ + && strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0) + + /* + * Look up user+foo@domain if the address has an extension, user@domain + * otherwise. + */ + lookup_strategy = MA_FIND_FULL | MA_FIND_NOEXT | MA_FIND_DOMAIN + | MA_FIND_LOCALPART_AT + | (access_parent_style == MATCH_FLAG_PARENT ? + MA_FIND_PDMS : MA_FIND_PDDMDS); + + if ((maps = (MAPS *) htable_find(map_command_table, table)) == 0) { + msg_warn("%s: unexpected dictionary: %s", myname, table); + value = "451 4.3.5 Server configuration error"; + return (check_table_result(state, table, value, + CONST_STR(reply->recipient), + reply_name, reply_class, + def_acl)); + } + if ((value = mail_addr_find_strategy(maps, CONST_STR(reply->recipient), + (char **) 0, lookup_strategy)) != 0) { + *found = 1; + status = check_table_result(state, table, value, + CONST_STR(reply->recipient), + reply_name, reply_class, def_acl); + return (status == SMTPD_CHECK_OK && SUSPICIOUS(reply, reply_class) ? + SMTPD_CHECK_DUNNO : status); + } else if (maps->error != 0) { + /* Warning is already logged. */ + value = "451 4.3.5 Server configuration error"; + return (check_table_result(state, table, value, + CONST_STR(reply->recipient), + reply_name, reply_class, + def_acl)); + } + + /* + * Undecided when no match found. + */ + return (SMTPD_CHECK_DUNNO); +} + +/* Support for different DNSXL lookup results. */ + +static SMTPD_RBL_STATE dnsxl_stat_soft[1]; + +#define SMTPD_DNSXL_STAT_SOFT(dnsxl_res) ((dnsxl_res) == dnsxl_stat_soft) +#define SMTPD_DNXSL_STAT_HARD(dnsxl_res) ((dnsxl_res) == 0) +#define SMTPD_DNSXL_STAT_OK(dnsxl_res) \ + !(SMTPD_DNXSL_STAT_HARD(dnsxl_res) || SMTPD_DNSXL_STAT_SOFT(dnsxl_res)) + +/* rbl_pagein - look up an RBL lookup result */ + +static void *rbl_pagein(const char *query, void *unused_context) +{ + DNS_RR *txt_list; + VSTRING *why; + int dns_status; + SMTPD_RBL_STATE *rbl = 0; + DNS_RR *addr_list; + DNS_RR *rr; + DNS_RR *next; + VSTRING *buf; + int space_left; + + /* + * Do the query. If the DNS lookup produces no definitive reply, give the + * requestor the benefit of the doubt. We can't block all email simply + * because an RBL server is unavailable. + * + * Don't do this for AAAA records. Yet. + */ + why = vstring_alloc(10); + dns_status = dns_lookup(query, T_A, 0, &addr_list, (VSTRING *) 0, why); + if (dns_status != DNS_OK && dns_status != DNS_NOTFOUND) { + msg_warn("%s: RBL lookup error: %s", query, STR(why)); + rbl = dnsxl_stat_soft; + } + vstring_free(why); + if (dns_status != DNS_OK) + return ((void *) rbl); + + /* + * Save the result. Yes, we cache negative results as well as positive + * results. Concatenate multiple TXT records, up to some limit. + */ +#define RBL_TXT_LIMIT 500 + + rbl = (SMTPD_RBL_STATE *) mymalloc(sizeof(*rbl)); + dns_status = dns_lookup(query, T_TXT, 0, &txt_list, + (VSTRING *) 0, (VSTRING *) 0); + if (dns_status == DNS_OK) { + buf = vstring_alloc(1); + space_left = RBL_TXT_LIMIT; + for (rr = txt_list; rr != 0 && space_left > 0; rr = next) { + vstring_strncat(buf, rr->data, (int) rr->data_len > space_left ? + space_left : rr->data_len); + space_left = RBL_TXT_LIMIT - VSTRING_LEN(buf); + next = rr->next; + if (next && space_left > 3) { + vstring_strcat(buf, " / "); + space_left -= 3; + } + } + rbl->txt = vstring_export(buf); + dns_rr_free(txt_list); + } else { + if (dns_status == DNS_POLICY) + msg_warn("%s: TXT lookup error: %s", + query, "DNS reply filter drops all results"); + rbl->txt = 0; + } + rbl->a = addr_list; + return ((void *) rbl); +} + +/* rbl_pageout - discard an RBL lookup result */ + +static void rbl_pageout(void *data, void *unused_context) +{ + SMTPD_RBL_STATE *rbl = (SMTPD_RBL_STATE *) data; + + if (SMTPD_DNSXL_STAT_OK(rbl)) { + if (rbl->txt) + myfree(rbl->txt); + if (rbl->a) + dns_rr_free(rbl->a); + myfree((void *) rbl); + } +} + +/* rbl_byte_pagein - parse RBL reply pattern, save byte codes */ + +static void *rbl_byte_pagein(const char *query, void *unused_context) +{ + VSTRING *byte_codes = vstring_alloc(100); + char *saved_query = mystrdup(query); + char *saved_byte_codes; + char *err; + + if ((err = ip_match_parse(byte_codes, saved_query)) != 0) + msg_fatal("RBL reply error: %s", err); + saved_byte_codes = ip_match_save(byte_codes); + myfree(saved_query); + vstring_free(byte_codes); + return (saved_byte_codes); +} + +/* rbl_byte_pageout - discard parsed RBL reply byte codes */ + +static void rbl_byte_pageout(void *data, void *unused_context) +{ + myfree(data); +} + +/* rbl_expand_lookup - RBL specific $name expansion */ + +static const char *rbl_expand_lookup(const char *name, int mode, + void *context) +{ + SMTPD_RBL_EXPAND_CONTEXT *rbl_exp = (SMTPD_RBL_EXPAND_CONTEXT *) context; + SMTPD_STATE *state = rbl_exp->state; + +#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0) + + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(10); + + if (msg_verbose > 1) + msg_info("rbl_expand_lookup: ${%s}", name); + + /* + * Be sure to return NULL only for non-existent names. + */ + if (STREQ(name, MAIL_ATTR_RBL_CODE)) { + vstring_sprintf(state->expand_buf, "%d", var_maps_rbl_code); + return (STR(state->expand_buf)); + } else if (STREQ(name, MAIL_ATTR_RBL_DOMAIN)) { + return (rbl_exp->domain); + } else if (STREQ(name, MAIL_ATTR_RBL_REASON)) { + return (rbl_exp->txt); + } else if (STREQ(name, MAIL_ATTR_RBL_TXT)) {/* LaMont compat */ + return (rbl_exp->txt); + } else if (STREQ(name, MAIL_ATTR_RBL_WHAT)) { + return (rbl_exp->what); + } else if (STREQ(name, MAIL_ATTR_RBL_CLASS)) { + return (rbl_exp->class); + } else { + return (smtpd_expand_lookup(name, mode, (void *) state)); + } +} + +/* rbl_reject_reply - format reply after RBL reject */ + +static int rbl_reject_reply(SMTPD_STATE *state, const SMTPD_RBL_STATE *rbl, + const char *rbl_domain, + const char *what, + const char *reply_class) +{ + const char *myname = "rbl_reject_reply"; + VSTRING *why = 0; + const char *template = 0; + SMTPD_RBL_EXPAND_CONTEXT rbl_exp; + int result; + DSN_SPLIT dp; + int code; + + /* + * Use the server-specific reply template or use the default one. + */ + if (*var_rbl_reply_maps) { + template = maps_find(rbl_reply_maps, rbl_domain, DICT_FLAG_NONE); + if (rbl_reply_maps->error) + reject_server_error(state); + } + why = vstring_alloc(100); + rbl_exp.state = state; + rbl_exp.domain = mystrdup(rbl_domain); + (void) split_at(rbl_exp.domain, '='); + rbl_exp.what = what; + rbl_exp.class = reply_class; + rbl_exp.txt = (rbl->txt == 0 ? "" : rbl->txt); + + for (;;) { + if (template == 0) + template = var_def_rbl_reply; + if (mac_expand(why, template, MAC_EXP_FLAG_NONE, + STR(smtpd_expand_filter), rbl_expand_lookup, + (void *) &rbl_exp) == 0) + break; + if (template == var_def_rbl_reply) + msg_fatal("%s: bad default rbl reply template: %s", + myname, var_def_rbl_reply); + msg_warn("%s: bad rbl reply template for domain %s: %s", + myname, rbl_domain, template); + template = 0; /* pretend not found */ + } + + /* + * XXX Impedance mis-match. + * + * Validate the response, that is, the response must begin with a + * three-digit status code, and the first digit must be 4 or 5. If the + * response is bad, log a warning and send a generic response instead. + */ + if ((STR(why)[0] != '4' && STR(why)[0] != '5') + || !ISDIGIT(STR(why)[1]) || !ISDIGIT(STR(why)[2]) + || STR(why)[3] != ' ') { + msg_warn("rbl response code configuration error: %s", STR(why)); + result = smtpd_check_reject(state, MAIL_ERROR_POLICY, + 450, "4.7.1", "Service unavailable"); + } else { + code = atoi(STR(why)); + dsn_split(&dp, "4.7.1", STR(why) + 4); + result = smtpd_check_reject(state, MAIL_ERROR_POLICY, + code, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "%s", *dp.text ? + dp.text : "Service unavailable"); + } + + /* + * Clean up. + */ + myfree(rbl_exp.domain); + vstring_free(why); + + return (result); +} + +/* rbl_match_addr - match address list */ + +static int rbl_match_addr(SMTPD_RBL_STATE *rbl, const char *byte_codes) +{ + const char *myname = "rbl_match_addr"; + DNS_RR *rr; + + for (rr = rbl->a; rr != 0; rr = rr->next) { + if (rr->type == T_A) { + if (ip_match_execute(byte_codes, rr->data)) + return (1); + } else { + msg_warn("%s: skipping record type %s for query %s", + myname, dns_strtype(rr->type), rr->qname); + } + } + return (0); +} + +/* find_dnsxl_addr - look up address in DNSXL */ + +static const SMTPD_RBL_STATE *find_dnsxl_addr(SMTPD_STATE *state, + const char *rbl_domain, + const char *addr) +{ + const char *myname = "find_dnsxl_addr"; + ARGV *octets; + VSTRING *query; + int i; + SMTPD_RBL_STATE *rbl; + const char *reply_addr; + const char *byte_codes; + struct addrinfo *res; + unsigned char *ipv6_addr; + + query = vstring_alloc(100); + + /* + * Reverse the client IPV6 address, represented as 32 hexadecimal + * nibbles. We use the binary address to avoid tricky code. Asking for an + * AAAA record makes no sense here. Just like with IPv4 we use the lookup + * result as a bit mask, not as an IP address. + */ +#ifdef HAS_IPV6 + if (valid_ipv6_hostaddr(addr, DONT_GRIPE)) { + if (hostaddr_to_sockaddr(addr, (char *) 0, 0, &res) != 0 + || res->ai_family != PF_INET6) + msg_fatal("%s: unable to convert address %s", myname, addr); + ipv6_addr = (unsigned char *) &SOCK_ADDR_IN6_ADDR(res->ai_addr); + for (i = sizeof(SOCK_ADDR_IN6_ADDR(res->ai_addr)) - 1; i >= 0; i--) + vstring_sprintf_append(query, "%x.%x.", + ipv6_addr[i] & 0xf, ipv6_addr[i] >> 4); + freeaddrinfo(res); + } else +#endif + + /* + * Reverse the client IPV4 address, represented as four decimal octet + * values. We use the textual address for convenience. + */ + { + octets = argv_split(addr, "."); + for (i = octets->argc - 1; i >= 0; i--) { + vstring_strcat(query, octets->argv[i]); + vstring_strcat(query, "."); + } + argv_free(octets); + } + + /* + * Tack on the RBL domain name and query the DNS for an A record. + */ + vstring_strcat(query, rbl_domain); + reply_addr = split_at(STR(query), '='); + rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query)); + if (reply_addr != 0) + byte_codes = ctable_locate(smtpd_rbl_byte_cache, reply_addr); + + /* + * If the record exists, match the result address. + */ + if (SMTPD_DNSXL_STAT_OK(rbl) && reply_addr != 0 + && !rbl_match_addr(rbl, byte_codes)) + rbl = 0; + vstring_free(query); + return (rbl); +} + +/* reject_rbl_addr - reject address in DNS deny list */ + +static int reject_rbl_addr(SMTPD_STATE *state, const char *rbl_domain, + const char *addr, const char *reply_class) +{ + const char *myname = "reject_rbl_addr"; + const SMTPD_RBL_STATE *rbl; + + if (msg_verbose) + msg_info("%s: %s %s", myname, reply_class, addr); + + rbl = find_dnsxl_addr(state, rbl_domain, addr); + if (!SMTPD_DNSXL_STAT_OK(rbl)) { + return (SMTPD_CHECK_DUNNO); + } else { + return (rbl_reject_reply(state, rbl, rbl_domain, addr, reply_class)); + } +} + +/* permit_dnswl_addr - permit address in DNSWL */ + +static int permit_dnswl_addr(SMTPD_STATE *state, const char *dnswl_domain, + const char *addr, const char *reply_class) +{ + const char *myname = "permit_dnswl_addr"; + const SMTPD_RBL_STATE *dnswl_result; + + if (msg_verbose) + msg_info("%s: %s", myname, addr); + + /* Safety: don't allowlist unauthorized recipients. */ + if (strcmp(state->where, SMTPD_CMD_RCPT) == 0 && state->recipient != 0 + && permit_auth_destination(state, state->recipient) != SMTPD_CHECK_OK) + return (SMTPD_CHECK_DUNNO); + + dnswl_result = find_dnsxl_addr(state, dnswl_domain, addr); + if (SMTPD_DNXSL_STAT_HARD(dnswl_result)) { + return (SMTPD_CHECK_DUNNO); + } else if (SMTPD_DNSXL_STAT_SOFT(dnswl_result)) { + /* XXX: Make configurable as dnswl_tempfail_action. */ + DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY, + 450, "4.7.1", + "<%s>: %s rejected: %s", + addr, reply_class, + "Service unavailable"); + return (SMTPD_CHECK_DUNNO); + } else if (SMTPD_DNSXL_STAT_OK(dnswl_result)) { + return (SMTPD_CHECK_OK); + } else { + /* Future proofing, in case find_dnsxl_addr() result is changed. */ + msg_panic("%s: find_dnsxl_addr API failure", myname); + } +} + +/* find_dnsxl_domain - reject if domain in DNS deny list */ + +static const SMTPD_RBL_STATE *find_dnsxl_domain(SMTPD_STATE *state, + const char *rbl_domain, const char *what) +{ + VSTRING *query; + SMTPD_RBL_STATE *rbl; + const char *domain; + const char *reply_addr; + const char *byte_codes; + const char *suffix; + const char *adomain; + + /* + * Extract the domain, tack on the RBL domain name and query the DNS for + * an A record. + */ + if ((domain = strrchr(what, '@')) != 0) { + domain += 1; + if (domain[0] == '[') + return (SMTPD_CHECK_DUNNO); + } else + domain = what; + + /* + * XXX Some Spamhaus RHSBL rejects lookups with "No IP queries" even if + * the name has an alphanumerical prefix. We play safe, and skip both + * RHSBL and RHSWL queries for names ending in a numerical suffix. + */ + if (domain[0] == 0) + return (SMTPD_CHECK_DUNNO); + suffix = strrchr(domain, '.'); + if (alldig(suffix == 0 ? domain : suffix + 1)) + return (SMTPD_CHECK_DUNNO); + + /* + * Fix 20140706: convert domain to ASCII. + */ +#ifndef NO_EAI + if (!allascii(domain) && (adomain = midna_domain_to_ascii(domain)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", domain, adomain); + domain = adomain; + } +#endif + if (domain[0] == 0 || valid_hostname(domain, DONT_GRIPE) == 0) + return (SMTPD_CHECK_DUNNO); + + query = vstring_alloc(100); + vstring_sprintf(query, "%s.%s", domain, rbl_domain); + reply_addr = split_at(STR(query), '='); + rbl = (SMTPD_RBL_STATE *) ctable_locate(smtpd_rbl_cache, STR(query)); + if (reply_addr != 0) + byte_codes = ctable_locate(smtpd_rbl_byte_cache, reply_addr); + + /* + * If the record exists, match the result address. + */ + if (SMTPD_DNSXL_STAT_OK(rbl) && reply_addr != 0 + && !rbl_match_addr(rbl, byte_codes)) + rbl = 0; + vstring_free(query); + return (rbl); +} + +/* reject_rbl_domain - reject if domain in DNS deny list */ + +static int reject_rbl_domain(SMTPD_STATE *state, const char *rbl_domain, + const char *what, const char *reply_class) +{ + const char *myname = "reject_rbl_domain"; + const SMTPD_RBL_STATE *rbl; + + if (msg_verbose) + msg_info("%s: %s %s", myname, rbl_domain, what); + + rbl = find_dnsxl_domain(state, rbl_domain, what); + if (!SMTPD_DNSXL_STAT_OK(rbl)) { + return (SMTPD_CHECK_DUNNO); + } else { + return (rbl_reject_reply(state, rbl, rbl_domain, what, reply_class)); + } +} + +/* permit_dnswl_domain - permit domain in DNSWL */ + +static int permit_dnswl_domain(SMTPD_STATE *state, const char *dnswl_domain, + const char *what, const char *reply_class) +{ + const char *myname = "permit_dnswl_domain"; + const SMTPD_RBL_STATE *dnswl_result; + + if (msg_verbose) + msg_info("%s: %s", myname, what); + + /* Safety: don't allowlist unauthorized recipients. */ + if (strcmp(state->where, SMTPD_CMD_RCPT) == 0 && state->recipient != 0 + && permit_auth_destination(state, state->recipient) != SMTPD_CHECK_OK) + return (SMTPD_CHECK_DUNNO); + + dnswl_result = find_dnsxl_domain(state, dnswl_domain, what); + if (SMTPD_DNXSL_STAT_HARD(dnswl_result)) { + return (SMTPD_CHECK_DUNNO); + } else if (SMTPD_DNSXL_STAT_SOFT(dnswl_result)) { + /* XXX: Make configurable as rhswl_tempfail_action. */ + DEFER_IF_REJECT3(state, MAIL_ERROR_POLICY, + 450, "4.7.1", + "<%s>: %s rejected: %s", + what, reply_class, + "Service unavailable"); + return (SMTPD_CHECK_DUNNO); + } else if (SMTPD_DNSXL_STAT_OK(dnswl_result)) { + return (SMTPD_CHECK_OK); + } else { + /* Future proofing, in case find_dnsxl_addr() result is changed. */ + msg_panic("%s: find_dnsxl_addr API failure", myname); + } +} + +/* reject_maps_rbl - reject if client address in DNS deny list */ + +static int reject_maps_rbl(SMTPD_STATE *state) +{ + const char *myname = "reject_maps_rbl"; + char *saved_domains = mystrdup(var_maps_rbl_domains); + char *bp = saved_domains; + char *rbl_domain; + int result = SMTPD_CHECK_DUNNO; + static int warned; + + if (msg_verbose) + msg_info("%s: %s", myname, state->addr); + + if (warned == 0) { + warned++; + msg_warn("support for restriction \"%s\" will be removed from %s; " + "use \"%s domain-name\" instead", + REJECT_MAPS_RBL, var_mail_name, REJECT_RBL_CLIENT); + } + while ((rbl_domain = mystrtok(&bp, CHARS_COMMA_SP)) != 0) { + result = reject_rbl_addr(state, rbl_domain, state->addr, + SMTPD_NAME_CLIENT); + if (result != SMTPD_CHECK_DUNNO) + break; + } + + /* + * Clean up. + */ + myfree(saved_domains); + + return (result); +} + +#ifdef USE_SASL_AUTH + +/* reject_auth_sender_login_mismatch - logged in client must own sender address */ + +static int reject_auth_sender_login_mismatch(SMTPD_STATE *state, const char *sender, int allow_unknown_sender) +{ + const RESOLVE_REPLY *reply; + const char *owners; + char *saved_owners; + char *cp; + char *name; + int found = 0; + +#define ALLOW_UNKNOWN_SENDER 1 +#define FORBID_UNKNOWN_SENDER 0 + + /* + * Reject if the client is logged in and does not own the sender address. + */ + if (smtpd_sender_login_maps && state->sasl_username) { + reply = smtpd_resolve_addr(state->recipient, sender); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, sender); + if ((owners = check_mail_addr_find(state, sender, smtpd_sender_login_maps, + STR(reply->recipient), (char **) 0)) != 0) { + cp = saved_owners = mystrdup(owners); + while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) { + if (strcasecmp_utf8(state->sasl_username, name) == 0) { + found = 1; + break; + } + } + myfree(saved_owners); + } else if (allow_unknown_sender) + return (SMTPD_CHECK_DUNNO); + if (!found) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, 553, "5.7.1", + "<%s>: Sender address rejected: not owned by user %s", + sender, state->sasl_username)); + } + return (SMTPD_CHECK_DUNNO); +} + +/* reject_unauth_sender_login_mismatch - sender requires client is logged in */ + +static int reject_unauth_sender_login_mismatch(SMTPD_STATE *state, const char *sender) +{ + const RESOLVE_REPLY *reply; + + /* + * Reject if the client is not logged in and the sender address has an + * owner. + */ + if (smtpd_sender_login_maps && !state->sasl_username) { + reply = smtpd_resolve_addr(state->recipient, sender); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, sender); + if (check_mail_addr_find(state, sender, smtpd_sender_login_maps, + STR(reply->recipient), (char **) 0) != 0) + return (smtpd_check_reject(state, MAIL_ERROR_POLICY, 553, "5.7.1", + "<%s>: Sender address rejected: not logged in", sender)); + } + return (SMTPD_CHECK_DUNNO); +} + +#endif + +/* valid_utf8_action - validate UTF-8 policy server response */ + +static int valid_utf8_action(const char *server, const char *action) +{ + int retval; + + if ((retval = valid_utf8_string(action, strlen(action))) == 0) + msg_warn("malformed UTF-8 in policy server %s response: \"%s\"", + server, action); + return (retval); +} + +/* check_policy_service - check delegated policy service */ + +static int check_policy_service(SMTPD_STATE *state, const char *server, + const char *reply_name, const char *reply_class, + const char *def_acl) +{ + static int warned = 0; + static VSTRING *action = 0; + SMTPD_POLICY_CLNT *policy_clnt; + +#ifdef USE_TLS + VSTRING *subject_buf; + VSTRING *issuer_buf; + const char *subject; + const char *issuer; + +#endif + int ret; + + /* + * Sanity check. + */ + if (!policy_clnt_table + || (policy_clnt = (SMTPD_POLICY_CLNT *) + htable_find(policy_clnt_table, server)) == 0) + msg_panic("check_policy_service: no client endpoint for server %s", + server); + + /* + * Initialize. + */ + if (action == 0) + action = vstring_alloc(10); + +#ifdef USE_TLS +#define ENCODE_CN(coded_CN, coded_CN_buf, CN) do { \ + if (!TLS_CERT_IS_TRUSTED(state->tls_context) || *(CN) == 0) { \ + coded_CN_buf = 0; \ + coded_CN = ""; \ + } else { \ + coded_CN_buf = vstring_alloc(strlen(CN) + 1); \ + xtext_quote(coded_CN_buf, CN, ""); \ + coded_CN = STR(coded_CN_buf); \ + } \ + } while (0); + + ENCODE_CN(subject, subject_buf, state->tls_context->peer_CN); + ENCODE_CN(issuer, issuer_buf, state->tls_context->issuer_CN); + + /* + * XXX: Too noisy to warn for each policy lookup, especially because we + * don't even know whether the policy server will use the fingerprint. So + * warn at most once per process, though on only lightly loaded servers, + * it might come close to one warning per inbound message. + */ + if (!warned + && warn_compat_break_smtpd_tls_fpt_dgst + && state->tls_context + && state->tls_context->peer_cert_fprint + && *state->tls_context->peer_cert_fprint) { + warned = 1; + msg_info("using backwards-compatible default setting " + VAR_SMTPD_TLS_FPT_DGST "=md5 to compute certificate " + "fingerprints"); + } +#endif + + if (attr_clnt_request(policy_clnt->client, + ATTR_FLAG_NONE, /* Query attributes. */ + SEND_ATTR_STR(MAIL_ATTR_REQ, "smtpd_access_policy"), + SEND_ATTR_STR(MAIL_ATTR_PROTO_STATE, + STREQ(state->where, SMTPD_CMD_BDAT) ? + SMTPD_CMD_DATA : state->where), + SEND_ATTR_STR(MAIL_ATTR_ACT_PROTO_NAME, state->protocol), + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_ADDR, state->addr), + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_NAME, state->name), + SEND_ATTR_STR(MAIL_ATTR_ACT_CLIENT_PORT, state->port), + SEND_ATTR_STR(MAIL_ATTR_ACT_REVERSE_CLIENT_NAME, + state->reverse_name), + SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_ADDR, + state->dest_addr), + SEND_ATTR_STR(MAIL_ATTR_ACT_SERVER_PORT, + state->dest_port), + SEND_ATTR_STR(MAIL_ATTR_ACT_HELO_NAME, + state->helo_name ? state->helo_name : ""), + SEND_ATTR_STR(MAIL_ATTR_SENDER, + state->sender ? state->sender : ""), + SEND_ATTR_STR(MAIL_ATTR_RECIP, + state->recipient ? state->recipient : ""), + SEND_ATTR_INT(MAIL_ATTR_RCPT_COUNT, + ((strcasecmp(state->where, SMTPD_CMD_DATA) == 0) || + (strcasecmp(state->where, SMTPD_CMD_BDAT) == 0) || + (strcasecmp(state->where, SMTPD_AFTER_EOM) == 0)) ? + state->rcpt_count : 0), + SEND_ATTR_STR(MAIL_ATTR_QUEUEID, + state->queue_id ? state->queue_id : ""), + SEND_ATTR_STR(MAIL_ATTR_INSTANCE, + STR(state->instance)), + SEND_ATTR_LONG(MAIL_ATTR_SIZE, + (unsigned long) (state->act_size > 0 ? + state->act_size : state->msg_size)), + SEND_ATTR_STR(MAIL_ATTR_ETRN_DOMAIN, + state->etrn_name ? state->etrn_name : ""), + SEND_ATTR_STR(MAIL_ATTR_STRESS, var_stress), +#ifdef USE_SASL_AUTH + SEND_ATTR_STR(MAIL_ATTR_SASL_METHOD, + state->sasl_method ? state->sasl_method : ""), + SEND_ATTR_STR(MAIL_ATTR_SASL_USERNAME, + state->sasl_username ? state->sasl_username : ""), + SEND_ATTR_STR(MAIL_ATTR_SASL_SENDER, + state->sasl_sender ? state->sasl_sender : ""), +#endif +#ifdef USE_TLS +#define IF_ENCRYPTED(x, y) ((state->tls_context && ((x) != 0)) ? (x) : (y)) + SEND_ATTR_STR(MAIL_ATTR_CCERT_SUBJECT, subject), + SEND_ATTR_STR(MAIL_ATTR_CCERT_ISSUER, issuer), + + /* + * When directly checking the fingerprint, it is OK if the issuing CA is + * not trusted. + */ + SEND_ATTR_STR(MAIL_ATTR_CCERT_CERT_FPRINT, + IF_ENCRYPTED(state->tls_context->peer_cert_fprint, "")), + SEND_ATTR_STR(MAIL_ATTR_CCERT_PKEY_FPRINT, + IF_ENCRYPTED(state->tls_context->peer_pkey_fprint, "")), + SEND_ATTR_STR(MAIL_ATTR_CRYPTO_PROTOCOL, + IF_ENCRYPTED(state->tls_context->protocol, "")), + SEND_ATTR_STR(MAIL_ATTR_CRYPTO_CIPHER, + IF_ENCRYPTED(state->tls_context->cipher_name, "")), + SEND_ATTR_INT(MAIL_ATTR_CRYPTO_KEYSIZE, + IF_ENCRYPTED(state->tls_context->cipher_usebits, 0)), +#endif + SEND_ATTR_STR(MAIL_ATTR_POL_CONTEXT, + policy_clnt->policy_context), + ATTR_TYPE_END, + ATTR_FLAG_MISSING, /* Reply attributes. */ + RECV_ATTR_STR(MAIL_ATTR_ACTION, action), + ATTR_TYPE_END) != 1 + || (var_smtputf8_enable && valid_utf8_action(server, STR(action)) == 0)) { + NOCLOBBER static int nesting_level = 0; + jmp_buf savebuf; + int status; + + /* + * Safety to prevent recursive execution of the default action. + */ + nesting_level += 1; + memcpy(ADDROF(savebuf), ADDROF(smtpd_check_buf), sizeof(savebuf)); + status = setjmp(smtpd_check_buf); + if (status != 0) { + nesting_level -= 1; + memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf), + sizeof(smtpd_check_buf)); + longjmp(smtpd_check_buf, status); + } + ret = check_table_result(state, server, nesting_level == 1 ? + policy_clnt->def_action : + DEF_SMTPD_POLICY_DEF_ACTION, + "policy query", reply_name, + reply_class, def_acl); + nesting_level -= 1; + memcpy(ADDROF(smtpd_check_buf), ADDROF(savebuf), + sizeof(smtpd_check_buf)); + } else { + + /* + * XXX This produces bogus error messages when the reply is + * malformed. + */ + ret = check_table_result(state, server, STR(action), + "policy query", reply_name, + reply_class, def_acl); + } +#ifdef USE_TLS + if (subject_buf) + vstring_free(subject_buf); + if (issuer_buf) + vstring_free(issuer_buf); +#endif + return (ret); +} + +/* is_map_command - restriction has form: check_xxx_access type:name */ + +static int is_map_command(SMTPD_STATE *state, const char *name, + const char *command, char ***argp) +{ + + /* + * This is a three-valued function: (a) this is not a check_xxx_access + * command, (b) this is a malformed check_xxx_access command, (c) this is + * a well-formed check_xxx_access command. That's too clumsy for function + * result values, so we use regular returns for (a) and (c), and use long + * jumps for the error case (b). + */ + if (strcasecmp(name, command) != 0) { + return (0); + } else if (*(*argp + 1) == 0 || strchr(*(*argp += 1), ':') == 0) { + msg_warn("restriction %s: bad argument \"%s\": need maptype:mapname", + command, **argp); + reject_server_error(state); + } else { + return (1); + } +} + +/* forbid_allowlist - disallow allowlisting */ + +static void forbid_allowlist(SMTPD_STATE *state, const char *name, + int status, const char *target) +{ + if (state->discard == 0 && status == SMTPD_CHECK_OK) { + msg_warn("restriction %s returns OK for %s", name, target); + msg_warn("this is not allowed for security reasons"); + msg_warn("use DUNNO instead of OK if you want to make an exception"); + reject_server_error(state); + } +} + +/* generic_checks - generic restrictions */ + +static int generic_checks(SMTPD_STATE *state, ARGV *restrictions, + const char *reply_name, + const char *reply_class, + const char *def_acl) +{ + const char *myname = "generic_checks"; + char **cpp; + const char *name; + int status = 0; + ARGV *list; + int found; + int saved_recursion = state->recursion++; + + if (msg_verbose) + msg_info(">>> START %s RESTRICTIONS <<<", reply_class); + + for (cpp = restrictions->argv; (name = *cpp) != 0; cpp++) { + + if (state->discard != 0) + break; + + if (msg_verbose) + msg_info("%s: name=%s", myname, name); + + /* + * Pseudo restrictions. + */ + if (strcasecmp(name, WARN_IF_REJECT) == 0) { + if (state->warn_if_reject == 0) + state->warn_if_reject = state->recursion; + continue; + } + + /* + * Spoof the is_map_command() routine, so that we do not have to make + * special cases for the implicit short-hand access map notation. + */ +#define NO_DEF_ACL 0 + + if (strchr(name, ':') != 0) { + if (def_acl == NO_DEF_ACL) { + msg_warn("specify one of (%s, %s, %s, %s, %s, %s) before %s restriction \"%s\"", + CHECK_CLIENT_ACL, CHECK_REVERSE_CLIENT_ACL, CHECK_HELO_ACL, CHECK_SENDER_ACL, + CHECK_RECIP_ACL, CHECK_ETRN_ACL, reply_class, name); + reject_server_error(state); + } + name = def_acl; + cpp -= 1; + } + + /* + * Generic restrictions. + */ + if (strcasecmp(name, PERMIT_ALL) == 0) { + status = smtpd_acl_permit(state, name, reply_class, + reply_name, NO_PRINT_ARGS); + if (status == SMTPD_CHECK_OK && cpp[1] != 0) + msg_warn("restriction `%s' after `%s' is ignored", + cpp[1], PERMIT_ALL); + } else if (strcasecmp(name, DEFER_ALL) == 0) { + status = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_defer_code, "4.3.2", + "<%s>: %s rejected: Try again later", + reply_name, reply_class); + if (cpp[1] != 0 && state->warn_if_reject == 0) + msg_warn("restriction `%s' after `%s' is ignored", + cpp[1], DEFER_ALL); + } else if (strcasecmp(name, REJECT_ALL) == 0) { + status = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_reject_code, "5.7.1", + "<%s>: %s rejected: Access denied", + reply_name, reply_class); + if (cpp[1] != 0 && state->warn_if_reject == 0) + msg_warn("restriction `%s' after `%s' is ignored", + cpp[1], REJECT_ALL); + } else if (strcasecmp(name, REJECT_UNAUTH_PIPE) == 0) { + status = reject_unauth_pipelining(state, reply_name, reply_class); + } else if (strcasecmp(name, CHECK_POLICY_SERVICE) == 0) { + if (cpp[1] == 0 || strchr(cpp[1], ':') == 0) { + msg_warn("restriction %s must be followed by transport:server", + CHECK_POLICY_SERVICE); + reject_server_error(state); + } else + status = check_policy_service(state, *++cpp, reply_name, + reply_class, def_acl); + } else if (strcasecmp(name, DEFER_IF_PERMIT) == 0) { + status = DEFER_IF_PERMIT2(DEFER_IF_PERMIT_ACT, + state, MAIL_ERROR_POLICY, + 450, "4.7.0", + "<%s>: %s rejected: defer_if_permit requested", + reply_name, reply_class); + } else if (strcasecmp(name, DEFER_IF_REJECT) == 0) { + DEFER_IF_REJECT2(state, MAIL_ERROR_POLICY, + 450, "4.7.0", + "<%s>: %s rejected: defer_if_reject requested", + reply_name, reply_class); + } else if (strcasecmp(name, SLEEP) == 0) { + if (cpp[1] == 0 || alldig(cpp[1]) == 0) { + msg_warn("restriction %s must be followed by number", SLEEP); + reject_server_error(state); + } else + sleep(atoi(*++cpp)); + } else if (strcasecmp(name, REJECT_PLAINTEXT_SESSION) == 0) { + status = reject_plaintext_session(state); + } + + /* + * Client name/address restrictions. + */ + else if (strcasecmp(name, REJECT_UNKNOWN_CLIENT_HOSTNAME) == 0 + || strcasecmp(name, REJECT_UNKNOWN_CLIENT) == 0) { + status = reject_unknown_client(state); + } else if (strcasecmp(name, REJECT_UNKNOWN_REVERSE_HOSTNAME) == 0) { + status = reject_unknown_reverse_name(state); + } else if (strcasecmp(name, PERMIT_INET_INTERFACES) == 0) { + status = permit_inet_interfaces(state); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } else if (strcasecmp(name, PERMIT_MYNETWORKS) == 0) { + status = permit_mynetworks(state); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } else if (is_map_command(state, name, CHECK_CLIENT_ACL, &cpp)) { + status = check_namadr_access(state, *cpp, state->name, state->addr, + FULL, &found, state->namaddr, + SMTPD_NAME_CLIENT, def_acl); + } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_ACL, &cpp)) { + status = check_namadr_access(state, *cpp, state->reverse_name, state->addr, + FULL, &found, state->reverse_name, + SMTPD_NAME_REV_CLIENT, def_acl); + forbid_allowlist(state, name, status, state->reverse_name); + } else if (strcasecmp(name, REJECT_MAPS_RBL) == 0) { + status = reject_maps_rbl(state); + } else if (strcasecmp(name, REJECT_RBL_CLIENT) == 0 + || strcasecmp(name, REJECT_RBL) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", name); + else + status = reject_rbl_addr(state, *(cpp += 1), state->addr, + SMTPD_NAME_CLIENT); + } else if (strcasecmp(name, PERMIT_DNSWL_CLIENT) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", name); + else { + status = permit_dnswl_addr(state, *(cpp += 1), state->addr, + SMTPD_NAME_CLIENT); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } + } else if (strcasecmp(name, REJECT_RHSBL_CLIENT) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", + name); + else { + cpp += 1; + if (strcasecmp(state->name, "unknown") != 0) + status = reject_rbl_domain(state, *cpp, state->name, + SMTPD_NAME_CLIENT); + } + } else if (strcasecmp(name, PERMIT_RHSWL_CLIENT) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", + name); + else { + cpp += 1; + if (strcasecmp(state->name, "unknown") != 0) { + status = permit_dnswl_domain(state, *cpp, state->name, + SMTPD_NAME_CLIENT); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, + SMTPD_NAME_CLIENT, state->namaddr, NO_PRINT_ARGS); + } + } + } else if (strcasecmp(name, REJECT_RHSBL_REVERSE_CLIENT) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", + name); + else { + cpp += 1; + if (strcasecmp(state->reverse_name, "unknown") != 0) + status = reject_rbl_domain(state, *cpp, state->reverse_name, + SMTPD_NAME_REV_CLIENT); + } + } else if (is_map_command(state, name, CHECK_CCERT_ACL, &cpp)) { + status = check_ccert_access(state, *cpp, def_acl); + } else if (is_map_command(state, name, CHECK_SASL_ACL, &cpp)) { +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable) { + if (state->sasl_username && state->sasl_username[0]) + status = check_sasl_access(state, *cpp, def_acl); + } else +#endif + msg_warn("restriction `%s' ignored: no SASL support", name); + } else if (is_map_command(state, name, CHECK_CLIENT_NS_ACL, &cpp)) { + if (strcasecmp(state->name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->name, + T_NS, state->namaddr, + SMTPD_NAME_CLIENT, def_acl); + forbid_allowlist(state, name, status, state->name); + } + } else if (is_map_command(state, name, CHECK_CLIENT_MX_ACL, &cpp)) { + if (strcasecmp(state->name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->name, + T_MX, state->namaddr, + SMTPD_NAME_CLIENT, def_acl); + forbid_allowlist(state, name, status, state->name); + } + } else if (is_map_command(state, name, CHECK_CLIENT_A_ACL, &cpp)) { + if (strcasecmp(state->name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->name, + T_A, state->namaddr, + SMTPD_NAME_CLIENT, def_acl); + forbid_allowlist(state, name, status, state->name); + } + } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_NS_ACL, &cpp)) { + if (strcasecmp(state->reverse_name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->reverse_name, + T_NS, state->reverse_name, + SMTPD_NAME_REV_CLIENT, def_acl); + forbid_allowlist(state, name, status, state->reverse_name); + } + } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_MX_ACL, &cpp)) { + if (strcasecmp(state->reverse_name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->reverse_name, + T_MX, state->reverse_name, + SMTPD_NAME_REV_CLIENT, def_acl); + forbid_allowlist(state, name, status, state->reverse_name); + } + } else if (is_map_command(state, name, CHECK_REVERSE_CLIENT_A_ACL, &cpp)) { + if (strcasecmp(state->reverse_name, "unknown") != 0) { + status = check_server_access(state, *cpp, state->reverse_name, + T_A, state->reverse_name, + SMTPD_NAME_REV_CLIENT, def_acl); + forbid_allowlist(state, name, status, state->reverse_name); + } + } + + /* + * HELO/EHLO parameter restrictions. + */ + else if (is_map_command(state, name, CHECK_HELO_ACL, &cpp)) { + if (state->helo_name) + status = check_domain_access(state, *cpp, state->helo_name, + FULL, &found, state->helo_name, + SMTPD_NAME_HELO, def_acl); + } else if (strcasecmp(name, REJECT_INVALID_HELO_HOSTNAME) == 0 + || strcasecmp(name, REJECT_INVALID_HOSTNAME) == 0) { + if (state->helo_name) { + if (*state->helo_name != '[') + status = reject_invalid_hostname(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + else + status = reject_invalid_hostaddr(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + } + } else if (strcasecmp(name, REJECT_UNKNOWN_HELO_HOSTNAME) == 0 + || strcasecmp(name, REJECT_UNKNOWN_HOSTNAME) == 0) { + if (state->helo_name) { + if (*state->helo_name != '[') + status = reject_unknown_hostname(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + else + status = reject_invalid_hostaddr(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + } + } else if (strcasecmp(name, PERMIT_NAKED_IP_ADDR) == 0) { + msg_warn("restriction %s is deprecated. Use %s or %s instead", + PERMIT_NAKED_IP_ADDR, PERMIT_MYNETWORKS, PERMIT_SASL_AUTH); + if (state->helo_name) { + if (state->helo_name[strspn(state->helo_name, "0123456789.:")] == 0 + && (status = reject_invalid_hostaddr(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO)) == 0) + status = smtpd_acl_permit(state, name, SMTPD_NAME_HELO, + state->helo_name, NO_PRINT_ARGS); + } + } else if (is_map_command(state, name, CHECK_HELO_NS_ACL, &cpp)) { + if (state->helo_name) { + status = check_server_access(state, *cpp, state->helo_name, + T_NS, state->helo_name, + SMTPD_NAME_HELO, def_acl); + forbid_allowlist(state, name, status, state->helo_name); + } + } else if (is_map_command(state, name, CHECK_HELO_MX_ACL, &cpp)) { + if (state->helo_name) { + status = check_server_access(state, *cpp, state->helo_name, + T_MX, state->helo_name, + SMTPD_NAME_HELO, def_acl); + forbid_allowlist(state, name, status, state->helo_name); + } + } else if (is_map_command(state, name, CHECK_HELO_A_ACL, &cpp)) { + if (state->helo_name) { + status = check_server_access(state, *cpp, state->helo_name, + T_A, state->helo_name, + SMTPD_NAME_HELO, def_acl); + forbid_allowlist(state, name, status, state->helo_name); + } + } else if (strcasecmp(name, REJECT_NON_FQDN_HELO_HOSTNAME) == 0 + || strcasecmp(name, REJECT_NON_FQDN_HOSTNAME) == 0) { + if (state->helo_name) { + if (*state->helo_name != '[') + status = reject_non_fqdn_hostname(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + else + status = reject_invalid_hostaddr(state, state->helo_name, + state->helo_name, SMTPD_NAME_HELO); + } + } else if (strcasecmp(name, REJECT_RHSBL_HELO) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", + name); + else { + cpp += 1; + if (state->helo_name) + status = reject_rbl_domain(state, *cpp, state->helo_name, + SMTPD_NAME_HELO); + } + } + + /* + * Sender mail address restrictions. + */ + else if (is_map_command(state, name, CHECK_SENDER_ACL, &cpp)) { + if (state->sender && *state->sender) + status = check_mail_access(state, *cpp, state->sender, + &found, state->sender, + SMTPD_NAME_SENDER, def_acl); + if (state->sender && !*state->sender) + status = check_access(state, *cpp, var_smtpd_null_key, FULL, + &found, state->sender, + SMTPD_NAME_SENDER, def_acl); + } else if (strcasecmp(name, REJECT_UNKNOWN_ADDRESS) == 0) { + if (state->sender && *state->sender) + status = reject_unknown_address(state, state->sender, + state->sender, SMTPD_NAME_SENDER); + } else if (strcasecmp(name, REJECT_UNKNOWN_SENDDOM) == 0) { + if (state->sender && *state->sender) + status = reject_unknown_address(state, state->sender, + state->sender, SMTPD_NAME_SENDER); + } else if (strcasecmp(name, REJECT_UNVERIFIED_SENDER) == 0) { + if (state->sender && *state->sender) + status = reject_unverified_address(state, state->sender, + state->sender, SMTPD_NAME_SENDER, + var_unv_from_dcode, var_unv_from_rcode, + unv_from_tf_act, + var_unv_from_why); + } else if (strcasecmp(name, REJECT_NON_FQDN_SENDER) == 0) { + if (state->sender && *state->sender) + status = reject_non_fqdn_address(state, state->sender, + state->sender, SMTPD_NAME_SENDER); + } else if (strcasecmp(name, REJECT_AUTH_SENDER_LOGIN_MISMATCH) == 0) { +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable) { + if (state->sender && *state->sender) + status = reject_auth_sender_login_mismatch(state, + state->sender, FORBID_UNKNOWN_SENDER); + } else +#endif + msg_warn("restriction `%s' ignored: no SASL support", name); + } else if (strcasecmp(name, REJECT_KNOWN_SENDER_LOGIN_MISMATCH) == 0) { +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable) { + if (state->sender && *state->sender) { + if (state->sasl_username) + status = reject_auth_sender_login_mismatch(state, + state->sender, ALLOW_UNKNOWN_SENDER); + else + status = reject_unauth_sender_login_mismatch(state, state->sender); + } + } else +#endif + msg_warn("restriction `%s' ignored: no SASL support", name); + } else if (strcasecmp(name, REJECT_UNAUTH_SENDER_LOGIN_MISMATCH) == 0) { +#ifdef USE_SASL_AUTH + if (var_smtpd_sasl_enable) { + if (state->sender && *state->sender) + status = reject_unauth_sender_login_mismatch(state, state->sender); + } else +#endif + msg_warn("restriction `%s' ignored: no SASL support", name); + } else if (is_map_command(state, name, CHECK_SENDER_NS_ACL, &cpp)) { + if (state->sender && *state->sender) { + status = check_server_access(state, *cpp, state->sender, + T_NS, state->sender, + SMTPD_NAME_SENDER, def_acl); + forbid_allowlist(state, name, status, state->sender); + } + } else if (is_map_command(state, name, CHECK_SENDER_MX_ACL, &cpp)) { + if (state->sender && *state->sender) { + status = check_server_access(state, *cpp, state->sender, + T_MX, state->sender, + SMTPD_NAME_SENDER, def_acl); + forbid_allowlist(state, name, status, state->sender); + } + } else if (is_map_command(state, name, CHECK_SENDER_A_ACL, &cpp)) { + if (state->sender && *state->sender) { + status = check_server_access(state, *cpp, state->sender, + T_A, state->sender, + SMTPD_NAME_SENDER, def_acl); + forbid_allowlist(state, name, status, state->sender); + } + } else if (strcasecmp(name, REJECT_RHSBL_SENDER) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", name); + else { + cpp += 1; + if (state->sender && *state->sender) + status = reject_rbl_domain(state, *cpp, state->sender, + SMTPD_NAME_SENDER); + } + } else if (strcasecmp(name, REJECT_UNLISTED_SENDER) == 0) { + if (state->sender && *state->sender) + status = check_sender_rcpt_maps(state, state->sender); + } + + /* + * Recipient mail address restrictions. + */ + else if (is_map_command(state, name, CHECK_RECIP_ACL, &cpp)) { + if (state->recipient) + status = check_mail_access(state, *cpp, state->recipient, + &found, state->recipient, + SMTPD_NAME_RECIPIENT, def_acl); + } else if (strcasecmp(name, PERMIT_MX_BACKUP) == 0) { + if (state->recipient) { + status = permit_mx_backup(state, state->recipient, + state->recipient, SMTPD_NAME_RECIPIENT); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT, + state->recipient, NO_PRINT_ARGS); + } + } else if (strcasecmp(name, PERMIT_AUTH_DEST) == 0) { + if (state->recipient) { + status = permit_auth_destination(state, state->recipient); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT, + state->recipient, NO_PRINT_ARGS); + } + } else if (strcasecmp(name, REJECT_UNAUTH_DEST) == 0) { + if (state->recipient) + status = reject_unauth_destination(state, state->recipient, + var_relay_code, "5.7.1"); + } else if (strcasecmp(name, DEFER_UNAUTH_DEST) == 0) { + if (state->recipient) + status = reject_unauth_destination(state, state->recipient, + var_relay_code - 100, "4.7.1"); + } else if (strcasecmp(name, CHECK_RELAY_DOMAINS) == 0) { + if (state->recipient) + status = check_relay_domains(state, state->recipient, + state->recipient, SMTPD_NAME_RECIPIENT); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_RECIPIENT, + state->recipient, NO_PRINT_ARGS); + if (cpp[1] != 0 && state->warn_if_reject == 0) + msg_warn("restriction `%s' after `%s' is ignored", + cpp[1], CHECK_RELAY_DOMAINS); + } else if (strcasecmp(name, PERMIT_SASL_AUTH) == 0) { +#ifdef USE_SASL_AUTH + if (smtpd_sasl_is_active(state)) { + status = permit_sasl_auth(state, + SMTPD_CHECK_OK, SMTPD_CHECK_DUNNO); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } +#endif + } else if (strcasecmp(name, PERMIT_TLS_ALL_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 1); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } else if (strcasecmp(name, PERMIT_TLS_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 0); + if (status == SMTPD_CHECK_OK) + status = smtpd_acl_permit(state, name, SMTPD_NAME_CLIENT, + state->namaddr, NO_PRINT_ARGS); + } else if (strcasecmp(name, REJECT_UNKNOWN_RCPTDOM) == 0) { + if (state->recipient) + status = reject_unknown_address(state, state->recipient, + state->recipient, SMTPD_NAME_RECIPIENT); + } else if (strcasecmp(name, REJECT_NON_FQDN_RCPT) == 0) { + if (state->recipient) + status = reject_non_fqdn_address(state, state->recipient, + state->recipient, SMTPD_NAME_RECIPIENT); + } else if (is_map_command(state, name, CHECK_RECIP_NS_ACL, &cpp)) { + if (state->recipient && *state->recipient) { + status = check_server_access(state, *cpp, state->recipient, + T_NS, state->recipient, + SMTPD_NAME_RECIPIENT, def_acl); + forbid_allowlist(state, name, status, state->recipient); + } + } else if (is_map_command(state, name, CHECK_RECIP_MX_ACL, &cpp)) { + if (state->recipient && *state->recipient) { + status = check_server_access(state, *cpp, state->recipient, + T_MX, state->recipient, + SMTPD_NAME_RECIPIENT, def_acl); + forbid_allowlist(state, name, status, state->recipient); + } + } else if (is_map_command(state, name, CHECK_RECIP_A_ACL, &cpp)) { + if (state->recipient && *state->recipient) { + status = check_server_access(state, *cpp, state->recipient, + T_A, state->recipient, + SMTPD_NAME_RECIPIENT, def_acl); + forbid_allowlist(state, name, status, state->recipient); + } + } else if (strcasecmp(name, REJECT_RHSBL_RECIPIENT) == 0) { + if (cpp[1] == 0) + msg_warn("restriction %s requires domain name argument", name); + else { + cpp += 1; + if (state->recipient) + status = reject_rbl_domain(state, *cpp, state->recipient, + SMTPD_NAME_RECIPIENT); + } + } else if (strcasecmp(name, CHECK_RCPT_MAPS) == 0 + || strcasecmp(name, REJECT_UNLISTED_RCPT) == 0) { + if (state->recipient && *state->recipient) + status = check_recipient_rcpt_maps(state, state->recipient); + } else if (strcasecmp(name, REJECT_MUL_RCPT_BOUNCE) == 0) { + if (state->sender && *state->sender == 0 && state->rcpt_count + > (strcmp(state->where, SMTPD_CMD_RCPT) != 0)) + status = smtpd_check_reject(state, MAIL_ERROR_POLICY, + var_mul_rcpt_code, "5.5.3", + "<%s>: %s rejected: Multi-recipient bounce", + reply_name, reply_class); + } else if (strcasecmp(name, REJECT_UNVERIFIED_RECIP) == 0) { + if (state->recipient && *state->recipient) + status = reject_unverified_address(state, state->recipient, + state->recipient, SMTPD_NAME_RECIPIENT, + var_unv_rcpt_dcode, var_unv_rcpt_rcode, + unv_rcpt_tf_act, + var_unv_rcpt_why); + } + + /* + * ETRN domain name restrictions. + */ + else if (is_map_command(state, name, CHECK_ETRN_ACL, &cpp)) { + if (state->etrn_name) + status = check_domain_access(state, *cpp, state->etrn_name, + FULL, &found, state->etrn_name, + SMTPD_NAME_ETRN, def_acl); + } + + /* + * User-defined restriction class. + */ + else if ((list = (ARGV *) htable_find(smtpd_rest_classes, name)) != 0) { + status = generic_checks(state, list, reply_name, + reply_class, def_acl); + } + + /* + * Error: undefined restriction name. + */ + else { + msg_warn("unknown smtpd restriction: \"%s\"", name); + reject_server_error(state); + } + if (msg_verbose) + msg_info("%s: name=%s status=%d", myname, name, status); + + if (status < 0) { + if (status == DICT_ERR_RETRY) + reject_dict_retry(state, reply_name); + else + reject_server_error(state); + } + if (state->warn_if_reject >= state->recursion) + state->warn_if_reject = 0; + + if (status != 0) + break; + + if (state->defer_if_permit.active && state->defer_if_reject.active) + break; + } + if (msg_verbose) + msg_info(">>> END %s RESTRICTIONS <<<", reply_class); + + state->recursion = saved_recursion; + + /* In case the list terminated with one or more warn_if_mumble. */ + if (state->warn_if_reject >= state->recursion) + state->warn_if_reject = 0; + + return (status); +} + +/* smtpd_check_addr - address sanity check */ + +int smtpd_check_addr(const char *sender, const char *addr, int smtputf8) +{ + const RESOLVE_REPLY *resolve_reply; + const char *myname = "smtpd_check_addr"; + const char *domain; + + if (msg_verbose) + msg_info("%s: addr=%s", myname, addr); + + /* + * Catch syntax errors early on if we can, but be prepared to re-compute + * the result later when the cache fills up with lots of recipients, at + * which time errors can still happen. + */ + if (addr == 0 || *addr == 0) + return (0); + resolve_reply = smtpd_resolve_addr(sender, addr); + if (resolve_reply->flags & RESOLVE_FLAG_ERROR) + return (-1); + + /* + * Backwards compatibility: if the client does not request SMTPUTF8 + * support, then behave like Postfix < 3.0 trivial-rewrite, and don't + * allow non-ASCII email domains. Historically, Postfix does not reject + * UTF8 etc. in the address localpart. + */ + if (smtputf8 == 0 + && (domain = strrchr(STR(resolve_reply->recipient), '@')) != 0 + && *(domain += 1) != 0 && !allascii(domain)) + return (-1); + + return (0); +} + +/* smtpd_check_rewrite - choose address qualification context */ + +char *smtpd_check_rewrite(SMTPD_STATE *state) +{ + const char *myname = "smtpd_check_rewrite"; + int status; + char **cpp; + MAPS *maps; + char *name; + + /* + * We don't use generic_checks() because it produces results that aren't + * applicable such as DEFER or REJECT. + */ + for (cpp = local_rewrite_clients->argv; *cpp != 0; cpp++) { + if (msg_verbose) + msg_info("%s: trying: %s", myname, *cpp); + status = SMTPD_CHECK_DUNNO; + if (strchr(name = *cpp, ':') != 0) { + name = CHECK_ADDR_MAP; + cpp -= 1; + } + if (strcasecmp(name, PERMIT_INET_INTERFACES) == 0) { + status = permit_inet_interfaces(state); + } else if (strcasecmp(name, PERMIT_MYNETWORKS) == 0) { + status = permit_mynetworks(state); + } else if (is_map_command(state, name, CHECK_ADDR_MAP, &cpp)) { + if ((maps = (MAPS *) htable_find(map_command_table, *cpp)) == 0) + msg_panic("%s: dictionary not found: %s", myname, *cpp); + if (maps_find(maps, state->addr, 0) != 0) + status = SMTPD_CHECK_OK; + else if (maps->error != 0) { + /* Warning is already logged. */ + status = maps->error; + } + } else if (strcasecmp(name, PERMIT_SASL_AUTH) == 0) { +#ifdef USE_SASL_AUTH + if (smtpd_sasl_is_active(state)) + status = permit_sasl_auth(state, SMTPD_CHECK_OK, + SMTPD_CHECK_DUNNO); +#endif + } else if (strcasecmp(name, PERMIT_TLS_ALL_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 1); + } else if (strcasecmp(name, PERMIT_TLS_CLIENTCERTS) == 0) { + status = permit_tls_clientcerts(state, 0); + } else { + msg_warn("parameter %s: invalid request: %s", + VAR_LOC_RWR_CLIENTS, name); + continue; + } + if (status < 0) { + if (status == DICT_ERR_RETRY) { + state->error_mask |= MAIL_ERROR_RESOURCE; + log_whatsup(state, "reject", + "451 4.3.0 Temporary lookup error"); + return ("451 4.3.0 Temporary lookup error"); + } else { + state->error_mask |= MAIL_ERROR_SOFTWARE; + log_whatsup(state, "reject", + "451 4.3.5 Server configuration error"); + return ("451 4.3.5 Server configuration error"); + } + } + if (status == SMTPD_CHECK_OK) { + state->rewrite_context = MAIL_ATTR_RWR_LOCAL; + return (0); + } + } + state->rewrite_context = MAIL_ATTR_RWR_REMOTE; + return (0); +} + +/* smtpd_check_client - validate client name or address */ + +char *smtpd_check_client(SMTPD_STATE *state) +{ + int status; + + /* + * Initialize. + */ + if (state->name == 0 || state->addr == 0) + return (0); + +#define SMTPD_CHECK_RESET() { \ + state->recursion = 0; \ + state->warn_if_reject = 0; \ + state->defer_if_reject.active = 0; \ + } + + /* + * Reset the defer_if_permit flag. + */ + state->defer_if_permit.active = 0; + + /* + * Apply restrictions in the order as specified. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && client_restrctions->argc) + status = generic_checks(state, client_restrctions, state->namaddr, + SMTPD_NAME_CLIENT, CHECK_CLIENT_ACL); + state->defer_if_permit_client = state->defer_if_permit.active; + + return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* smtpd_check_helo - validate HELO hostname */ + +char *smtpd_check_helo(SMTPD_STATE *state, char *helohost) +{ + int status; + char *saved_helo; + + /* + * Initialize. + */ + if (helohost == 0) + return (0); + + /* + * Minor kluge so that we can delegate work to the generic routine and so + * that we can syslog the recipient with the reject messages. + */ +#define SMTPD_CHECK_PUSH(backup, current, new) { \ + backup = current; \ + current = (new ? mystrdup(new) : 0); \ + } + +#define SMTPD_CHECK_POP(current, backup) { \ + if (current) myfree(current); \ + current = backup; \ + } + + SMTPD_CHECK_PUSH(saved_helo, state->helo_name, helohost); + +#define SMTPD_CHECK_HELO_RETURN(x) { \ + SMTPD_CHECK_POP(state->helo_name, saved_helo); \ + return (x); \ + } + + /* + * Restore the defer_if_permit flag to its value before HELO/EHLO, and do + * not set the flag when it was already raised by a previous protocol + * stage. + */ + state->defer_if_permit.active = state->defer_if_permit_client; + + /* + * Apply restrictions in the order as specified. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && helo_restrctions->argc) + status = generic_checks(state, helo_restrctions, state->helo_name, + SMTPD_NAME_HELO, CHECK_HELO_ACL); + state->defer_if_permit_helo = state->defer_if_permit.active; + + SMTPD_CHECK_HELO_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* smtpd_check_mail - validate sender address, driver */ + +char *smtpd_check_mail(SMTPD_STATE *state, char *sender) +{ + int status; + char *saved_sender; + + /* + * Initialize. + */ + if (sender == 0) + return (0); + + /* + * Minor kluge so that we can delegate work to the generic routine and so + * that we can syslog the recipient with the reject messages. + */ + SMTPD_CHECK_PUSH(saved_sender, state->sender, sender); + +#define SMTPD_CHECK_MAIL_RETURN(x) { \ + SMTPD_CHECK_POP(state->sender, saved_sender); \ + return (x); \ + } + + /* + * Restore the defer_if_permit flag to its value before MAIL FROM, and do + * not set the flag when it was already raised by a previous protocol + * stage. The client may skip the helo/ehlo. + */ + state->defer_if_permit.active = state->defer_if_permit_client + | state->defer_if_permit_helo; + state->sender_rcptmap_checked = 0; + + /* + * Apply restrictions in the order as specified. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && mail_restrctions->argc) + status = generic_checks(state, mail_restrctions, sender, + SMTPD_NAME_SENDER, CHECK_SENDER_ACL); + state->defer_if_permit_sender = state->defer_if_permit.active; + + /* + * If the "reject_unlisted_sender" restriction still needs to be applied, + * validate the sender here. + */ + if (var_smtpd_rej_unl_from + && status != SMTPD_CHECK_REJECT && state->sender_rcptmap_checked == 0 + && state->discard == 0 && *sender) + status = check_sender_rcpt_maps(state, sender); + + SMTPD_CHECK_MAIL_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* smtpd_check_rcpt - validate recipient address, driver */ + +char *smtpd_check_rcpt(SMTPD_STATE *state, char *recipient) +{ + int status; + char *saved_recipient; + char *err; + ARGV *restrctions[2]; + int n; + int rcpt_index; + int relay_index; + + /* + * Initialize. + */ + if (recipient == 0) + return (0); + + /* + * XXX 2821: Section 3.6 requires that "postmaster" be accepted even when + * specified without a fully qualified domain name. + */ + if (strcasecmp(recipient, "postmaster") == 0) + return (0); + + /* + * Minor kluge so that we can delegate work to the generic routine and so + * that we can syslog the recipient with the reject messages. + */ + SMTPD_CHECK_PUSH(saved_recipient, state->recipient, recipient); + +#define SMTPD_CHECK_RCPT_RETURN(x) { \ + SMTPD_CHECK_POP(state->recipient, saved_recipient); \ + return (x); \ + } + + /* + * The "check_recipient_maps" restriction is relevant only when + * responding to RCPT TO or VRFY. + */ + state->recipient_rcptmap_checked = 0; + + /* + * Apply delayed restrictions. + */ + if (var_smtpd_delay_reject) + if ((err = smtpd_check_client(state)) != 0 + || (err = smtpd_check_helo(state, state->helo_name)) != 0 + || (err = smtpd_check_mail(state, state->sender)) != 0) + SMTPD_CHECK_RCPT_RETURN(err); + + /* + * Restore the defer_if_permit flag to its value before RCPT TO, and do + * not set the flag when it was already raised by a previous protocol + * stage. + */ + state->defer_if_permit.active = state->defer_if_permit_sender; + + /* + * Apply restrictions in the order as specified. We allow relay + * restrictions to be empty, for sites that require backwards + * compatibility. + * + * If compatibility_level < 1 and smtpd_relay_restrictions is left at its + * default value, find out if the new smtpd_relay_restrictions default + * value would block the request, without logging REJECT messages. + * Approach: evaluate fake relay restrictions (permit_mynetworks, + * permit_sasl_authenticated, permit_auth_destination) and log a warning + * if the result is DUNNO instead of OK, i.e. a reject_unauth_destination + * at the end would have blocked the request. + * + * If warn_compat_break_relay_restrictions is true, always evaluate + * smtpd_relay_restrictions last (rcpt_index == 0). The backwards + * compatibility warning says that it avoids blocking a recipient (with + * "Relay access denied"); that is not useful information when moments + * later, smtpd_recipient_restrictions blocks the recipient anyway (with + * 'Relay access denied' or some other cause). + */ + SMTPD_CHECK_RESET(); + rcpt_index = (var_relay_before_rcpt_checks + && !warn_compat_break_relay_restrictions); + relay_index = !rcpt_index; + + restrctions[rcpt_index] = rcpt_restrctions; + restrctions[relay_index] = warn_compat_break_relay_restrictions ? + fake_relay_restrctions : relay_restrctions; + for (n = 0; n < 2; n++) { + status = setjmp(smtpd_check_buf); + if (status == 0 && restrctions[n]->argc) + status = generic_checks(state, restrctions[n], + recipient, SMTPD_NAME_RECIPIENT, CHECK_RECIP_ACL); + if (n == relay_index && warn_compat_break_relay_restrictions + && status == SMTPD_CHECK_DUNNO) { + msg_info("using backwards-compatible default setting \"" + VAR_RELAY_CHECKS " = (empty)\" to avoid \"Relay " + "access denied\" error for recipient \"%s\" from " + "client \"%s\"", state->recipient, state->namaddr); + } + if (status == SMTPD_CHECK_REJECT) + break; + } + if (status == SMTPD_CHECK_REJECT + && warn_compat_relay_before_rcpt_checks && n == 0) + msg_info("using backwards-compatible default setting " + VAR_RELAY_BEFORE_RCPT_CHECKS "=no to reject " + "recipient \"%s\" from client \"%s\"", + state->recipient, state->namaddr); + + /* + * Force permission into deferral when some earlier temporary error may + * have prevented us from rejecting mail, and report the earlier problem. + */ + if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active) + status = smtpd_check_reject(state, state->defer_if_permit.class, + state->defer_if_permit.code, + STR(state->defer_if_permit.dsn), + "%s", STR(state->defer_if_permit.reason)); + + /* + * If the "reject_unlisted_recipient" restriction still needs to be + * applied, validate the recipient here. + */ + if (var_smtpd_rej_unl_rcpt + && status != SMTPD_CHECK_REJECT + && state->recipient_rcptmap_checked == 0 + && state->discard == 0) + status = check_recipient_rcpt_maps(state, recipient); + + SMTPD_CHECK_RCPT_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* smtpd_check_etrn - validate ETRN request */ + +char *smtpd_check_etrn(SMTPD_STATE *state, char *domain) +{ + int status; + char *saved_etrn_name; + char *err; + + /* + * Initialize. + */ + if (domain == 0) + return (0); + + /* + * Minor kluge so that we can delegate work to the generic routine and so + * that we can syslog the recipient with the reject messages. + */ + SMTPD_CHECK_PUSH(saved_etrn_name, state->etrn_name, domain); + +#define SMTPD_CHECK_ETRN_RETURN(x) { \ + SMTPD_CHECK_POP(state->etrn_name, saved_etrn_name); \ + return (x); \ + } + + /* + * Apply delayed restrictions. + */ + if (var_smtpd_delay_reject) + if ((err = smtpd_check_client(state)) != 0 + || (err = smtpd_check_helo(state, state->helo_name)) != 0) + SMTPD_CHECK_ETRN_RETURN(err); + + /* + * Restore the defer_if_permit flag to its value before ETRN, and do not + * set the flag when it was already raised by a previous protocol stage. + * The client may skip the helo/ehlo. + */ + state->defer_if_permit.active = state->defer_if_permit_client + | state->defer_if_permit_helo; + + /* + * Apply restrictions in the order as specified. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && etrn_restrctions->argc) + status = generic_checks(state, etrn_restrctions, domain, + SMTPD_NAME_ETRN, CHECK_ETRN_ACL); + + /* + * Force permission into deferral when some earlier temporary error may + * have prevented us from rejecting mail, and report the earlier problem. + */ + if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active) + status = smtpd_check_reject(state, state->defer_if_permit.class, + state->defer_if_permit.code, + STR(state->defer_if_permit.dsn), + "%s", STR(state->defer_if_permit.reason)); + + SMTPD_CHECK_ETRN_RETURN(status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* check_recipient_rcpt_maps - generic_checks() recipient table check */ + +static int check_recipient_rcpt_maps(SMTPD_STATE *state, const char *recipient) +{ + + /* + * Duplicate suppression. There's an implicit check_recipient_maps + * restriction at the end of all recipient restrictions. + */ + if (smtpd_input_transp_mask & INPUT_TRANSP_UNKNOWN_RCPT) + return (0); + if (state->recipient_rcptmap_checked == 1) + return (0); + if (state->warn_if_reject == 0) + /* We really validate the recipient address. */ + state->recipient_rcptmap_checked = 1; + return (check_rcpt_maps(state, state->sender, recipient, + SMTPD_NAME_RECIPIENT)); +} + +/* check_sender_rcpt_maps - generic_checks() sender table check */ + +static int check_sender_rcpt_maps(SMTPD_STATE *state, const char *sender) +{ + + /* + * Duplicate suppression. There's an implicit check_sender_maps + * restriction at the end of all sender restrictions. + */ + if (smtpd_input_transp_mask & INPUT_TRANSP_UNKNOWN_RCPT) + return (0); + if (state->sender_rcptmap_checked == 1) + return (0); + if (state->warn_if_reject == 0) + /* We really validate the sender address. */ + state->sender_rcptmap_checked = 1; + return (check_rcpt_maps(state, state->recipient, sender, + SMTPD_NAME_SENDER)); +} + +/* check_rcpt_maps - generic_checks() interface for recipient table check */ + +static int check_rcpt_maps(SMTPD_STATE *state, const char *sender, + const char *recipient, + const char *reply_class) +{ + const RESOLVE_REPLY *reply; + DSN_SPLIT dp; + + if (msg_verbose) + msg_info(">>> CHECKING %s VALIDATION MAPS <<<", reply_class); + + /* + * Resolve the address. + */ + reply = smtpd_resolve_addr(sender, recipient); + if (reply->flags & RESOLVE_FLAG_FAIL) + reject_dict_retry(state, recipient); + + /* + * Make complex expressions more readable? + */ +#define MATCH(map, rcpt) \ + check_mail_addr_find(state, recipient, map, rcpt, (char **) 0) + +#define NOMATCH(map, rcpt) (MATCH(map, rcpt) == 0) + + /* + * XXX We assume the recipient address is OK if it matches a canonical + * map or virtual alias map. Eventually, the address resolver should give + * us the final resolved recipient address, and the SMTP server should + * write the final resolved recipient address to the output record + * stream. See also the next comment block on recipients in virtual alias + * domains. + */ + if (MATCH(rcpt_canon_maps, CONST_STR(reply->recipient)) + || (strcmp(reply_class, SMTPD_NAME_SENDER) == 0 + && MATCH(send_canon_maps, CONST_STR(reply->recipient))) + || MATCH(canonical_maps, CONST_STR(reply->recipient)) + || MATCH(virt_alias_maps, CONST_STR(reply->recipient))) + return (0); + + /* + * At this point, anything that resolves to the error mailer is known to + * be undeliverable. + * + * XXX Until the address resolver does final address resolution, known and + * unknown recipients in virtual alias domains will both resolve to + * "error:user unknown". + */ + if (strcmp(STR(reply->transport), MAIL_SERVICE_ERROR) == 0) { + dsn_split(&dp, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "5.1.0" : "5.1.1", STR(reply->nexthop)); + return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, + (reply->flags & RESOLVE_CLASS_ALIAS) ? + var_virt_alias_code : 550, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "<%s>: %s rejected: %s", + recipient, reply_class, + dp.text)); + } + if (strcmp(STR(reply->transport), MAIL_SERVICE_RETRY) == 0) { + dsn_split(&dp, strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "4.1.0" : "4.1.1", STR(reply->nexthop)); + return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, 450, + smtpd_dsn_fix(DSN_STATUS(dp.dsn), + reply_class), + "<%s>: %s rejected: %s", + recipient, reply_class, + dp.text)); + } + + /* + * Search the recipient lookup tables of the respective address class. + * + * XXX Use the less expensive maps_find() (built-in case folding) instead of + * the baroque mail_addr_find(). But then we have to strip the domain and + * deal with address extensions ourselves. + * + * XXX But that would break sites that use the virtual delivery agent for + * local delivery, because the virtual delivery agent requires + * user@domain style addresses in its user database. + */ +#define MATCH_LEFT(l, r, n) \ + (strncasecmp_utf8((l), (r), (n)) == 0 && (r)[n] == '@') + + switch (reply->flags & RESOLVE_CLASS_MASK) { + + /* + * Reject mail to unknown addresses in local domains (domains that + * match $mydestination or ${proxy,inet}_interfaces). + */ + case RESOLVE_CLASS_LOCAL: + if (*var_local_rcpt_maps + /* Generated by bounce, absorbed by qmgr. */ + && !MATCH_LEFT(var_double_bounce_sender, CONST_STR(reply->recipient), + strlen(var_double_bounce_sender)) + /* Absorbed by qmgr. */ + && !MATCH_LEFT(MAIL_ADDR_POSTMASTER, CONST_STR(reply->recipient), + strlen(MAIL_ADDR_POSTMASTER)) + /* Generated by bounce. */ + && !MATCH_LEFT(MAIL_ADDR_MAIL_DAEMON, CONST_STR(reply->recipient), + strlen(MAIL_ADDR_MAIL_DAEMON)) + && NOMATCH(local_rcpt_maps, CONST_STR(reply->recipient))) + return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, + var_local_rcpt_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "5.1.0" : "5.1.1", + "<%s>: %s rejected: User unknown%s", + recipient, reply_class, + var_show_unk_rcpt_table ? + " in local recipient table" : "")); + break; + + /* + * Reject mail to unknown addresses in virtual mailbox domains. + */ + case RESOLVE_CLASS_VIRTUAL: + if (*var_virt_mailbox_maps + && NOMATCH(virt_mailbox_maps, CONST_STR(reply->recipient))) + return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, + var_virt_mailbox_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "5.1.0" : "5.1.1", + "<%s>: %s rejected: User unknown%s", + recipient, reply_class, + var_show_unk_rcpt_table ? + " in virtual mailbox table" : "")); + break; + + /* + * Reject mail to unknown addresses in relay domains. + */ + case RESOLVE_CLASS_RELAY: + if (*var_relay_rcpt_maps + && NOMATCH(relay_rcpt_maps, CONST_STR(reply->recipient))) + return (smtpd_check_reject(state, MAIL_ERROR_BOUNCE, + var_relay_rcpt_code, + strcmp(reply_class, SMTPD_NAME_SENDER) == 0 ? + "5.1.0" : "5.1.1", + "<%s>: %s rejected: User unknown%s", + recipient, reply_class, + var_show_unk_rcpt_table ? + " in relay recipient table" : "")); + if (warn_compat_break_relay_domains) + msg_info("using backwards-compatible default setting " + VAR_RELAY_DOMAINS "=$mydestination to accept mail " + "for address \"%s\"", recipient); + break; + } + + /* + * Accept all other addresses - including addresses that passed the above + * tests because of some table lookup problem. + */ + return (0); +} + +/* smtpd_check_size - check optional SIZE parameter value */ + +char *smtpd_check_size(SMTPD_STATE *state, off_t size) +{ + int status; + + /* + * Return here in case of serious trouble. + */ + SMTPD_CHECK_RESET(); + if ((status = setjmp(smtpd_check_buf)) != 0) + return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); + + /* + * Check against file size limit. + */ + if (ENFORCING_SIZE_LIMIT(var_message_limit) && size > var_message_limit) { + (void) smtpd_check_reject(state, MAIL_ERROR_POLICY, + 552, "5.3.4", + "Message size exceeds fixed limit"); + return (STR(error_text)); + } + return (0); +} + +/* smtpd_check_queue - check queue space */ + +char *smtpd_check_queue(SMTPD_STATE *state) +{ + const char *myname = "smtpd_check_queue"; + struct fsspace fsbuf; + int status; + + /* + * Return here in case of serious trouble. + */ + SMTPD_CHECK_RESET(); + if ((status = setjmp(smtpd_check_buf)) != 0) + return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); + + /* + * Avoid overflow/underflow when comparing message size against available + * space. + */ +#define BLOCKS(x) ((x) / fsbuf.block_size) + + fsspace(".", &fsbuf); + if (msg_verbose) + msg_info("%s: blocks %lu avail %lu min_free %lu msg_size_limit %lu", + myname, + (unsigned long) fsbuf.block_size, + (unsigned long) fsbuf.block_free, + (unsigned long) var_queue_minfree, + (unsigned long) var_message_limit); + if (BLOCKS(var_queue_minfree) >= fsbuf.block_free + || BLOCKS(var_message_limit) >= fsbuf.block_free / smtpd_space_multf) { + (void) smtpd_check_reject(state, MAIL_ERROR_RESOURCE, + 452, "4.3.1", + "Insufficient system storage"); + msg_warn("not enough free space in mail queue: %lu bytes < " + "%g*message size limit", + (unsigned long) fsbuf.block_free * fsbuf.block_size, + smtpd_space_multf); + return (STR(error_text)); + } + return (0); +} + +/* smtpd_check_data - check DATA command */ + +char *smtpd_check_data(SMTPD_STATE *state) +{ + int status; + char *NOCLOBBER saved_recipient; + + /* + * Minor kluge so that we can delegate work to the generic routine. We + * provide no recipient information in the case of multiple recipients, + * This restriction applies to all recipients alike, and logging only one + * of them would be misleading. + */ + if (state->rcpt_count > 1) { + saved_recipient = state->recipient; + state->recipient = 0; + } + + /* + * Reset the defer_if_permit flag. This is necessary when some recipients + * were accepted but the last one was rejected. + */ + state->defer_if_permit.active = 0; + + /* + * Apply restrictions in the order as specified. + * + * XXX We cannot specify a default target for a bare access map. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && data_restrctions->argc) + status = generic_checks(state, data_restrctions, + SMTPD_CMD_DATA, SMTPD_NAME_DATA, NO_DEF_ACL); + + /* + * Force permission into deferral when some earlier temporary error may + * have prevented us from rejecting mail, and report the earlier problem. + */ + if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active) + status = smtpd_check_reject(state, state->defer_if_permit.class, + state->defer_if_permit.code, + STR(state->defer_if_permit.dsn), + "%s", STR(state->defer_if_permit.reason)); + + if (state->rcpt_count > 1) + state->recipient = saved_recipient; + + return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +/* smtpd_check_eod - check end-of-data command */ + +char *smtpd_check_eod(SMTPD_STATE *state) +{ + int status; + char *NOCLOBBER saved_recipient; + + /* + * Minor kluge so that we can delegate work to the generic routine. We + * provide no recipient information in the case of multiple recipients, + * This restriction applies to all recipients alike, and logging only one + * of them would be misleading. + */ + if (state->rcpt_count > 1) { + saved_recipient = state->recipient; + state->recipient = 0; + } + + /* + * Reset the defer_if_permit flag. This is necessary when some recipients + * were accepted but the last one was rejected. + */ + state->defer_if_permit.active = 0; + + /* + * Apply restrictions in the order as specified. + * + * XXX We cannot specify a default target for a bare access map. + */ + SMTPD_CHECK_RESET(); + status = setjmp(smtpd_check_buf); + if (status == 0 && eod_restrictions->argc) + status = generic_checks(state, eod_restrictions, + SMTPD_CMD_EOD, SMTPD_NAME_EOD, NO_DEF_ACL); + + /* + * Force permission into deferral when some earlier temporary error may + * have prevented us from rejecting mail, and report the earlier problem. + */ + if (status != SMTPD_CHECK_REJECT && state->defer_if_permit.active) + status = smtpd_check_reject(state, state->defer_if_permit.class, + state->defer_if_permit.code, + STR(state->defer_if_permit.dsn), + "%s", STR(state->defer_if_permit.reason)); + + if (state->rcpt_count > 1) + state->recipient = saved_recipient; + + return (status == SMTPD_CHECK_REJECT ? STR(error_text) : 0); +} + +#ifdef TEST + + /* + * Test program to try out all these restrictions without having to go live. + * This is not entirely stand-alone, as it requires access to the Postfix + * rewrite/resolve service. This is just for testing code, not for debugging + * configuration files. + */ +#include + +#include +#include + +#include +#include +#include + +#include + +int smtpd_input_transp_mask; + + /* + * Dummies. These are never set. + */ +char *var_client_checks = ""; +char *var_helo_checks = ""; +char *var_mail_checks = ""; +char *var_relay_checks = ""; +char *var_rcpt_checks = ""; +char *var_etrn_checks = ""; +char *var_data_checks = ""; +char *var_eod_checks = ""; +char *var_smtpd_uproxy_proto = ""; +int var_smtpd_uproxy_tmout = 0; + +#ifdef USE_TLS +char *var_relay_ccerts = ""; + +#endif +char *var_notify_classes = ""; +char *var_smtpd_policy_def_action = ""; +char *var_smtpd_policy_context = ""; + + /* + * String-valued configuration parameters. + */ +char *var_maps_rbl_domains; +char *var_rest_classes; +char *var_alias_maps; +char *var_send_canon_maps; +char *var_rcpt_canon_maps; +char *var_canonical_maps; +char *var_virt_alias_maps; +char *var_virt_alias_doms; +char *var_virt_mailbox_maps; +char *var_virt_mailbox_doms; +char *var_local_rcpt_maps; +char *var_perm_mx_networks; +char *var_smtpd_null_key; +char *var_smtpd_snd_auth_maps; +char *var_rbl_reply_maps; +char *var_smtpd_exp_filter; +char *var_def_rbl_reply; +char *var_relay_rcpt_maps; +char *var_verify_sender; +char *var_smtpd_sasl_opts; +char *var_local_rwr_clients; +char *var_smtpd_relay_ccerts; +char *var_unv_from_why; +char *var_unv_rcpt_why; +char *var_stress; +char *var_unk_name_tf_act; +char *var_unk_addr_tf_act; +char *var_unv_rcpt_tf_act; +char *var_unv_from_tf_act; +char *var_smtpd_acl_perm_log; + +typedef struct { + char *name; + char *defval; + char **target; +} STRING_TABLE; + +#undef DEF_VIRT_ALIAS_MAPS +#define DEF_VIRT_ALIAS_MAPS "" + +#undef DEF_LOCAL_RCPT_MAPS +#define DEF_LOCAL_RCPT_MAPS "" + +static const STRING_TABLE string_table[] = { + VAR_MAPS_RBL_DOMAINS, DEF_MAPS_RBL_DOMAINS, &var_maps_rbl_domains, + VAR_MYORIGIN, DEF_MYORIGIN, &var_myorigin, + VAR_MYDEST, DEF_MYDEST, &var_mydest, + VAR_INET_INTERFACES, DEF_INET_INTERFACES, &var_inet_interfaces, + VAR_PROXY_INTERFACES, DEF_PROXY_INTERFACES, &var_proxy_interfaces, + VAR_RCPT_DELIM, DEF_RCPT_DELIM, &var_rcpt_delim, + VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes, + VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, + VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, + VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, + VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, + VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, + VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, + VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, + VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, + VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, + VAR_PERM_MX_NETWORKS, DEF_PERM_MX_NETWORKS, &var_perm_mx_networks, + VAR_PAR_DOM_MATCH, DEF_PAR_DOM_MATCH, &var_par_dom_match, + VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, + VAR_SMTPD_NULL_KEY, DEF_SMTPD_NULL_KEY, &var_smtpd_null_key, + VAR_DOUBLE_BOUNCE, DEF_DOUBLE_BOUNCE, &var_double_bounce_sender, + VAR_RBL_REPLY_MAPS, DEF_RBL_REPLY_MAPS, &var_rbl_reply_maps, + VAR_SMTPD_EXP_FILTER, DEF_SMTPD_EXP_FILTER, &var_smtpd_exp_filter, + VAR_DEF_RBL_REPLY, DEF_DEF_RBL_REPLY, &var_def_rbl_reply, + VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, + VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender, + VAR_MAIL_NAME, DEF_MAIL_NAME, &var_mail_name, + VAR_SMTPD_SASL_OPTS, DEF_SMTPD_SASL_OPTS, &var_smtpd_sasl_opts, + VAR_LOC_RWR_CLIENTS, DEF_LOC_RWR_CLIENTS, &var_local_rwr_clients, + VAR_RELAY_CCERTS, DEF_RELAY_CCERTS, &var_smtpd_relay_ccerts, + VAR_UNV_FROM_WHY, DEF_UNV_FROM_WHY, &var_unv_from_why, + VAR_UNV_RCPT_WHY, DEF_UNV_RCPT_WHY, &var_unv_rcpt_why, + VAR_STRESS, DEF_STRESS, &var_stress, + /* XXX Can't use ``$name'' type default values below. */ + VAR_UNK_NAME_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unk_name_tf_act, + VAR_UNK_ADDR_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unk_addr_tf_act, + VAR_UNV_RCPT_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unv_rcpt_tf_act, + VAR_UNV_FROM_TF_ACT, DEF_REJECT_TMPF_ACT, &var_unv_from_tf_act, + /* XXX Can't use ``$name'' type default values above. */ + VAR_SMTPD_ACL_PERM_LOG, DEF_SMTPD_ACL_PERM_LOG, &var_smtpd_acl_perm_log, + VAR_SMTPD_DNS_RE_FILTER, DEF_SMTPD_DNS_RE_FILTER, &var_smtpd_dns_re_filter, + VAR_INFO_LOG_ADDR_FORM, DEF_INFO_LOG_ADDR_FORM, &var_info_log_addr_form, + /* XXX No static initialization with "", because owned by a library. */ + VAR_MYNETWORKS, "", &var_mynetworks, + VAR_RELAY_DOMAINS, "", &var_relay_domains, + 0, +}; + +/* string_init - initialize string parameters */ + +static void string_init(void) +{ + const STRING_TABLE *sp; + + for (sp = string_table; sp->name; sp++) + sp->target[0] = mystrdup(sp->defval); +} + +/* string_update - update string parameter */ + +static int string_update(char **argv) +{ + const STRING_TABLE *sp; + + for (sp = string_table; sp->name; sp++) { + if (strcasecmp(argv[0], sp->name) == 0) { + myfree(sp->target[0]); + sp->target[0] = mystrdup(argv[1]); + return (1); + } + } + return (0); +} + + /* + * Integer parameters. + */ +long var_queue_minfree; /* XXX use off_t */ +typedef struct { + char *name; + int defval; + int *target; +} INT_TABLE; + +int var_unk_client_code; +int var_bad_name_code; +int var_unk_name_code; +int var_unk_addr_code; +int var_relay_code; +int var_maps_rbl_code; +int var_map_reject_code; +int var_map_defer_code; +int var_reject_code; +int var_defer_code; +int var_non_fqdn_code; +int var_smtpd_delay_reject; +int var_allow_untrust_route; +int var_mul_rcpt_code; +int var_unv_from_rcode; +int var_unv_from_dcode; +int var_unv_rcpt_rcode; +int var_unv_rcpt_dcode; +int var_local_rcpt_code; +int var_relay_rcpt_code; +int var_virt_mailbox_code; +int var_virt_alias_code; +int var_show_unk_rcpt_table; +int var_verify_poll_count; +int var_verify_poll_delay; +int var_smtpd_policy_tmout; +int var_smtpd_policy_idle; +int var_smtpd_policy_ttl; +int var_smtpd_policy_req_limit; +int var_smtpd_policy_try_limit; +int var_smtpd_policy_try_delay; +int var_smtpd_rej_unl_from; +int var_smtpd_rej_unl_rcpt; +int var_plaintext_code; +bool var_smtpd_peername_lookup; +bool var_smtpd_client_port_log; +char *var_smtpd_dns_re_filter; +bool var_smtpd_tls_ask_ccert; + +#define int_table test_int_table + +static const INT_TABLE int_table[] = { + "msg_verbose", 0, &msg_verbose, + VAR_UNK_CLIENT_CODE, DEF_UNK_CLIENT_CODE, &var_unk_client_code, + VAR_BAD_NAME_CODE, DEF_BAD_NAME_CODE, &var_bad_name_code, + VAR_UNK_NAME_CODE, DEF_UNK_NAME_CODE, &var_unk_name_code, + VAR_UNK_ADDR_CODE, DEF_UNK_ADDR_CODE, &var_unk_addr_code, + VAR_RELAY_CODE, DEF_RELAY_CODE, &var_relay_code, + VAR_MAPS_RBL_CODE, DEF_MAPS_RBL_CODE, &var_maps_rbl_code, + VAR_MAP_REJECT_CODE, DEF_MAP_REJECT_CODE, &var_map_reject_code, + VAR_MAP_DEFER_CODE, DEF_MAP_DEFER_CODE, &var_map_defer_code, + VAR_REJECT_CODE, DEF_REJECT_CODE, &var_reject_code, + VAR_DEFER_CODE, DEF_DEFER_CODE, &var_defer_code, + VAR_NON_FQDN_CODE, DEF_NON_FQDN_CODE, &var_non_fqdn_code, + VAR_SMTPD_DELAY_REJECT, DEF_SMTPD_DELAY_REJECT, &var_smtpd_delay_reject, + VAR_ALLOW_UNTRUST_ROUTE, DEF_ALLOW_UNTRUST_ROUTE, &var_allow_untrust_route, + VAR_MUL_RCPT_CODE, DEF_MUL_RCPT_CODE, &var_mul_rcpt_code, + VAR_UNV_FROM_RCODE, DEF_UNV_FROM_RCODE, &var_unv_from_rcode, + VAR_UNV_FROM_DCODE, DEF_UNV_FROM_DCODE, &var_unv_from_dcode, + VAR_UNV_RCPT_RCODE, DEF_UNV_RCPT_RCODE, &var_unv_rcpt_rcode, + VAR_UNV_RCPT_DCODE, DEF_UNV_RCPT_DCODE, &var_unv_rcpt_dcode, + VAR_LOCAL_RCPT_CODE, DEF_LOCAL_RCPT_CODE, &var_local_rcpt_code, + VAR_RELAY_RCPT_CODE, DEF_RELAY_RCPT_CODE, &var_relay_rcpt_code, + VAR_VIRT_ALIAS_CODE, DEF_VIRT_ALIAS_CODE, &var_virt_alias_code, + VAR_VIRT_MAILBOX_CODE, DEF_VIRT_MAILBOX_CODE, &var_virt_mailbox_code, + VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table, + VAR_VERIFY_POLL_COUNT, 3, &var_verify_poll_count, + VAR_SMTPD_REJ_UNL_FROM, DEF_SMTPD_REJ_UNL_FROM, &var_smtpd_rej_unl_from, + VAR_SMTPD_REJ_UNL_RCPT, DEF_SMTPD_REJ_UNL_RCPT, &var_smtpd_rej_unl_rcpt, + VAR_PLAINTEXT_CODE, DEF_PLAINTEXT_CODE, &var_plaintext_code, + VAR_SMTPD_PEERNAME_LOOKUP, DEF_SMTPD_PEERNAME_LOOKUP, &var_smtpd_peername_lookup, + VAR_SMTPD_CLIENT_PORT_LOG, DEF_SMTPD_CLIENT_PORT_LOG, &var_smtpd_client_port_log, + VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert, + 0, +}; + +/* int_init - initialize int parameters */ + +static void int_init(void) +{ + const INT_TABLE *sp; + + for (sp = int_table; sp->name; sp++) + sp->target[0] = sp->defval; +} + +/* int_update - update int parameter */ + +static int int_update(char **argv) +{ + const INT_TABLE *ip; + + for (ip = int_table; ip->name; ip++) { + if (strcasecmp(argv[0], ip->name) == 0) { + if (!ISDIGIT(*argv[1])) + msg_fatal("bad number: %s %s", ip->name, argv[1]); + ip->target[0] = atoi(argv[1]); + return (1); + } + } + return (0); +} + + /* + * Boolean parameters. + */ +bool var_relay_before_rcpt_checks; + + /* + * Restrictions. + */ +typedef struct { + char *name; + ARGV **target; +} REST_TABLE; + +static const REST_TABLE rest_table[] = { + "client_restrictions", &client_restrctions, + "helo_restrictions", &helo_restrctions, + "sender_restrictions", &mail_restrctions, + "relay_restrictions", &relay_restrctions, + "recipient_restrictions", &rcpt_restrctions, + "etrn_restrictions", &etrn_restrctions, + 0, +}; + +/* rest_update - update restriction */ + +static int rest_update(char **argv) +{ + const REST_TABLE *rp; + + for (rp = rest_table; rp->name; rp++) { + if (strcasecmp(rp->name, argv[0]) == 0) { + argv_free(rp->target[0]); + rp->target[0] = smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, argv[1]); + return (1); + } + } + return (0); +} + +/* rest_class - (re)define a restriction class */ + +static void rest_class(char *class) +{ + char *cp = class; + char *name; + HTABLE_INFO *entry; + + if (smtpd_rest_classes == 0) + smtpd_rest_classes = htable_create(1); + + if ((name = mystrtok(&cp, CHARS_COMMA_SP)) == 0) + msg_panic("rest_class: null class name"); + if ((entry = htable_locate(smtpd_rest_classes, name)) != 0) + argv_free((ARGV *) entry->value); + else + entry = htable_enter(smtpd_rest_classes, name, (void *) 0); + entry->value = (void *) smtpd_check_parse(SMTPD_CHECK_PARSE_ALL, cp); +} + +/* resolve_clnt_init - initialize reply */ + +void resolve_clnt_init(RESOLVE_REPLY *reply) +{ + reply->flags = 0; + reply->transport = vstring_alloc(100); + reply->nexthop = vstring_alloc(100); + reply->recipient = vstring_alloc(100); +} + +void resolve_clnt_free(RESOLVE_REPLY *reply) +{ + vstring_free(reply->transport); + vstring_free(reply->nexthop); + vstring_free(reply->recipient); +} + +bool var_smtpd_sasl_enable = 0; + +#ifdef USE_SASL_AUTH + +/* smtpd_sasl_activate - stub */ + +void smtpd_sasl_activate(SMTPD_STATE *state, const char *opts_name, + const char *opts_var) +{ + msg_panic("smtpd_sasl_activate was called"); +} + +/* smtpd_sasl_deactivate - stub */ + +void smtpd_sasl_deactivate(SMTPD_STATE *state) +{ + msg_panic("smtpd_sasl_deactivate was called"); +} + +/* permit_sasl_auth - stub */ + +int permit_sasl_auth(SMTPD_STATE *state, int ifyes, int ifnot) +{ + return (ifnot); +} + +/* smtpd_sasl_state_init - the real deal */ + +void smtpd_sasl_state_init(SMTPD_STATE *state) +{ + state->sasl_username = 0; + state->sasl_method = 0; + state->sasl_sender = 0; +} + +#endif + +/* verify_clnt_query - stub */ + +int verify_clnt_query(const char *addr, int *addr_status, VSTRING *why) +{ + *addr_status = DEL_RCPT_STAT_OK; + return (VRFY_STAT_OK); +} + +/* rewrite_clnt_internal - stub */ + +VSTRING *rewrite_clnt_internal(const char *context, const char *addr, + VSTRING *result) +{ + if (addr == STR(result)) + msg_panic("rewrite_clnt_internal: result clobbers input"); + if (*addr && strchr(addr, '@') == 0) + msg_fatal("%s: address rewriting is disabled", addr); + vstring_strcpy(result, addr); + return (result); +} + +/* resolve_clnt_query - stub */ + +void resolve_clnt(const char *class, const char *unused_sender, const char *addr, + RESOLVE_REPLY *reply) +{ + const char *domain; + int rc; + + if (addr == CONST_STR(reply->recipient)) + msg_panic("resolve_clnt_query: result clobbers input"); + if (strchr(addr, '%')) + msg_fatal("%s: address rewriting is disabled", addr); + if ((domain = strrchr(addr, '@')) == 0) + msg_fatal("%s: unqualified address", addr); + domain += 1; + if ((rc = resolve_local(domain)) > 0) { + reply->flags = RESOLVE_CLASS_LOCAL; + vstring_strcpy(reply->transport, MAIL_SERVICE_LOCAL); + vstring_strcpy(reply->nexthop, domain); + } else if (rc < 0) { + reply->flags = RESOLVE_FLAG_FAIL; + } else if (string_list_match(virt_alias_doms, domain)) { + reply->flags = RESOLVE_CLASS_ALIAS; + vstring_strcpy(reply->transport, MAIL_SERVICE_ERROR); + vstring_strcpy(reply->nexthop, "user unknown"); + } else if (virt_alias_doms->error) { + reply->flags = RESOLVE_FLAG_FAIL; + } else if (string_list_match(virt_mailbox_doms, domain)) { + reply->flags = RESOLVE_CLASS_VIRTUAL; + vstring_strcpy(reply->transport, MAIL_SERVICE_VIRTUAL); + vstring_strcpy(reply->nexthop, domain); + } else if (virt_mailbox_doms->error) { + reply->flags = RESOLVE_FLAG_FAIL; + } else if (domain_list_match(relay_domains, domain)) { + reply->flags = RESOLVE_CLASS_RELAY; + vstring_strcpy(reply->transport, MAIL_SERVICE_RELAY); + vstring_strcpy(reply->nexthop, domain); + } else if (relay_domains->error) { + reply->flags = RESOLVE_FLAG_FAIL; + } else { + reply->flags = RESOLVE_CLASS_DEFAULT; + vstring_strcpy(reply->transport, MAIL_SERVICE_SMTP); + vstring_strcpy(reply->nexthop, domain); + } + vstring_strcpy(reply->recipient, addr); +} + +/* smtpd_chat_reset - stub */ + +void smtpd_chat_reset(SMTPD_STATE *unused_state) +{ +} + +/* usage - scream and terminate */ + +static NORETURN usage(char *myname) +{ + msg_fatal("usage: %s", myname); +} + +int main(int argc, char **argv) +{ + VSTRING *buf = vstring_alloc(100); + SMTPD_STATE state; + ARGV *args; + char *bp; + char *resp; + char *addr; + + /* + * Initialization. Use dummies for client information. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + if (argc != 1) + usage(argv[0]); + string_init(); + int_init(); + smtpd_check_init(); + smtpd_expand_init(); + (void) inet_proto_init(argv[0], INET_PROTO_NAME_IPV4); + smtpd_state_init(&state, VSTREAM_IN, "smtpd"); + state.queue_id = ""; + + /* + * Main loop: update config parameters or test the client, helo, sender + * and recipient restrictions. + */ + while (vstring_fgets_nonl(buf, VSTREAM_IN) != 0) { + + /* + * Tokenize the command. Note, the comma is not a separator, so that + * restriction lists can be entered as comma-separated lists. + */ + bp = STR(buf); + if (!isatty(0)) { + vstream_printf(">>> %s\n", bp); + vstream_fflush(VSTREAM_OUT); + } + if (*bp == '#') + continue; + + if (*bp == '!') { + vstream_printf("exit %d\n", system(bp + 1)); + continue; + } + args = argv_splitq(bp, CHARS_SPACE, CHARS_BRACE); + + /* + * Recognize the command. + */ + resp = "bad command"; + switch (args->argc) { + + /* + * Emtpy line. + */ + case 0: + argv_free(args); + continue; + + /* + * Special case: rewrite context. + */ + case 1: + if (strcasecmp(args->argv[0], "rewrite") == 0) { + resp = smtpd_check_rewrite(&state); + break; + } + + /* + * Other parameter-less commands. + */ + if (strcasecmp(args->argv[0], "flush_dnsxl_cache") == 0) { + if (smtpd_rbl_cache) { + ctable_free(smtpd_rbl_cache); + ctable_free(smtpd_rbl_byte_cache); + } + smtpd_rbl_cache = ctable_create(100, rbl_pagein, + rbl_pageout, (void *) 0); + smtpd_rbl_byte_cache = ctable_create(1000, rbl_byte_pagein, + rbl_byte_pageout, (void *) 0); + resp = 0; + break; + } + + /* + * Special case: client identity. + */ + case 4: + case 3: + if (strcasecmp(args->argv[0], "client") == 0) { + state.where = SMTPD_AFTER_CONNECT; + UPDATE_STRING(state.name, args->argv[1]); + UPDATE_STRING(state.reverse_name, args->argv[1]); + UPDATE_STRING(state.addr, args->argv[2]); + if (args->argc == 4) + state.name_status = + state.reverse_name_status = + atoi(args->argv[3]); + else if (strcmp(state.name, "unknown") == 0) + state.name_status = + state.reverse_name_status = + SMTPD_PEER_CODE_TEMP; + else + state.name_status = + state.reverse_name_status = + SMTPD_PEER_CODE_OK; + if (state.namaddr) + myfree(state.namaddr); + state.namaddr = concatenate(state.name, "[", state.addr, + "]", (char *) 0); + resp = smtpd_check_client(&state); + } + break; + + /* + * Try config settings. + */ +#define UPDATE_MAPS(ptr, var, val, lock) \ + { if (ptr) maps_free(ptr); ptr = maps_create(var, val, lock); } + +#define UPDATE_LIST(ptr, var, val) \ + { if (ptr) string_list_free(ptr); \ + ptr = string_list_init(var, MATCH_FLAG_NONE, val); } + + case 2: + if (strcasecmp(args->argv[0], VAR_MYDEST) == 0) { + UPDATE_STRING(var_mydest, args->argv[1]); + resolve_local_init(); + smtpd_resolve_init(100); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_VIRT_ALIAS_MAPS) == 0) { + UPDATE_STRING(var_virt_alias_maps, args->argv[1]); + UPDATE_MAPS(virt_alias_maps, VAR_VIRT_ALIAS_MAPS, + var_virt_alias_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_VIRT_ALIAS_DOMS) == 0) { + UPDATE_STRING(var_virt_alias_doms, args->argv[1]); + UPDATE_LIST(virt_alias_doms, VAR_VIRT_ALIAS_DOMS, + var_virt_alias_doms); + smtpd_resolve_init(100); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_MAPS) == 0) { + UPDATE_STRING(var_virt_mailbox_maps, args->argv[1]); + UPDATE_MAPS(virt_mailbox_maps, VAR_VIRT_MAILBOX_MAPS, + var_virt_mailbox_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_VIRT_MAILBOX_DOMS) == 0) { + UPDATE_STRING(var_virt_mailbox_doms, args->argv[1]); + UPDATE_LIST(virt_mailbox_doms, VAR_VIRT_MAILBOX_DOMS, + var_virt_mailbox_doms); + smtpd_resolve_init(100); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_LOCAL_RCPT_MAPS) == 0) { + UPDATE_STRING(var_local_rcpt_maps, args->argv[1]); + UPDATE_MAPS(local_rcpt_maps, VAR_LOCAL_RCPT_MAPS, + var_local_rcpt_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_RELAY_RCPT_MAPS) == 0) { + UPDATE_STRING(var_relay_rcpt_maps, args->argv[1]); + UPDATE_MAPS(relay_rcpt_maps, VAR_RELAY_RCPT_MAPS, + var_relay_rcpt_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_CANONICAL_MAPS) == 0) { + UPDATE_STRING(var_canonical_maps, args->argv[1]); + UPDATE_MAPS(canonical_maps, VAR_CANONICAL_MAPS, + var_canonical_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_SEND_CANON_MAPS) == 0) { + UPDATE_STRING(var_send_canon_maps, args->argv[1]); + UPDATE_MAPS(send_canon_maps, VAR_SEND_CANON_MAPS, + var_send_canon_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_RCPT_CANON_MAPS) == 0) { + UPDATE_STRING(var_rcpt_canon_maps, args->argv[1]); + UPDATE_MAPS(rcpt_canon_maps, VAR_RCPT_CANON_MAPS, + var_rcpt_canon_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_RBL_REPLY_MAPS) == 0) { + UPDATE_STRING(var_rbl_reply_maps, args->argv[1]); + UPDATE_MAPS(rbl_reply_maps, VAR_RBL_REPLY_MAPS, + var_rbl_reply_maps, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX | DICT_FLAG_UTF8_REQUEST); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_MYNETWORKS) == 0) { + /* NOT: UPDATE_STRING */ + namadr_list_free(mynetworks_curr); + mynetworks_curr = + namadr_list_init(VAR_MYNETWORKS, MATCH_FLAG_RETURN + | match_parent_style(VAR_MYNETWORKS), + args->argv[1]); + smtpd_resolve_init(100); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_RELAY_DOMAINS) == 0) { + /* NOT: UPDATE_STRING */ + domain_list_free(relay_domains); + relay_domains = + domain_list_init(VAR_RELAY_DOMAINS, + match_parent_style(VAR_RELAY_DOMAINS), + args->argv[1]); + smtpd_resolve_init(100); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_PERM_MX_NETWORKS) == 0) { + UPDATE_STRING(var_perm_mx_networks, args->argv[1]); + domain_list_free(perm_mx_networks); + perm_mx_networks = + namadr_list_init(VAR_PERM_MX_NETWORKS, MATCH_FLAG_RETURN + | match_parent_style(VAR_PERM_MX_NETWORKS), + args->argv[1]); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_SMTPD_DNS_RE_FILTER) == 0) { + /* NOT: UPDATE_STRING */ + dns_rr_filter_compile(VAR_SMTPD_DNS_RE_FILTER, args->argv[1]); + resp = 0; + break; + } +#ifdef USE_TLS + if (strcasecmp(args->argv[0], VAR_RELAY_CCERTS) == 0) { + UPDATE_STRING(var_smtpd_relay_ccerts, args->argv[1]); + UPDATE_MAPS(relay_ccerts, VAR_RELAY_CCERTS, + var_smtpd_relay_ccerts, DICT_FLAG_LOCK + | DICT_FLAG_FOLD_FIX); + resp = 0; + } +#endif + if (strcasecmp(args->argv[0], "restriction_class") == 0) { + rest_class(args->argv[1]); + resp = 0; + break; + } + if (strcasecmp(args->argv[0], VAR_LOC_RWR_CLIENTS) == 0) { + UPDATE_STRING(var_local_rwr_clients, args->argv[1]); + argv_free(local_rewrite_clients); + local_rewrite_clients = smtpd_check_parse(SMTPD_CHECK_PARSE_MAPS, + var_local_rwr_clients); + } + if (int_update(args->argv) + || string_update(args->argv) + || rest_update(args->argv)) { + resp = 0; + break; + } + + /* + * Try restrictions. + */ +#define TRIM_ADDR(src, res) { \ + if (*(res = src) == '<') { \ + res += strlen(res) - 1; \ + if (*res == '>') \ + *res = 0; \ + res = src + 1; \ + } \ + } + + if (strcasecmp(args->argv[0], "helo") == 0) { + state.where = "HELO"; + resp = smtpd_check_helo(&state, args->argv[1]); + UPDATE_STRING(state.helo_name, args->argv[1]); + } else if (strcasecmp(args->argv[0], "mail") == 0) { + state.where = "MAIL"; + TRIM_ADDR(args->argv[1], addr); + UPDATE_STRING(state.sender, addr); + resp = smtpd_check_mail(&state, addr); + } else if (strcasecmp(args->argv[0], "rcpt") == 0) { + state.where = "RCPT"; + TRIM_ADDR(args->argv[1], addr); + resp = smtpd_check_rcpt(&state, addr); +#ifdef USE_TLS + } else if (strcasecmp(args->argv[0], "fingerprint") == 0) { + if (state.tls_context == 0) { + state.tls_context = + (TLS_SESS_STATE *) mymalloc(sizeof(*state.tls_context)); + memset((void *) state.tls_context, 0, + sizeof(*state.tls_context)); + state.tls_context->peer_cert_fprint = + state.tls_context->peer_pkey_fprint = 0; + } + state.tls_context->peer_status |= TLS_CERT_FLAG_PRESENT; + UPDATE_STRING(state.tls_context->peer_cert_fprint, + args->argv[1]); + state.tls_context->peer_pkey_fprint = + state.tls_context->peer_cert_fprint; + resp = "OK"; + break; +#endif + } + break; + + /* + * Show commands. + */ + default: + if (strcasecmp(args->argv[0], "check_rewrite") == 0) { + smtpd_check_rewrite(&state); + resp = state.rewrite_context; + break; + } + resp = "Commands...\n\ + client
[]\n\ + helo \n\ + sender
\n\ + recipient
\n\ + check_rewrite\n\ + msg_verbose \n\ + client_restrictions \n\ + helo_restrictions \n\ + sender_restrictions \n\ + recipient_restrictions \n\ + restriction_class name,\n\ + flush_dnsxl_cache\n\ + \n\ + Note: no address rewriting \n"; + break; + } + vstream_printf("%s\n", resp ? resp : "OK"); + vstream_fflush(VSTREAM_OUT); + argv_free(args); + } + vstring_free(buf); + smtpd_state_reset(&state); +#define FREE_STRING(s) { if (s) myfree(s); } + FREE_STRING(state.helo_name); + FREE_STRING(state.sender); +#ifdef USE_TLS + if (state.tls_context) { + FREE_STRING(state.tls_context->peer_cert_fprint); + myfree((void *) state.tls_context); + } +#endif + exit(0); +} + +#endif diff --git a/src/smtpd/smtpd_check.h b/src/smtpd/smtpd_check.h new file mode 100644 index 0000000..bf0fe00 --- /dev/null +++ b/src/smtpd/smtpd_check.h @@ -0,0 +1,44 @@ +/*++ +/* NAME +/* smtpd_check 3h +/* SUMMARY +/* SMTP client request filtering +/* SYNOPSIS +/* #include "smtpd.h" +/* #include "smtpd_check.h" +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void smtpd_check_init(void); +extern int smtpd_check_addr(const char *, const char *, int); +extern char *smtpd_check_rewrite(SMTPD_STATE *); +extern char *smtpd_check_client(SMTPD_STATE *); +extern char *smtpd_check_helo(SMTPD_STATE *, char *); +extern char *smtpd_check_mail(SMTPD_STATE *, char *); +extern char *smtpd_check_size(SMTPD_STATE *, off_t); +extern char *smtpd_check_queue(SMTPD_STATE *); +extern char *smtpd_check_rcpt(SMTPD_STATE *, char *); +extern char *smtpd_check_etrn(SMTPD_STATE *, char *); +extern char *smtpd_check_data(SMTPD_STATE *); +extern char *smtpd_check_eod(SMTPD_STATE *); +extern char *smtpd_check_policy(SMTPD_STATE *, char *); +extern void log_whatsup(SMTPD_STATE *, const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/smtpd/smtpd_check.in b/src/smtpd/smtpd_check.in new file mode 100644 index 0000000..efeba5b --- /dev/null +++ b/src/smtpd/smtpd_check.in @@ -0,0 +1,182 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.3.0/28 +relay_domains porcupine.org +maps_rbl_domains dnsbltest.porcupine.org +# +# Test the client restrictions. +# +client_restrictions permit_mynetworks,reject_unknown_client,hash:./smtpd_check_access +client unknown 131.155.210.17 +client unknown 168.100.3.13 +client random.bad.domain 123.123.123.123 +client friend.bad.domain 123.123.123.123 +client bad.domain 123.123.123.123 +client wzv.win.tue.nl 131.155.210.17 +client aa.win.tue.nl 131.155.210.18 +client_restrictions permit_mynetworks +# +# Test the helo restrictions +# +helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,hash:./smtpd_check_access +client unknown 131.155.210.17 +helo foo. +client foo 123.123.123.123 +helo foo. +helo foo +helo spike.porcupine.org +helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access +helo random.bad.domain +helo friend.bad.domain +helo_restrictions reject_invalid_hostname,reject_unknown_hostname +helo 123.123.123.123 +helo [123.123.123.123] +helo [::] +helo [ipv6:::] +helo [ipv6::::] +helo_restrictions permit_naked_ip_address,reject_invalid_hostname,reject_unknown_hostname +helo 123.123.123.123 +# +# Test the sender restrictions +# +sender_restrictions permit_mynetworks,reject_unknown_client +client unknown 131.155.210.17 +mail foo@ibm.com +client unknown 168.100.3.13 +mail foo@ibm.com +client foo 123.123.123.123 +mail foo@ibm.com +sender_restrictions reject_unknown_address +mail foo@ibm.com +mail foo@bad.domain +sender_restrictions hash:./smtpd_check_access +mail bad-sender@any.domain +mail bad-sender@good.domain +mail reject@this.address +mail Reject@this.address +mail foo@bad.domain +mail foo@Bad.domain +mail foo@random.bad.domain +mail foo@friend.bad.domain +# +# Test the recipient restrictions +# +recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains +client unknown 131.155.210.17 +rcpt foo@ibm.com +client unknown 168.100.3.13 +rcpt foo@ibm.com +client foo 123.123.123.123 +rcpt foo@ibm.com +rcpt foo@porcupine.org +recipient_restrictions check_relay_domains +client foo.porcupine.org 168.100.3.13 +rcpt foo@ibm.com +rcpt foo@porcupine.org +client foo 123.123.123.123 +rcpt foo@ibm.com +rcpt foo@porcupine.org +recipient_restrictions hash:./smtpd_check_access +mail bad-sender@any.domain +mail bad-sender@good.domain +mail reject@this.address +mail foo@bad.domain +mail foo@random.bad.domain +mail foo@friend.bad.domain +# +# RBL +# +client_restrictions reject_maps_rbl +client spike.porcupine.org 168.100.3.2 +client foo 127.0.0.2 +# +# Hybrids +# +recipient_restrictions check_relay_domains +client foo 131.155.210.17 +rcpt foo@ibm.com +recipient_restrictions check_client_access,hash:./smtpd_check_access,check_relay_domains +client foo 131.155.210.17 +rcpt foo@porcupine.org +helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access +recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_relay_domains +helo bad.domain +rcpt foo@porcupine.org +helo 131.155.210.17 +rcpt foo@porcupine.org +recipient_restrictions check_sender_access,hash:./smtpd_check_access,check_relay_domains +mail foo@bad.domain +rcpt foo@porcupine.org +mail foo@friend.bad.domain +rcpt foo@porcupine.org +# +# MX backup +# +#mydestination spike.porcupine.org,localhost.porcupine.org +#inet_interfaces 168.100.3.2,127.0.0.1 +#recipient_restrictions permit_mx_backup,reject +#rcpt wietse@wzv.win.tue.nl +#rcpt wietse@trouble.org +#rcpt wietse@porcupine.org +# +# Deferred restrictions +# +client_restrictions permit +helo_restrictions permit +sender_restrictions permit +recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_sender_access,hash:./smtpd_check_access +helo bad.domain +mail foo@good.domain +rcpt foo@porcupine.org +helo good.domain +mail foo@bad.domain +rcpt foo@porcupine.org +# +# FQDN restrictions +# +helo_restrictions reject_non_fqdn_hostname +sender_restrictions reject_non_fqdn_sender +recipient_restrictions reject_non_fqdn_recipient +helo foo.bar. +helo foo.bar +helo foo +mail foo@foo.bar. +mail foo@foo.bar +mail foo@foo +mail foo +rcpt foo@foo.bar. +rcpt foo@foo.bar +rcpt foo@foo +rcpt foo +# +# Numerical HELO checks +# +helo_restrictions permit_naked_ip_address,reject_non_fqdn_hostname +helo [1.2.3.4] +helo [321.255.255.255] +helo [0.255.255.255] +helo [1.2.3.321] +helo [1.2.3] +helo [1.2.3.4.5] +helo [1..2.3.4] +helo [.1.2.3.4] +helo [1.2.3.4.5.] +helo 1.2.3.4 +helo 321.255.255.255 +helo 0.255.255.255 +helo 1.2.3.321 +helo 1.2.3 +helo 1.2.3.4.5 +helo 1..2.3.4 +helo .1.2.3.4 +helo 1.2.3.4.5. +# +# The defer restriction +# +defer_code 444 +helo_restrictions defer +helo foobar diff --git a/src/smtpd/smtpd_check.in2 b/src/smtpd/smtpd_check.in2 new file mode 100644 index 0000000..804fde1 --- /dev/null +++ b/src/smtpd/smtpd_check.in2 @@ -0,0 +1,116 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.3.0/28 +relay_domains porcupine.org +maps_rbl_domains dnsbltest.porcupine.org +# +# Test the client restrictions. +# +client_restrictions permit_mynetworks,reject_unknown_client,check_client_access,hash:./smtpd_check_access +client unknown 131.155.210.17 +client unknown 168.100.3.13 +client random.bad.domain 123.123.123.123 +client friend.bad.domain 123.123.123.123 +client bad.domain 123.123.123.123 +client wzv.win.tue.nl 131.155.210.17 +client aa.win.tue.nl 131.155.210.18 +client_restrictions permit_mynetworks +# +# Test the helo restrictions +# +helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,check_helo_access,hash:./smtpd_check_access +client unknown 131.155.210.17 +helo foo. +client foo 123.123.123.123 +helo foo. +helo foo +helo spike.porcupine.org +helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,check_helo_access,hash:./smtpd_check_access +helo random.bad.domain +helo friend.bad.domain +# +# Test the sender restrictions +# +sender_restrictions permit_mynetworks,reject_unknown_client +client unknown 131.155.210.17 +mail foo@ibm.com +client unknown 168.100.3.13 +mail foo@ibm.com +client foo 123.123.123.123 +mail foo@ibm.com +sender_restrictions reject_unknown_address +mail foo@ibm.com +mail foo@bad.domain +sender_restrictions check_sender_access,hash:./smtpd_check_access +mail bad-sender@any.domain +mail bad-sender@good.domain +mail reject@this.address +mail Reject@this.address +mail foo@bad.domain +mail foo@Bad.domain +mail foo@random.bad.domain +mail foo@friend.bad.domain +# +# Test the recipient restrictions +# +recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains +client unknown 131.155.210.17 +rcpt foo@ibm.com +client unknown 168.100.3.13 +rcpt foo@ibm.com +client foo 123.123.123.123 +rcpt foo@ibm.com +rcpt foo@porcupine.org +recipient_restrictions check_relay_domains +client foo.porcupine.org 168.100.3.13 +rcpt foo@ibm.com +rcpt foo@porcupine.org +client foo 123.123.123.123 +rcpt foo@ibm.com +rcpt foo@porcupine.org +recipient_restrictions check_recipient_access,hash:./smtpd_check_access +mail bad-sender@any.domain +mail bad-sender@good.domain +mail reject@this.address +mail foo@bad.domain +mail foo@random.bad.domain +mail foo@friend.bad.domain +# +# RBL +# +client_restrictions reject_maps_rbl +client spike.porcupine.org 168.100.3.2 +client foo 127.0.0.2 +# +# unknown sender/recipient domain +# +unknown_address_reject_code 554 +recipient_restrictions reject_unknown_recipient_domain,reject_unknown_sender_domain +mail wietse@porcupine.org +rcpt wietse@porcupine.org +rcpt wietse@no.recipient.domain +mail wietse@no.sender.domain +rcpt wietse@porcupine.org +# +# {permit_auth,reject_unauth}_destination +# +relay_domains foo.com,bar.com +mail user@some.where +recipient_restrictions permit_auth_destination,reject +rcpt user@foo.org +rcpt user@foo.com +recipient_restrictions reject_unauth_destination,permit +rcpt user@foo.org +rcpt user@foo.com +# +# unknown client tests +# +unknown_client_reject_code 550 +client_restrictions reject_unknown_client +client spike.porcupine.org 160.100.189.2 2 +client unknown 1.1.1.1 4 +client unknown 1.1.1.1 5 diff --git a/src/smtpd/smtpd_check.in3 b/src/smtpd/smtpd_check.in3 new file mode 100644 index 0000000..808f562 --- /dev/null +++ b/src/smtpd/smtpd_check.in3 @@ -0,0 +1,27 @@ +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.3.0/28 +relay_domains porcupine.org +local_recipient_maps unix:passwd.byname +client unknown 131.155.210.17 +canonical_maps tcp:localhost:200 +# +recipient_restrictions permit +rcpt no.such.user@[127.0.0.1] +# +virtual_alias_maps tcp:localhost:100 +# +recipient_restrictions permit_mx_backup +rcpt wietse@nowhere1.com +# +recipient_restrictions check_relay_domains +rcpt wietse@nowhere2.com +# +recipient_restrictions reject_unknown_recipient_domain +rcpt wietse@nowhere3.com +# +recipient_restrictions permit_auth_destination +rcpt wietse@nowhere4.com +# +recipient_restrictions reject_unauth_destination +rcpt wietse@nowhere5.com diff --git a/src/smtpd/smtpd_check.in4 b/src/smtpd/smtpd_check.in4 new file mode 100644 index 0000000..d401de9 --- /dev/null +++ b/src/smtpd/smtpd_check.in4 @@ -0,0 +1,19 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +# +# Test the new access map features +# +sender_restrictions hash:./smtpd_check_access +mail rejecttext@bad.domain +mail filter@filter.domain +mail filtertext@filter.domain +mail filtertexttext@filter.domain +mail hold@hold.domain +mail holdtext@hold.domain +mail discard@hold.domain +mail discardtext@hold.domain +mail dunnotext@dunno.domain diff --git a/src/smtpd/smtpd_check.ref b/src/smtpd/smtpd_check.ref new file mode 100644 index 0000000..8051f01 --- /dev/null +++ b/src/smtpd/smtpd_check.ref @@ -0,0 +1,398 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.3.0/28 +OK +>>> relay_domains porcupine.org +OK +>>> maps_rbl_domains dnsbltest.porcupine.org +OK +>>> # +>>> # Test the client restrictions. +>>> # +>>> client_restrictions permit_mynetworks,reject_unknown_client,hash:./smtpd_check_access +OK +>>> client unknown 131.155.210.17 +./smtpd_check: : reject: CONNECT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.3.13 +OK +>>> client random.bad.domain 123.123.123.123 +./smtpd_check: : reject: CONNECT from random.bad.domain[123.123.123.123]: 554 5.7.1 : Client host rejected: match bad.domain; proto=SMTP +554 5.7.1 : Client host rejected: match bad.domain +>>> client friend.bad.domain 123.123.123.123 +OK +>>> client bad.domain 123.123.123.123 +./smtpd_check: : reject: CONNECT from bad.domain[123.123.123.123]: 554 5.7.1 : Client host rejected: match bad.domain; proto=SMTP +554 5.7.1 : Client host rejected: match bad.domain +>>> client wzv.win.tue.nl 131.155.210.17 +OK +>>> client aa.win.tue.nl 131.155.210.18 +./smtpd_check: : reject: CONNECT from aa.win.tue.nl[131.155.210.18]: 554 5.7.1 : Client host rejected: match 131.155.210; proto=SMTP +554 5.7.1 : Client host rejected: match 131.155.210 +>>> client_restrictions permit_mynetworks +OK +>>> # +>>> # Test the helo restrictions +>>> # +>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,hash:./smtpd_check_access +OK +>>> client unknown 131.155.210.17 +OK +>>> helo foo. +./smtpd_check: : reject: HELO from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP helo= +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client foo 123.123.123.123 +OK +>>> helo foo. +./smtpd_check: : reject: HELO from foo[123.123.123.123]: 450 4.7.1 : Helo command rejected: Host not found; proto=SMTP helo= +450 4.7.1 : Helo command rejected: Host not found +>>> helo foo +./smtpd_check: : reject: HELO from foo[123.123.123.123]: 450 4.7.1 : Helo command rejected: Host not found; proto=SMTP helo= +450 4.7.1 : Helo command rejected: Host not found +>>> helo spike.porcupine.org +./smtpd_check: : reject: HELO from foo[123.123.123.123]: 554 5.7.1 : Helo command rejected: ns or mx server spike.porcupine.org; proto=SMTP helo= +554 5.7.1 : Helo command rejected: ns or mx server spike.porcupine.org +>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access +OK +>>> helo random.bad.domain +./smtpd_check: : reject: HELO from foo[123.123.123.123]: 554 5.7.1 : Helo command rejected: match bad.domain; proto=SMTP helo= +554 5.7.1 : Helo command rejected: match bad.domain +>>> helo friend.bad.domain +OK +>>> helo_restrictions reject_invalid_hostname,reject_unknown_hostname +OK +>>> helo 123.123.123.123 +./smtpd_check: : reject: HELO from foo[123.123.123.123]: 450 4.7.1 <123.123.123.123>: Helo command rejected: Host not found; proto=SMTP helo=<123.123.123.123> +450 4.7.1 <123.123.123.123>: Helo command rejected: Host not found +>>> helo [123.123.123.123] +OK +>>> helo [::] +./smtpd_check: : reject: HELO from foo[123.123.123.123]: 501 5.5.2 <[::]>: Helo command rejected: invalid ip address; proto=SMTP helo=<[::]> +501 5.5.2 <[::]>: Helo command rejected: invalid ip address +>>> helo [ipv6:::] +OK +>>> helo [ipv6::::] +./smtpd_check: : reject: HELO from foo[123.123.123.123]: 501 5.5.2 <[ipv6::::]>: Helo command rejected: invalid ip address; proto=SMTP helo=<[ipv6::::]> +501 5.5.2 <[ipv6::::]>: Helo command rejected: invalid ip address +>>> helo_restrictions permit_naked_ip_address,reject_invalid_hostname,reject_unknown_hostname +OK +>>> helo 123.123.123.123 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +OK +>>> # +>>> # Test the sender restrictions +>>> # +>>> sender_restrictions permit_mynetworks,reject_unknown_client +OK +>>> client unknown 131.155.210.17 +OK +>>> mail foo@ibm.com +./smtpd_check: : reject: MAIL from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from= proto=SMTP helo=<123.123.123.123> +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.3.13 +OK +>>> mail foo@ibm.com +OK +>>> client foo 123.123.123.123 +OK +>>> mail foo@ibm.com +OK +>>> sender_restrictions reject_unknown_address +OK +>>> mail foo@ibm.com +OK +>>> mail foo@bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 450 4.1.8 : Sender address rejected: Domain not found; from= proto=SMTP helo=<123.123.123.123> +450 4.1.8 : Sender address rejected: Domain not found +>>> sender_restrictions hash:./smtpd_check_access +OK +>>> mail bad-sender@any.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad-sender@; from= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Sender address rejected: match bad-sender@ +>>> mail bad-sender@good.domain +OK +>>> mail reject@this.address +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match reject@this.address; from= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Sender address rejected: match reject@this.address +>>> mail Reject@this.address +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match reject@this.address; from= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Sender address rejected: match reject@this.address +>>> mail foo@bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad.domain; from= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Sender address rejected: match bad.domain +>>> mail foo@Bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad.domain; from= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Sender address rejected: match bad.domain +>>> mail foo@random.bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad.domain; from= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Sender address rejected: match bad.domain +>>> mail foo@friend.bad.domain +OK +>>> # +>>> # Test the recipient restrictions +>>> # +>>> recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains +OK +>>> client unknown 131.155.210.17 +OK +>>> rcpt foo@ibm.com +./smtpd_check: : reject: RCPT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from= to= proto=SMTP helo=<123.123.123.123> +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.3.13 +OK +>>> rcpt foo@ibm.com +OK +>>> client foo 123.123.123.123 +OK +>>> rcpt foo@ibm.com +./smtpd_check: warning: support for restriction "check_relay_domains" will be removed from Postfix; use "reject_unauth_destination" instead +./smtpd_check: : reject: RCPT from foo[123.123.123.123]: 554 5.7.1 : Recipient address rejected: Relay access denied; from= to= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Recipient address rejected: Relay access denied +>>> rcpt foo@porcupine.org +OK +>>> recipient_restrictions check_relay_domains +OK +>>> client foo.porcupine.org 168.100.3.13 +OK +>>> rcpt foo@ibm.com +OK +>>> rcpt foo@porcupine.org +OK +>>> client foo 123.123.123.123 +OK +>>> rcpt foo@ibm.com +./smtpd_check: : reject: RCPT from foo[123.123.123.123]: 554 5.7.1 : Recipient address rejected: Relay access denied; from= to= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Recipient address rejected: Relay access denied +>>> rcpt foo@porcupine.org +OK +>>> recipient_restrictions hash:./smtpd_check_access +OK +>>> mail bad-sender@any.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad-sender@; from= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Sender address rejected: match bad-sender@ +>>> mail bad-sender@good.domain +OK +>>> mail reject@this.address +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match reject@this.address; from= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Sender address rejected: match reject@this.address +>>> mail foo@bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad.domain; from= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Sender address rejected: match bad.domain +>>> mail foo@random.bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad.domain; from= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Sender address rejected: match bad.domain +>>> mail foo@friend.bad.domain +OK +>>> # +>>> # RBL +>>> # +>>> client_restrictions reject_maps_rbl +OK +>>> client spike.porcupine.org 168.100.3.2 +./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead +OK +>>> client foo 127.0.0.2 +./smtpd_check: : reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from= proto=SMTP helo=<123.123.123.123> +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org +>>> # +>>> # Hybrids +>>> # +>>> recipient_restrictions check_relay_domains +OK +>>> client foo 131.155.210.17 +OK +>>> rcpt foo@ibm.com +./smtpd_check: : reject: RCPT from foo[131.155.210.17]: 554 5.7.1 : Recipient address rejected: Relay access denied; from= to= proto=SMTP helo=<123.123.123.123> +554 5.7.1 : Recipient address rejected: Relay access denied +>>> recipient_restrictions check_client_access,hash:./smtpd_check_access,check_relay_domains +OK +>>> client foo 131.155.210.17 +OK +>>> rcpt foo@porcupine.org +OK +>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,hash:./smtpd_check_access +OK +>>> recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_relay_domains +OK +>>> helo bad.domain +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 554 5.7.1 : Helo command rejected: match bad.domain; from= proto=SMTP helo= +554 5.7.1 : Helo command rejected: match bad.domain +>>> rcpt foo@porcupine.org +./smtpd_check: : reject: RCPT from foo[131.155.210.17]: 554 5.7.1 : Helo command rejected: match bad.domain; from= to= proto=SMTP helo= +554 5.7.1 : Helo command rejected: match bad.domain +>>> helo 131.155.210.17 +OK +>>> rcpt foo@porcupine.org +OK +>>> recipient_restrictions check_sender_access,hash:./smtpd_check_access,check_relay_domains +OK +>>> mail foo@bad.domain +./smtpd_check: : reject: MAIL from foo[131.155.210.17]: 554 5.7.1 : Sender address rejected: match bad.domain; from= proto=SMTP helo=<131.155.210.17> +554 5.7.1 : Sender address rejected: match bad.domain +>>> rcpt foo@porcupine.org +./smtpd_check: : reject: RCPT from foo[131.155.210.17]: 554 5.7.1 : Sender address rejected: match bad.domain; from= to= proto=SMTP helo=<131.155.210.17> +554 5.7.1 : Sender address rejected: match bad.domain +>>> mail foo@friend.bad.domain +OK +>>> rcpt foo@porcupine.org +OK +>>> # +>>> # MX backup +>>> # +>>> #mydestination spike.porcupine.org,localhost.porcupine.org +>>> #inet_interfaces 168.100.3.2,127.0.0.1 +>>> #recipient_restrictions permit_mx_backup,reject +>>> #rcpt wietse@wzv.win.tue.nl +>>> #rcpt wietse@trouble.org +>>> #rcpt wietse@porcupine.org +>>> # +>>> # Deferred restrictions +>>> # +>>> client_restrictions permit +OK +>>> helo_restrictions permit +OK +>>> sender_restrictions permit +OK +>>> recipient_restrictions check_helo_access,hash:./smtpd_check_access,check_sender_access,hash:./smtpd_check_access +OK +>>> helo bad.domain +OK +>>> mail foo@good.domain +OK +>>> rcpt foo@porcupine.org +./smtpd_check: : reject: RCPT from foo[131.155.210.17]: 554 5.7.1 : Helo command rejected: match bad.domain; from= to= proto=SMTP helo= +554 5.7.1 : Helo command rejected: match bad.domain +>>> helo good.domain +OK +>>> mail foo@bad.domain +OK +>>> rcpt foo@porcupine.org +./smtpd_check: : reject: RCPT from foo[131.155.210.17]: 554 5.7.1 : Sender address rejected: match bad.domain; from= to= proto=SMTP helo= +554 5.7.1 : Sender address rejected: match bad.domain +>>> # +>>> # FQDN restrictions +>>> # +>>> helo_restrictions reject_non_fqdn_hostname +OK +>>> sender_restrictions reject_non_fqdn_sender +OK +>>> recipient_restrictions reject_non_fqdn_recipient +OK +>>> helo foo.bar. +OK +>>> helo foo.bar +OK +>>> helo foo +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 504 5.5.2 : Helo command rejected: need fully-qualified hostname; from= proto=SMTP helo= +504 5.5.2 : Helo command rejected: need fully-qualified hostname +>>> mail foo@foo.bar. +OK +>>> mail foo@foo.bar +OK +>>> mail foo@foo +./smtpd_check: : reject: MAIL from foo[131.155.210.17]: 504 5.5.2 : Sender address rejected: need fully-qualified address; from= proto=SMTP helo= +504 5.5.2 : Sender address rejected: need fully-qualified address +>>> mail foo +./smtpd_check: : reject: MAIL from foo[131.155.210.17]: 504 5.5.2 : Sender address rejected: need fully-qualified address; from= proto=SMTP helo= +504 5.5.2 : Sender address rejected: need fully-qualified address +>>> rcpt foo@foo.bar. +OK +>>> rcpt foo@foo.bar +OK +>>> rcpt foo@foo +./smtpd_check: : reject: RCPT from foo[131.155.210.17]: 504 5.5.2 : Recipient address rejected: need fully-qualified address; from= to= proto=SMTP helo= +504 5.5.2 : Recipient address rejected: need fully-qualified address +>>> rcpt foo +./smtpd_check: : reject: RCPT from foo[131.155.210.17]: 504 5.5.2 : Recipient address rejected: need fully-qualified address; from= to= proto=SMTP helo= +504 5.5.2 : Recipient address rejected: need fully-qualified address +>>> # +>>> # Numerical HELO checks +>>> # +>>> helo_restrictions permit_naked_ip_address,reject_non_fqdn_hostname +OK +>>> helo [1.2.3.4] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +OK +>>> helo [321.255.255.255] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[321.255.255.255]>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<[321.255.255.255]> +501 5.5.2 <[321.255.255.255]>: Helo command rejected: invalid ip address +>>> helo [0.255.255.255] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[0.255.255.255]>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<[0.255.255.255]> +501 5.5.2 <[0.255.255.255]>: Helo command rejected: invalid ip address +>>> helo [1.2.3.321] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.321]>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<[1.2.3.321]> +501 5.5.2 <[1.2.3.321]>: Helo command rejected: invalid ip address +>>> helo [1.2.3] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3]>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<[1.2.3]> +501 5.5.2 <[1.2.3]>: Helo command rejected: invalid ip address +>>> helo [1.2.3.4.5] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.4.5]>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<[1.2.3.4.5]> +501 5.5.2 <[1.2.3.4.5]>: Helo command rejected: invalid ip address +>>> helo [1..2.3.4] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1..2.3.4]>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<[1..2.3.4]> +501 5.5.2 <[1..2.3.4]>: Helo command rejected: invalid ip address +>>> helo [.1.2.3.4] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[.1.2.3.4]>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<[.1.2.3.4]> +501 5.5.2 <[.1.2.3.4]>: Helo command rejected: invalid ip address +>>> helo [1.2.3.4.5.] +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <[1.2.3.4.5.]>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<[1.2.3.4.5.]> +501 5.5.2 <[1.2.3.4.5.]>: Helo command rejected: invalid ip address +>>> helo 1.2.3.4 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +OK +>>> helo 321.255.255.255 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <321.255.255.255>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<321.255.255.255> +501 5.5.2 <321.255.255.255>: Helo command rejected: invalid ip address +>>> helo 0.255.255.255 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <0.255.255.255>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<0.255.255.255> +501 5.5.2 <0.255.255.255>: Helo command rejected: invalid ip address +>>> helo 1.2.3.321 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.321>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<1.2.3.321> +501 5.5.2 <1.2.3.321>: Helo command rejected: invalid ip address +>>> helo 1.2.3 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<1.2.3> +501 5.5.2 <1.2.3>: Helo command rejected: invalid ip address +>>> helo 1.2.3.4.5 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.4.5>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<1.2.3.4.5> +501 5.5.2 <1.2.3.4.5>: Helo command rejected: invalid ip address +>>> helo 1..2.3.4 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1..2.3.4>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<1..2.3.4> +501 5.5.2 <1..2.3.4>: Helo command rejected: invalid ip address +>>> helo .1.2.3.4 +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <.1.2.3.4>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<.1.2.3.4> +501 5.5.2 <.1.2.3.4>: Helo command rejected: invalid ip address +>>> helo 1.2.3.4.5. +./smtpd_check: warning: restriction permit_naked_ip_address is deprecated. Use permit_mynetworks or permit_sasl_authenticated instead +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 501 5.5.2 <1.2.3.4.5.>: Helo command rejected: invalid ip address; from= proto=SMTP helo=<1.2.3.4.5.> +501 5.5.2 <1.2.3.4.5.>: Helo command rejected: invalid ip address +>>> # +>>> # The defer restriction +>>> # +>>> defer_code 444 +OK +>>> helo_restrictions defer +OK +>>> helo foobar +./smtpd_check: : reject: HELO from foo[131.155.210.17]: 444 4.3.2 : Helo command rejected: Try again later; from= proto=SMTP helo= +444 4.3.2 : Helo command rejected: Try again later diff --git a/src/smtpd/smtpd_check.ref2 b/src/smtpd/smtpd_check.ref2 new file mode 100644 index 0000000..e22f9e3 --- /dev/null +++ b/src/smtpd/smtpd_check.ref2 @@ -0,0 +1,236 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.3.0/28 +OK +>>> relay_domains porcupine.org +OK +>>> maps_rbl_domains dnsbltest.porcupine.org +OK +>>> # +>>> # Test the client restrictions. +>>> # +>>> client_restrictions permit_mynetworks,reject_unknown_client,check_client_access,hash:./smtpd_check_access +OK +>>> client unknown 131.155.210.17 +./smtpd_check: : reject: CONNECT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.3.13 +OK +>>> client random.bad.domain 123.123.123.123 +./smtpd_check: : reject: CONNECT from random.bad.domain[123.123.123.123]: 554 5.7.1 : Client host rejected: match bad.domain; proto=SMTP +554 5.7.1 : Client host rejected: match bad.domain +>>> client friend.bad.domain 123.123.123.123 +OK +>>> client bad.domain 123.123.123.123 +./smtpd_check: : reject: CONNECT from bad.domain[123.123.123.123]: 554 5.7.1 : Client host rejected: match bad.domain; proto=SMTP +554 5.7.1 : Client host rejected: match bad.domain +>>> client wzv.win.tue.nl 131.155.210.17 +OK +>>> client aa.win.tue.nl 131.155.210.18 +./smtpd_check: : reject: CONNECT from aa.win.tue.nl[131.155.210.18]: 554 5.7.1 : Client host rejected: match 131.155.210; proto=SMTP +554 5.7.1 : Client host rejected: match 131.155.210 +>>> client_restrictions permit_mynetworks +OK +>>> # +>>> # Test the helo restrictions +>>> # +>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,reject_unknown_hostname,check_helo_access,hash:./smtpd_check_access +OK +>>> client unknown 131.155.210.17 +OK +>>> helo foo. +./smtpd_check: : reject: HELO from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; proto=SMTP helo= +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client foo 123.123.123.123 +OK +>>> helo foo. +./smtpd_check: : reject: HELO from foo[123.123.123.123]: 450 4.7.1 : Helo command rejected: Host not found; proto=SMTP helo= +450 4.7.1 : Helo command rejected: Host not found +>>> helo foo +./smtpd_check: : reject: HELO from foo[123.123.123.123]: 450 4.7.1 : Helo command rejected: Host not found; proto=SMTP helo= +450 4.7.1 : Helo command rejected: Host not found +>>> helo spike.porcupine.org +./smtpd_check: : reject: HELO from foo[123.123.123.123]: 554 5.7.1 : Helo command rejected: name server spike.porcupine.org; proto=SMTP helo= +554 5.7.1 : Helo command rejected: name server spike.porcupine.org +>>> helo_restrictions permit_mynetworks,reject_unknown_client,reject_invalid_hostname,check_helo_access,hash:./smtpd_check_access +OK +>>> helo random.bad.domain +./smtpd_check: : reject: HELO from foo[123.123.123.123]: 554 5.7.1 : Helo command rejected: match bad.domain; proto=SMTP helo= +554 5.7.1 : Helo command rejected: match bad.domain +>>> helo friend.bad.domain +OK +>>> # +>>> # Test the sender restrictions +>>> # +>>> sender_restrictions permit_mynetworks,reject_unknown_client +OK +>>> client unknown 131.155.210.17 +OK +>>> mail foo@ibm.com +./smtpd_check: : reject: MAIL from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from= proto=SMTP helo= +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.3.13 +OK +>>> mail foo@ibm.com +OK +>>> client foo 123.123.123.123 +OK +>>> mail foo@ibm.com +OK +>>> sender_restrictions reject_unknown_address +OK +>>> mail foo@ibm.com +OK +>>> mail foo@bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 450 4.1.8 : Sender address rejected: Domain not found; from= proto=SMTP helo= +450 4.1.8 : Sender address rejected: Domain not found +>>> sender_restrictions check_sender_access,hash:./smtpd_check_access +OK +>>> mail bad-sender@any.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad-sender@; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: match bad-sender@ +>>> mail bad-sender@good.domain +OK +>>> mail reject@this.address +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match reject@this.address; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: match reject@this.address +>>> mail Reject@this.address +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match reject@this.address; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: match reject@this.address +>>> mail foo@bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad.domain; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: match bad.domain +>>> mail foo@Bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad.domain; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: match bad.domain +>>> mail foo@random.bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad.domain; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: match bad.domain +>>> mail foo@friend.bad.domain +OK +>>> # +>>> # Test the recipient restrictions +>>> # +>>> recipient_restrictions permit_mynetworks,reject_unknown_client,check_relay_domains +OK +>>> client unknown 131.155.210.17 +OK +>>> rcpt foo@ibm.com +./smtpd_check: : reject: RCPT from unknown[131.155.210.17]: 450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17]; from= to= proto=SMTP helo= +450 4.7.1 Client host rejected: cannot find your hostname, [131.155.210.17] +>>> client unknown 168.100.3.13 +OK +>>> rcpt foo@ibm.com +OK +>>> client foo 123.123.123.123 +OK +>>> rcpt foo@ibm.com +./smtpd_check: warning: support for restriction "check_relay_domains" will be removed from Postfix; use "reject_unauth_destination" instead +./smtpd_check: : reject: RCPT from foo[123.123.123.123]: 554 5.7.1 : Recipient address rejected: Relay access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Relay access denied +>>> rcpt foo@porcupine.org +OK +>>> recipient_restrictions check_relay_domains +OK +>>> client foo.porcupine.org 168.100.3.13 +OK +>>> rcpt foo@ibm.com +OK +>>> rcpt foo@porcupine.org +OK +>>> client foo 123.123.123.123 +OK +>>> rcpt foo@ibm.com +./smtpd_check: : reject: RCPT from foo[123.123.123.123]: 554 5.7.1 : Recipient address rejected: Relay access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Relay access denied +>>> rcpt foo@porcupine.org +OK +>>> recipient_restrictions check_recipient_access,hash:./smtpd_check_access +OK +>>> mail bad-sender@any.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad-sender@; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: match bad-sender@ +>>> mail bad-sender@good.domain +OK +>>> mail reject@this.address +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match reject@this.address; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: match reject@this.address +>>> mail foo@bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad.domain; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: match bad.domain +>>> mail foo@random.bad.domain +./smtpd_check: : reject: MAIL from foo[123.123.123.123]: 554 5.7.1 : Sender address rejected: match bad.domain; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: match bad.domain +>>> mail foo@friend.bad.domain +OK +>>> # +>>> # RBL +>>> # +>>> client_restrictions reject_maps_rbl +OK +>>> client spike.porcupine.org 168.100.3.2 +./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead +OK +>>> client foo 127.0.0.2 +./smtpd_check: : reject: CONNECT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from= proto=SMTP helo= +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org +>>> # +>>> # unknown sender/recipient domain +>>> # +>>> unknown_address_reject_code 554 +OK +>>> recipient_restrictions reject_unknown_recipient_domain,reject_unknown_sender_domain +OK +>>> mail wietse@porcupine.org +OK +>>> rcpt wietse@porcupine.org +OK +>>> rcpt wietse@no.recipient.domain +./smtpd_check: : reject: RCPT from foo[127.0.0.2]: 554 5.1.2 : Recipient address rejected: Domain not found; from= to= proto=SMTP helo= +554 5.1.2 : Recipient address rejected: Domain not found +>>> mail wietse@no.sender.domain +OK +>>> rcpt wietse@porcupine.org +./smtpd_check: : reject: RCPT from foo[127.0.0.2]: 554 5.1.8 : Sender address rejected: Domain not found; from= to= proto=SMTP helo= +554 5.1.8 : Sender address rejected: Domain not found +>>> # +>>> # {permit_auth,reject_unauth}_destination +>>> # +>>> relay_domains foo.com,bar.com +OK +>>> mail user@some.where +OK +>>> recipient_restrictions permit_auth_destination,reject +OK +>>> rcpt user@foo.org +./smtpd_check: : reject: RCPT from foo[127.0.0.2]: 554 5.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Access denied +>>> rcpt user@foo.com +OK +>>> recipient_restrictions reject_unauth_destination,permit +OK +>>> rcpt user@foo.org +./smtpd_check: : reject: RCPT from foo[127.0.0.2]: 554 5.7.1 : Relay access denied; from= to= proto=SMTP helo= +554 5.7.1 : Relay access denied +>>> rcpt user@foo.com +OK +>>> # +>>> # unknown client tests +>>> # +>>> unknown_client_reject_code 550 +OK +>>> client_restrictions reject_unknown_client +OK +>>> client spike.porcupine.org 160.100.189.2 2 +OK +>>> client unknown 1.1.1.1 4 +./smtpd_check: : reject: CONNECT from unknown[1.1.1.1]: 450 4.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]; from= proto=SMTP helo= +450 4.7.1 Client host rejected: cannot find your hostname, [1.1.1.1] +>>> client unknown 1.1.1.1 5 +./smtpd_check: : reject: CONNECT from unknown[1.1.1.1]: 550 5.7.1 Client host rejected: cannot find your hostname, [1.1.1.1]; from= proto=SMTP helo= +550 5.7.1 Client host rejected: cannot find your hostname, [1.1.1.1] diff --git a/src/smtpd/smtpd_check.ref4 b/src/smtpd/smtpd_check.ref4 new file mode 100644 index 0000000..8e9a6df --- /dev/null +++ b/src/smtpd/smtpd_check.ref4 @@ -0,0 +1,38 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> # +>>> # Test the new access map features +>>> # +>>> sender_restrictions hash:./smtpd_check_access +OK +>>> mail rejecttext@bad.domain +./smtpd_check: : reject: MAIL from localhost[127.0.0.1]: 554 5.7.1 : Sender address rejected: text; from= proto=SMTP +554 5.7.1 : Sender address rejected: text +>>> mail filter@filter.domain +./smtpd_check: warning: access table hash:./smtpd_check_access entry "filter@filter.domain" has FILTER entry without value +OK +>>> mail filtertext@filter.domain +./smtpd_check: warning: access table hash:./smtpd_check_access entry "filtertext@filter.domain" requires transport:destination +OK +>>> mail filtertexttext@filter.domain +./smtpd_check: : filter: MAIL from localhost[127.0.0.1]: : Sender address triggers FILTER text:text; from= proto=SMTP +OK +>>> mail hold@hold.domain +./smtpd_check: : hold: MAIL from localhost[127.0.0.1]: : Sender address triggers HOLD action; from= proto=SMTP +OK +>>> mail holdtext@hold.domain +./smtpd_check: : hold: MAIL from localhost[127.0.0.1]: : Sender address text; from= proto=SMTP +OK +>>> mail discard@hold.domain +./smtpd_check: : discard: MAIL from localhost[127.0.0.1]: : Sender address triggers DISCARD action; from= proto=SMTP +OK +>>> mail discardtext@hold.domain +./smtpd_check: : discard: MAIL from localhost[127.0.0.1]: : Sender address text; from= proto=SMTP +OK +>>> mail dunnotext@dunno.domain +OK diff --git a/src/smtpd/smtpd_check_access b/src/smtpd/smtpd_check_access new file mode 100644 index 0000000..788276a --- /dev/null +++ b/src/smtpd/smtpd_check_access @@ -0,0 +1,91 @@ +bad.domain 554 match bad.domain +friend.bad.domain OK +bad-sender@ 554 match bad-sender@ +bad-sender@good.domain OK +good-sender@ OK +131.155.210 554 match 131.155.210 +131.155.210.17 OK +131.155.210.19 REJECT +reject@this.address 554 match reject@this.address +open_user@some.site open +strict_user@some.site strict +auth_client 123456 + +dunno.com dunno +foo.dunno.com reject + +44.33.22 dunno +44.33.22.11 REJECT +44.33 REJECT + +reject@dunno.domain REJECT +ok@dunno.domain OK +dunno.domain DUNNO + +reject@reject.domain REJECT +ok@reject.domain OK +reject.domain REJECT + +reject@ok.domain REJECT +ok@ok.domain OK +ok.domain OK +<> 550 Go away postmaster + +54.187.136.235 reject bizsat.net, gypsysoul.org spam + +blackholes.mail-abuse.org $rbl_code client=$client + client_address=$client_address + client_name=$client_name helo_name=$helo_name + sender=$sender sender_name=$sender_name sender_domain=$sender_domain + recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain + rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what + rbl_class=$rbl_class + +rhsbl.porcupine.org $rbl_code client=$client + client_address=$client_address + client_name=$client_name helo_name=$helo_name + sender=$sender sender_name=$sender_name sender_domain=$sender_domain + recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain + rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what + rbl_class=$rbl_class + +dnswl.porcupine.org $rbl_code client=$client + client_address=$client_address + client_name=$client_name helo_name=$helo_name + sender=$sender sender_name=$sender_name sender_domain=$sender_domain + recipient=$recipient recipient_name=$recipient_name recipient_domain=$recipient_domain + rbl_code=$rbl_code rbl_domain=$rbl_domain rbl_txt=$rbl_txt rbl_what=$rbl_what + rbl_class=$rbl_class + +rejecttext@bad.domain reject text +filter@filter.domain filter +filtertext@filter.domain filter text +filtertexttext@filter.domain filter text:text +hold@hold.domain hold +holdtext@hold.domain hold text +discard@hold.domain discard +discardtext@hold.domain discard text +dunnotext@dunno.domain dunno text +64.94.110.11 reject Verisign wild-card +topica.com reject +10.10.10.10 reject mail server 10.10.10.10 +spike.porcupine.org reject ns or mx server spike.porcupine.org +241 reject class E subnet +4.1.1_dsn reject 4.1.1 reject +4.1.2_dsn reject 4.1.2 reject +4.1.3_dsn reject 4.1.3 reject +4.1.4_dsn reject 4.1.4 reject +4.1.5_dsn reject 4.1.5 reject +4.1.6_dsn reject 4.1.6 reject +4.1.7_dsn reject 4.1.7 reject +4.1.8_dsn reject 4.1.8 reject +4.4.0_dsn reject 4.4.0 reject +user@4.1.1_dsn reject 4.1.1 reject +user@4.1.2_dsn reject 4.1.2 reject +user@4.1.3_dsn reject 4.1.3 reject +user@4.1.4_dsn reject 4.1.4 reject +user@4.1.5_dsn reject 4.1.5 reject +user@4.1.6_dsn reject 4.1.6 reject +user@4.1.7_dsn reject 4.1.7 reject +user@4.1.8_dsn reject 4.1.8 reject +user@4.4.0_dsn reject 4.4.0 reject diff --git a/src/smtpd/smtpd_check_backup.in b/src/smtpd/smtpd_check_backup.in new file mode 100644 index 0000000..7fb9242 --- /dev/null +++ b/src/smtpd/smtpd_check_backup.in @@ -0,0 +1,20 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.3.0/28 +# +# MX backup +# +mydestination wzv.porcupine.org,localhost.porcupine.org +inet_interfaces 168.100.3.7,127.0.0.1 +recipient_restrictions permit_mx_backup,reject +rcpt wietse@wzv.porcupine.org +rcpt wietse@backup.porcupine.org +rcpt wietse@porcupine.org +permit_mx_backup_networks 168.100.3.5 +rcpt wietse@backup.porcupine.org +permit_mx_backup_networks 168.100.3.4 +rcpt wietse@backup.porcupine.org diff --git a/src/smtpd/smtpd_check_backup.ref b/src/smtpd/smtpd_check_backup.ref new file mode 100644 index 0000000..8f4a0f2 --- /dev/null +++ b/src/smtpd/smtpd_check_backup.ref @@ -0,0 +1,34 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.3.0/28 +OK +>>> # +>>> # MX backup +>>> # +>>> mydestination wzv.porcupine.org,localhost.porcupine.org +OK +>>> inet_interfaces 168.100.3.7,127.0.0.1 +OK +>>> recipient_restrictions permit_mx_backup,reject +OK +>>> rcpt wietse@wzv.porcupine.org +OK +>>> rcpt wietse@backup.porcupine.org +OK +>>> rcpt wietse@porcupine.org +./smtpd_check: : reject: RCPT from localhost[127.0.0.1]: 554 5.7.1 : Recipient address rejected: Access denied; to= proto=SMTP +554 5.7.1 : Recipient address rejected: Access denied +>>> permit_mx_backup_networks 168.100.3.5 +OK +>>> rcpt wietse@backup.porcupine.org +./smtpd_check: : reject: RCPT from localhost[127.0.0.1]: 554 5.7.1 : Recipient address rejected: Access denied; to= proto=SMTP +554 5.7.1 : Recipient address rejected: Access denied +>>> permit_mx_backup_networks 168.100.3.4 +OK +>>> rcpt wietse@backup.porcupine.org +OK diff --git a/src/smtpd/smtpd_check_dsn.in b/src/smtpd/smtpd_check_dsn.in new file mode 100644 index 0000000..cf174e8 --- /dev/null +++ b/src/smtpd/smtpd_check_dsn.in @@ -0,0 +1,60 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.3.0/28 +# +# Test the client restrictions. +# +client_restrictions hash:./smtpd_check_access +client 4.1.1_dsn 1.2.3.4 +client 4.1.2_dsn 1.2.3.4 +client 4.1.3_dsn 1.2.3.4 +client 4.1.4_dsn 1.2.3.4 +client 4.1.5_dsn 1.2.3.4 +client 4.1.6_dsn 1.2.3.4 +client 4.1.7_dsn 1.2.3.4 +client 4.1.8_dsn 1.2.3.4 +client 4.4.0_dsn 1.2.3.4 +client dummy dummy +# +# Test the helo restrictions +# +helo_restrictions hash:./smtpd_check_access +helo 4.1.1_dsn +helo 4.1.2_dsn +helo 4.1.3_dsn +helo 4.1.4_dsn +helo 4.1.5_dsn +helo 4.1.6_dsn +helo 4.1.7_dsn +helo 4.1.8_dsn +helo 4.4.0_dsn +# +# Test the sender restrictions +# +sender_restrictions hash:./smtpd_check_access +mail user@4.1.1_dsn +mail user@4.1.2_dsn +mail user@4.1.3_dsn +mail user@4.1.4_dsn +mail user@4.1.5_dsn +mail user@4.1.6_dsn +mail user@4.1.7_dsn +mail user@4.1.8_dsn +mail user@4.4.0_dsn +# +# Test the recipient restrictions +# +recipient_restrictions hash:./smtpd_check_access +rcpt user@4.1.1_dsn +rcpt user@4.1.2_dsn +rcpt user@4.1.3_dsn +rcpt user@4.1.4_dsn +rcpt user@4.1.5_dsn +rcpt user@4.1.6_dsn +rcpt user@4.1.7_dsn +rcpt user@4.1.8_dsn +rcpt user@4.4.0_dsn diff --git a/src/smtpd/smtpd_check_dsn.ref b/src/smtpd/smtpd_check_dsn.ref new file mode 100644 index 0000000..168676d --- /dev/null +++ b/src/smtpd/smtpd_check_dsn.ref @@ -0,0 +1,163 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.3.0/28 +OK +>>> # +>>> # Test the client restrictions. +>>> # +>>> client_restrictions hash:./smtpd_check_access +OK +>>> client 4.1.1_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.1 into Client host status 4.0.0 +./smtpd_check: : reject: CONNECT from 4.1.1_dsn[1.2.3.4]: 554 5.0.0 <4.1.1_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.1_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.2_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.2 into Client host status 4.0.0 +./smtpd_check: : reject: CONNECT from 4.1.2_dsn[1.2.3.4]: 554 5.0.0 <4.1.2_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.2_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.3_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.3 into Client host status 4.0.0 +./smtpd_check: : reject: CONNECT from 4.1.3_dsn[1.2.3.4]: 554 5.0.0 <4.1.3_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.3_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.4_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.4 into Client host status 4.0.0 +./smtpd_check: : reject: CONNECT from 4.1.4_dsn[1.2.3.4]: 554 5.0.0 <4.1.4_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.4_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.5_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.5 into Client host status 4.0.0 +./smtpd_check: : reject: CONNECT from 4.1.5_dsn[1.2.3.4]: 554 5.0.0 <4.1.5_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.5_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.6_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.6 into Client host status 4.0.0 +./smtpd_check: : reject: CONNECT from 4.1.6_dsn[1.2.3.4]: 554 5.0.0 <4.1.6_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.6_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.7_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.7 into Client host status 4.0.0 +./smtpd_check: : reject: CONNECT from 4.1.7_dsn[1.2.3.4]: 554 5.0.0 <4.1.7_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.7_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.1.8_dsn 1.2.3.4 +./smtpd_check: mapping DSN status 4.1.8 into Client host status 4.0.0 +./smtpd_check: : reject: CONNECT from 4.1.8_dsn[1.2.3.4]: 554 5.0.0 <4.1.8_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.0.0 <4.1.8_dsn[1.2.3.4]>: Client host rejected: reject +>>> client 4.4.0_dsn 1.2.3.4 +./smtpd_check: : reject: CONNECT from 4.4.0_dsn[1.2.3.4]: 554 5.4.0 <4.4.0_dsn[1.2.3.4]>: Client host rejected: reject; proto=SMTP +554 5.4.0 <4.4.0_dsn[1.2.3.4]>: Client host rejected: reject +>>> client dummy dummy +OK +>>> # +>>> # Test the helo restrictions +>>> # +>>> helo_restrictions hash:./smtpd_check_access +OK +>>> helo 4.1.1_dsn +./smtpd_check: mapping DSN status 4.1.1 into Helo command status 4.0.0 +./smtpd_check: : reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.1_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.1_dsn> +554 5.0.0 <4.1.1_dsn>: Helo command rejected: reject +>>> helo 4.1.2_dsn +./smtpd_check: mapping DSN status 4.1.2 into Helo command status 4.0.0 +./smtpd_check: : reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.2_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.2_dsn> +554 5.0.0 <4.1.2_dsn>: Helo command rejected: reject +>>> helo 4.1.3_dsn +./smtpd_check: mapping DSN status 4.1.3 into Helo command status 4.0.0 +./smtpd_check: : reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.3_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.3_dsn> +554 5.0.0 <4.1.3_dsn>: Helo command rejected: reject +>>> helo 4.1.4_dsn +./smtpd_check: mapping DSN status 4.1.4 into Helo command status 4.0.0 +./smtpd_check: : reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.4_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.4_dsn> +554 5.0.0 <4.1.4_dsn>: Helo command rejected: reject +>>> helo 4.1.5_dsn +./smtpd_check: mapping DSN status 4.1.5 into Helo command status 4.0.0 +./smtpd_check: : reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.5_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.5_dsn> +554 5.0.0 <4.1.5_dsn>: Helo command rejected: reject +>>> helo 4.1.6_dsn +./smtpd_check: mapping DSN status 4.1.6 into Helo command status 4.0.0 +./smtpd_check: : reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.6_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.6_dsn> +554 5.0.0 <4.1.6_dsn>: Helo command rejected: reject +>>> helo 4.1.7_dsn +./smtpd_check: mapping DSN status 4.1.7 into Helo command status 4.0.0 +./smtpd_check: : reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.7_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.7_dsn> +554 5.0.0 <4.1.7_dsn>: Helo command rejected: reject +>>> helo 4.1.8_dsn +./smtpd_check: mapping DSN status 4.1.8 into Helo command status 4.0.0 +./smtpd_check: : reject: HELO from dummy[dummy]: 554 5.0.0 <4.1.8_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.1.8_dsn> +554 5.0.0 <4.1.8_dsn>: Helo command rejected: reject +>>> helo 4.4.0_dsn +./smtpd_check: : reject: HELO from dummy[dummy]: 554 5.4.0 <4.4.0_dsn>: Helo command rejected: reject; proto=SMTP helo=<4.4.0_dsn> +554 5.4.0 <4.4.0_dsn>: Helo command rejected: reject +>>> # +>>> # Test the sender restrictions +>>> # +>>> sender_restrictions hash:./smtpd_check_access +OK +>>> mail user@4.1.1_dsn +./smtpd_check: mapping DSN status 4.1.1 into Sender address status 4.1.7 +./smtpd_check: : reject: MAIL from dummy[dummy]: 554 5.1.7 : Sender address rejected: reject; from= proto=SMTP helo=<4.4.0_dsn> +554 5.1.7 : Sender address rejected: reject +>>> mail user@4.1.2_dsn +./smtpd_check: mapping DSN status 4.1.2 into Sender address status 4.1.8 +./smtpd_check: : reject: MAIL from dummy[dummy]: 554 5.1.8 : Sender address rejected: reject; from= proto=SMTP helo=<4.4.0_dsn> +554 5.1.8 : Sender address rejected: reject +>>> mail user@4.1.3_dsn +./smtpd_check: mapping DSN status 4.1.3 into Sender address status 4.1.7 +./smtpd_check: : reject: MAIL from dummy[dummy]: 554 5.1.7 : Sender address rejected: reject; from= proto=SMTP helo=<4.4.0_dsn> +554 5.1.7 : Sender address rejected: reject +>>> mail user@4.1.4_dsn +./smtpd_check: mapping DSN status 4.1.4 into Sender address status 4.1.7 +./smtpd_check: : reject: MAIL from dummy[dummy]: 554 5.1.7 : Sender address rejected: reject; from= proto=SMTP helo=<4.4.0_dsn> +554 5.1.7 : Sender address rejected: reject +>>> mail user@4.1.5_dsn +./smtpd_check: mapping DSN status 4.1.5 into Sender address status 4.1.0 +./smtpd_check: : reject: MAIL from dummy[dummy]: 554 5.1.0 : Sender address rejected: reject; from= proto=SMTP helo=<4.4.0_dsn> +554 5.1.0 : Sender address rejected: reject +>>> mail user@4.1.6_dsn +./smtpd_check: mapping DSN status 4.1.6 into Sender address status 4.1.7 +./smtpd_check: : reject: MAIL from dummy[dummy]: 554 5.1.7 : Sender address rejected: reject; from= proto=SMTP helo=<4.4.0_dsn> +554 5.1.7 : Sender address rejected: reject +>>> mail user@4.1.7_dsn +./smtpd_check: : reject: MAIL from dummy[dummy]: 554 5.1.7 : Sender address rejected: reject; from= proto=SMTP helo=<4.4.0_dsn> +554 5.1.7 : Sender address rejected: reject +>>> mail user@4.1.8_dsn +./smtpd_check: : reject: MAIL from dummy[dummy]: 554 5.1.8 : Sender address rejected: reject; from= proto=SMTP helo=<4.4.0_dsn> +554 5.1.8 : Sender address rejected: reject +>>> mail user@4.4.0_dsn +./smtpd_check: : reject: MAIL from dummy[dummy]: 554 5.4.0 : Sender address rejected: reject; from= proto=SMTP helo=<4.4.0_dsn> +554 5.4.0 : Sender address rejected: reject +>>> # +>>> # Test the recipient restrictions +>>> # +>>> recipient_restrictions hash:./smtpd_check_access +OK +>>> rcpt user@4.1.1_dsn +./smtpd_check: : reject: RCPT from dummy[dummy]: 554 5.1.1 : Recipient address rejected: reject; from= to= proto=SMTP helo=<4.4.0_dsn> +554 5.1.1 : Recipient address rejected: reject +>>> rcpt user@4.1.2_dsn +./smtpd_check: : reject: RCPT from dummy[dummy]: 554 5.1.2 : Recipient address rejected: reject; from= to= proto=SMTP helo=<4.4.0_dsn> +554 5.1.2 : Recipient address rejected: reject +>>> rcpt user@4.1.3_dsn +./smtpd_check: : reject: RCPT from dummy[dummy]: 554 5.1.3 : Recipient address rejected: reject; from= to= proto=SMTP helo=<4.4.0_dsn> +554 5.1.3 : Recipient address rejected: reject +>>> rcpt user@4.1.4_dsn +./smtpd_check: : reject: RCPT from dummy[dummy]: 554 5.1.4 : Recipient address rejected: reject; from= to= proto=SMTP helo=<4.4.0_dsn> +554 5.1.4 : Recipient address rejected: reject +>>> rcpt user@4.1.5_dsn +./smtpd_check: : reject: RCPT from dummy[dummy]: 554 5.1.5 : Recipient address rejected: reject; from= to= proto=SMTP helo=<4.4.0_dsn> +554 5.1.5 : Recipient address rejected: reject +>>> rcpt user@4.1.6_dsn +./smtpd_check: : reject: RCPT from dummy[dummy]: 554 5.1.6 : Recipient address rejected: reject; from= to= proto=SMTP helo=<4.4.0_dsn> +554 5.1.6 : Recipient address rejected: reject +>>> rcpt user@4.1.7_dsn +./smtpd_check: mapping DSN status 4.1.7 into Recipient address status 4.1.3 +./smtpd_check: : reject: RCPT from dummy[dummy]: 554 5.1.3 : Recipient address rejected: reject; from= to= proto=SMTP helo=<4.4.0_dsn> +554 5.1.3 : Recipient address rejected: reject +>>> rcpt user@4.1.8_dsn +./smtpd_check: mapping DSN status 4.1.8 into Recipient address status 4.1.2 +./smtpd_check: : reject: RCPT from dummy[dummy]: 554 5.1.2 : Recipient address rejected: reject; from= to= proto=SMTP helo=<4.4.0_dsn> +554 5.1.2 : Recipient address rejected: reject +>>> rcpt user@4.4.0_dsn +./smtpd_check: : reject: RCPT from dummy[dummy]: 554 5.4.0 : Recipient address rejected: reject; from= to= proto=SMTP helo=<4.4.0_dsn> +554 5.4.0 : Recipient address rejected: reject diff --git a/src/smtpd/smtpd_dns_filter.in b/src/smtpd/smtpd_dns_filter.in new file mode 100644 index 0000000..df1d8ef --- /dev/null +++ b/src/smtpd/smtpd_dns_filter.in @@ -0,0 +1,83 @@ +# +# Initialize +# +client localhost 127.0.0.1 +smtpd_delay_reject 0 +# +# Test reject_unknown_helo_hostname +# +smtpd_dns_reply_filter regexp:../dns/no-mx.reg +helo_restrictions reject_unknown_helo_hostname,permit +# EXPECT OK + "all MX records dropped" warning. +helo xn--1xa.porcupine.org +# EXPECT OK (nullmx has A record) +helo nullmx.porcupine.org +# EXPECT reject (nxdomain is not filtered). +helo nxdomain.porcupine.org +smtpd_dns_reply_filter regexp:../dns/no-a.reg +# EXPECT OK (host has AAAA record). +mail user@spike.porcupine.org +helo spike.porcupine.org +# EXPECT OK + "all A records dropped" warning + no delayed reject. +helo fist.porcupine.org +mail user@spike.porcupine.org +rcpt user@spike.porcupine.org +smtpd_dns_reply_filter regexp:../dns/error.reg +# EXPECT OK + "filter config error" warning + delayed reject. +helo spike.porcupine.org +mail user@spike.porcupine.org +rcpt user@spike.porcupine.org +# EXPECT OK + "filter config error" warning (nullmx has A record) + delayed reject. +helo nullmx.porcupine.org +mail user@spike.porcupine.org +rcpt user@spike.porcupine.org +# EXPECT reject (nxdomain is not filtered). +helo nxdomain.porcupine.org +# +# Test reject_unknown_sender_domain (same code as +# reject_unknown_recipient_domain). +# +smtpd_dns_reply_filter regexp:../dns/no-mx.reg +helo localhost +sender_restrictions reject_unknown_sender_domain +# EXPECT OK + "all MX records dropped" warning. +mail user@xn--1xa.porcupine.org +# EXPECT reject (nullmx is not filtered). +mail user@nullmx.porcupine.org +# EXPECT reject (nxdomain is not filtered). +mail user@nxdomain.porcupine.org +# EXPECT OK +mail user@localhost +smtpd_dns_reply_filter regexp:../dns/no-a.reg +# EXPECT OK (host has AAAA record). +mail user@spike.porcupine.org +# EXPECT OK + "all A records dropped" warning. +mail user@fist.porcupine.org +smtpd_dns_reply_filter regexp:../dns/error.reg +# EXPECT OK + "filter config error" warning + delayed reject. +mail user@xn--1xa.porcupine.org +rcpt user +# EXPECT reject (nullmx is not filtered). +mail user@nullmx.porcupine.org +# EXPECT reject (nxdomain is not filtered). +mail user@nxdomain.porcupine.org +# +# Test reject_rbl_client +# +client_restrictions reject_rbl_client,dnsbltest.porcupine.org +smtpd_dns_reply_filter regexp:../dns/no-mx.reg +flush_dnsxl_cache +# EXPECT reject + A and TXT record. +client localhost 127.0.0.2 +smtpd_dns_reply_filter regexp:../dns/no-a.reg +flush_dnsxl_cache +# EXPECT OK + "all A results dropped" warning. +client localhost 127.0.0.2 +smtpd_dns_reply_filter regexp:../dns/no-txt.reg +flush_dnsxl_cache +# EXPECT reject + A record, "all TXT results dropped" warning. +client localhost 127.0.0.2 +smtpd_dns_reply_filter regexp:../dns/error.reg +flush_dnsxl_cache +# EXPECT OK + "filter configuration error" +client localhost 127.0.0.2 diff --git a/src/smtpd/smtpd_dns_filter.ref b/src/smtpd/smtpd_dns_filter.ref new file mode 100644 index 0000000..ce1710f --- /dev/null +++ b/src/smtpd/smtpd_dns_filter.ref @@ -0,0 +1,163 @@ +>>> # +>>> # Initialize +>>> # +>>> client localhost 127.0.0.1 +OK +>>> smtpd_delay_reject 0 +OK +>>> # +>>> # Test reject_unknown_helo_hostname +>>> # +>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg +OK +>>> helo_restrictions reject_unknown_helo_hostname,permit +OK +>>> # EXPECT OK + "all MX records dropped" warning. +>>> helo xn--1xa.porcupine.org +./smtpd_check: ignoring DNS RR: xn--1xa.porcupine.org. TTL IN MX 10 spike.porcupine.org. +./smtpd_check: warning: xn--1xa.porcupine.org: address or MX lookup error: DNS reply filter drops all results +OK +>>> # EXPECT OK (nullmx has A record) +>>> helo nullmx.porcupine.org +OK +>>> # EXPECT reject (nxdomain is not filtered). +>>> helo nxdomain.porcupine.org +./smtpd_check: : reject: HELO from localhost[127.0.0.1]: 450 4.7.1 : Helo command rejected: Host not found; proto=SMTP helo= +450 4.7.1 : Helo command rejected: Host not found +>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg +OK +>>> # EXPECT OK (host has AAAA record). +>>> mail user@spike.porcupine.org +OK +>>> helo spike.porcupine.org +./smtpd_check: ignoring DNS RR: spike.porcupine.org. TTL IN A 168.100.3.2 +OK +>>> # EXPECT OK + "all A records dropped" warning + no delayed reject. +>>> helo fist.porcupine.org +./smtpd_check: ignoring DNS RR: fist.porcupine.org. TTL IN A 168.100.3.4 +./smtpd_check: warning: fist.porcupine.org: address or MX lookup error: DNS reply filter drops all results +OK +>>> mail user@spike.porcupine.org +OK +>>> rcpt user@spike.porcupine.org +OK +>>> smtpd_dns_reply_filter regexp:../dns/error.reg +OK +>>> # EXPECT OK + "filter config error" warning + delayed reject. +>>> helo spike.porcupine.org +./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops" +./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops" +OK +>>> mail user@spike.porcupine.org +OK +>>> rcpt user@spike.porcupine.org +./smtpd_check: : reject: RCPT from localhost[127.0.0.1]: 450 4.7.1 : Helo command rejected: Host not found; from= to= proto=SMTP helo= +450 4.7.1 : Helo command rejected: Host not found +>>> # EXPECT OK + "filter config error" warning (nullmx has A record) + delayed reject. +>>> helo nullmx.porcupine.org +./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops" +OK +>>> mail user@spike.porcupine.org +OK +>>> rcpt user@spike.porcupine.org +./smtpd_check: : reject: RCPT from localhost[127.0.0.1]: 450 4.7.1 : Helo command rejected: Host not found; from= to= proto=SMTP helo= +450 4.7.1 : Helo command rejected: Host not found +>>> # EXPECT reject (nxdomain is not filtered). +>>> helo nxdomain.porcupine.org +./smtpd_check: : reject: HELO from localhost[127.0.0.1]: 450 4.7.1 : Helo command rejected: Host not found; from= proto=SMTP helo= +450 4.7.1 : Helo command rejected: Host not found +>>> # +>>> # Test reject_unknown_sender_domain (same code as +>>> # reject_unknown_recipient_domain). +>>> # +>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg +OK +>>> helo localhost +OK +>>> sender_restrictions reject_unknown_sender_domain +OK +>>> # EXPECT OK + "all MX records dropped" warning. +>>> mail user@xn--1xa.porcupine.org +./smtpd_check: ignoring DNS RR: xn--1xa.porcupine.org. TTL IN MX 10 spike.porcupine.org. +./smtpd_check: warning: xn--1xa.porcupine.org: MX or address lookup error: DNS reply filter drops all results +OK +>>> # EXPECT reject (nullmx is not filtered). +>>> mail user@nullmx.porcupine.org +./smtpd_check: : reject: MAIL from localhost[127.0.0.1]: 550 5.7.27 : Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from= proto=SMTP helo= +550 5.7.27 : Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX) +>>> # EXPECT reject (nxdomain is not filtered). +>>> mail user@nxdomain.porcupine.org +./smtpd_check: : reject: MAIL from localhost[127.0.0.1]: 450 4.1.8 : Sender address rejected: Domain not found; from= proto=SMTP helo= +450 4.1.8 : Sender address rejected: Domain not found +>>> # EXPECT OK +>>> mail user@localhost +OK +>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg +OK +>>> # EXPECT OK (host has AAAA record). +>>> mail user@spike.porcupine.org +./smtpd_check: ignoring DNS RR: spike.porcupine.org. TTL IN A 168.100.3.2 +OK +>>> # EXPECT OK + "all A records dropped" warning. +>>> mail user@fist.porcupine.org +./smtpd_check: ignoring DNS RR: fist.porcupine.org. TTL IN A 168.100.3.4 +./smtpd_check: warning: fist.porcupine.org: MX or address lookup error: DNS reply filter drops all results +OK +>>> smtpd_dns_reply_filter regexp:../dns/error.reg +OK +>>> # EXPECT OK + "filter config error" warning + delayed reject. +>>> mail user@xn--1xa.porcupine.org +./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops" +OK +>>> rcpt user +./smtpd_check: : reject: RCPT from localhost[127.0.0.1]: 450 4.1.8 : Sender address rejected: Domain not found; from= to= proto=SMTP helo= +450 4.1.8 : Sender address rejected: Domain not found +>>> # EXPECT reject (nullmx is not filtered). +>>> mail user@nullmx.porcupine.org +./smtpd_check: : reject: MAIL from localhost[127.0.0.1]: 550 5.7.27 : Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from= proto=SMTP helo= +550 5.7.27 : Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX) +>>> # EXPECT reject (nxdomain is not filtered). +>>> mail user@nxdomain.porcupine.org +./smtpd_check: : reject: MAIL from localhost[127.0.0.1]: 450 4.1.8 : Sender address rejected: Domain not found; from= proto=SMTP helo= +450 4.1.8 : Sender address rejected: Domain not found +>>> # +>>> # Test reject_rbl_client +>>> # +>>> client_restrictions reject_rbl_client,dnsbltest.porcupine.org +OK +>>> smtpd_dns_reply_filter regexp:../dns/no-mx.reg +OK +>>> flush_dnsxl_cache +OK +>>> # EXPECT reject + A and TXT record. +>>> client localhost 127.0.0.2 +./smtpd_check: : reject: CONNECT from localhost[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from= proto=SMTP helo= +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test +>>> smtpd_dns_reply_filter regexp:../dns/no-a.reg +OK +>>> flush_dnsxl_cache +OK +>>> # EXPECT OK + "all A results dropped" warning. +>>> client localhost 127.0.0.2 +./smtpd_check: ignoring DNS RR: 2.0.0.127.dnsbltest.porcupine.org. TTL IN A 127.0.0.2 +./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: RBL lookup error: Error looking up name=2.0.0.127.dnsbltest.porcupine.org type=A: DNS reply filter drops all results +OK +>>> smtpd_dns_reply_filter regexp:../dns/no-txt.reg +OK +>>> flush_dnsxl_cache +OK +>>> # EXPECT reject + A record, "all TXT results dropped" warning. +>>> client localhost 127.0.0.2 +./smtpd_check: ignoring DNS RR: 2.0.0.127.dnsbltest.porcupine.org. TTL IN TXT DNS blocklist test +./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: TXT lookup error: DNS reply filter drops all results +./smtpd_check: : reject: CONNECT from localhost[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; from= proto=SMTP helo= +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org +>>> smtpd_dns_reply_filter regexp:../dns/error.reg +OK +>>> flush_dnsxl_cache +OK +>>> # EXPECT OK + "filter configuration error" +>>> client localhost 127.0.0.2 +./smtpd_check: warning: smtpd_dns_reply_filter: unknown DNS filter action: "oops" +./smtpd_check: warning: 2.0.0.127.dnsbltest.porcupine.org: RBL lookup error: Error looking up name=2.0.0.127.dnsbltest.porcupine.org type=A: Invalid DNS reply filter syntax +OK diff --git a/src/smtpd/smtpd_dnswl.in b/src/smtpd/smtpd_dnswl.in new file mode 100644 index 0000000..db25474 --- /dev/null +++ b/src/smtpd/smtpd_dnswl.in @@ -0,0 +1,60 @@ +# +# Initialize. +# +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.3.0/28 +mydestination porcupine.org +relay_domains porcupine.org +helo foobar + +# +# DNSWL (by IP address) +# + +# Allowlist overrides reject. +client_restrictions permit_dnswl_client,wild.porcupine.org,reject +client spike.porcupine.org 168.100.3.2 + +# Allowlist does not fire - reject. +client_restrictions permit_dnswl_client,porcupine.org,reject +client spike.porcupine.org 168.100.3.2 + +# Allowlist does not override reject_unauth_destination. +client_restrictions permit +recipient_restrictions permit_dnswl_client,wild.porcupine.org,reject_unauth_destination +# Unauthorized destination - reject. +rcpt rname@rdomain +# Authorized destination - accept. +rcpt wietse@porcupine.org + +# +# RHSWL (by domain name) +# + +# Allowlist overrides reject. +client_restrictions permit_rhswl_client,dnswl.porcupine.org,reject +# Non-allowlisted client name - reject. +client spike.porcupine.org 168.100.3.2 +# Allowlisted client name - accept. +client example.tld 168.100.3.2 + +# Allowlist does not override reject_unauth_destination. +client_restrictions permit +recipient_restrictions permit_rhswl_client,dnswl.porcupine.org,reject_unauth_destination +# Non-allowlisted client name. +client spike.porcupine.org 168.100.3.2 +# Unauthorized destination - reject. +rcpt rname@rdomain +# Authorized destination - accept. +rcpt wietse@porcupine.org +# Allowlisted client name. +client example.tld 168.100.3.2 +# Unauthorized destination - reject. +rcpt rname@rdomain +# Authorized destination - accept. +rcpt wietse@porcupine.org +# Numeric TLD - dunno. +rcpt wietse@12345 +rcpt wietse@12345.porcupine.org +rcpt wietse@porcupine.12345 diff --git a/src/smtpd/smtpd_dnswl.ref b/src/smtpd/smtpd_dnswl.ref new file mode 100644 index 0000000..dacda6c --- /dev/null +++ b/src/smtpd/smtpd_dnswl.ref @@ -0,0 +1,94 @@ +>>> # +>>> # Initialize. +>>> # +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.3.0/28 +OK +>>> mydestination porcupine.org +OK +>>> relay_domains porcupine.org +OK +>>> helo foobar +OK +>>> +>>> # +>>> # DNSWL (by IP address) +>>> # +>>> +>>> # Allowlist overrides reject. +>>> client_restrictions permit_dnswl_client,wild.porcupine.org,reject +OK +>>> client spike.porcupine.org 168.100.3.2 +OK +>>> +>>> # Allowlist does not fire - reject. +>>> client_restrictions permit_dnswl_client,porcupine.org,reject +OK +>>> client spike.porcupine.org 168.100.3.2 +./smtpd_check: : reject: CONNECT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Client host rejected: Access denied; proto=SMTP helo= +554 5.7.1 : Client host rejected: Access denied +>>> +>>> # Allowlist does not override reject_unauth_destination. +>>> client_restrictions permit +OK +>>> recipient_restrictions permit_dnswl_client,wild.porcupine.org,reject_unauth_destination +OK +>>> # Unauthorized destination - reject. +>>> rcpt rname@rdomain +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Relay access denied; to= proto=SMTP helo= +554 5.7.1 : Relay access denied +>>> # Authorized destination - accept. +>>> rcpt wietse@porcupine.org +OK +>>> +>>> # +>>> # RHSWL (by domain name) +>>> # +>>> +>>> # Allowlist overrides reject. +>>> client_restrictions permit_rhswl_client,dnswl.porcupine.org,reject +OK +>>> # Non-allowlisted client name - reject. +>>> client spike.porcupine.org 168.100.3.2 +./smtpd_check: : reject: CONNECT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Client host rejected: Access denied; proto=SMTP helo= +554 5.7.1 : Client host rejected: Access denied +>>> # Allowlisted client name - accept. +>>> client example.tld 168.100.3.2 +OK +>>> +>>> # Allowlist does not override reject_unauth_destination. +>>> client_restrictions permit +OK +>>> recipient_restrictions permit_rhswl_client,dnswl.porcupine.org,reject_unauth_destination +OK +>>> # Non-allowlisted client name. +>>> client spike.porcupine.org 168.100.3.2 +OK +>>> # Unauthorized destination - reject. +>>> rcpt rname@rdomain +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Relay access denied; to= proto=SMTP helo= +554 5.7.1 : Relay access denied +>>> # Authorized destination - accept. +>>> rcpt wietse@porcupine.org +OK +>>> # Allowlisted client name. +>>> client example.tld 168.100.3.2 +OK +>>> # Unauthorized destination - reject. +>>> rcpt rname@rdomain +./smtpd_check: : reject: RCPT from example.tld[168.100.3.2]: 554 5.7.1 : Relay access denied; to= proto=SMTP helo= +554 5.7.1 : Relay access denied +>>> # Authorized destination - accept. +>>> rcpt wietse@porcupine.org +OK +>>> # Numeric TLD - dunno. +>>> rcpt wietse@12345 +./smtpd_check: : reject: RCPT from example.tld[168.100.3.2]: 554 5.7.1 : Relay access denied; to= proto=SMTP helo= +554 5.7.1 : Relay access denied +>>> rcpt wietse@12345.porcupine.org +OK +>>> rcpt wietse@porcupine.12345 +./smtpd_check: : reject: RCPT from example.tld[168.100.3.2]: 554 5.7.1 : Relay access denied; to= proto=SMTP helo= +554 5.7.1 : Relay access denied diff --git a/src/smtpd/smtpd_dsn_fix.c b/src/smtpd/smtpd_dsn_fix.c new file mode 100644 index 0000000..d436967 --- /dev/null +++ b/src/smtpd/smtpd_dsn_fix.c @@ -0,0 +1,149 @@ +/*++ +/* NAME +/* smtpd_dsn_fix 3 +/* SUMMARY +/* fix DSN status +/* SYNOPSIS +/* #include +/* +/* const char *smtpd_dsn_fix(status, reply_class) +/* const char *status; +/* const char *reply_class; +/* DESCRIPTION +/* smtpd_dsn_fix() transforms DSN status codes according to the +/* status information that is actually being reported. The +/* following transformations are implemented: +/* .IP \(bu +/* Transform a recipient address DSN into a sender address DSN +/* when reporting sender address status information, and vice +/* versa. This transformation may be needed because some Postfix +/* access control features don't know whether the address being +/* rejected is a sender or recipient. Examples are smtpd access +/* tables, rbl reply templates, and the error mailer. +/* .IP \(bu +/* Transform a sender or recipient address DSN into a non-address +/* DSN when reporting non-address status information. For +/* example, if something rejects HELO with DSN status 4.1.1 +/* (unknown recipient address), then we send the more neutral +/* 4.0.0 DSN instead. This transformation is needed when the +/* same smtpd access map entry or rbl reply template is used +/* for both address and non-address information. +/* .PP +/* A non-address DSN is not transformed +/* when reporting sender or recipient address status information, +/* as there are many legitimate instances of such usage. +/* +/* It is left up to the caller to update the initial DSN digit +/* appropriately; in Postfix this is done as late as possible, +/* because hard rejects may be changed into soft rejects for +/* all kinds of reasons. +/* +/* Arguments: +/* .IP status +/* A DSN status as per RFC 3463. +/* .IP reply_class +/* SMTPD_NAME_SENDER, SMTPD_NAME_RECIPIENT or some other +/* null-terminated string. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +/* Application-specific. */ + +#include + +struct dsn_map { + const char *micro_code; /* Final digits in mailbox D.S.N. */ + const char *sender_dsn; /* Replacement sender D.S.N. */ + const char *rcpt_dsn; /* Replacement recipient D.S.N. */ +}; + +static struct dsn_map dsn_map[] = { + /* - Sender - Recipient */ + "1", SND_DSN, "4.1.1", /* 4.1.1: Bad dest mbox addr */ + "2", "4.1.8", "4.1.2", /* 4.1.2: Bad dest system addr */ + "3", "4.1.7", "4.1.3", /* 4.1.3: Bad dest mbox addr syntax */ + "4", SND_DSN, "4.1.4", /* 4.1.4: Dest mbox addr ambiguous */ + "5", "4.1.0", "4.1.5", /* 4.1.5: Dest mbox addr valid */ + "6", SND_DSN, "4.1.6", /* 4.1.6: Mailbox has moved */ + "7", "4.1.7", "4.1.3", /* 4.1.7: Bad sender mbox addr syntax */ + "8", "4.1.8", "4.1.2", /* 4.1.8: Bad sender system addr */ + 0, "4.1.0", "4.1.0", /* Default mapping */ +}; + +/* smtpd_dsn_fix - fix DSN status */ + +const char *smtpd_dsn_fix(const char *status, const char *reply_class) +{ + struct dsn_map *dp; + const char *result = status; + + /* + * Update an address-specific DSN according to what is being rejected. + */ + if (ISDIGIT(status[0]) && strncmp(status + 1, ".1.", 3) == 0) { + + /* + * Fix recipient address DSN while rejecting a sender address. Don't + * let future recipient-specific DSN codes slip past us. + */ + if (strcmp(reply_class, SMTPD_NAME_SENDER) == 0) { + for (dp = dsn_map; dp->micro_code != 0; dp++) + if (strcmp(status + 4, dp->micro_code) == 0) + break; + result = dp->sender_dsn; + } + + /* + * Fix sender address DSN while rejecting a recipient address. Don't + * let future sender-specific DSN codes slip past us. + */ + else if (strcmp(reply_class, SMTPD_NAME_RECIPIENT) == 0) { + for (dp = dsn_map; dp->micro_code != 0; dp++) + if (strcmp(status + 4, dp->micro_code) == 0) + break; + result = dp->rcpt_dsn; + } + + /* + * Fix address-specific DSN while rejecting a non-address. + */ + else { + result = "4.0.0"; + } + + /* + * Give them a clue of what is going on. + */ + if (strcmp(status + 2, result + 2) != 0) + msg_info("mapping DSN status %s into %s status %c%s", + status, reply_class, status[0], result + 1); + return (result); + } + + /* + * Don't update a non-address DSN. There are many legitimate uses for + * these while rejecting address or non-address information. + */ + else { + return (status); + } +} diff --git a/src/smtpd/smtpd_dsn_fix.h b/src/smtpd/smtpd_dsn_fix.h new file mode 100644 index 0000000..c608e34 --- /dev/null +++ b/src/smtpd/smtpd_dsn_fix.h @@ -0,0 +1,44 @@ +/*++ +/* NAME +/* smtpd_check 3h +/* SUMMARY +/* SMTP client request filtering +/* SYNOPSIS +/* #include "smtpd.h" +/* #include "smtpd_check_int.h" +/* DESCRIPTION +/* .nf + + /* + * Internal interface. + */ +#define SMTPD_NAME_CLIENT "Client host" +#define SMTPD_NAME_REV_CLIENT "Unverified Client host" +#define SMTPD_NAME_CCERT "Client certificate" +#define SMTPD_NAME_SASL_USER "SASL login name" +#define SMTPD_NAME_HELO "Helo command" +#define SMTPD_NAME_SENDER "Sender address" +#define SMTPD_NAME_RECIPIENT "Recipient address" +#define SMTPD_NAME_ETRN "Etrn command" +#define SMTPD_NAME_DATA "Data command" +#define SMTPD_NAME_EOD "End-of-data" + + /* + * Workaround for absence of "bad sender address" status code: use "bad + * sender address syntax" instead. If we were to use "4.1.0" then we would + * lose the critical distinction between sender and recipient problems. + */ +#define SND_DSN "4.1.7" + +extern const char *smtpd_dsn_fix(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtpd/smtpd_error.in b/src/smtpd/smtpd_error.in new file mode 100644 index 0000000..a7fb3e2 --- /dev/null +++ b/src/smtpd/smtpd_error.in @@ -0,0 +1,81 @@ +# +# Initialize +# +smtpd_delay_reject 0 +# +# Test check_domain_access() +# +helo_restrictions fail:1_helo_access +# Expect: REJECT (temporary lookup failure) +helo foobar +# +# Test check_namadr_access() +# +client_restrictions fail:1_client_access +# Expect: REJECT (temporary lookup failure) +client foo.dunno.com 131.155.210.17 +# +# Test check_mail_access() +# +sender_restrictions fail:1_sender_access +# Expect: REJECT (temporary lookup failure) +mail reject@dunno.domain +# +# Test check_rcpt_access() +# +recipient_restrictions fail:1_rcpt_access +# Expect: REJECT (temporary lookup failure) +rcpt reject@dunno.domain +# Expect: OK +rcpt postmaster +# +# Test mynetworks in generic_checks(). +# +mynetworks fail:1_mynetworks +# +# Expect REJECT (temporary lookup failure) +# +recipient_restrictions permit_mynetworks +rcpt reject@dunno.domain +# +# Test mynetworks. +# +mynetworks 168.100.3.1/27 +# +# Expect REJECT (server configuration error) +# +rcpt reject@dunno.domain +# +# check_sender_access specific +# +smtpd_null_access_lookup_key <> +mail <> +# +# Test permit_tls_client_certs in generic_restrictions +# +relay_clientcerts fail:1_certs +fingerprint abcdef +recipient_restrictions permit_tls_clientcerts +rcpt reject@dunno.domain +# +# Test smtpd_check_rewrite(). +# +local_header_rewrite_clients fail:1_rewrite +# +# Expect: REJECT (temporary lookup failure) +# +rewrite +# +# Test resolve_local() +# +mydestination example.com +recipient_restrictions reject_unauth_destination +rcpt user@example.com +mydestination fail:1_mydestination +rcpt user@example.com +# +# Test virtual alias lookup. +# +mydestination example.com +virtual_alias_maps fail:1_virtual +rcpt user@example.com diff --git a/src/smtpd/smtpd_error.ref b/src/smtpd/smtpd_error.ref new file mode 100644 index 0000000..d375522 --- /dev/null +++ b/src/smtpd/smtpd_error.ref @@ -0,0 +1,135 @@ +>>> # +>>> # Initialize +>>> # +>>> smtpd_delay_reject 0 +OK +>>> # +>>> # Test check_domain_access() +>>> # +>>> helo_restrictions fail:1_helo_access +OK +>>> # Expect: REJECT (temporary lookup failure) +>>> helo foobar +./smtpd_check: warning: fail:1_helo_access lookup error for "foobar" +./smtpd_check: : reject: HELO from localhost[127.0.0.1]: 451 4.3.5 : Helo command rejected: Server configuration error; proto=SMTP helo= +451 4.3.5 : Helo command rejected: Server configuration error +>>> # +>>> # Test check_namadr_access() +>>> # +>>> client_restrictions fail:1_client_access +OK +>>> # Expect: REJECT (temporary lookup failure) +>>> client foo.dunno.com 131.155.210.17 +./smtpd_check: warning: fail:1_client_access lookup error for "foo.dunno.com" +./smtpd_check: : reject: CONNECT from foo.dunno.com[131.155.210.17]: 451 4.3.5 : Client host rejected: Server configuration error; proto=SMTP helo= +451 4.3.5 : Client host rejected: Server configuration error +>>> # +>>> # Test check_mail_access() +>>> # +>>> sender_restrictions fail:1_sender_access +OK +>>> # Expect: REJECT (temporary lookup failure) +>>> mail reject@dunno.domain +./smtpd_check: warning: fail:1_sender_access lookup error for "reject@dunno.domain" +./smtpd_check: : reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.5 : Sender address rejected: Server configuration error; from= proto=SMTP helo= +451 4.3.5 : Sender address rejected: Server configuration error +>>> # +>>> # Test check_rcpt_access() +>>> # +>>> recipient_restrictions fail:1_rcpt_access +OK +>>> # Expect: REJECT (temporary lookup failure) +>>> rcpt reject@dunno.domain +./smtpd_check: warning: fail:1_rcpt_access lookup error for "reject@dunno.domain" +./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.5 : Recipient address rejected: Server configuration error; from= to= proto=SMTP helo= +451 4.3.5 : Recipient address rejected: Server configuration error +>>> # Expect: OK +>>> rcpt postmaster +OK +>>> # +>>> # Test mynetworks in generic_checks(). +>>> # +>>> mynetworks fail:1_mynetworks +OK +>>> # +>>> # Expect REJECT (temporary lookup failure) +>>> # +>>> recipient_restrictions permit_mynetworks +OK +>>> rcpt reject@dunno.domain +./smtpd_check: warning: mynetworks: fail:1_mynetworks: table lookup problem +./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from= to= proto=SMTP helo= +451 4.3.0 : Temporary lookup failure +>>> # +>>> # Test mynetworks. +>>> # +>>> mynetworks 168.100.3.1/27 +OK +>>> # +>>> # Expect REJECT (server configuration error) +>>> # +>>> rcpt reject@dunno.domain +./smtpd_check: warning: mynetworks: non-null host address bits in "168.100.3.1/27", perhaps you should use "168.100.3.0/27" instead +./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from= to= proto=SMTP helo= +451 4.3.0 : Temporary lookup failure +>>> # +>>> # check_sender_access specific +>>> # +>>> smtpd_null_access_lookup_key <> +OK +>>> mail <> +./smtpd_check: warning: fail:1_sender_access lookup error for "<>" +./smtpd_check: : reject: MAIL from foo.dunno.com[131.155.210.17]: 451 4.3.5 <>: Sender address rejected: Server configuration error; from=<> proto=SMTP helo= +451 4.3.5 <>: Sender address rejected: Server configuration error +>>> # +>>> # Test permit_tls_client_certs in generic_restrictions +>>> # +>>> relay_clientcerts fail:1_certs +OK +>>> fingerprint abcdef +OK +>>> recipient_restrictions permit_tls_clientcerts +OK +>>> rcpt reject@dunno.domain +./smtpd_check: warning: fail:1_certs lookup error for "abcdef" +./smtpd_check: warning: relay_clientcerts: lookup error for fingerprint 'abcdef', pkey fingerprint abcdef +./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from=<> to= proto=SMTP helo= +451 4.3.0 : Temporary lookup failure +>>> # +>>> # Test smtpd_check_rewrite(). +>>> # +>>> local_header_rewrite_clients fail:1_rewrite +OK +>>> # +>>> # Expect: REJECT (temporary lookup failure) +>>> # +>>> rewrite +./smtpd_check: warning: fail:1_rewrite lookup error for "131.155.210.17" +./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 Temporary lookup error; from=<> proto=SMTP helo= +451 4.3.0 Temporary lookup error +>>> # +>>> # Test resolve_local() +>>> # +>>> mydestination example.com +OK +>>> recipient_restrictions reject_unauth_destination +OK +>>> rcpt user@example.com +OK +>>> mydestination fail:1_mydestination +OK +>>> rcpt user@example.com +./smtpd_check: warning: mydestination: fail:1_mydestination: table lookup problem +./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from=<> to= proto=SMTP helo= +451 4.3.0 : Temporary lookup failure +>>> # +>>> # Test virtual alias lookup. +>>> # +>>> mydestination example.com +OK +>>> virtual_alias_maps fail:1_virtual +OK +>>> rcpt user@example.com +./smtpd_check: warning: fail:1_virtual lookup error for "user@example.com" +./smtpd_check: : reject: RCPT from foo.dunno.com[131.155.210.17]: 451 4.3.0 : Temporary lookup failure; from=<> to= proto=SMTP helo= +451 4.3.0 : Temporary lookup failure diff --git a/src/smtpd/smtpd_exp.in b/src/smtpd/smtpd_exp.in new file mode 100644 index 0000000..8370404 --- /dev/null +++ b/src/smtpd/smtpd_exp.in @@ -0,0 +1,62 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +mynetworks 127.0.0.0/8,168.100.3.0/28 +relay_domains porcupine.org +maps_rbl_domains dnsbltest.porcupine.org +rbl_reply_maps hash:smtpd_check_access +helo foobar +# +# RBL +# +mail sname@sdomain +recipient_restrictions reject_maps_rbl +client spike.porcupine.org 168.100.3.2 +rcpt rname@rdomain +client foo 127.0.0.2 +rcpt rname@rdomain +# +recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org +client spike.porcupine.org 168.100.3.2 +rcpt rname@rdomain +client foo 127.0.0.2 +rcpt rname@rdomain +recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org=127.0.0.2 +client foo 127.0.0.2 +rcpt rname@rdomain +client foo 127.0.0.1 +rcpt rname@rdomain +# +# RHSBL sender domain name +# +recipient_restrictions reject_rhsbl_sender,rhsbl.porcupine.org +client spike.porcupine.org 168.100.3.2 +mail sname@example.tld +rcpt rname@rdomain +mail sname@sdomain +rcpt rname@rdomain +# +# RHSBL client domain name +# +recipient_restrictions reject_rhsbl_client,rhsbl.porcupine.org +client example.tld 1.2.3.4 +mail sname@sdomain +rcpt rname@rdomain +# +# RHSBL recipient domain name +# +recipient_restrictions reject_rhsbl_recipient,rhsbl.porcupine.org +client spike.porcupine.org 168.100.3.2 +mail sname@sdomain +rcpt rname@rdomain +rcpt rname@example.tld +# +# RHSBL helo domain name +# +recipient_restrictions reject_rhsbl_helo,rhsbl.porcupine.org +helo example.tld +mail sname@sdomain +rcpt rname@rdomain diff --git a/src/smtpd/smtpd_exp.ref b/src/smtpd/smtpd_exp.ref new file mode 100644 index 0000000..22c027e --- /dev/null +++ b/src/smtpd/smtpd_exp.ref @@ -0,0 +1,111 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> mynetworks 127.0.0.0/8,168.100.3.0/28 +OK +>>> relay_domains porcupine.org +OK +>>> maps_rbl_domains dnsbltest.porcupine.org +OK +>>> rbl_reply_maps hash:smtpd_check_access +OK +>>> helo foobar +OK +>>> # +>>> # RBL +>>> # +>>> mail sname@sdomain +OK +>>> recipient_restrictions reject_maps_rbl +OK +>>> client spike.porcupine.org 168.100.3.2 +OK +>>> rcpt rname@rdomain +./smtpd_check: warning: support for restriction "reject_maps_rbl" will be removed from Postfix; use "reject_rbl_client domain-name" instead +OK +>>> client foo 127.0.0.2 +OK +>>> rcpt rname@rdomain +./smtpd_check: : reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from= to= proto=SMTP helo= +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test +>>> # +>>> recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org +OK +>>> client spike.porcupine.org 168.100.3.2 +OK +>>> rcpt rname@rdomain +OK +>>> client foo 127.0.0.2 +OK +>>> rcpt rname@rdomain +./smtpd_check: : reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from= to= proto=SMTP helo= +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test +>>> recipient_restrictions reject_rbl_client,dnsbltest.porcupine.org=127.0.0.2 +OK +>>> client foo 127.0.0.2 +OK +>>> rcpt rname@rdomain +./smtpd_check: : reject: RCPT from foo[127.0.0.2]: 554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test; from= to= proto=SMTP helo= +554 5.7.1 Service unavailable; Client host [127.0.0.2] blocked using dnsbltest.porcupine.org; DNS blocklist test +>>> client foo 127.0.0.1 +OK +>>> rcpt rname@rdomain +OK +>>> # +>>> # RHSBL sender domain name +>>> # +>>> recipient_restrictions reject_rhsbl_sender,rhsbl.porcupine.org +OK +>>> client spike.porcupine.org 168.100.3.2 +OK +>>> mail sname@example.tld +OK +>>> rcpt rname@rdomain +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@example.tld sender_name=sname sender_domain=example.tld recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=sname@example.tld rbl_class=Sender address; from= to= proto=SMTP helo= +554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@example.tld sender_name=sname sender_domain=example.tld recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=sname@example.tld rbl_class=Sender address +>>> mail sname@sdomain +OK +>>> rcpt rname@rdomain +OK +>>> # +>>> # RHSBL client domain name +>>> # +>>> recipient_restrictions reject_rhsbl_client,rhsbl.porcupine.org +OK +>>> client example.tld 1.2.3.4 +OK +>>> mail sname@sdomain +OK +>>> rcpt rname@rdomain +./smtpd_check: : reject: RCPT from example.tld[1.2.3.4]: 554 5.7.1 client=example.tld[1.2.3.4] client_address=1.2.3.4 client_name=example.tld helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Client host; from= to= proto=SMTP helo= +554 5.7.1 client=example.tld[1.2.3.4] client_address=1.2.3.4 client_name=example.tld helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Client host +>>> # +>>> # RHSBL recipient domain name +>>> # +>>> recipient_restrictions reject_rhsbl_recipient,rhsbl.porcupine.org +OK +>>> client spike.porcupine.org 168.100.3.2 +OK +>>> mail sname@sdomain +OK +>>> rcpt rname@rdomain +OK +>>> rcpt rname@example.tld +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@example.tld recipient_name=rname recipient_domain=example.tld rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=rname@example.tld rbl_class=Recipient address; from= to= proto=SMTP helo= +554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=foobar sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@example.tld recipient_name=rname recipient_domain=example.tld rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=rname@example.tld rbl_class=Recipient address +>>> # +>>> # RHSBL helo domain name +>>> # +>>> recipient_restrictions reject_rhsbl_helo,rhsbl.porcupine.org +OK +>>> helo example.tld +OK +>>> mail sname@sdomain +OK +>>> rcpt rname@rdomain +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=example.tld sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Helo command; from= to= proto=SMTP helo= +554 5.7.1 client=spike.porcupine.org[168.100.3.2] client_address=168.100.3.2 client_name=spike.porcupine.org helo_name=example.tld sender=sname@sdomain sender_name=sname sender_domain=sdomain recipient=rname@rdomain recipient_name=rname recipient_domain=rdomain rbl_code=554 rbl_domain=rhsbl.porcupine.org rbl_txt=RHSBL test rbl_what=example.tld rbl_class=Helo command diff --git a/src/smtpd/smtpd_expand.c b/src/smtpd/smtpd_expand.c new file mode 100644 index 0000000..8362bd7 --- /dev/null +++ b/src/smtpd/smtpd_expand.c @@ -0,0 +1,247 @@ +/*++ +/* NAME +/* smtpd_expand 3 +/* SUMMARY +/* SMTP server macro expansion +/* SYNOPSIS +/* #include +/* #include +/* +/* void smtpd_expand_init() +/* +/* int smtpd_expand(state, result, template, flags) +/* SMTPD_STATE *state; +/* VSTRING *result; +/* const char *template; +/* int flags; +/* LOW_LEVEL INTERFACE +/* VSTRING *smtpd_expand_filter; +/* +/* const char *smtpd_expand_lookup(name, unused_mode, context) +/* const char *name; +/* int unused_mode; +/* void *context; +/* const char *template; +/* DESCRIPTION +/* This module expands session-related macros. +/* +/* smtpd_expand_init() performs one-time initialization. +/* +/* smtpd_expand() expands macros in the template, using session +/* attributes in the state argument, and writes the result to +/* the result argument. The flags and result value are as with +/* mac_expand(). +/* +/* smtpd_expand_filter and smtpd_expand_lookup() provide access +/* to lower-level interfaces that are used by smtpd_expand(). +/* smtpd_expand_lookup() returns null when a string is not +/* found (or when it is a null pointer). +/* DIAGNOSTICS +/* Panic: interface violations. Fatal errors: out of memory. +/* internal protocol errors. smtpd_expand() returns the binary +/* OR of MAC_PARSE_ERROR (syntax error) and MAC_PARSE_UNDEF +/* (undefined macro name). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +#include +#include + + /* + * Pre-parsed expansion filter. + */ +VSTRING *smtpd_expand_filter; + + /* + * SLMs. + */ +#define STR vstring_str + +/* smtpd_expand_init - initialize once during process lifetime */ + +void smtpd_expand_init(void) +{ + + /* + * Expand the expansion filter :-) + */ + smtpd_expand_filter = vstring_alloc(10); + unescape(smtpd_expand_filter, var_smtpd_exp_filter); +} + +/* smtpd_expand_unknown - report unknown macro name */ + +static void smtpd_expand_unknown(const char *name) +{ + msg_warn("unknown macro name \"%s\" in expansion request", name); +} + +/* smtpd_expand_addr - return address or substring thereof */ + +static const char *smtpd_expand_addr(VSTRING *buf, const char *addr, + const char *name, int prefix_len) +{ + const char *p; + const char *suffix; + + /* + * Return NULL only for unknown names in expansion requests. + */ + if (addr == 0) + return (""); + + suffix = name + prefix_len; + + /* + * MAIL_ATTR_SENDER or MAIL_ATTR_RECIP. + */ + if (*suffix == 0) { + if (*addr) + return (addr); + else + return ("<>"); + } + + /* + * "sender_name" or "recipient_name". + */ +#define STREQ(x,y) (*(x) == *(y) && strcmp((x), (y)) == 0) + + else if (STREQ(suffix, MAIL_ATTR_S_NAME)) { + if (*addr) { + if ((p = strrchr(addr, '@')) != 0) { + vstring_strncpy(buf, addr, p - addr); + return (STR(buf)); + } else { + return (addr); + } + } else + return ("<>"); + } + + /* + * "sender_domain" or "recipient_domain". + */ + else if (STREQ(suffix, MAIL_ATTR_S_DOMAIN)) { + if (*addr) { + if ((p = strrchr(addr, '@')) != 0) { + return (p + 1); + } else { + return (""); + } + } else + return (""); + } + + /* + * Unknown. Return NULL to indicate an "unknown name" error. + */ + else { + smtpd_expand_unknown(name); + return (0); + } +} + +/* smtpd_expand_lookup - generic SMTP attribute $name expansion */ + +const char *smtpd_expand_lookup(const char *name, int unused_mode, + void *context) +{ + SMTPD_STATE *state = (SMTPD_STATE *) context; + time_t now; + struct tm *lt; + + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(10); + + if (msg_verbose > 1) + msg_info("smtpd_expand_lookup: ${%s}", name); + +#define STREQN(x,y,n) (*(x) == *(y) && strncmp((x), (y), (n)) == 0) +#define CONST_LEN(x) (sizeof(x) - 1) + + /* + * Don't query main.cf parameters, as the result of expansion could + * reveal system-internal information in server replies. + * + * XXX: This said, multiple servers may be behind a single client-visible + * name or IP address, and each may generate its own logs. Therefore, it + * may be useful to expose the replying MTA id (myhostname) in the + * contact footer, to identify the right logs. So while we don't expose + * the raw configuration dictionary, we do expose "$myhostname" as + * expanded in var_myhostname. + * + * Return NULL only for non-existent names. + */ + if (STREQ(name, MAIL_ATTR_SERVER_NAME)) { + return (var_myhostname); + } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT)) { + return (state->namaddr); + } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_PORT)) { + return (state->port); + } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_ADDR)) { + return (state->addr); + } else if (STREQ(name, MAIL_ATTR_ACT_CLIENT_NAME)) { + return (state->name); + } else if (STREQ(name, MAIL_ATTR_ACT_REVERSE_CLIENT_NAME)) { + return (state->reverse_name); + } else if (STREQ(name, MAIL_ATTR_ACT_HELO_NAME)) { + return (state->helo_name ? state->helo_name : ""); + } else if (STREQN(name, MAIL_ATTR_SENDER, CONST_LEN(MAIL_ATTR_SENDER))) { + return (smtpd_expand_addr(state->expand_buf, state->sender, + name, CONST_LEN(MAIL_ATTR_SENDER))); + } else if (STREQN(name, MAIL_ATTR_RECIP, CONST_LEN(MAIL_ATTR_RECIP))) { + return (smtpd_expand_addr(state->expand_buf, state->recipient, + name, CONST_LEN(MAIL_ATTR_RECIP))); + } if (STREQ(name, MAIL_ATTR_LOCALTIME)) { + if (time(&now) == (time_t) -1) + msg_fatal("time lookup failed: %m"); + lt = localtime(&now); + VSTRING_RESET(state->expand_buf); + do { + VSTRING_SPACE(state->expand_buf, 100); + } while (strftime(STR(state->expand_buf), + vstring_avail(state->expand_buf), + "%b %d %H:%M:%S", lt) == 0); + return (STR(state->expand_buf)); + } else { + smtpd_expand_unknown(name); + return (0); + } +} + +/* smtpd_expand - expand session attributes in string */ + +int smtpd_expand(SMTPD_STATE *state, VSTRING *result, + const char *template, int flags) +{ + return (mac_expand(result, template, flags, STR(smtpd_expand_filter), + smtpd_expand_lookup, (void *) state)); +} diff --git a/src/smtpd/smtpd_expand.h b/src/smtpd/smtpd_expand.h new file mode 100644 index 0000000..eb95983 --- /dev/null +++ b/src/smtpd/smtpd_expand.h @@ -0,0 +1,40 @@ +/*++ +/* NAME +/* smtpd_expand 3h +/* SUMMARY +/* SMTP server macro expansion +/* SYNOPSIS +/* #include +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * External interface. + */ +extern VSTRING *smtpd_expand_filter; +void smtpd_expand_init(void); +const char *smtpd_expand_lookup(const char *, int, void *); +int smtpd_expand(SMTPD_STATE *, VSTRING *, const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/smtpd/smtpd_haproxy.c b/src/smtpd/smtpd_haproxy.c new file mode 100644 index 0000000..542c3fe --- /dev/null +++ b/src/smtpd/smtpd_haproxy.c @@ -0,0 +1,135 @@ +/*++ +/* NAME +/* smtpd_haproxy 3 +/* SUMMARY +/* Postfix SMTP server haproxy adapter +/* SYNOPSIS +/* #include "smtpd.h" +/* +/* int smtpd_peer_from_haproxy(state) +/* SMTPD_STATE *state; +/* DESCRIPTION +/* smtpd_peer_from_haproxy() receives endpoint address and +/* port information via the haproxy protocol. +/* +/* The following summarizes what the Postfix SMTP server expects +/* from an up-stream proxy adapter. +/* .IP \(bu +/* Call smtpd_peer_from_default() if the up-stream proxy +/* indicates that the connection is not proxied. In that case, +/* a proxy adapter MUST NOT update any STATE fields: the +/* smtpd_peer_from_default() function will do that instead. +/* .IP \(bu +/* Validate protocol, address and port syntax. Permit only +/* protocols that are configured with the main.cf:inet_protocols +/* setting. +/* .IP \(bu +/* Convert IPv4-in-IPv6 address syntax to IPv4 syntax when +/* both IPv6 and IPv4 support are enabled with main.cf:inet_protocols. +/* .IP \(bu +/* Update the following session context fields: addr, port, +/* rfc_addr, addr_family, dest_addr, dest_port. The addr_family +/* field applies to the client address. +/* .IP \(bu +/* Dynamically allocate storage for string information with +/* mystrdup(). In case of error, leave unassigned string fields +/* at their initial zero value. +/* .IP \(bu +/* Log a clear warning message that explains why a request +/* fails. +/* .IP \(bu +/* Never talk to the remote SMTP client. +/* .PP +/* Arguments: +/* .IP state +/* Session context. +/* DIAGNOSTICS +/* Warnings: I/O errors, malformed haproxy line. +/* +/* The result value is 0 in case of success, -1 in case of +/* error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include + +/* SLMs. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* smtpd_peer_from_haproxy - initialize peer information from haproxy */ + +int smtpd_peer_from_haproxy(SMTPD_STATE *state) +{ + MAI_HOSTADDR_STR smtp_client_addr; + MAI_SERVPORT_STR smtp_client_port; + MAI_HOSTADDR_STR smtp_server_addr; + MAI_SERVPORT_STR smtp_server_port; + int non_proxy = 0; + + if (read_wait(vstream_fileno(state->client), var_smtpd_uproxy_tmout) < 0) { + msg_warn("haproxy read: timeout error"); + return (-1); + } + if (haproxy_srvr_receive(vstream_fileno(state->client), &non_proxy, + &smtp_client_addr, &smtp_client_port, + &smtp_server_addr, &smtp_server_port) < 0) { + return (-1); + } + if (non_proxy) { + smtpd_peer_from_default(state); + return (0); + } + state->addr = mystrdup(smtp_client_addr.buf); + if (strrchr(state->addr, ':') != 0) { + state->rfc_addr = concatenate(IPV6_COL, state->addr, (char *) 0); + state->addr_family = AF_INET6; + } else { + state->rfc_addr = mystrdup(state->addr); + state->addr_family = AF_INET; + } + state->port = mystrdup(smtp_client_port.buf); + + /* + * The Dovecot authentication server needs the server IP address. + */ + state->dest_addr = mystrdup(smtp_server_addr.buf); + state->dest_port = mystrdup(smtp_server_port.buf); + return (0); +} diff --git a/src/smtpd/smtpd_milter.c b/src/smtpd/smtpd_milter.c new file mode 100644 index 0000000..5deba67 --- /dev/null +++ b/src/smtpd/smtpd_milter.c @@ -0,0 +1,229 @@ +/*++ +/* NAME +/* smtpd_milter 3 +/* SUMMARY +/* SMTP server milter glue +/* SYNOPSIS +/* #include +/* #include +/* +/* const char *smtpd_milter_eval(name, context) +/* const char *name; +/* void *context; +/* DESCRIPTION +/* smtpd_milter_eval() is a milter(3) call-back routine to +/* expand Sendmail macros before they are sent to filters. +/* DIAGNOSTICS +/* Panic: interface violations. Fatal errors: out of memory. +/* internal protocol errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include +#include + +/* Milter library. */ + +#include + +/* Application-specific. */ + +#include +#include +#include +#include + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* smtpd_milter_eval - evaluate milter macro */ + +const char *smtpd_milter_eval(const char *name, void *ptr) +{ + SMTPD_STATE *state = (SMTPD_STATE *) ptr; + const RESOLVE_REPLY *reply; + char *cp; + + /* + * On-the-fly initialization. + */ + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(10); + + /* + * System macros. + */ + if (strcmp(name, S8_MAC_DAEMON_NAME) == 0) + return (var_milt_daemon_name); + if (strcmp(name, S8_MAC_V) == 0) + return (var_milt_v); + + /* + * Connect macros. + */ + if (strcmp(name, S8_MAC__) == 0) { + vstring_sprintf(state->expand_buf, "%s [%s]", + state->reverse_name, state->addr); + if (strcasecmp_utf8(state->name, state->reverse_name) != 0) + vstring_strcat(state->expand_buf, " (may be forged)"); + return (STR(state->expand_buf)); + } + if (strcmp(name, S8_MAC_J) == 0) + return (var_myhostname); + if (strcmp(name, S8_MAC_CLIENT_ADDR) == 0) + return (state->rfc_addr); + if (strcmp(name, S8_MAC_CLIENT_PORT) == 0) + return (strcmp(state->port, CLIENT_PORT_UNKNOWN) ? state->port : "0"); + if (strcmp(name, S8_MAC_CLIENT_CONN) == 0) { + vstring_sprintf(state->expand_buf, "%d", state->conn_count); + return (STR(state->expand_buf)); + } + if (strcmp(name, S8_MAC_CLIENT_NAME) == 0) + return (state->name); + if (strcmp(name, S8_MAC_CLIENT_PTR) == 0) + return (state->reverse_name); + if (strcmp(name, S8_MAC_CLIENT_RES) == 0) + return (state->name_status == SMTPD_PEER_CODE_OK ? "OK" : + state->name_status == SMTPD_PEER_CODE_FORGED ? "FORGED" : + state->name_status == SMTPD_PEER_CODE_TEMP ? "TEMP" : "FAIL"); + + if (strcmp(name, S8_MAC_DAEMON_ADDR) == 0) + return (state->dest_addr); + if (strcmp(name, S8_MAC_DAEMON_PORT) == 0) + return (state->dest_port); + + /* + * HELO macros. + */ +#ifdef USE_TLS +#define IF_ENCRYPTED(x) (state->tls_context ? (x) : 0) +#define IF_TRUSTED(x) (TLS_CERT_IS_TRUSTED(state->tls_context) ? (x) : 0) + + if (strcmp(name, S8_MAC_TLS_VERSION) == 0) + return (IF_ENCRYPTED(state->tls_context->protocol)); + if (strcmp(name, S8_MAC_CIPHER) == 0) + return (IF_ENCRYPTED(state->tls_context->cipher_name)); + if (strcmp(name, S8_MAC_CIPHER_BITS) == 0) { + if (state->tls_context == 0) + return (0); + vstring_sprintf(state->expand_buf, "%d", + IF_ENCRYPTED(state->tls_context->cipher_usebits)); + return (STR(state->expand_buf)); + } + if (strcmp(name, S8_MAC_CERT_SUBJECT) == 0) + return (IF_TRUSTED(state->tls_context->peer_CN)); + if (strcmp(name, S8_MAC_CERT_ISSUER) == 0) + return (IF_TRUSTED(state->tls_context->issuer_CN)); +#endif + + /* + * MAIL FROM macros. + */ +#define IF_SASL_ENABLED(s) ((s) ? (s) : 0) + + if (strcmp(name, S8_MAC_I) == 0) + return (state->queue_id); +#ifdef USE_SASL_AUTH + if (strcmp(name, S8_MAC_AUTH_TYPE) == 0) + return (IF_SASL_ENABLED(state->sasl_method)); + if (strcmp(name, S8_MAC_AUTH_AUTHEN) == 0) + return (IF_SASL_ENABLED(state->sasl_username)); + if (strcmp(name, S8_MAC_AUTH_AUTHOR) == 0) + return (IF_SASL_ENABLED(state->sasl_sender)); +#endif + if (strcmp(name, S8_MAC_MAIL_ADDR) == 0) { + if (state->sender == 0) + return (0); + if (state->sender[0] == 0) + return (""); + reply = smtpd_resolve_addr(state->recipient, state->sender); + /* Sendmail 8.13 does not externalize the null string. */ + if (STR(reply->recipient)[0]) + quote_821_local(state->expand_buf, STR(reply->recipient)); + else + vstring_strcpy(state->expand_buf, STR(reply->recipient)); + return (STR(state->expand_buf)); + } + if (strcmp(name, S8_MAC_MAIL_HOST) == 0) { + if (state->sender == 0) + return (0); + reply = smtpd_resolve_addr(state->recipient, state->sender); + return (STR(reply->nexthop)); + } + if (strcmp(name, S8_MAC_MAIL_MAILER) == 0) { + if (state->sender == 0) + return (0); + reply = smtpd_resolve_addr(state->recipient, state->sender); + return (STR(reply->transport)); + } + + /* + * RCPT TO macros. + */ + if (strcmp(name, S8_MAC_RCPT_ADDR) == 0) { + if (state->recipient == 0) + return (0); + if (state->recipient[0] == 0) + return (""); + if (state->milter_reject_text) { + /* 554 5.7.1 : Relay access denied */ + vstring_strcpy(state->expand_buf, state->milter_reject_text + 4); + cp = split_at(STR(state->expand_buf), ' '); + return (cp ? split_at(cp, ' ') : cp); + } + reply = smtpd_resolve_addr(state->sender, state->recipient); + /* Sendmail 8.13 does not externalize the null string. */ + if (STR(reply->recipient)[0]) + quote_821_local(state->expand_buf, STR(reply->recipient)); + else + vstring_strcpy(state->expand_buf, STR(reply->recipient)); + return (STR(state->expand_buf)); + } + if (strcmp(name, S8_MAC_RCPT_HOST) == 0) { + if (state->recipient == 0) + return (0); + if (state->milter_reject_text) { + /* 554 5.7.1 : Relay access denied */ + vstring_strcpy(state->expand_buf, state->milter_reject_text + 4); + (void) split_at(STR(state->expand_buf), ' '); + return (STR(state->expand_buf)); + } + reply = smtpd_resolve_addr(state->sender, state->recipient); + return (STR(reply->nexthop)); + } + if (strcmp(name, S8_MAC_RCPT_MAILER) == 0) { + if (state->recipient == 0) + return (0); + if (state->milter_reject_text) + return (S8_RCPT_MAILER_ERROR); + reply = smtpd_resolve_addr(state->sender, state->recipient); + return (STR(reply->transport)); + } + return (0); +} diff --git a/src/smtpd/smtpd_milter.h b/src/smtpd/smtpd_milter.h new file mode 100644 index 0000000..4006bde --- /dev/null +++ b/src/smtpd/smtpd_milter.h @@ -0,0 +1,27 @@ +/*++ +/* NAME +/* smtpd_milter 3h +/* SUMMARY +/* SMTP server milter glue +/* SYNOPSIS +/* #include +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern const char *smtpd_milter_eval(const char *, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + diff --git a/src/smtpd/smtpd_nullmx.in b/src/smtpd/smtpd_nullmx.in new file mode 100644 index 0000000..58eede3 --- /dev/null +++ b/src/smtpd/smtpd_nullmx.in @@ -0,0 +1,58 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +#smtpd_delay_reject 0 +#mynetworks 127.0.0.0/8,168.100.3.0/28 +#relay_domains porcupine.org +#maps_rbl_domains dnsbltest.porcupine.org +#rbl_reply_maps hash:smtpd_check_access +#helo foobar +# +# reject_unknown_helo_hostname +# +smtpd_delay_reject 0 +helo_restrictions reject_unknown_helo_hostname +client spike.porcupine.org 168.100.3.2 +mail sname@sdomain +rcpt rname@rdomain +helo nxdomain.porcupine.org +helo nullmx.porcupine.org +helo spike.porcupine.org +# +# reject_unknown_sender_domain +# +smtpd_delay_reject 0 +sender_restrictions reject_unknown_sender_domain +client spike.porcupine.org 168.100.3.2 +helo spike.porcupine.org +rcpt rname@rdomain +mail sname@nxdomain.porcupine.org +mail sname@nullmx.porcupine.org +mail sname@spike.porcupine.org +# +# reject_unknown_recipient_domain +# +smtpd_delay_reject 0 +sender_restrictions permit +recipient_restrictions reject_unknown_recipient_domain +relay_restrictions reject_unauth_destination +client spike.porcupine.org 168.100.3.2 +helo spike.porcupine.org +mail sname@sdomain +relay_domains nxdomain.porcupine.org +rcpt rname@nxdomain.porcupine.org +relay_domains nullmx.porcupine.org +rcpt rname@nullmx.porcupine.org +relay_domains spike.porcupine.org +rcpt rname@spike.porcupine.org +# +# check_mx_access +# +smtpd_delay_reject 0 +sender_restrictions check_sender_mx_access,hash:smtpd_check_access +client spike.porcupine.org 168.100.3.2 +mail sname@nxdomain.porcupine.org +mail sname@nullmx.porcupine.org +mail sname@spike.porcupine.org diff --git a/src/smtpd/smtpd_nullmx.ref b/src/smtpd/smtpd_nullmx.ref new file mode 100644 index 0000000..410c0c0 --- /dev/null +++ b/src/smtpd/smtpd_nullmx.ref @@ -0,0 +1,100 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> #smtpd_delay_reject 0 +>>> #mynetworks 127.0.0.0/8,168.100.3.0/28 +>>> #relay_domains porcupine.org +>>> #maps_rbl_domains dnsbltest.porcupine.org +>>> #rbl_reply_maps hash:smtpd_check_access +>>> #helo foobar +>>> # +>>> # reject_unknown_helo_hostname +>>> # +>>> smtpd_delay_reject 0 +OK +>>> helo_restrictions reject_unknown_helo_hostname +OK +>>> client spike.porcupine.org 168.100.3.2 +OK +>>> mail sname@sdomain +OK +>>> rcpt rname@rdomain +OK +>>> helo nxdomain.porcupine.org +./smtpd_check: : reject: HELO from spike.porcupine.org[168.100.3.2]: 450 4.7.1 : Helo command rejected: Host not found; from= proto=SMTP helo= +450 4.7.1 : Helo command rejected: Host not found +>>> helo nullmx.porcupine.org +OK +>>> helo spike.porcupine.org +OK +>>> # +>>> # reject_unknown_sender_domain +>>> # +>>> smtpd_delay_reject 0 +OK +>>> sender_restrictions reject_unknown_sender_domain +OK +>>> client spike.porcupine.org 168.100.3.2 +OK +>>> helo spike.porcupine.org +OK +>>> rcpt rname@rdomain +OK +>>> mail sname@nxdomain.porcupine.org +./smtpd_check: : reject: MAIL from spike.porcupine.org[168.100.3.2]: 450 4.1.8 : Sender address rejected: Domain not found; from= proto=SMTP helo= +450 4.1.8 : Sender address rejected: Domain not found +>>> mail sname@nullmx.porcupine.org +./smtpd_check: : reject: MAIL from spike.porcupine.org[168.100.3.2]: 550 5.7.27 : Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from= proto=SMTP helo= +550 5.7.27 : Sender address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX) +>>> mail sname@spike.porcupine.org +OK +>>> # +>>> # reject_unknown_recipient_domain +>>> # +>>> smtpd_delay_reject 0 +OK +>>> sender_restrictions permit +OK +>>> recipient_restrictions reject_unknown_recipient_domain +OK +>>> relay_restrictions reject_unauth_destination +OK +>>> client spike.porcupine.org 168.100.3.2 +OK +>>> helo spike.porcupine.org +OK +>>> mail sname@sdomain +OK +>>> relay_domains nxdomain.porcupine.org +OK +>>> rcpt rname@nxdomain.porcupine.org +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.3.2]: 450 4.1.2 : Recipient address rejected: Domain not found; from= to= proto=SMTP helo= +450 4.1.2 : Recipient address rejected: Domain not found +>>> relay_domains nullmx.porcupine.org +OK +>>> rcpt rname@nullmx.porcupine.org +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.3.2]: 556 5.1.10 : Recipient address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX); from= to= proto=SMTP helo= +556 5.1.10 : Recipient address rejected: Domain nullmx.porcupine.org does not accept mail (nullMX) +>>> relay_domains spike.porcupine.org +OK +>>> rcpt rname@spike.porcupine.org +OK +>>> # +>>> # check_mx_access +>>> # +>>> smtpd_delay_reject 0 +OK +>>> sender_restrictions check_sender_mx_access,hash:smtpd_check_access +OK +>>> client spike.porcupine.org 168.100.3.2 +OK +>>> mail sname@nxdomain.porcupine.org +./smtpd_check: warning: Unable to look up MX host nxdomain.porcupine.org for Sender address sname@nxdomain.porcupine.org: hostname nor servname provided, or not known +OK +>>> mail sname@nullmx.porcupine.org +OK +>>> mail sname@spike.porcupine.org +./smtpd_check: : reject: MAIL from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Sender address rejected: ns or mx server spike.porcupine.org; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: ns or mx server spike.porcupine.org diff --git a/src/smtpd/smtpd_peer.c b/src/smtpd/smtpd_peer.c new file mode 100644 index 0000000..3a5c1d4 --- /dev/null +++ b/src/smtpd/smtpd_peer.c @@ -0,0 +1,658 @@ +/*++ +/* NAME +/* smtpd_peer 3 +/* SUMMARY +/* look up peer name/address information +/* SYNOPSIS +/* #include "smtpd.h" +/* +/* void smtpd_peer_init(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_peer_reset(state) +/* SMTPD_STATE *state; +/* AUXILIARY METHODS +/* void smtpd_peer_from_default(state) +/* SMTPD_STATE *state; +/* DESCRIPTION +/* The smtpd_peer_init() routine attempts to produce a printable +/* version of the peer name and address of the specified socket. +/* Where information is unavailable, the name and/or address +/* are set to "unknown". +/* +/* Alternatively, the peer address and port may be obtained +/* from a proxy server. +/* +/* This module uses the local name service via getaddrinfo() +/* and getnameinfo(). It does not query the DNS directly. +/* +/* smtpd_peer_init() updates the following fields: +/* .IP name +/* The verified client hostname. This name is represented by +/* the string "unknown" when 1) the address->name lookup failed, +/* 2) the name->address mapping fails, or 3) the name->address +/* mapping does not produce the client IP address. +/* .IP reverse_name +/* The unverified client hostname as found with address->name +/* lookup; it is not verified for consistency with the client +/* IP address result from name->address lookup. +/* .IP forward_name +/* The unverified client hostname as found with address->name +/* lookup followed by name->address lookup; it is not verified +/* for consistency with the result from address->name lookup. +/* For example, when the address->name lookup produces as +/* hostname an alias, the name->address lookup will produce +/* as hostname the expansion of that alias, so that the two +/* lookups produce different names. +/* .IP addr +/* Printable representation of the client address. +/* .IP namaddr +/* String of the form: "name[addr]:port". +/* .IP rfc_addr +/* String of the form "ipv4addr" or "ipv6:ipv6addr" for use +/* in Received: message headers. +/* .IP dest_addr +/* Server address, used by the Dovecot authentication server, +/* available as Milter {daemon_addr} macro, and as server_address +/* policy delegation attribute. +/* .IP dest_port +/* Server port, available as Milter {daemon_port} macro, and +/* as server_port policy delegation attribute. +/* .IP name_status +/* The name_status result field specifies how the name +/* information should be interpreted: +/* .RS +/* .IP 2 +/* The address->name lookup and name->address lookup produced +/* the client IP address. +/* .IP 4 +/* The address->name lookup or name->address lookup failed +/* with a recoverable error. +/* .IP 5 +/* The address->name lookup or name->address lookup failed +/* with an unrecoverable error, or the result did not match +/* the client IP address. +/* .RE +/* .IP reverse_name_status +/* The reverse_name_status result field specifies how the +/* reverse_name information should be interpreted: +/* .RS +/* .IP 2 +/* The address->name lookup succeeded. +/* .IP 4 +/* The address->name lookup failed with a recoverable error. +/* .IP 5 +/* The address->name lookup failed with an unrecoverable error. +/* .RE +/* .IP forward_name_status +/* The forward_name_status result field specifies how the +/* forward_name information should be interpreted: +/* .RS +/* .IP 2 +/* The address->name and name->address lookup succeeded. +/* .IP 4 +/* The address->name lookup or name->address failed with a +/* recoverable error. +/* .IP 5 +/* The address->name lookup or name->address failed with an +/* unrecoverable error. +/* .RE +/* .PP +/* smtpd_peer_reset() releases memory allocated by smtpd_peer_init(). +/* +/* smtpd_peer_from_default() looks up connection information +/* when an up-stream proxy indicates that a connection is not +/* proxied. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include "smtpd.h" + +static const INET_PROTO_INFO *proto_info; + + /* + * XXX If we make local port information available via logging, then we must + * also support these attributes with the XFORWARD command. + * + * XXX If support were to be added for Milter applications in down-stream MTAs, + * then consistency demands that we propagate a lot of Sendmail macro + * information via the XFORWARD command. Otherwise we could end up with a + * very confusing situation. + */ + +/* smtpd_peer_sockaddr_to_hostaddr - client address/port to printable form */ + +static int smtpd_peer_sockaddr_to_hostaddr(SMTPD_STATE *state) +{ + const char *myname = "smtpd_peer_sockaddr_to_hostaddr"; + struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr); + SOCKADDR_SIZE sa_length = state->sockaddr_len; + + /* + * XXX If we're given an IPv6 (or IPv4) connection from, e.g., inetd, + * while Postfix IPv6 (or IPv4) support is turned off, don't (skip to the + * final else clause, pretend the origin is localhost[127.0.0.1], and + * become an open relay). + */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) { + MAI_HOSTADDR_STR client_addr; + MAI_SERVPORT_STR client_port; + MAI_HOSTADDR_STR server_addr; + MAI_SERVPORT_STR server_port; + int aierr; + char *colonp; + + /* + * Sanity check: we can't use sockets that we're not configured for. + */ + if (strchr((char *) proto_info->sa_family_list, sa->sa_family) == 0) + msg_fatal("cannot handle socket type %s with \"%s = %s\"", +#ifdef AF_INET6 + sa->sa_family == AF_INET6 ? "AF_INET6" : +#endif + sa->sa_family == AF_INET ? "AF_INET" : + "other", VAR_INET_PROTOCOLS, var_inet_protocols); + + /* + * Sorry, but there are some things that we just cannot do while + * connected to the network. + */ + if (geteuid() != var_owner_uid || getuid() != var_owner_uid) { + msg_error("incorrect SMTP server privileges: uid=%lu euid=%lu", + (unsigned long) getuid(), (unsigned long) geteuid()); + msg_fatal("the Postfix SMTP server must run with $%s privileges", + VAR_MAIL_OWNER); + } + + /* + * Convert the client address to printable form. + */ + if ((aierr = sockaddr_to_hostaddr(sa, sa_length, &client_addr, + &client_port, 0)) != 0) + msg_fatal("%s: cannot convert client address/port to string: %s", + myname, MAI_STRERROR(aierr)); + state->port = mystrdup(client_port.buf); + + /* + * XXX Require that the infrastructure strips off the IPv6 datalink + * suffix to avoid false alarms with strict address syntax checks. + */ +#ifdef HAS_IPV6 + if (strchr(client_addr.buf, '%') != 0) + msg_panic("%s: address %s has datalink suffix", + myname, client_addr.buf); +#endif + + /* + * We convert IPv4-in-IPv6 address to 'true' IPv4 address early on, + * but only if IPv4 support is enabled (why would anyone want to turn + * it off)? With IPv4 support enabled we have no need for the IPv6 + * form in logging, hostname verification and access checks. + */ +#ifdef HAS_IPV6 + if (sa->sa_family == AF_INET6) { + if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0 + && IN6_IS_ADDR_V4MAPPED(&SOCK_ADDR_IN6_ADDR(sa)) + && (colonp = strrchr(client_addr.buf, ':')) != 0) { + struct addrinfo *res0; + + if (msg_verbose > 1) + msg_info("%s: rewriting V4-mapped address \"%s\" to \"%s\"", + myname, client_addr.buf, colonp + 1); + + state->addr = mystrdup(colonp + 1); + state->rfc_addr = mystrdup(colonp + 1); + state->addr_family = AF_INET; + aierr = hostaddr_to_sockaddr(state->addr, (char *) 0, 0, &res0); + if (aierr) + msg_fatal("%s: cannot convert %s from string to binary: %s", + myname, state->addr, MAI_STRERROR(aierr)); + sa_length = res0->ai_addrlen; + if (sa_length > sizeof(state->sockaddr)) + sa_length = sizeof(state->sockaddr); + memcpy((void *) sa, res0->ai_addr, sa_length); + freeaddrinfo(res0); /* 200412 */ + } + + /* + * Following RFC 2821 section 4.1.3, an IPv6 address literal gets + * a prefix of 'IPv6:'. We do this consistently for all IPv6 + * addresses that appear in headers or envelopes. The fact + * that valid_mailhost_addr() enforces the form helps of course. + * We use the form without IPV6: prefix when doing access + * control, or when accessing the connection cache. + */ + else { + state->addr = mystrdup(client_addr.buf); + state->rfc_addr = + concatenate(IPV6_COL, client_addr.buf, (char *) 0); + state->addr_family = sa->sa_family; + } + } + + /* + * An IPv4 address is in dotted quad decimal form. + */ + else +#endif + { + state->addr = mystrdup(client_addr.buf); + state->rfc_addr = mystrdup(client_addr.buf); + state->addr_family = sa->sa_family; + } + + /* + * Convert the server address/port to printable form. + */ + if ((aierr = sockaddr_to_hostaddr((struct sockaddr *) + &state->dest_sockaddr, + state->dest_sockaddr_len, + &server_addr, + &server_port, 0)) != 0) + msg_fatal("%s: cannot convert server address/port to string: %s", + myname, MAI_STRERROR(aierr)); + /* TODO: convert IPv4-in-IPv6 to IPv4 form. */ + state->dest_addr = mystrdup(server_addr.buf); + state->dest_port = mystrdup(server_port.buf); + + return (0); + } + + /* + * It's not Internet. + */ + else { + return (-1); + } +} + +/* smtpd_peer_sockaddr_to_hostname - client hostname lookup */ + +static void smtpd_peer_sockaddr_to_hostname(SMTPD_STATE *state) +{ + struct sockaddr *sa = (struct sockaddr *) &(state->sockaddr); + SOCKADDR_SIZE sa_length = state->sockaddr_len; + MAI_HOSTNAME_STR client_name; + int aierr; + + /* + * Look up and sanity check the client hostname. + * + * It is unsafe to allow numeric hostnames, especially because there exists + * pressure to turn off the name->addr double check. In that case an + * attacker could trivally bypass access restrictions. + * + * sockaddr_to_hostname() already rejects malformed or numeric names. + */ +#define TEMP_AI_ERROR(e) \ + ((e) == EAI_AGAIN || (e) == EAI_MEMORY || (e) == EAI_SYSTEM) + +#define REJECT_PEER_NAME(state, code) { \ + myfree(state->name); \ + state->name = mystrdup(CLIENT_NAME_UNKNOWN); \ + state->name_status = code; \ + } + + if (var_smtpd_peername_lookup == 0) { + state->name = mystrdup(CLIENT_NAME_UNKNOWN); + state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN); + state->name_status = SMTPD_PEER_CODE_PERM; + state->reverse_name_status = SMTPD_PEER_CODE_PERM; + } else if ((aierr = sockaddr_to_hostname(sa, sa_length, &client_name, + (MAI_SERVNAME_STR *) 0, 0)) != 0) { + state->name = mystrdup(CLIENT_NAME_UNKNOWN); + state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN); + state->name_status = (TEMP_AI_ERROR(aierr) ? + SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM); + state->reverse_name_status = (TEMP_AI_ERROR(aierr) ? + SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_PERM); + } else { + struct addrinfo *res0; + struct addrinfo *res; + + state->name = mystrdup(client_name.buf); + state->reverse_name = mystrdup(client_name.buf); + state->name_status = SMTPD_PEER_CODE_OK; + state->reverse_name_status = SMTPD_PEER_CODE_OK; + + /* + * Reject the hostname if it does not list the peer address. Without + * further validation or qualification, such information must not be + * allowed to enter the audit trail, as people would draw false + * conclusions. + */ + aierr = hostname_to_sockaddr_pf(state->name, state->addr_family, + (char *) 0, 0, &res0); + if (aierr) { + msg_warn("hostname %s does not resolve to address %s: %s", + state->name, state->addr, MAI_STRERROR(aierr)); + REJECT_PEER_NAME(state, (TEMP_AI_ERROR(aierr) ? + SMTPD_PEER_CODE_TEMP : SMTPD_PEER_CODE_FORGED)); + } else { + for (res = res0; /* void */ ; res = res->ai_next) { + if (res == 0) { + msg_warn("hostname %s does not resolve to address %s", + state->name, state->addr); + REJECT_PEER_NAME(state, SMTPD_PEER_CODE_FORGED); + break; + } + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + msg_info("skipping address family %d for host %s", + res->ai_family, state->name); + continue; + } + if (sock_addr_cmp_addr(res->ai_addr, sa) == 0) + break; /* keep peer name */ + } + freeaddrinfo(res0); + } + } +} + +/* smtpd_peer_hostaddr_to_sockaddr - convert numeric string to binary */ + +static void smtpd_peer_hostaddr_to_sockaddr(SMTPD_STATE *state) +{ + const char *myname = "smtpd_peer_hostaddr_to_sockaddr"; + struct addrinfo *res; + int aierr; + + if ((aierr = hostaddr_to_sockaddr(state->addr, state->port, + SOCK_STREAM, &res)) != 0) + msg_fatal("%s: cannot convert client address/port to string: %s", + myname, MAI_STRERROR(aierr)); + if (res->ai_addrlen > sizeof(state->sockaddr)) + msg_panic("%s: address length > struct sockaddr_storage", myname); + memcpy((void *) &(state->sockaddr), res->ai_addr, res->ai_addrlen); + state->sockaddr_len = res->ai_addrlen; + freeaddrinfo(res); +} + +/* smtpd_peer_not_inet - non-socket or non-Internet endpoint */ + +static void smtpd_peer_not_inet(SMTPD_STATE *state) +{ + + /* + * If it's not Internet, assume the client is local, and avoid using the + * naming service because that can hang when the machine is disconnected. + */ + state->name = mystrdup("localhost"); + state->reverse_name = mystrdup("localhost"); +#ifdef AF_INET6 + if (proto_info->sa_family_list[0] == PF_INET6) { + state->addr = mystrdup("::1"); /* XXX bogus. */ + state->rfc_addr = mystrdup(IPV6_COL "::1"); /* XXX bogus. */ + } else +#endif + { + state->addr = mystrdup("127.0.0.1"); /* XXX bogus. */ + state->rfc_addr = mystrdup("127.0.0.1");/* XXX bogus. */ + } + state->addr_family = AF_UNSPEC; + state->name_status = SMTPD_PEER_CODE_OK; + state->reverse_name_status = SMTPD_PEER_CODE_OK; + state->port = mystrdup("0"); /* XXX bogus. */ + + state->dest_addr = mystrdup(state->addr); /* XXX bogus. */ + state->dest_port = mystrdup(state->port); /* XXX bogus. */ +} + +/* smtpd_peer_no_client - peer went away, or peer info unavailable */ + +static void smtpd_peer_no_client(SMTPD_STATE *state) +{ + smtpd_peer_reset(state); + state->name = mystrdup(CLIENT_NAME_UNKNOWN); + state->reverse_name = mystrdup(CLIENT_NAME_UNKNOWN); + state->addr = mystrdup(CLIENT_ADDR_UNKNOWN); + state->rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN); + state->addr_family = AF_UNSPEC; + state->name_status = SMTPD_PEER_CODE_PERM; + state->reverse_name_status = SMTPD_PEER_CODE_PERM; + state->port = mystrdup(CLIENT_PORT_UNKNOWN); + + state->dest_addr = mystrdup(SERVER_ADDR_UNKNOWN); + state->dest_port = mystrdup(SERVER_PORT_UNKNOWN); +} + +/* smtpd_peer_from_pass_attr - initialize from attribute hash */ + +static void smtpd_peer_from_pass_attr(SMTPD_STATE *state) +{ + HTABLE *attr = (HTABLE *) vstream_context(state->client); + const char *cp; + + /* + * Extract the client endpoint information from the attribute hash. + */ + if ((cp = htable_find(attr, MAIL_ATTR_ACT_CLIENT_ADDR)) == 0) + msg_fatal("missing client address from proxy"); + if (strrchr(cp, ':') != 0) { + if (valid_ipv6_hostaddr(cp, DO_GRIPE) == 0) + msg_fatal("bad IPv6 client address syntax from proxy: %s", cp); + state->addr = mystrdup(cp); + state->rfc_addr = concatenate(IPV6_COL, cp, (char *) 0); + state->addr_family = AF_INET6; + } else { + if (valid_ipv4_hostaddr(cp, DO_GRIPE) == 0) + msg_fatal("bad IPv4 client address syntax from proxy: %s", cp); + state->addr = mystrdup(cp); + state->rfc_addr = mystrdup(cp); + state->addr_family = AF_INET; + } + if ((cp = htable_find(attr, MAIL_ATTR_ACT_CLIENT_PORT)) == 0) + msg_fatal("missing client port from proxy"); + if (valid_hostport(cp, DO_GRIPE) == 0) + msg_fatal("bad TCP client port number syntax from proxy: %s", cp); + state->port = mystrdup(cp); + + /* + * The Dovecot authentication server needs the server IP address. + */ + if ((cp = htable_find(attr, MAIL_ATTR_ACT_SERVER_ADDR)) == 0) + msg_fatal("missing server address from proxy"); + if (valid_hostaddr(cp, DO_GRIPE) == 0) + msg_fatal("bad IPv6 server address syntax from proxy: %s", cp); + state->dest_addr = mystrdup(cp); + + if ((cp = htable_find(attr, MAIL_ATTR_ACT_SERVER_PORT)) == 0) + msg_fatal("missing server port from proxy"); + if (valid_hostport(cp, DO_GRIPE) == 0) + msg_fatal("bad TCP server port number syntax from proxy: %s", cp); + state->dest_port = mystrdup(cp); + + /* + * Convert the client address from string to binary form. + */ + smtpd_peer_hostaddr_to_sockaddr(state); +} + +/* smtpd_peer_from_default - try to initialize peer information from socket */ + +void smtpd_peer_from_default(SMTPD_STATE *state) +{ + + /* + * The "no client" routine provides surrogate information so that the + * application can produce sensible logging when a client disconnects + * before the server wakes up. The "not inet" routine provides surrogate + * state for (presumably) local IPC channels. + */ + state->sockaddr_len = sizeof(state->sockaddr); + state->dest_sockaddr_len = sizeof(state->dest_sockaddr); + if (getpeername(vstream_fileno(state->client), + (struct sockaddr *) &state->sockaddr, + &state->sockaddr_len) <0 + || getsockname(vstream_fileno(state->client), + (struct sockaddr *) &state->dest_sockaddr, + &state->dest_sockaddr_len) < 0) { + if (errno == ENOTSOCK) + smtpd_peer_not_inet(state); + else + smtpd_peer_no_client(state); + } else { + if (smtpd_peer_sockaddr_to_hostaddr(state) < 0) + smtpd_peer_not_inet(state); + } +} + +/* smtpd_peer_from_proxy - get endpoint info from proxy agent */ + +static void smtpd_peer_from_proxy(SMTPD_STATE *state) +{ + typedef struct { + const char *name; + int (*endpt_lookup) (SMTPD_STATE *); + } SMTPD_ENDPT_LOOKUP_INFO; + static const SMTPD_ENDPT_LOOKUP_INFO smtpd_endpt_lookup_info[] = { + HAPROXY_PROTO_NAME, smtpd_peer_from_haproxy, + 0, + }; + const SMTPD_ENDPT_LOOKUP_INFO *pp; + + /* + * When the proxy information is unavailable, we can't maintain an audit + * trail or enforce access control, therefore we forcibly hang up. + */ + for (pp = smtpd_endpt_lookup_info; /* see below */ ; pp++) { + if (pp->name == 0) + msg_fatal("unsupported %s value: %s", + VAR_SMTPD_UPROXY_PROTO, var_smtpd_uproxy_proto); + if (strcmp(var_smtpd_uproxy_proto, pp->name) == 0) + break; + } + if (pp->endpt_lookup(state) < 0) { + smtpd_peer_from_default(state); + state->flags |= SMTPD_FLAG_HANGUP; + } else { + smtpd_peer_hostaddr_to_sockaddr(state); + } +} + +/* smtpd_peer_init - initialize peer information */ + +void smtpd_peer_init(SMTPD_STATE *state) +{ + + /* + * Initialize. + */ + if (proto_info == 0) + proto_info = inet_proto_info(); + + /* + * Prepare for partial initialization after error. + */ + memset((void *) &(state->sockaddr), 0, sizeof(state->sockaddr)); + state->sockaddr_len = 0; + state->name = 0; + state->reverse_name = 0; + state->addr = 0; + state->namaddr = 0; + state->rfc_addr = 0; + state->port = 0; + state->dest_addr = 0; + state->dest_port = 0; + + /* + * Determine the remote SMTP client address and port. + * + * XXX In stand-alone mode, don't assume that the peer will be a local + * process. That could introduce a gaping hole when the SMTP daemon is + * hooked up to the network via inetd or some other super-server. + */ + if (vstream_context(state->client) != 0) { + smtpd_peer_from_pass_attr(state); + if (*var_smtpd_uproxy_proto != 0) + msg_warn("ignoring non-empty %s setting behind postscreen", + VAR_SMTPD_UPROXY_PROTO); + } else if (SMTPD_STAND_ALONE(state) || *var_smtpd_uproxy_proto == 0) { + smtpd_peer_from_default(state); + } else { + smtpd_peer_from_proxy(state); + } + + /* + * Determine the remote SMTP client hostname. Note: some of the handlers + * above provide surrogate endpoint information in case of error. In that + * case, leave the surrogate information alone. + */ + if (state->name == 0) + smtpd_peer_sockaddr_to_hostname(state); + + /* + * Do the name[addr]:port formatting for pretty reports. + */ + state->namaddr = SMTPD_BUILD_NAMADDRPORT(state->name, state->addr, + state->port); +} + +/* smtpd_peer_reset - destroy peer information */ + +void smtpd_peer_reset(SMTPD_STATE *state) +{ + if (state->name) + myfree(state->name); + if (state->reverse_name) + myfree(state->reverse_name); + if (state->addr) + myfree(state->addr); + if (state->namaddr) + myfree(state->namaddr); + if (state->rfc_addr) + myfree(state->rfc_addr); + if (state->port) + myfree(state->port); + if (state->dest_addr) + myfree(state->dest_addr); + if (state->dest_port) + myfree(state->dest_port); +} diff --git a/src/smtpd/smtpd_proxy.c b/src/smtpd/smtpd_proxy.c new file mode 100644 index 0000000..1c1f9fc --- /dev/null +++ b/src/smtpd/smtpd_proxy.c @@ -0,0 +1,1171 @@ +/*++ +/* NAME +/* smtpd_proxy 3 +/* SUMMARY +/* SMTP server pass-through proxy client +/* SYNOPSIS +/* #include +/* #include +/* +/* typedef struct { +/* .in +4 +/* VSTREAM *stream; /* SMTP proxy or replay log */ +/* VSTRING *buffer; /* last SMTP proxy response */ +/* /* other fields... */ +/* .in -4 +/* } SMTPD_PROXY; +/* +/* int smtpd_proxy_create(state, flags, service, timeout, +/* ehlo_name, mail_from) +/* SMTPD_STATE *state; +/* int flags; +/* const char *service; +/* int timeout; +/* const char *ehlo_name; +/* const char *mail_from; +/* +/* int proxy->cmd(state, expect, format, ...) +/* SMTPD_PROXY *proxy; +/* SMTPD_STATE *state; +/* int expect; +/* const char *format; +/* +/* void smtpd_proxy_free(state) +/* SMTPD_STATE *state; +/* +/* int smtpd_proxy_parse_opts(param_name, param_val) +/* const char *param_name; +/* const char *param_val; +/* RECORD-LEVEL ROUTINES +/* int proxy->rec_put(proxy->stream, rec_type, data, len) +/* SMTPD_PROXY *proxy; +/* int rec_type; +/* const char *data; +/* ssize_t len; +/* +/* int proxy->rec_fprintf(proxy->stream, rec_type, format, ...) +/* SMTPD_PROXY *proxy; +/* int rec_type; +/* cont char *format; +/* DESCRIPTION +/* The functions in this module implement a pass-through proxy +/* client. +/* +/* In order to minimize the intrusiveness of pass-through +/* proxying, 1) the proxy server must support the same MAIL +/* FROM/RCPT syntax that Postfix supports, 2) the record-level +/* routines for message content proxying have the same interface +/* as the routines that are used for non-proxied mail. +/* +/* smtpd_proxy_create() takes a description of a before-queue +/* filter. Depending on flags, it either arranges to buffer +/* up commands and message content until the entire message +/* is received, or it immediately connects to the proxy service, +/* sends EHLO, sends client information with the XFORWARD +/* command if possible, sends the MAIL FROM command, and +/* receives the reply. +/* A non-zero result value means trouble: either the proxy is +/* unavailable, or it did not send the expected reply. +/* All results are reported via the proxy->buffer field in a +/* form that can be sent to the SMTP client. An unexpected +/* 2xx or 3xx proxy server response is replaced by a generic +/* error response to avoid support problems. +/* In case of error, smtpd_proxy_create() updates the +/* state->error_mask and state->err fields, and leaves the +/* SMTPD_PROXY handle in an unconnected state. Destroy the +/* handle after reporting the error reply in the proxy->buffer +/* field. +/* +/* proxy->cmd() formats and either buffers up the command and +/* expected response until the entire message is received, or +/* it immediately sends the specified command to the proxy +/* server, and receives the proxy server reply. +/* A non-zero result value means trouble: either the proxy is +/* unavailable, or it did not send the expected reply. +/* All results are reported via the proxy->buffer field in a +/* form that can be sent to the SMTP client. An unexpected +/* 2xx or 3xx proxy server response is replaced by a generic +/* error response to avoid support problems. +/* In case of error, proxy->cmd() updates the state->error_mask +/* and state->err fields. +/* +/* smtpd_proxy_free() destroys a proxy server handle and resets +/* the state->proxy field. +/* +/* smtpd_proxy_parse_opts() parses main.cf processing options. +/* +/* proxy->rec_put() is a rec_put() clone that either buffers +/* up arbitrary message content records until the entire message +/* is received, or that immediately sends it to the proxy +/* server. +/* All data is expected to be in SMTP dot-escaped form. +/* All errors are reported as a REC_TYPE_ERROR result value, +/* with the state->error_mask, state->err and proxy-buffer +/* fields given appropriate values. +/* +/* proxy->rec_fprintf() is a rec_fprintf() clone that formats +/* message content and either buffers up the record until the +/* entire message is received, or that immediately sends it +/* to the proxy server. +/* All data is expected to be in SMTP dot-escaped form. +/* All errors are reported as a REC_TYPE_ERROR result value, +/* with the state->error_mask, state->err and proxy-buffer +/* fields given appropriate values. +/* +/* Arguments: +/* .IP flags +/* Zero, or SMTPD_PROXY_FLAG_SPEED_ADJUST to buffer up the entire +/* message before contacting a before-queue content filter. +/* Note: when this feature is requested, the before-queue +/* filter MUST use the same 2xx, 4xx or 5xx reply code for all +/* recipients of a multi-recipient message. +/* .IP server +/* The SMTP proxy server host:port. The host or host: part is optional. +/* This argument is not duplicated. +/* .IP timeout +/* Time limit for connecting to the proxy server and for +/* sending and receiving proxy server commands and replies. +/* .IP ehlo_name +/* The EHLO Hostname that will be sent to the proxy server. +/* This argument is not duplicated. +/* .IP mail_from +/* The MAIL FROM command. This argument is not duplicated. +/* .IP state +/* SMTP server state. +/* .IP expect +/* Expected proxy server reply status code range. A warning is logged +/* when an unexpected reply is received. Specify one of the following: +/* .RS +/* .IP SMTPD_PROX_WANT_OK +/* The caller expects a reply in the 200 range. +/* .IP SMTPD_PROX_WANT_MORE +/* The caller expects a reply in the 300 range. +/* .IP SMTPD_PROX_WANT_ANY +/* The caller has no expectation. Do not warn for unexpected replies. +/* .IP SMTPD_PROX_WANT_NONE +/* Do not bother waiting for a reply. +/* .RE +/* .IP format +/* A format string. +/* .IP stream +/* Connection to proxy server. +/* .IP data +/* Pointer to the content of one message content record. +/* .IP len +/* The length of a message content record. +/* SEE ALSO +/* smtpd(8) Postfix smtp server +/* DIAGNOSTICS +/* Panic: internal API violations. +/* +/* Fatal errors: memory allocation problem. +/* +/* Warnings: unexpected response from proxy server, unable +/* to connect to proxy server, proxy server read/write error, +/* proxy speed-adjust buffer read/write error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include +#include + + /* + * XFORWARD server features, recognized by the pass-through proxy client. + */ +#define SMTPD_PROXY_XFORWARD_NAME (1<<0) /* client name */ +#define SMTPD_PROXY_XFORWARD_ADDR (1<<1) /* client address */ +#define SMTPD_PROXY_XFORWARD_PROTO (1<<2) /* protocol */ +#define SMTPD_PROXY_XFORWARD_HELO (1<<3) /* client helo */ +#define SMTPD_PROXY_XFORWARD_IDENT (1<<4) /* message identifier */ +#define SMTPD_PROXY_XFORWARD_DOMAIN (1<<5) /* origin type */ +#define SMTPD_PROXY_XFORWARD_PORT (1<<6) /* client port */ + + /* + * Spead-matching: we use an unlinked file for transient storage. + */ +static VSTREAM *smtpd_proxy_replay_stream; + + /* + * Forward declarations. + */ +static void smtpd_proxy_fake_server_reply(SMTPD_STATE *, int); +static int smtpd_proxy_rdwr_error(SMTPD_STATE *, int); +static int PRINTFLIKE(3, 4) smtpd_proxy_cmd(SMTPD_STATE *, int, const char *,...); +static int smtpd_proxy_rec_put(VSTREAM *, int, const char *, ssize_t); + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) +#define STREQ(x, y) (strcmp((x), (y)) == 0) + +/* smtpd_proxy_xforward_flush - flush forwarding information */ + +static int smtpd_proxy_xforward_flush(SMTPD_STATE *state, VSTRING *buf) +{ + int ret; + + if (VSTRING_LEN(buf) > 0) { + ret = smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, + XFORWARD_CMD "%s", STR(buf)); + VSTRING_RESET(buf); + return (ret); + } + return (0); +} + +/* smtpd_proxy_xforward_send - send forwarding information */ + +static int smtpd_proxy_xforward_send(SMTPD_STATE *state, VSTRING *buf, + const char *name, + int value_available, + const char *value) +{ + size_t new_len; + int ret; + +#define CONSTR_LEN(s) (sizeof(s) - 1) +#define PAYLOAD_LIMIT (512 - CONSTR_LEN("250 " XFORWARD_CMD "\r\n")) + + if (!value_available) + value = XFORWARD_UNAVAILABLE; + + /* + * Encode the attribute value. + */ + if (state->expand_buf == 0) + state->expand_buf = vstring_alloc(100); + xtext_quote(state->expand_buf, value, ""); + + /* + * How much space does this attribute need? SPACE name = value. + */ + new_len = strlen(name) + strlen(STR(state->expand_buf)) + 2; + if (new_len > PAYLOAD_LIMIT) + msg_warn("%s command payload %s=%.10s... exceeds SMTP protocol limit", + XFORWARD_CMD, name, value); + + /* + * Flush the buffer if we need to, and store the attribute. + */ + if (VSTRING_LEN(buf) > 0 && VSTRING_LEN(buf) + new_len > PAYLOAD_LIMIT) + if ((ret = smtpd_proxy_xforward_flush(state, buf)) < 0) + return (ret); + vstring_sprintf_append(buf, " %s=%s", name, STR(state->expand_buf)); + + return (0); +} + +/* smtpd_proxy_connect - open proxy connection */ + +static int smtpd_proxy_connect(SMTPD_STATE *state) +{ + SMTPD_PROXY *proxy = state->proxy; + int fd; + char *lines; + char *words; + VSTRING *buf; + int bad; + char *word; + static const NAME_CODE known_xforward_features[] = { + XFORWARD_NAME, SMTPD_PROXY_XFORWARD_NAME, + XFORWARD_ADDR, SMTPD_PROXY_XFORWARD_ADDR, + XFORWARD_PORT, SMTPD_PROXY_XFORWARD_PORT, + XFORWARD_PROTO, SMTPD_PROXY_XFORWARD_PROTO, + XFORWARD_HELO, SMTPD_PROXY_XFORWARD_HELO, + XFORWARD_IDENT, SMTPD_PROXY_XFORWARD_IDENT, + XFORWARD_DOMAIN, SMTPD_PROXY_XFORWARD_DOMAIN, + 0, 0, + }; + int server_xforward_features; + int (*connect_fn) (const char *, int, int); + const char *endpoint; + + /* + * Find connection method (default inet) + */ + if (strncasecmp("unix:", proxy->service_name, 5) == 0) { + endpoint = proxy->service_name + 5; + connect_fn = unix_connect; + } else { + if (strncasecmp("inet:", proxy->service_name, 5) == 0) + endpoint = proxy->service_name + 5; + else + endpoint = proxy->service_name; + connect_fn = inet_connect; + } + + /* + * Connect to proxy. + */ + if ((fd = connect_fn(endpoint, BLOCKING, proxy->timeout)) < 0) { + msg_warn("connect to proxy filter %s: %m", proxy->service_name); + return (smtpd_proxy_rdwr_error(state, 0)); + } + proxy->service_stream = vstream_fdopen(fd, O_RDWR); + /* Needed by our DATA-phase record emulation routines. */ + vstream_control(proxy->service_stream, + CA_VSTREAM_CTL_CONTEXT((void *) state), + CA_VSTREAM_CTL_END); + /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ + if (connect_fn == inet_connect) + vstream_tweak_tcp(proxy->service_stream); + smtp_timeout_setup(proxy->service_stream, proxy->timeout); + + /* + * Get server greeting banner. + * + * If this fails then we have a problem because the proxy should always + * accept our connection. Make up our own response instead of passing + * back a negative greeting banner: the proxy open is delayed to the + * point that the client expects a MAIL FROM or RCPT TO reply. + */ + if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", "")) { + smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); + smtpd_proxy_close(state); + return (-1); + } + + /* + * Send our own EHLO command. If this fails then we have a problem + * because the proxy should always accept our EHLO command. Make up our + * own response instead of passing back a negative EHLO reply: the proxy + * open is delayed to the point that the remote SMTP client expects a + * MAIL FROM or RCPT TO reply. + */ + if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "EHLO %s", + proxy->ehlo_name)) { + smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); + smtpd_proxy_close(state); + return (-1); + } + + /* + * Parse the EHLO reply and see if we can forward logging information. + */ + server_xforward_features = 0; + lines = STR(proxy->reply); + while ((words = mystrtok(&lines, "\r\n")) != 0) { + if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) { + if (strcasecmp(word, XFORWARD_CMD) == 0) + while ((word = mystrtok(&words, " \t")) != 0) + server_xforward_features |= + name_code(known_xforward_features, + NAME_CODE_FLAG_NONE, word); + } + } + + /* + * Send XFORWARD attributes. For robustness, explicitly specify what SMTP + * session attributes are known and unknown. Make up our own response + * instead of passing back a negative XFORWARD reply: the proxy open is + * delayed to the point that the remote SMTP client expects a MAIL FROM + * or RCPT TO reply. + */ + if (server_xforward_features) { + buf = vstring_alloc(100); + bad = + (((server_xforward_features & SMTPD_PROXY_XFORWARD_NAME) + && smtpd_proxy_xforward_send(state, buf, XFORWARD_NAME, + IS_AVAIL_CLIENT_NAME(FORWARD_NAME(state)), + FORWARD_NAME(state))) + || ((server_xforward_features & SMTPD_PROXY_XFORWARD_ADDR) + && smtpd_proxy_xforward_send(state, buf, XFORWARD_ADDR, + IS_AVAIL_CLIENT_ADDR(FORWARD_ADDR(state)), + FORWARD_ADDR(state))) + || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PORT) + && smtpd_proxy_xforward_send(state, buf, XFORWARD_PORT, + IS_AVAIL_CLIENT_PORT(FORWARD_PORT(state)), + FORWARD_PORT(state))) + || ((server_xforward_features & SMTPD_PROXY_XFORWARD_HELO) + && smtpd_proxy_xforward_send(state, buf, XFORWARD_HELO, + IS_AVAIL_CLIENT_HELO(FORWARD_HELO(state)), + FORWARD_HELO(state))) + || ((server_xforward_features & SMTPD_PROXY_XFORWARD_IDENT) + && smtpd_proxy_xforward_send(state, buf, XFORWARD_IDENT, + IS_AVAIL_CLIENT_IDENT(FORWARD_IDENT(state)), + FORWARD_IDENT(state))) + || ((server_xforward_features & SMTPD_PROXY_XFORWARD_PROTO) + && smtpd_proxy_xforward_send(state, buf, XFORWARD_PROTO, + IS_AVAIL_CLIENT_PROTO(FORWARD_PROTO(state)), + FORWARD_PROTO(state))) + || ((server_xforward_features & SMTPD_PROXY_XFORWARD_DOMAIN) + && smtpd_proxy_xforward_send(state, buf, XFORWARD_DOMAIN, 1, + STREQ(FORWARD_DOMAIN(state), MAIL_ATTR_RWR_LOCAL) ? + XFORWARD_DOM_LOCAL : XFORWARD_DOM_REMOTE)) + || smtpd_proxy_xforward_flush(state, buf)); + vstring_free(buf); + if (bad) { + smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); + smtpd_proxy_close(state); + return (-1); + } + } + + /* + * Pass-through the remote SMTP client's MAIL FROM command. If this + * fails, then we have a problem because the proxy should always accept + * any MAIL FROM command that was accepted by us. + */ + if (smtpd_proxy_cmd(state, SMTPD_PROX_WANT_OK, "%s", + proxy->mail_from) != 0) { + /* NOT: smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); */ + smtpd_proxy_close(state); + return (-1); + } + return (0); +} + +/* smtpd_proxy_fake_server_reply - produce generic error response */ + +static void smtpd_proxy_fake_server_reply(SMTPD_STATE *state, int status) +{ + const CLEANUP_STAT_DETAIL *detail; + + /* + * Either we have no server reply (connection refused), or we have an + * out-of-protocol server reply, so we make up a generic server error + * response instead. + */ + detail = cleanup_stat_detail(status); + vstring_sprintf(state->proxy->reply, + "%d %s Error: %s", + detail->smtp, detail->dsn, detail->text); +} + +/* smtpd_proxy_replay_rdwr_error - report replay log I/O error */ + +static int smtpd_proxy_replay_rdwr_error(SMTPD_STATE *state) +{ + + /* + * Log an appropriate warning message. + */ + msg_warn("proxy speed-adjust log I/O error: %m"); + + /* + * Set the appropriate flags and server reply. + */ + state->error_mask |= MAIL_ERROR_RESOURCE; + /* Update state->err in case we are past the client's DATA command. */ + state->err |= CLEANUP_STAT_PROXY; + smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); + return (-1); +} + +/* smtpd_proxy_rdwr_error - report proxy communication error */ + +static int smtpd_proxy_rdwr_error(SMTPD_STATE *state, int err) +{ + const char *myname = "smtpd_proxy_rdwr_error"; + SMTPD_PROXY *proxy = state->proxy; + + /* + * Sanity check. + */ + if (err != 0 && err != SMTP_ERR_NONE && proxy == 0) + msg_panic("%s: proxy error %d without proxy handle", myname, err); + + /* + * Log an appropriate warning message. + */ + switch (err) { + case 0: + case SMTP_ERR_NONE: + break; + case SMTP_ERR_EOF: + msg_warn("lost connection with proxy %s", proxy->service_name); + break; + case SMTP_ERR_TIME: + msg_warn("timeout talking to proxy %s", proxy->service_name); + break; + default: + msg_panic("%s: unknown proxy %s error %d", + myname, proxy->service_name, err); + } + + /* + * Set the appropriate flags and server reply. + */ + state->error_mask |= MAIL_ERROR_SOFTWARE; + /* Update state->err in case we are past the client's DATA command. */ + state->err |= CLEANUP_STAT_PROXY; + smtpd_proxy_fake_server_reply(state, CLEANUP_STAT_PROXY); + return (-1); +} + +/* smtpd_proxy_replay_send - replay saved SMTP session from speed-match log */ + +static int smtpd_proxy_replay_send(SMTPD_STATE *state) +{ + const char *myname = "smtpd_proxy_replay_send"; + static VSTRING *replay_buf = 0; + SMTPD_PROXY *proxy = state->proxy; + int rec_type; + int expect = SMTPD_PROX_WANT_BAD; + + /* + * Sanity check. + */ + if (smtpd_proxy_replay_stream == 0) + msg_panic("%s: no before-queue filter speed-adjust log", myname); + + /* + * Errors first. + */ + if (vstream_ferror(smtpd_proxy_replay_stream) + || vstream_feof(smtpd_proxy_replay_stream) + || rec_put(smtpd_proxy_replay_stream, REC_TYPE_END, "", 0) != REC_TYPE_END + || vstream_fflush(smtpd_proxy_replay_stream)) + /* NOT: fsync(vstream_fileno(smtpd_proxy_replay_stream)) */ + return (smtpd_proxy_replay_rdwr_error(state)); + + /* + * Delayed connection to the before-queue filter. + */ + if (smtpd_proxy_connect(state) < 0) + return (-1); + + /* + * Replay the speed-match log. We do sanity check record content, but we + * don't implement a protocol state engine here, since we are reading + * from a file that we just wrote ourselves. + * + * This is different than the MailChannels patented solution that + * multiplexes a large number of slowed-down inbound connections over a + * small number of fast connections to a local MTA. + * + * - MailChannels receives mail directly from the Internet. It uses one + * connection to the local MTA to reject invalid recipients before + * receiving the entire email message at reduced bit rates, and then uses + * a different connection to quickly deliver the message to the local + * MTA. + * + * - Postfix receives mail directly from the Internet. The Postfix SMTP + * server rejects invalid recipients before receiving the entire message + * over the Internet, and then delivers the message quickly to a local + * SMTP-based content filter. + */ + if (replay_buf == 0) + replay_buf = vstring_alloc(100); + if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) + return (smtpd_proxy_replay_rdwr_error(state)); + + for (;;) { + switch (rec_type = rec_get(smtpd_proxy_replay_stream, replay_buf, + REC_FLAG_NONE)) { + + /* + * Message content. + */ + case REC_TYPE_NORM: + case REC_TYPE_CONT: + if (smtpd_proxy_rec_put(proxy->service_stream, rec_type, + STR(replay_buf), LEN(replay_buf)) < 0) + return (-1); + break; + + /* + * Expected server reply type. + */ + case REC_TYPE_RCPT: + if (!alldig(STR(replay_buf)) + || (expect = atoi(STR(replay_buf))) == SMTPD_PROX_WANT_BAD) + msg_panic("%s: malformed server reply type: %s", + myname, STR(replay_buf)); + break; + + /* + * Client command, or void. Bail out on the first negative proxy + * response. This is OK, because the filter must use the same + * reply code for all recipients of a multi-recipient message. + */ + case REC_TYPE_FROM: + if (expect == SMTPD_PROX_WANT_BAD) + msg_panic("%s: missing server reply type", myname); + if (smtpd_proxy_cmd(state, expect, "%s", STR(replay_buf)) < 0) + return (-1); + expect = SMTPD_PROX_WANT_BAD; + break; + + /* + * Explicit end marker, instead of implicit EOF. + */ + case REC_TYPE_END: + return (0); + + /* + * Errors. + */ + case REC_TYPE_ERROR: + return (smtpd_proxy_replay_rdwr_error(state)); + default: + msg_panic("%s: unexpected record type; %d", myname, rec_type); + } + } +} + +/* smtpd_proxy_save_cmd - save SMTP command + expected response to replay log */ + +static int PRINTFLIKE(3, 4) smtpd_proxy_save_cmd(SMTPD_STATE *state, int expect, const char *fmt,...) +{ + va_list ap; + + /* + * Errors first. + */ + if (vstream_ferror(smtpd_proxy_replay_stream) + || vstream_feof(smtpd_proxy_replay_stream)) + return (smtpd_proxy_replay_rdwr_error(state)); + + /* + * Save the expected reply first, so that the replayer can safely + * overwrite the input buffer with the command. + */ + rec_fprintf(smtpd_proxy_replay_stream, REC_TYPE_RCPT, "%d", expect); + + /* + * The command can be omitted at the start of an SMTP session. This is + * not documented as part of the official interface because it is used + * only internally to this module. + */ + + /* + * Save the command to the replay log, and send it to the before-queue + * filter after we have received the entire message. + */ + va_start(ap, fmt); + rec_vfprintf(smtpd_proxy_replay_stream, REC_TYPE_FROM, fmt, ap); + va_end(ap); + + /* + * If we just saved the "." command, replay the log. + */ + return (strcmp(fmt, ".") ? 0 : smtpd_proxy_replay_send(state)); +} + +/* smtpd_proxy_cmd - send command to proxy, receive reply */ + +static int smtpd_proxy_cmd(SMTPD_STATE *state, int expect, const char *fmt,...) +{ + SMTPD_PROXY *proxy = state->proxy; + va_list ap; + char *cp; + int last_char; + int err = 0; + static VSTRING *buffer = 0; + + /* + * Errors first. Be prepared for delayed errors from the DATA phase. + */ + if (vstream_ferror(proxy->service_stream) + || vstream_feof(proxy->service_stream) + || (err = vstream_setjmp(proxy->service_stream)) != 0) { + return (smtpd_proxy_rdwr_error(state, err)); + } + + /* + * Format the command. + */ + va_start(ap, fmt); + vstring_vsprintf(proxy->request, fmt, ap); + va_end(ap); + + /* + * The command can be omitted at the start of an SMTP session. This is + * not documented as part of the official interface because it is used + * only internally to this module. + */ + if (LEN(proxy->request) > 0) { + + /* + * Optionally log the command first, so that we can see in the log + * what the program is trying to do. + */ + if (msg_verbose) + msg_info("> %s: %s", proxy->service_name, STR(proxy->request)); + + /* + * Send the command to the proxy server. Since we're going to read a + * reply immediately, there is no need to flush buffers. + */ + smtp_fputs(STR(proxy->request), LEN(proxy->request), + proxy->service_stream); + } + + /* + * Early return if we don't want to wait for a server reply (such as + * after sending QUIT). + */ + if (expect == SMTPD_PROX_WANT_NONE) + return (0); + + /* + * Censor out non-printable characters in server responses and save + * complete multi-line responses if possible. + * + * We can't parse or store input that exceeds var_line_limit, so we just + * skip over it to simplify the remainder of the code below. + */ + VSTRING_RESET(proxy->reply); + if (buffer == 0) + buffer = vstring_alloc(10); + for (;;) { + last_char = smtp_get(buffer, proxy->service_stream, var_line_limit, + SMTP_GET_FLAG_SKIP); + printable(STR(buffer), '?'); + if (last_char != '\n') + msg_warn("%s: response longer than %d: %.30s...", + proxy->service_name, var_line_limit, + STR(buffer)); + if (msg_verbose) + msg_info("< %s: %.100s", proxy->service_name, STR(buffer)); + + /* + * Defend against a denial of service attack by limiting the amount + * of multi-line text that we are willing to store. + */ + if (LEN(proxy->reply) < var_line_limit) { + if (VSTRING_LEN(proxy->reply)) + vstring_strcat(proxy->reply, "\r\n"); + vstring_strcat(proxy->reply, STR(buffer)); + } + + /* + * Parse the response into code and text. Ignore unrecognized + * garbage. This means that any character except space (or end of + * line) will have the same effect as the '-' line continuation + * character. + */ + for (cp = STR(buffer); *cp && ISDIGIT(*cp); cp++) + /* void */ ; + if (cp - STR(buffer) == 3) { + if (*cp == '-') + continue; + if (*cp == ' ' || *cp == 0) + break; + } + msg_warn("received garbage from proxy %s: %.100s", + proxy->service_name, STR(buffer)); + } + + /* + * Log a warning in case the proxy does not send the expected response. + * Silently accept any response when the client expressed no expectation. + * + * Starting with Postfix 2.6 we don't pass through unexpected 2xx or 3xx + * proxy replies. They are a source of support problems, so we replace + * them by generic server error replies. + */ + if (expect != SMTPD_PROX_WANT_ANY && expect != *STR(proxy->reply)) { + msg_warn("proxy %s rejected \"%s\": \"%s\"", + proxy->service_name, LEN(proxy->request) == 0 ? + "connection request" : STR(proxy->request), + STR(proxy->reply)); + if (*STR(proxy->reply) == SMTPD_PROX_WANT_OK + || *STR(proxy->reply) == SMTPD_PROX_WANT_MORE) { + smtpd_proxy_rdwr_error(state, 0); + } + return (-1); + } else { + return (0); + } +} + +/* smtpd_proxy_save_rec_put - save message content to replay log */ + +static int smtpd_proxy_save_rec_put(VSTREAM *stream, int rec_type, + const char *data, ssize_t len) +{ + const char *myname = "smtpd_proxy_save_rec_put"; + int ret; + +#define VSTREAM_TO_SMTPD_STATE(s) ((SMTPD_STATE *) vstream_context(s)) + + /* + * Sanity check. + */ + if (stream == 0) + msg_panic("%s: attempt to use closed stream", myname); + + /* + * Send one content record. Errors and results must be as with rec_put(). + */ + if (rec_type == REC_TYPE_NORM || rec_type == REC_TYPE_CONT) + ret = rec_put(stream, rec_type, data, len); + else + msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname); + + /* + * Errors last. + */ + if (ret != rec_type) { + (void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream)); + return (REC_TYPE_ERROR); + } + return (rec_type); +} + +/* smtpd_proxy_rec_put - send message content, rec_put() clone */ + +static int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type, + const char *data, ssize_t len) +{ + const char *myname = "smtpd_proxy_rec_put"; + int err = 0; + + /* + * Errors first. + */ + if (vstream_ferror(stream) || vstream_feof(stream) + || (err = vstream_setjmp(stream)) != 0) { + (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err); + return (REC_TYPE_ERROR); + } + + /* + * Send one content record. Errors and results must be as with rec_put(). + */ + if (rec_type == REC_TYPE_NORM) + smtp_fputs(data, len, stream); + else if (rec_type == REC_TYPE_CONT) + smtp_fwrite(data, len, stream); + else + msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname); + return (rec_type); +} + +/* smtpd_proxy_save_rec_fprintf - save message content to replay log */ + +static int smtpd_proxy_save_rec_fprintf(VSTREAM *stream, int rec_type, + const char *fmt,...) +{ + const char *myname = "smtpd_proxy_save_rec_fprintf"; + va_list ap; + int ret; + + /* + * Sanity check. + */ + if (stream == 0) + msg_panic("%s: attempt to use closed stream", myname); + + /* + * Save one content record. Errors and results must be as with + * rec_fprintf(). + */ + va_start(ap, fmt); + if (rec_type == REC_TYPE_NORM) + ret = rec_vfprintf(stream, rec_type, fmt, ap); + else + msg_panic("%s: need REC_TYPE_NORM", myname); + va_end(ap); + + /* + * Errors last. + */ + if (ret != rec_type) { + (void) smtpd_proxy_replay_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream)); + return (REC_TYPE_ERROR); + } + return (rec_type); +} + +/* smtpd_proxy_rec_fprintf - send message content, rec_fprintf() clone */ + +static int smtpd_proxy_rec_fprintf(VSTREAM *stream, int rec_type, + const char *fmt,...) +{ + const char *myname = "smtpd_proxy_rec_fprintf"; + va_list ap; + int err = 0; + + /* + * Errors first. + */ + if (vstream_ferror(stream) || vstream_feof(stream) + || (err = vstream_setjmp(stream)) != 0) { + (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err); + return (REC_TYPE_ERROR); + } + + /* + * Send one content record. Errors and results must be as with + * rec_fprintf(). + */ + va_start(ap, fmt); + if (rec_type == REC_TYPE_NORM) + smtp_vprintf(stream, fmt, ap); + else + msg_panic("%s: need REC_TYPE_NORM", myname); + va_end(ap); + return (rec_type); +} + +#ifndef NO_TRUNCATE + +/* smtpd_proxy_replay_setup - prepare the replay logfile */ + +static int smtpd_proxy_replay_setup(SMTPD_STATE *state) +{ + const char *myname = "smtpd_proxy_replay_setup"; + off_t file_offs; + + /* + * Where possible reuse an existing replay logfile, because creating a + * file is expensive compared to reading or writing. For security reasons + * we must truncate the file before reuse. For performance reasons we + * should truncate the file immediately after the end of a mail + * transaction. We enforce the security guarantee upon reuse, by + * requiring that no I/O happened since the file was truncated. This is + * less expensive than truncating the file redundantly. + */ + if (smtpd_proxy_replay_stream != 0) { + /* vstream_ftell() won't invoke the kernel, so all errors are mine. */ + if ((file_offs = vstream_ftell(smtpd_proxy_replay_stream)) != 0) + msg_panic("%s: bad before-queue filter speed-adjust log offset %lu", + myname, (unsigned long) file_offs); + vstream_clearerr(smtpd_proxy_replay_stream); + if (msg_verbose) + msg_info("%s: reuse speed-adjust stream fd=%d", myname, + vstream_fileno(smtpd_proxy_replay_stream)); + /* Here, smtpd_proxy_replay_stream != 0 */ + } + + /* + * Create a new replay logfile. + */ + if (smtpd_proxy_replay_stream == 0) { + smtpd_proxy_replay_stream = mail_queue_enter(MAIL_QUEUE_INCOMING, 0, + (struct timeval *) 0); + if (smtpd_proxy_replay_stream == 0) + return (smtpd_proxy_replay_rdwr_error(state)); + if (unlink(VSTREAM_PATH(smtpd_proxy_replay_stream)) < 0) + msg_warn("remove before-queue filter speed-adjust log %s: %m", + VSTREAM_PATH(smtpd_proxy_replay_stream)); + if (msg_verbose) + msg_info("%s: new speed-adjust stream fd=%d", myname, + vstream_fileno(smtpd_proxy_replay_stream)); + } + + /* + * Needed by our DATA-phase record emulation routines. + */ + vstream_control(smtpd_proxy_replay_stream, + CA_VSTREAM_CTL_CONTEXT((void *) state), + CA_VSTREAM_CTL_END); + return (0); +} + +#endif + +/* smtpd_proxy_create - set up smtpd proxy handle */ + +int smtpd_proxy_create(SMTPD_STATE *state, int flags, const char *service, + int timeout, const char *ehlo_name, + const char *mail_from) +{ + SMTPD_PROXY *proxy; + + /* + * When an operation has many arguments it is safer to use named + * parameters, and have the compiler enforce the argument count. + */ +#define SMTPD_PROXY_ALLOC(p, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) \ + ((p) = (SMTPD_PROXY *) mymalloc(sizeof(*(p))), (p)->a1, (p)->a2, \ + (p)->a3, (p)->a4, (p)->a5, (p)->a6, (p)->a7, (p)->a8, (p)->a9, \ + (p)->a10, (p)->a11, (p)->a12, (p)) + + /* + * Sanity check. + */ + if (state->proxy != 0) + msg_panic("smtpd_proxy_create: handle still exists"); + + /* + * Connect to the before-queue filter immediately. + */ + if ((flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) == 0) { + state->proxy = + SMTPD_PROXY_ALLOC(proxy, stream = 0, request = vstring_alloc(10), + reply = vstring_alloc(10), + cmd = smtpd_proxy_cmd, + rec_fprintf = smtpd_proxy_rec_fprintf, + rec_put = smtpd_proxy_rec_put, + flags = flags, service_stream = 0, + service_name = service, timeout = timeout, + ehlo_name = ehlo_name, mail_from = mail_from); + if (smtpd_proxy_connect(state) < 0) { + /* NOT: smtpd_proxy_free(state); we still need proxy->reply. */ + return (-1); + } + proxy->stream = proxy->service_stream; + return (0); + } + + /* + * Connect to the before-queue filter after we receive the entire + * message. Open the replay logfile early to simplify code. The file is + * reused for multiple mail transactions, so there is no need to minimize + * its life time. + */ + else { +#ifdef NO_TRUNCATE + msg_panic("smtpd_proxy_create: speed-adjust support is not available"); +#else + if (smtpd_proxy_replay_setup(state) < 0) + return (-1); + state->proxy = + SMTPD_PROXY_ALLOC(proxy, stream = smtpd_proxy_replay_stream, + request = vstring_alloc(10), + reply = vstring_alloc(10), + cmd = smtpd_proxy_save_cmd, + rec_fprintf = smtpd_proxy_save_rec_fprintf, + rec_put = smtpd_proxy_save_rec_put, + flags = flags, service_stream = 0, + service_name = service, timeout = timeout, + ehlo_name = ehlo_name, mail_from = mail_from); + return (0); +#endif + } +} + +/* smtpd_proxy_close - close proxy connection without destroying handle */ + +void smtpd_proxy_close(SMTPD_STATE *state) +{ + SMTPD_PROXY *proxy = state->proxy; + + /* + * Specify SMTPD_PROX_WANT_NONE so that the server reply will not clobber + * the END-OF-DATA reply. + */ + if (proxy->service_stream != 0) { + if (vstream_feof(proxy->service_stream) == 0 + && vstream_ferror(proxy->service_stream) == 0) + (void) smtpd_proxy_cmd(state, SMTPD_PROX_WANT_NONE, + SMTPD_CMD_QUIT); + (void) vstream_fclose(proxy->service_stream); + if (proxy->stream == proxy->service_stream) + proxy->stream = 0; + proxy->service_stream = 0; + } +} + +/* smtpd_proxy_free - destroy smtpd proxy handle */ + +void smtpd_proxy_free(SMTPD_STATE *state) +{ + SMTPD_PROXY *proxy = state->proxy; + + /* + * Clean up. + */ + if (proxy->service_stream != 0) + (void) smtpd_proxy_close(state); + if (proxy->request != 0) + vstring_free(proxy->request); + if (proxy->reply != 0) + vstring_free(proxy->reply); + myfree((void *) proxy); + state->proxy = 0; + + /* + * Reuse the replay logfile if possible. For security reasons we must + * truncate the replay logfile before reuse. For performance reasons we + * should truncate the replay logfile immediately after the end of a mail + * transaction. We truncate the file here, and enforce the security + * guarantee by requiring that no I/O happens before the file is reused. + */ + if (smtpd_proxy_replay_stream == 0) + return; + if (vstream_ferror(smtpd_proxy_replay_stream)) { + /* Errors are already reported. */ + (void) vstream_fclose(smtpd_proxy_replay_stream); + smtpd_proxy_replay_stream = 0; + return; + } + /* Flush output from aborted transaction before truncating the file!! */ + if (vstream_fseek(smtpd_proxy_replay_stream, (off_t) 0, SEEK_SET) < 0) { + msg_warn("seek before-queue filter speed-adjust log: %m"); + (void) vstream_fclose(smtpd_proxy_replay_stream); + smtpd_proxy_replay_stream = 0; + return; + } + if (ftruncate(vstream_fileno(smtpd_proxy_replay_stream), (off_t) 0) < 0) { + msg_warn("truncate before-queue filter speed-adjust log: %m"); + (void) vstream_fclose(smtpd_proxy_replay_stream); + smtpd_proxy_replay_stream = 0; + return; + } +} + +/* smtpd_proxy_parse_opts - parse main.cf options */ + +int smtpd_proxy_parse_opts(const char *param_name, const char *param_val) +{ + static const NAME_MASK proxy_opts_table[] = { + SMTPD_PROXY_NAME_SPEED_ADJUST, SMTPD_PROXY_FLAG_SPEED_ADJUST, + 0, 0, + }; + int flags; + + /* + * The optional before-filter speed-adjust buffers use disk space. + * However, we don't know if they compete for storage space with the + * after-filter queue, so we can't simply bump up the free space + * requirement to 2.5 * message_size_limit. + */ + flags = name_mask(param_name, proxy_opts_table, param_val); + if (flags & SMTPD_PROXY_FLAG_SPEED_ADJUST) { +#ifdef NO_TRUNCATE + msg_warn("smtpd_proxy %s support is not available", + SMTPD_PROXY_NAME_SPEED_ADJUST); + flags &= ~SMTPD_PROXY_FLAG_SPEED_ADJUST; +#endif + } + return (flags); +} diff --git a/src/smtpd/smtpd_proxy.h b/src/smtpd/smtpd_proxy.h new file mode 100644 index 0000000..3d35d07 --- /dev/null +++ b/src/smtpd/smtpd_proxy.h @@ -0,0 +1,66 @@ +/*++ +/* NAME +/* smtpd_proxy 3h +/* SUMMARY +/* SMTP server pass-through proxy client +/* SYNOPSIS +/* #include +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Application-specific. + */ +typedef int PRINTFPTRLIKE(3, 4) (*SMTPD_PROXY_CMD_FN) (SMTPD_STATE *, int, const char *,...); +typedef int PRINTFPTRLIKE(3, 4) (*SMTPD_PROXY_REC_FPRINTF_FN) (VSTREAM *, int, const char *,...); +typedef int (*SMTPD_PROXY_REC_PUT_FN) (VSTREAM *, int, const char *, ssize_t); + +typedef struct SMTPD_PROXY { + /* Public. */ + VSTREAM *stream; + VSTRING *request; /* proxy request buffer */ + VSTRING *reply; /* proxy reply buffer */ + SMTPD_PROXY_CMD_FN cmd; + SMTPD_PROXY_REC_FPRINTF_FN rec_fprintf; + SMTPD_PROXY_REC_PUT_FN rec_put; + /* Private. */ + int flags; + VSTREAM *service_stream; + const char *service_name; + int timeout; + const char *ehlo_name; + const char *mail_from; +} SMTPD_PROXY; + +#define SMTPD_PROXY_FLAG_SPEED_ADJUST (1<<0) + +#define SMTPD_PROXY_NAME_SPEED_ADJUST "speed_adjust" + +#define SMTPD_PROX_WANT_BAD 0xff /* Do not use */ +#define SMTPD_PROX_WANT_NONE '\0' /* Do not receive reply */ +#define SMTPD_PROX_WANT_ANY '0' /* Expect any reply */ +#define SMTPD_PROX_WANT_OK '2' /* Expect 2XX reply */ +#define SMTPD_PROX_WANT_MORE '3' /* Expect 3XX reply */ + +extern int smtpd_proxy_create(SMTPD_STATE *, int, const char *, int, const char *, const char *); +extern void smtpd_proxy_close(SMTPD_STATE *); +extern void smtpd_proxy_free(SMTPD_STATE *); +extern int smtpd_proxy_parse_opts(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtpd/smtpd_resolve.c b/src/smtpd/smtpd_resolve.c new file mode 100644 index 0000000..1dd6914 --- /dev/null +++ b/src/smtpd/smtpd_resolve.c @@ -0,0 +1,190 @@ +/*++ +/* NAME +/* smtpd_resolve 3 +/* SUMMARY +/* caching resolve client +/* SYNOPSIS +/* #include +/* +/* void smtpd_resolve_init(cache_size) +/* int cache_size; +/* +/* const RESOLVE_REPLY *smtpd_resolve_addr(sender, addr) +/* const char *sender; +/* const char *addr; +/* DESCRIPTION +/* This module maintains a resolve client cache that persists +/* across SMTP sessions (not process life times). Addresses +/* are always resolved in local rewriting context. +/* +/* smtpd_resolve_init() initializes the cache and must be +/* called before the cache can be used. This function may also +/* be called to flush the cache after an address class update. +/* +/* smtpd_resolve_addr() resolves one address or returns +/* a known result from cache. +/* +/* Arguments: +/* .IP cache_size +/* The requested cache size. +/* .IP sender +/* The message sender, or null pointer. +/* .IP addr +/* The address to resolve. +/* DIAGNOSTICS +/* All errors are fatal. +/* BUGS +/* The recipient address is always case folded to lowercase. +/* Changing this requires great care, since the address is used +/* for policy lookups. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + +#include + +static CTABLE *smtpd_resolve_cache; + +#define STR(x) vstring_str(x) +#define SENDER_ADDR_JOIN_CHAR '\n' + +/* resolve_pagein - page in an address resolver result */ + +static void *resolve_pagein(const char *sender_plus_addr, void *unused_context) +{ + const char myname[] = "resolve_pagein"; + static VSTRING *query; + static VSTRING *junk; + static VSTRING *sender_buf; + RESOLVE_REPLY *reply; + const char *sender; + const char *addr; + + /* + * Initialize on the fly. + */ + if (query == 0) { + query = vstring_alloc(10); + junk = vstring_alloc(10); + sender_buf = vstring_alloc(10); + } + + /* + * Initialize. + */ + reply = (RESOLVE_REPLY *) mymalloc(sizeof(*reply)); + resolve_clnt_init(reply); + + /* + * Split the sender and address. + */ + vstring_strcpy(junk, sender_plus_addr); + sender = STR(junk); + if ((addr = split_at(STR(junk), SENDER_ADDR_JOIN_CHAR)) == 0) + msg_panic("%s: bad search key: \"%s\"", myname, sender_plus_addr); + + /* + * Resolve the address. + */ + rewrite_clnt_internal(MAIL_ATTR_RWR_LOCAL, sender, sender_buf); + rewrite_clnt_internal(MAIL_ATTR_RWR_LOCAL, addr, query); + resolve_clnt_query_from(STR(sender_buf), STR(query), reply); + vstring_strcpy(junk, STR(reply->recipient)); + casefold(reply->recipient, STR(junk)); /* XXX */ + + /* + * Save the result. + */ + return ((void *) reply); +} + +/* resolve_pageout - page out an address resolver result */ + +static void resolve_pageout(void *data, void *unused_context) +{ + RESOLVE_REPLY *reply = (RESOLVE_REPLY *) data; + + resolve_clnt_free(reply); + myfree((void *) reply); +} + +/* smtpd_resolve_init - set up global cache */ + +void smtpd_resolve_init(int cache_size) +{ + + /* + * Flush a pre-existing cache. The smtpd_check test program requires this + * after an address class change. + */ + if (smtpd_resolve_cache) + ctable_free(smtpd_resolve_cache); + + /* + * Initialize the resolved address cache. Note: the cache persists across + * SMTP sessions so we cannot make it dependent on session state. + */ + smtpd_resolve_cache = ctable_create(cache_size, resolve_pagein, + resolve_pageout, (void *) 0); +} + +/* smtpd_resolve_addr - resolve cached address */ + +const RESOLVE_REPLY *smtpd_resolve_addr(const char *sender, const char *addr) +{ + static VSTRING *sender_plus_addr_buf; + + /* + * Initialize on the fly. + */ + if (sender_plus_addr_buf == 0) + sender_plus_addr_buf = vstring_alloc(10); + + /* + * Sanity check. + */ + if (smtpd_resolve_cache == 0) + msg_panic("smtpd_resolve_addr: missing initialization"); + + /* + * Reply from the read-through cache. + */ + vstring_sprintf(sender_plus_addr_buf, "%s%c%s", + sender ? sender : RESOLVE_NULL_FROM, + SENDER_ADDR_JOIN_CHAR, addr); + return (const RESOLVE_REPLY *) + ctable_locate(smtpd_resolve_cache, STR(sender_plus_addr_buf)); +} diff --git a/src/smtpd/smtpd_resolve.h b/src/smtpd/smtpd_resolve.h new file mode 100644 index 0000000..cd0257a --- /dev/null +++ b/src/smtpd/smtpd_resolve.h @@ -0,0 +1,43 @@ +/*++ +/* NAME +/* smtpd_resolve 3h +/* SUMMARY +/* caching resolve client +/* SYNOPSIS +/* include +/* DESCRIPTION +/* .nf + + /* + * Global library. + */ +#include + + /* + * External interface. + */ +extern void smtpd_resolve_init(int); +extern const RESOLVE_REPLY *smtpd_resolve_addr(const char*, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ diff --git a/src/smtpd/smtpd_sasl_glue.c b/src/smtpd/smtpd_sasl_glue.c new file mode 100644 index 0000000..2dc6aad --- /dev/null +++ b/src/smtpd/smtpd_sasl_glue.c @@ -0,0 +1,396 @@ +/*++ +/* NAME +/* smtpd_sasl_glue 3 +/* SUMMARY +/* Postfix SMTP server, SASL support interface +/* SYNOPSIS +/* #include "smtpd_sasl_glue.h" +/* +/* void smtpd_sasl_state_init(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_sasl_initialize() +/* +/* void smtpd_sasl_activate(state, sasl_opts_name, sasl_opts_val) +/* SMTPD_STATE *state; +/* const char *sasl_opts_name; +/* const char *sasl_opts_val; +/* +/* char *smtpd_sasl_authenticate(state, sasl_method, init_response) +/* SMTPD_STATE *state; +/* const char *sasl_method; +/* const char *init_response; +/* +/* void smtpd_sasl_logout(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_sasl_login(state, sasl_username, sasl_method) +/* SMTPD_STATE *state; +/* const char *sasl_username; +/* const char *sasl_method; +/* +/* void smtpd_sasl_deactivate(state) +/* SMTPD_STATE *state; +/* +/* int smtpd_sasl_is_active(state) +/* SMTPD_STATE *state; +/* +/* int smtpd_sasl_set_inactive(state) +/* SMTPD_STATE *state; +/* DESCRIPTION +/* This module encapsulates most of the detail specific to SASL +/* authentication. +/* +/* smtpd_sasl_state_init() performs minimal server state +/* initialization to support external authentication (e.g., +/* XCLIENT) without having to enable SASL in main.cf. This +/* should always be called at process startup. +/* +/* smtpd_sasl_initialize() initializes the SASL library. This +/* routine should be called once at process start-up. It may +/* need access to the file system for run-time loading of +/* plug-in modules. There is no corresponding cleanup routine. +/* +/* smtpd_sasl_activate() performs per-connection initialization. +/* This routine should be called once at the start of every +/* connection. The sasl_opts_name and sasl_opts_val parameters +/* are the postfix configuration parameters setting the security +/* policy of the SASL authentication. +/* +/* smtpd_sasl_authenticate() implements the authentication +/* dialog. The result is zero in case of success, -1 in case +/* of failure. smtpd_sasl_authenticate() updates the following +/* state structure members: +/* .IP sasl_method +/* The authentication method that was successfully applied. +/* This member is a null pointer in the absence of successful +/* authentication. +/* .IP sasl_username +/* The username that was successfully authenticated. +/* This member is a null pointer in the absence of successful +/* authentication. +/* .PP +/* smtpd_sasl_login() records the result of successful external +/* authentication, i.e. without invoking smtpd_sasl_authenticate(), +/* but produces an otherwise equivalent result. +/* +/* smtpd_sasl_logout() cleans up after smtpd_sasl_authenticate(). +/* This routine exists for the sake of symmetry. +/* +/* smtpd_sasl_deactivate() performs per-connection cleanup. +/* This routine should be called at the end of every connection. +/* +/* smtpd_sasl_is_active() is a predicate that returns true +/* if the SMTP server session state is between smtpd_sasl_activate() +/* and smtpd_sasl_deactivate(). +/* +/* smtpd_sasl_set_inactive() initializes the SMTP session +/* state before the first smtpd_sasl_activate() call. +/* +/* Arguments: +/* .IP state +/* SMTP session context. +/* .IP sasl_opts_name +/* Security options parameter name. +/* .IP sasl_opts_val +/* Security options parameter value. +/* .IP sasl_method +/* A SASL mechanism name +/* .IP init_reply +/* An optional initial client response. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* XSASL library. */ + +#include + +/* Application-specific. */ + +#include "smtpd.h" +#include "smtpd_sasl_glue.h" +#include "smtpd_chat.h" + +#ifdef USE_SASL_AUTH + + /* + * SASL mechanism filter. + */ +static STRING_LIST *smtpd_sasl_mech_filter; + +/* + * Silly little macros. + */ +#define STR(s) vstring_str(s) + + /* + * SASL server implementation handle. + */ +static XSASL_SERVER_IMPL *smtpd_sasl_impl; + +/* smtpd_sasl_initialize - per-process initialization */ + +void smtpd_sasl_initialize(void) +{ + + /* + * Sanity check. + */ + if (smtpd_sasl_impl) + msg_panic("smtpd_sasl_initialize: repeated call"); + + /* + * Initialize the SASL library. + */ + if ((smtpd_sasl_impl = xsasl_server_init(var_smtpd_sasl_type, + var_smtpd_sasl_path)) == 0) + msg_fatal("SASL per-process initialization failed"); + + /* + * Initialize the SASL mechanism filter. + */ + smtpd_sasl_mech_filter = string_list_init(VAR_SMTPD_SASL_MECH_FILTER, + MATCH_FLAG_NONE, + var_smtpd_sasl_mech_filter); +} + +/* smtpd_sasl_activate - per-connection initialization */ + +void smtpd_sasl_activate(SMTPD_STATE *state, const char *sasl_opts_name, + const char *sasl_opts_val) +{ + const char *mechanism_list; + const char *filtered_mechanism_list; + XSASL_SERVER_CREATE_ARGS create_args; + int tls_flag; + + /* + * Sanity check. + */ + if (smtpd_sasl_is_active(state)) + msg_panic("smtpd_sasl_activate: already active"); + + /* + * Initialize SASL-specific state variables. Use long-lived storage for + * base 64 conversion results, rather than local variables, to avoid + * memory leaks when a read or write routine returns abnormally after + * timeout or I/O error. + */ + state->sasl_reply = vstring_alloc(20); + state->sasl_mechanism_list = 0; + + /* + * Set up a new server context for this connection. + */ +#ifdef USE_TLS + tls_flag = state->tls_context != 0; +#else + tls_flag = 0; +#endif +#define ADDR_OR_EMPTY(addr, unknown) (strcmp(addr, unknown) ? addr : "") +#define REALM_OR_NULL(realm) (*(realm) ? (realm) : (char *) 0) + + if ((state->sasl_server = + XSASL_SERVER_CREATE(smtpd_sasl_impl, &create_args, + stream = state->client, + addr_family = state->addr_family, + server_addr = ADDR_OR_EMPTY(state->dest_addr, + SERVER_ADDR_UNKNOWN), + server_port = ADDR_OR_EMPTY(state->dest_port, + SERVER_PORT_UNKNOWN), + client_addr = ADDR_OR_EMPTY(state->addr, + CLIENT_ADDR_UNKNOWN), + client_port = ADDR_OR_EMPTY(state->port, + CLIENT_PORT_UNKNOWN), + service = var_smtpd_sasl_service, + user_realm = REALM_OR_NULL(var_smtpd_sasl_realm), + security_options = sasl_opts_val, + tls_flag = tls_flag)) == 0) + msg_fatal("SASL per-connection initialization failed"); + + /* + * Get the list of authentication mechanisms. + */ + if ((mechanism_list = + xsasl_server_get_mechanism_list(state->sasl_server)) == 0) + msg_fatal("no SASL authentication mechanisms"); + filtered_mechanism_list = + sasl_mech_filter(smtpd_sasl_mech_filter, mechanism_list); + if (*filtered_mechanism_list == 0) + msg_fatal("%s discards all mechanisms in '%s'", + VAR_SMTPD_SASL_MECH_FILTER, mechanism_list); + state->sasl_mechanism_list = mystrdup(filtered_mechanism_list); +} + +/* smtpd_sasl_state_init - initialize state to allow extern authentication. */ + +void smtpd_sasl_state_init(SMTPD_STATE *state) +{ + /* Initialization to support external authentication (e.g., XCLIENT). */ + state->sasl_username = 0; + state->sasl_method = 0; + state->sasl_sender = 0; +} + +/* smtpd_sasl_deactivate - per-connection cleanup */ + +void smtpd_sasl_deactivate(SMTPD_STATE *state) +{ + if (state->sasl_reply) { + vstring_free(state->sasl_reply); + state->sasl_reply = 0; + } + if (state->sasl_mechanism_list) { + myfree(state->sasl_mechanism_list); + state->sasl_mechanism_list = 0; + } + if (state->sasl_username) { + myfree(state->sasl_username); + state->sasl_username = 0; + } + if (state->sasl_method) { + myfree(state->sasl_method); + state->sasl_method = 0; + } + if (state->sasl_sender) { + myfree(state->sasl_sender); + state->sasl_sender = 0; + } + if (state->sasl_server) { + xsasl_server_free(state->sasl_server); + state->sasl_server = 0; + } +} + +/* smtpd_sasl_authenticate - per-session authentication */ + +int smtpd_sasl_authenticate(SMTPD_STATE *state, + const char *sasl_method, + const char *init_response) +{ + int status; + const char *sasl_username; + + /* + * SASL authentication protocol start-up. Process any initial client + * response that was sent along in the AUTH command. + */ + for (status = xsasl_server_first(state->sasl_server, sasl_method, + init_response, state->sasl_reply); + status == XSASL_AUTH_MORE; + status = xsasl_server_next(state->sasl_server, STR(state->buffer), + state->sasl_reply)) { + + /* + * Send a server challenge. + */ + smtpd_chat_reply(state, "334 %s", STR(state->sasl_reply)); + + /* + * Receive the client response. "*" means that the client gives up. + */ + if (!smtpd_chat_query_limit(state, var_smtpd_sasl_resp_limit)) { + smtpd_chat_reply(state, "500 5.5.6 SASL response limit exceeded"); + return (-1); + } + if (strcmp(STR(state->buffer), "*") == 0) { + msg_warn("%s: SASL %s authentication aborted", + state->namaddr, sasl_method); + smtpd_chat_reply(state, "501 5.7.0 Authentication aborted"); + return (-1); + } + } + if (status != XSASL_AUTH_DONE) { + sasl_username = xsasl_server_get_username(state->sasl_server); + msg_warn("%s: SASL %.100s authentication failed: %s, sasl_username=%.100s", + state->namaddr, sasl_method, *STR(state->sasl_reply) ? + STR(state->sasl_reply) : "(reason unavailable)", + sasl_username ? sasl_username : "(unavailable)"); + /* RFC 4954 Section 6. */ + if (status == XSASL_AUTH_TEMP) + smtpd_chat_reply(state, "454 4.7.0 Temporary authentication failure: %s", + STR(state->sasl_reply)); + else + smtpd_chat_reply(state, "535 5.7.8 Error: authentication failed: %s", + STR(state->sasl_reply)); + return (-1); + } + /* RFC 4954 Section 6. */ + smtpd_chat_reply(state, "235 2.7.0 Authentication successful"); + if ((sasl_username = xsasl_server_get_username(state->sasl_server)) == 0) + msg_panic("cannot look up the authenticated SASL username"); + state->sasl_username = mystrdup(sasl_username); + printable(state->sasl_username, '?'); + state->sasl_method = mystrdup(sasl_method); + printable(state->sasl_method, '?'); + + return (0); +} + +/* smtpd_sasl_logout - clean up after smtpd_sasl_authenticate */ + +void smtpd_sasl_logout(SMTPD_STATE *state) +{ + if (state->sasl_username) { + myfree(state->sasl_username); + state->sasl_username = 0; + } + if (state->sasl_method) { + myfree(state->sasl_method); + state->sasl_method = 0; + } +} + +/* smtpd_sasl_login - set login information */ + +void smtpd_sasl_login(SMTPD_STATE *state, const char *sasl_username, + const char *sasl_method) +{ + if (state->sasl_username) + myfree(state->sasl_username); + state->sasl_username = mystrdup(sasl_username); + if (state->sasl_method) + myfree(state->sasl_method); + state->sasl_method = mystrdup(sasl_method); +} + +#endif diff --git a/src/smtpd/smtpd_sasl_glue.h b/src/smtpd/smtpd_sasl_glue.h new file mode 100644 index 0000000..d81eec1 --- /dev/null +++ b/src/smtpd/smtpd_sasl_glue.h @@ -0,0 +1,41 @@ +/*++ +/* NAME +/* smtpd_sasl_glue 3h +/* SUMMARY +/* Postfix SMTP server, SASL support interface +/* SYNOPSIS +/* #include "smtpd_sasl_glue.h" +/* DESCRIPTION +/* .nf + + /* + * SASL protocol interface + */ +extern void smtpd_sasl_state_init(SMTPD_STATE *); +extern void smtpd_sasl_initialize(void); +extern void smtpd_sasl_activate(SMTPD_STATE *, const char *, const char *); +extern void smtpd_sasl_deactivate(SMTPD_STATE *); +extern int smtpd_sasl_authenticate(SMTPD_STATE *, const char *, const char *); +extern void smtpd_sasl_login(SMTPD_STATE *, const char *, const char *); +extern void smtpd_sasl_logout(SMTPD_STATE *); +extern int permit_sasl_auth(SMTPD_STATE *, int, int); + +#define smtpd_sasl_is_active(s) ((s)->sasl_server != 0) +#define smtpd_sasl_set_inactive(s) ((void) ((s)->sasl_server = 0)) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtpd/smtpd_sasl_proto.c b/src/smtpd/smtpd_sasl_proto.c new file mode 100644 index 0000000..476752d --- /dev/null +++ b/src/smtpd/smtpd_sasl_proto.c @@ -0,0 +1,274 @@ +/*++ +/* NAME +/* smtpd_sasl_proto 3 +/* SUMMARY +/* Postfix SMTP protocol support for SASL authentication +/* SYNOPSIS +/* #include "smtpd.h" +/* #include "smtpd_sasl_proto.h" +/* +/* int smtpd_sasl_auth_cmd(state, argc, argv) +/* SMTPD_STATE *state; +/* int argc; +/* SMTPD_TOKEN *argv; +/* +/* void smtpd_sasl_auth_extern(state, username, method) +/* SMTPD_STATE *state; +/* const char *username; +/* const char *method; +/* +/* void smtpd_sasl_auth_reset(state) +/* SMTPD_STATE *state; +/* +/* char *smtpd_sasl_mail_opt(state, sender) +/* SMTPD_STATE *state; +/* const char *sender; +/* +/* void smtpd_sasl_mail_reset(state) +/* SMTPD_STATE *state; +/* +/* static int permit_sasl_auth(state, authenticated, unauthenticated) +/* SMTPD_STATE *state; +/* int authenticated; +/* int unauthenticated; +/* DESCRIPTION +/* This module contains random chunks of code that implement +/* the SMTP protocol interface for SASL negotiation. The goal +/* is to reduce clutter of the main SMTP server source code. +/* +/* smtpd_sasl_auth_cmd() implements the AUTH command and updates +/* the following state structure members: +/* .IP sasl_method +/* The authentication method that was successfully applied. +/* This member is a null pointer in the absence of successful +/* authentication. +/* .IP sasl_username +/* The username that was successfully authenticated. +/* This member is a null pointer in the absence of successful +/* authentication. +/* .PP +/* smtpd_sasl_auth_reset() cleans up after the AUTH command. +/* This is required before smtpd_sasl_auth_cmd() can be used again. +/* This may be called even if SASL authentication is turned off +/* in main.cf. +/* +/* smtpd_sasl_auth_extern() records authentication information +/* that is received from an external source. +/* This may be called even if SASL authentication is turned off +/* in main.cf. +/* +/* smtpd_sasl_mail_opt() implements the SASL-specific AUTH=sender +/* option to the MAIL FROM command. The result is an error response +/* in case of problems. +/* +/* smtpd_sasl_mail_reset() performs cleanup for the SASL-specific +/* AUTH=sender option to the MAIL FROM command. +/* +/* permit_sasl_auth() permits access from an authenticated client. +/* This test fails for clients that use anonymous authentication. +/* +/* Arguments: +/* .IP state +/* SMTP session context. +/* .IP argc +/* Number of command line tokens. +/* .IP argv +/* The command line parsed into tokens. +/* .IP sender +/* Sender address from the AUTH=sender option in the MAIL FROM +/* command. +/* .IP authenticated +/* Result for authenticated client. +/* .IP unauthenticated +/* Result for unauthenticated client. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ + +/* System library. */ + +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include "smtpd.h" +#include "smtpd_token.h" +#include "smtpd_chat.h" +#include "smtpd_sasl_proto.h" +#include "smtpd_sasl_glue.h" + +#ifdef USE_SASL_AUTH + +/* smtpd_sasl_auth_cmd - process AUTH command */ + +int smtpd_sasl_auth_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *argv) +{ + char *auth_mechanism; + char *initial_response; + const char *err; + + if (var_helo_required && state->helo_name == 0) { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "503 5.5.1 Error: send HELO/EHLO first"); + return (-1); + } + if (SMTPD_STAND_ALONE(state) || !smtpd_sasl_is_active(state) + || (state->ehlo_discard_mask & EHLO_MASK_AUTH)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: authentication not enabled"); + return (-1); + } + if (SMTPD_IN_MAIL_TRANSACTION(state)) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: MAIL transaction in progress"); + return (-1); + } + if (state->milters != 0 && (err = milter_other_event(state->milters)) != 0) { + if (err[0] == '5') { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "%s", err); + return (-1); + } + /* Sendmail compatibility: map 4xx into 454. */ + else if (err[0] == '4') { + state->error_mask |= MAIL_ERROR_POLICY; + smtpd_chat_reply(state, "454 4.3.0 Try again later"); + return (-1); + } + } +#ifdef USE_TLS + if (var_smtpd_tls_auth_only && !state->tls_context) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + /* RFC 4954, Section 4. */ + smtpd_chat_reply(state, "504 5.5.4 Encryption required for requested authentication mechanism"); + return (-1); + } +#endif + if (state->sasl_username) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "503 5.5.1 Error: already authenticated"); + return (-1); + } + if (argc < 2 || argc > 3) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + smtpd_chat_reply(state, "501 5.5.4 Syntax: AUTH mechanism"); + return (-1); + } + /* Don't reuse the SASL handle after authentication failure. */ +#ifndef XSASL_TYPE_CYRUS +#define XSASL_TYPE_CYRUS "cyrus" +#endif + if (state->flags & SMTPD_FLAG_AUTH_USED) { + smtpd_sasl_deactivate(state); +#ifdef USE_TLS + if (state->tls_context != 0) + smtpd_sasl_activate(state, VAR_SMTPD_SASL_TLS_OPTS, + var_smtpd_sasl_tls_opts); + else +#endif + smtpd_sasl_activate(state, VAR_SMTPD_SASL_OPTS, + var_smtpd_sasl_opts); + } else if (strcmp(var_smtpd_sasl_type, XSASL_TYPE_CYRUS) == 0) { + state->flags |= SMTPD_FLAG_AUTH_USED; + } + + /* + * All authentication failures shall be logged. The 5xx reply code from + * the SASL authentication routine triggers tar-pit delays, which help to + * slow down password guessing attacks. + */ + auth_mechanism = argv[1].strval; + initial_response = (argc == 3 ? argv[2].strval : 0); + return (smtpd_sasl_authenticate(state, auth_mechanism, initial_response)); +} + +/* smtpd_sasl_mail_opt - SASL-specific MAIL FROM option */ + +char *smtpd_sasl_mail_opt(SMTPD_STATE *state, const char *addr) +{ + + /* + * Do not store raw RFC2554 protocol data. + */ +#if 0 + if (state->sasl_username == 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + return ("503 5.5.4 Error: send AUTH command first"); + } +#endif + if (state->sasl_sender != 0) { + state->error_mask |= MAIL_ERROR_PROTOCOL; + return ("503 5.5.4 Error: multiple AUTH= options"); + } + if (strcmp(addr, "<>") != 0) { + state->sasl_sender = mystrdup(addr); + printable(state->sasl_sender, '?'); + } + return (0); +} + +/* smtpd_sasl_mail_reset - SASL-specific MAIL FROM cleanup */ + +void smtpd_sasl_mail_reset(SMTPD_STATE *state) +{ + if (state->sasl_sender) { + myfree(state->sasl_sender); + state->sasl_sender = 0; + } +} + +/* permit_sasl_auth - OK for authenticated connection */ + +int permit_sasl_auth(SMTPD_STATE *state, int ifyes, int ifnot) +{ + if (state->sasl_method && strcasecmp(state->sasl_method, "anonymous")) + return (ifyes); + return (ifnot); +} + +#endif diff --git a/src/smtpd/smtpd_sasl_proto.h b/src/smtpd/smtpd_sasl_proto.h new file mode 100644 index 0000000..d52bd4b --- /dev/null +++ b/src/smtpd/smtpd_sasl_proto.h @@ -0,0 +1,37 @@ +/*++ +/* NAME +/* smtpd_sasl_proto 3h +/* SUMMARY +/* Postfix SMTP protocol support for SASL authentication +/* SYNOPSIS +/* #include "smtpd_sasl_proto.h" +/* DESCRIPTION +/* .nf + + /* + * SMTP protocol interface. + */ +extern int smtpd_sasl_auth_cmd(SMTPD_STATE *, int, SMTPD_TOKEN *); +extern void smtpd_sasl_auth_reset(SMTPD_STATE *); +extern char *smtpd_sasl_mail_opt(SMTPD_STATE *, const char *); +extern void smtpd_sasl_mail_reset(SMTPD_STATE *); + +#define smtpd_sasl_auth_extern smtpd_sasl_login +#define smtpd_sasl_auth_reset smtpd_sasl_logout + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtpd/smtpd_server.in b/src/smtpd/smtpd_server.in new file mode 100644 index 0000000..691b8c6 --- /dev/null +++ b/src/smtpd/smtpd_server.in @@ -0,0 +1,56 @@ +# +# Initialize. +# +#! ../bin/postmap smtpd_check_access +#msg_verbose 1 +smtpd_delay_reject 0 +relay_domains porcupine.org +client spike.porcupine.org 168.100.3.2 +# +# Check MX access +# +helo_restrictions check_helo_mx_access,inline:{168.100.3.2=reject} +helo www.porcupine.org +helo example.tld +helo foo@postfix.org +sender_restrictions check_sender_mx_access,inline:{168.100.3.2=reject} +mail foo@www.porcupine.org +mail example.tld +mail foo@postfix.org +recipient_restrictions check_recipient_mx_access,inline:{168.100.3.2=reject} +rcpt foo@www.porcupine.org +rcpt foo@example.tld +rcpt foo@postfix.org +# +# Check NS access +# +helo_restrictions check_helo_ns_access,inline:{168.100.3.75=reject} +helo www.porcupine.org +helo example.tld +helo foo@postfix.org +sender_restrictions check_sender_ns_access,inline:{168.100.3.75=reject} +mail foo@www.porcupine.org +mail example.tld +mail foo@postfix.org +recipient_restrictions check_recipient_ns_access,inline:{168.100.3.75=reject} +rcpt foo@www.porcupine.org +rcpt foo@example.tld +rcpt foo@postfix.org +# +# Check A access +# +helo_restrictions check_helo_a_access,inline:{168.100.3.2=reject} +helo spike.porcupine.org +helo www.porcupine.org +client_restrictions check_client_a_access,inline:{168.100.3.2=reject} +client spike.porcupine.org 1.2.3.4 +client www.porcupine.org 1.2.3.4 +reverse_client_restrictions check_reverse_client_a_access,inline:{168.100.3.2=reject} +client spike.porcupine.org 1.2.3.4 +client www.porcupine.org 1.2.3.4 +sender_restrictions check_sender_a_access,inline:{168.100.3.2=reject} +mail foo@spike.porcupine.org +mail foo@www.porcupine.org +recipient_restrictions check_recipient_a_access,inline:{168.100.3.2=reject} +rcpt foo@spike.porcupine.org +rcpt foo@www.porcupine.org diff --git a/src/smtpd/smtpd_server.ref b/src/smtpd/smtpd_server.ref new file mode 100644 index 0000000..b396a57 --- /dev/null +++ b/src/smtpd/smtpd_server.ref @@ -0,0 +1,118 @@ +>>> # +>>> # Initialize. +>>> # +>>> #! ../bin/postmap smtpd_check_access +>>> #msg_verbose 1 +>>> smtpd_delay_reject 0 +OK +>>> relay_domains porcupine.org +OK +>>> client spike.porcupine.org 168.100.3.2 +OK +>>> # +>>> # Check MX access +>>> # +>>> helo_restrictions check_helo_mx_access,inline:{168.100.3.2=reject} +OK +>>> helo www.porcupine.org +./smtpd_check: : reject: HELO from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Helo command rejected: Access denied; proto=SMTP helo= +554 5.7.1 : Helo command rejected: Access denied +>>> helo example.tld +./smtpd_check: warning: Unable to look up MX host example.tld for Helo command example.tld: hostname nor servname provided, or not known +OK +>>> helo foo@postfix.org +OK +>>> sender_restrictions check_sender_mx_access,inline:{168.100.3.2=reject} +OK +>>> mail foo@www.porcupine.org +./smtpd_check: : reject: MAIL from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Sender address rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: Access denied +>>> mail example.tld +./smtpd_check: warning: Unable to look up MX host example.tld for Sender address example.tld: hostname nor servname provided, or not known +OK +>>> mail foo@postfix.org +OK +>>> recipient_restrictions check_recipient_mx_access,inline:{168.100.3.2=reject} +OK +>>> rcpt foo@www.porcupine.org +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Access denied +>>> rcpt foo@example.tld +./smtpd_check: warning: Unable to look up MX host example.tld for Recipient address foo@example.tld: hostname nor servname provided, or not known +OK +>>> rcpt foo@postfix.org +OK +>>> # +>>> # Check NS access +>>> # +>>> helo_restrictions check_helo_ns_access,inline:{168.100.3.75=reject} +OK +>>> helo www.porcupine.org +./smtpd_check: : reject: HELO from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Helo command rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Helo command rejected: Access denied +>>> helo example.tld +./smtpd_check: warning: Unable to look up NS host for example.tld: Host not found +OK +>>> helo foo@postfix.org +./smtpd_check: : reject: HELO from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Helo command rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Helo command rejected: Access denied +>>> sender_restrictions check_sender_ns_access,inline:{168.100.3.75=reject} +OK +>>> mail foo@www.porcupine.org +./smtpd_check: : reject: MAIL from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Sender address rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: Access denied +>>> mail example.tld +./smtpd_check: warning: Unable to look up NS host for example.tld: Host not found +OK +>>> mail foo@postfix.org +./smtpd_check: : reject: MAIL from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Sender address rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: Access denied +>>> recipient_restrictions check_recipient_ns_access,inline:{168.100.3.75=reject} +OK +>>> rcpt foo@www.porcupine.org +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Access denied +>>> rcpt foo@example.tld +./smtpd_check: warning: Unable to look up NS host for foo@example.tld: Host not found +OK +>>> rcpt foo@postfix.org +./smtpd_check: : reject: RCPT from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Access denied +>>> # +>>> # Check A access +>>> # +>>> helo_restrictions check_helo_a_access,inline:{168.100.3.2=reject} +OK +>>> helo spike.porcupine.org +./smtpd_check: : reject: HELO from spike.porcupine.org[168.100.3.2]: 554 5.7.1 : Helo command rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Helo command rejected: Access denied +>>> helo www.porcupine.org +OK +>>> client_restrictions check_client_a_access,inline:{168.100.3.2=reject} +OK +>>> client spike.porcupine.org 1.2.3.4 +./smtpd_check: : reject: CONNECT from spike.porcupine.org[1.2.3.4]: 554 5.7.1 : Client host rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Client host rejected: Access denied +>>> client www.porcupine.org 1.2.3.4 +OK +>>> reverse_client_restrictions check_reverse_client_a_access,inline:{168.100.3.2=reject} +bad command +>>> client spike.porcupine.org 1.2.3.4 +./smtpd_check: : reject: CONNECT from spike.porcupine.org[1.2.3.4]: 554 5.7.1 : Client host rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Client host rejected: Access denied +>>> client www.porcupine.org 1.2.3.4 +OK +>>> sender_restrictions check_sender_a_access,inline:{168.100.3.2=reject} +OK +>>> mail foo@spike.porcupine.org +./smtpd_check: : reject: MAIL from www.porcupine.org[1.2.3.4]: 554 5.7.1 : Sender address rejected: Access denied; from= proto=SMTP helo= +554 5.7.1 : Sender address rejected: Access denied +>>> mail foo@www.porcupine.org +OK +>>> recipient_restrictions check_recipient_a_access,inline:{168.100.3.2=reject} +OK +>>> rcpt foo@spike.porcupine.org +./smtpd_check: : reject: RCPT from www.porcupine.org[1.2.3.4]: 554 5.7.1 : Recipient address rejected: Access denied; from= to= proto=SMTP helo= +554 5.7.1 : Recipient address rejected: Access denied +>>> rcpt foo@www.porcupine.org +OK diff --git a/src/smtpd/smtpd_state.c b/src/smtpd/smtpd_state.c new file mode 100644 index 0000000..f2f5f89 --- /dev/null +++ b/src/smtpd/smtpd_state.c @@ -0,0 +1,248 @@ +/*++ +/* NAME +/* smtpd_state 3 +/* SUMMARY +/* Postfix SMTP server +/* SYNOPSIS +/* #include "smtpd.h" +/* +/* void smtpd_state_init(state, stream, service) +/* SMTPD_STATE *state; +/* VSTREAM *stream; +/* const char *service; +/* +/* void smtpd_state_reset(state) +/* SMTPD_STATE *state; +/* DESCRIPTION +/* smtpd_state_init() initializes session context. +/* +/* smtpd_state_reset() cleans up session context. +/* +/* Arguments: +/* .IP state +/* Session context. +/* .IP stream +/* Stream connected to peer. The stream is not copied. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* TLS support originally by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include "smtpd.h" +#include "smtpd_chat.h" +#include "smtpd_sasl_glue.h" + +/* smtpd_state_init - initialize after connection establishment */ + +void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream, + const char *service) +{ + + /* + * Initialize the state information for this connection, and fill in the + * connection-specific fields. + */ + state->flags = 0; + state->err = CLEANUP_STAT_OK; + state->client = stream; + state->service = mystrdup(service); + state->buffer = vstring_alloc(100); + state->addr_buf = vstring_alloc(100); + state->conn_count = state->conn_rate = 0; + state->error_count = 0; + state->error_mask = 0; + state->notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks, + var_notify_classes); + state->helo_name = 0; + state->queue_id = 0; + state->cleanup = 0; + state->dest = 0; + state->rcpt_count = 0; + state->access_denied = 0; + state->history = 0; + state->reason = 0; + state->sender = 0; + state->verp_delims = 0; + state->recipient = 0; + state->etrn_name = 0; + state->protocol = mystrdup(MAIL_PROTO_SMTP); + state->where = SMTPD_AFTER_CONNECT; + state->recursion = 0; + state->msg_size = 0; + state->act_size = 0; + state->junk_cmds = 0; + state->rcpt_overshoot = 0; + state->defer_if_permit_client = 0; + state->defer_if_permit_helo = 0; + state->defer_if_permit_sender = 0; + state->defer_if_reject.dsn = 0; + state->defer_if_reject.reason = 0; + state->defer_if_permit.dsn = 0; + state->defer_if_permit.reason = 0; + state->discard = 0; + state->expand_buf = 0; + state->prepend = 0; + state->proxy = 0; + state->proxy_mail = 0; + state->saved_filter = 0; + state->saved_redirect = 0; + state->saved_bcc = 0; + state->saved_flags = 0; +#ifdef DELAY_ACTION + state->saved_delay = 0; +#endif + state->instance = vstring_alloc(10); + state->seqno = 0; + state->rewrite_context = 0; +#if 0 + state->ehlo_discard_mask = ~0; +#else + state->ehlo_discard_mask = 0; +#endif + state->dsn_envid = 0; + state->dsn_buf = vstring_alloc(100); + state->dsn_orcpt_buf = vstring_alloc(100); +#ifdef USE_TLS +#ifdef USE_TLSPROXY + state->tlsproxy = 0; +#endif + state->tls_context = 0; +#endif + + /* + * Minimal initialization to support external authentication (e.g., + * XCLIENT) without having to enable SASL in main.cf. + */ +#ifdef USE_SASL_AUTH + if (SMTPD_STAND_ALONE(state)) + var_smtpd_sasl_enable = 0; + smtpd_sasl_set_inactive(state); + smtpd_sasl_state_init(state); +#endif + + state->milter_argv = 0; + state->milter_argc = 0; + state->milters = 0; + + /* + * Initialize peer information. + */ + smtpd_peer_init(state); + + /* + * Initialize xforward information. + */ + smtpd_xforward_init(state); + + /* + * Initialize the conversation history. + */ + smtpd_chat_reset(state); + + state->ehlo_argv = 0; + state->ehlo_buf = 0; + + /* + * BDAT. + */ + state->bdat_state = SMTPD_BDAT_STAT_NONE; + state->bdat_get_stream = 0; + state->bdat_get_buffer = 0; +} + +/* smtpd_state_reset - cleanup after disconnect */ + +void smtpd_state_reset(SMTPD_STATE *state) +{ + + /* + * When cleaning up, touch only those fields that smtpd_state_init() + * filled in. The other fields are taken care of by their own + * "destructor" functions. + */ + if (state->service) + myfree(state->service); + if (state->buffer) + vstring_free(state->buffer); + if (state->addr_buf) + vstring_free(state->addr_buf); + if (state->access_denied) + myfree(state->access_denied); + if (state->protocol) + myfree(state->protocol); + smtpd_peer_reset(state); + + /* + * Buffers that are created on the fly and that may be shared among mail + * deliveries within the same SMTP session. + */ + if (state->defer_if_permit.dsn) + vstring_free(state->defer_if_permit.dsn); + if (state->defer_if_permit.reason) + vstring_free(state->defer_if_permit.reason); + if (state->defer_if_reject.dsn) + vstring_free(state->defer_if_reject.dsn); + if (state->defer_if_reject.reason) + vstring_free(state->defer_if_reject.reason); + if (state->expand_buf) + vstring_free(state->expand_buf); + if (state->instance) + vstring_free(state->instance); + if (state->dsn_buf) + vstring_free(state->dsn_buf); + if (state->dsn_orcpt_buf) + vstring_free(state->dsn_orcpt_buf); +#if (defined(USE_TLS) && defined(USE_TLSPROXY)) + if (state->tlsproxy) /* still open after longjmp */ + vstream_fclose(state->tlsproxy); +#endif + + /* + * BDAT. + */ + if (state->bdat_get_stream) + (void) vstream_fclose(state->bdat_get_stream); + if (state->bdat_get_buffer) + vstring_free(state->bdat_get_buffer); +} diff --git a/src/smtpd/smtpd_token.c b/src/smtpd/smtpd_token.c new file mode 100644 index 0000000..927088f --- /dev/null +++ b/src/smtpd/smtpd_token.c @@ -0,0 +1,233 @@ +/*++ +/* NAME +/* smtpd_token 3 +/* SUMMARY +/* tokenize SMTPD command +/* SYNOPSIS +/* #include +/* +/* typedef struct { +/* .in +4 +/* int tokval; +/* char *strval; +/* /* other stuff... */ +/* .in -4 +/* } SMTPD_TOKEN; +/* +/* int smtpd_token(str, argvp) +/* char *str; +/* SMTPD_TOKEN **argvp; +/* DESCRIPTION +/* smtpd_token() routine converts the string in \fIstr\fR to an +/* array of tokens in \fIargvp\fR. The number of tokens is returned +/* via the function return value. +/* +/* Token types: +/* .IP SMTPD_TOK_OTHER +/* The token is something else. +/* .IP SMTPD_TOK_ERROR +/* A malformed token. +/* BUGS +/* This tokenizer understands just enough to tokenize SMTPD commands. +/* It understands backslash escapes, white space, quoted strings, +/* and addresses (including quoted text) enclosed by < and >. +/* The input is broken up into tokens by whitespace, except for +/* whitespace that is protected by quotes etc. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include "smtpd_token.h" + +/* smtp_quoted - read until closing quote */ + +static char *smtp_quoted(char *cp, SMTPD_TOKEN *arg, int start, int last) +{ + static VSTRING *stack; + int wanted; + int c; + + /* + * Parser stack. `ch' is always the most-recently entered character. + */ +#define ENTER_CHAR(buf, ch) VSTRING_ADDCH(buf, ch); +#define LEAVE_CHAR(buf, ch) { \ + vstring_truncate(buf, VSTRING_LEN(buf) - 1); \ + ch = vstring_end(buf)[-1]; \ + } + + if (stack == 0) + stack = vstring_alloc(1); + VSTRING_RESET(stack); + ENTER_CHAR(stack, wanted = last); + + VSTRING_ADDCH(arg->vstrval, start); + for (;;) { + if ((c = *cp) == 0) + break; + cp++; + VSTRING_ADDCH(arg->vstrval, c); + if (c == '\\') { /* parse escape sequence */ + if ((c = *cp) == 0) + break; + cp++; + VSTRING_ADDCH(arg->vstrval, c); + } else if (c == wanted) { /* closing quote etc. */ + if (VSTRING_LEN(stack) == 1) + return (cp); + LEAVE_CHAR(stack, wanted); + } else if (c == '"') { + ENTER_CHAR(stack, wanted = '"'); /* highest precedence */ + } else if (c == '<' && wanted == '>') { + ENTER_CHAR(stack, wanted = '>'); /* lowest precedence */ + } + } + arg->tokval = SMTPD_TOK_ERROR; /* missing end */ + return (cp); +} + +/* smtp_next_token - extract next token from input, update cp */ + +static char *smtp_next_token(char *cp, SMTPD_TOKEN *arg) +{ + int c; + + VSTRING_RESET(arg->vstrval); + arg->tokval = SMTPD_TOK_OTHER; + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) +#define STREQ(x,y,l) (strncasecmp((x), (y), (l)) == 0) + + for (;;) { + if ((c = *cp) == 0) /* end of input */ + break; + cp++; + if (ISSPACE(c)) { /* whitespace, skip */ + while (*cp && ISSPACE(*cp)) + cp++; + if (LEN(arg->vstrval) > 0) /* end of token */ + break; + } else if (c == '<') { /* */ + cp = smtp_quoted(cp, arg, c, '>'); + } else if (c == '"') { /* "stuff" */ + cp = smtp_quoted(cp, arg, c, c); + } else if (c == ':') { /* this is gross, but... */ + VSTRING_ADDCH(arg->vstrval, c); + if (STREQ(STR(arg->vstrval), "to:", LEN(arg->vstrval)) + || STREQ(STR(arg->vstrval), "from:", LEN(arg->vstrval))) + break; + } else { /* other */ + if (c == '\\') { + VSTRING_ADDCH(arg->vstrval, c); + if ((c = *cp) == 0) + break; + cp++; + } + VSTRING_ADDCH(arg->vstrval, c); + } + } + if (LEN(arg->vstrval) <= 0) /* no token found */ + return (0); + VSTRING_TERMINATE(arg->vstrval); + arg->strval = vstring_str(arg->vstrval); + return (cp); +} + +/* smtpd_token_init - initialize token structures */ + +static void smtpd_token_init(char *ptr, ssize_t count) +{ + SMTPD_TOKEN *arg; + int n; + + for (arg = (SMTPD_TOKEN *) ptr, n = 0; n < count; arg++, n++) + arg->vstrval = vstring_alloc(10); +} + +/* smtpd_token - tokenize SMTPD command */ + +int smtpd_token(char *cp, SMTPD_TOKEN **argvp) +{ + static SMTPD_TOKEN *smtp_argv; + static MVECT mvect; + int n; + + if (smtp_argv == 0) + smtp_argv = (SMTPD_TOKEN *) mvect_alloc(&mvect, sizeof(*smtp_argv), 1, + smtpd_token_init, (MVECT_FN) 0); + for (n = 0; /* void */ ; n++) { + smtp_argv = (SMTPD_TOKEN *) mvect_realloc(&mvect, n + 1); + if ((cp = smtp_next_token(cp, smtp_argv + n)) == 0) + break; + } + *argvp = smtp_argv; + return (n); +} + +#ifdef TEST + + /* + * Test program for the SMTPD command tokenizer. + */ + +#include +#include +#include + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *vp = vstring_alloc(10); + int tok_argc; + SMTPD_TOKEN *tok_argv; + int i; + + for (;;) { + if (isatty(STDIN_FILENO)) + vstream_printf("enter SMTPD command: "); + vstream_fflush(VSTREAM_OUT); + if (vstring_get_nonl(vp, VSTREAM_IN) == VSTREAM_EOF) + break; + if (*vstring_str(vp) == '#') + continue; + if (!isatty(STDIN_FILENO)) + vstream_printf("%s\n", vstring_str(vp)); + tok_argc = smtpd_token(vstring_str(vp), &tok_argv); + for (i = 0; i < tok_argc; i++) { + vstream_printf("Token type: %s\n", + tok_argv[i].tokval == SMTPD_TOK_OTHER ? "other" : + tok_argv[i].tokval == SMTPD_TOK_ERROR ? "error" : + "unknown"); + vstream_printf("Token value: %s\n", tok_argv[i].strval); + } + } + vstring_free(vp); + exit(0); +} + +#endif diff --git a/src/smtpd/smtpd_token.h b/src/smtpd/smtpd_token.h new file mode 100644 index 0000000..88489fa --- /dev/null +++ b/src/smtpd/smtpd_token.h @@ -0,0 +1,40 @@ +/*++ +/* NAME +/* smtpd_token 3h +/* SUMMARY +/* tokenize SMTPD command +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +typedef struct SMTPD_TOKEN { + int tokval; + char *strval; + VSTRING *vstrval; +} SMTPD_TOKEN; + +#define SMTPD_TOK_OTHER 0 +#define SMTPD_TOK_ADDR 1 +#define SMTPD_TOK_ERROR 2 + +extern int smtpd_token(char *, SMTPD_TOKEN **); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/smtpd/smtpd_token.in b/src/smtpd/smtpd_token.in new file mode 100644 index 0000000..d67d5d0 --- /dev/null +++ b/src/smtpd/smtpd_token.in @@ -0,0 +1,12 @@ +mail from: +mail from:<"wietse venema"@porcupine.org> +mail from:wietse@porcupine.org +mail from: +mail from:<"wietse venema"@porcupine.org ("wietse ) venema")> +mail from:<"wietse venema" > +mail from:<"wietse venema"@porcupine.org ( ("wietse ) venema") )> +mail from:"wietse venema"@porcupine.org +mail from:wietse\ venema@porcupine.org +mail to:<"wietse venema> +mail to: +mail to: diff --git a/src/smtpd/smtpd_token.ref b/src/smtpd/smtpd_token.ref new file mode 100644 index 0000000..21dc969 --- /dev/null +++ b/src/smtpd/smtpd_token.ref @@ -0,0 +1,84 @@ +mail from: +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: +mail from:<"wietse venema"@porcupine.org> +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: <"wietse venema"@porcupine.org> +mail from:wietse@porcupine.org +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: wietse@porcupine.org +mail from: +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: +mail from:<"wietse venema"@porcupine.org ("wietse ) venema")> +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: <"wietse venema"@porcupine.org ("wietse ) venema")> +mail from:<"wietse venema" > +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: <"wietse venema" > +mail from:<"wietse venema"@porcupine.org ( ("wietse ) venema") )> +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: <"wietse venema"@porcupine.org ( ("wietse ) venema") )> +mail from:"wietse venema"@porcupine.org +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: "wietse venema"@porcupine.org +mail from:wietse\ venema@porcupine.org +Token type: other +Token value: mail +Token type: other +Token value: from: +Token type: other +Token value: wietse\ venema@porcupine.org +mail to:<"wietse venema> +Token type: other +Token value: mail +Token type: other +Token value: to: +Token type: error +Token value: <"wietse venema> +mail to: +Token type: other +Token value: mail +Token type: other +Token value: to: +Token type: other +Token value: +mail to: +Token type: other +Token value: mail +Token type: other +Token value: to: +Token type: error +Token value: diff --git a/src/smtpd/smtpd_xforward.c b/src/smtpd/smtpd_xforward.c new file mode 100644 index 0000000..053d377 --- /dev/null +++ b/src/smtpd/smtpd_xforward.c @@ -0,0 +1,114 @@ +/*++ +/* NAME +/* smtpd_xforward 3 +/* SUMMARY +/* maintain XCLIENT information +/* SYNOPSIS +/* #include "smtpd.h" +/* +/* void smtpd_xforward_init(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_xforward_preset(state) +/* SMTPD_STATE *state; +/* +/* void smtpd_xforward_reset(state) +/* SMTPD_STATE *state; +/* DESCRIPTION +/* smtpd_xforward_init() zeroes the attributes for storage of +/* XFORWARD command parameters. +/* +/* smtpd_xforward_preset() takes the result from smtpd_xforward_init() +/* and sets all fields to the same "unknown" value that regular +/* client attributes would have. +/* +/* smtpd_xforward_reset() restores the state from smtpd_xforward_init(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include + +/* smtpd_xforward_init - initialize xforward attributes */ + +void smtpd_xforward_init(SMTPD_STATE *state) +{ + state->xforward.flags = 0; + state->xforward.name = 0; + state->xforward.addr = 0; + state->xforward.port = 0; + state->xforward.namaddr = 0; + state->xforward.protocol = 0; + state->xforward.helo_name = 0; + state->xforward.ident = 0; + state->xforward.domain = 0; +} + +/* smtpd_xforward_preset - set xforward attributes to "unknown" */ + +void smtpd_xforward_preset(SMTPD_STATE *state) +{ + + /* + * Sanity checks. + */ + if (state->xforward.flags) + msg_panic("smtpd_xforward_preset: bad flags: 0x%x", + state->xforward.flags); + + /* + * This is a temporary solution. Unknown forwarded attributes get the + * same values as unknown normal attributes, so that we don't break + * assumptions in pre-existing code. + */ + state->xforward.flags = SMTPD_STATE_XFORWARD_INIT; + state->xforward.name = mystrdup(CLIENT_NAME_UNKNOWN); + state->xforward.addr = mystrdup(CLIENT_ADDR_UNKNOWN); + state->xforward.port = mystrdup(CLIENT_PORT_UNKNOWN); + state->xforward.namaddr = mystrdup(CLIENT_NAMADDR_UNKNOWN); + state->xforward.rfc_addr = mystrdup(CLIENT_ADDR_UNKNOWN); + /* Leave helo at zero. */ + state->xforward.protocol = mystrdup(CLIENT_PROTO_UNKNOWN); + /* Leave ident at zero. */ + /* Leave domain context at zero. */ +} + +/* smtpd_xforward_reset - reset xforward attributes */ + +void smtpd_xforward_reset(SMTPD_STATE *state) +{ +#define FREE_AND_WIPE(s) { if (s) myfree(s); s = 0; } + + state->xforward.flags = 0; + FREE_AND_WIPE(state->xforward.name); + FREE_AND_WIPE(state->xforward.addr); + FREE_AND_WIPE(state->xforward.port); + FREE_AND_WIPE(state->xforward.namaddr); + FREE_AND_WIPE(state->xforward.rfc_addr); + FREE_AND_WIPE(state->xforward.protocol); + FREE_AND_WIPE(state->xforward.helo_name); + FREE_AND_WIPE(state->xforward.ident); + FREE_AND_WIPE(state->xforward.domain); +} diff --git a/src/smtpstone/.indent.pro b/src/smtpstone/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/smtpstone/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/smtpstone/.printfck b/src/smtpstone/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/smtpstone/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/smtpstone/Makefile.in b/src/smtpstone/Makefile.in new file mode 100644 index 0000000..f86e019 --- /dev/null +++ b/src/smtpstone/Makefile.in @@ -0,0 +1,172 @@ +SHELL = /bin/sh +SRCS = smtp-source.c smtp-sink.c qmqp-source.c qmqp-sink.c +OBJS = smtp-source.o smtp-sink.o qmqp-source.o qmqp-sink.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +INC_DIR = ../../include +PROG = smtp-source smtp-sink qmqp-source qmqp-sink +LIBS = ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +all: $(PROG) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +smtp-sink: smtp-sink.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ smtp-sink.o $(LIBS) $(SYSLIBS) + +smtp-source: smtp-source.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ smtp-source.o $(LIBS) $(SYSLIBS) + +qmqp-sink: qmqp-sink.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ qmqp-sink.o $(LIBS) $(SYSLIBS) + +qmqp-source: qmqp-source.o $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ qmqp-source.o $(LIBS) $(SYSLIBS) + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../bin/smtp-source ../../bin/smtp-sink ../../bin/qmqp-source \ + ../../bin/qmqp-sink + +../../bin/smtp-source: smtp-source + cp $? $@ + +../../bin/smtp-sink: smtp-sink + cp $? $@ + +../../bin/qmqp-source: qmqp-source + cp $? $@ + +../../bin/qmqp-sink: qmqp-sink + cp $? $@ + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +qmqp-sink.o: ../../include/check_arg.h +qmqp-sink.o: ../../include/events.h +qmqp-sink.o: ../../include/htable.h +qmqp-sink.o: ../../include/inet_proto.h +qmqp-sink.o: ../../include/iostuff.h +qmqp-sink.o: ../../include/listen.h +qmqp-sink.o: ../../include/mail_version.h +qmqp-sink.o: ../../include/msg.h +qmqp-sink.o: ../../include/msg_vstream.h +qmqp-sink.o: ../../include/mymalloc.h +qmqp-sink.o: ../../include/netstring.h +qmqp-sink.o: ../../include/qmqp_proto.h +qmqp-sink.o: ../../include/sys_defs.h +qmqp-sink.o: ../../include/vbuf.h +qmqp-sink.o: ../../include/vstream.h +qmqp-sink.o: ../../include/vstring.h +qmqp-sink.o: qmqp-sink.c +qmqp-source.o: ../../include/check_arg.h +qmqp-source.o: ../../include/connect.h +qmqp-source.o: ../../include/events.h +qmqp-source.o: ../../include/get_hostname.h +qmqp-source.o: ../../include/host_port.h +qmqp-source.o: ../../include/inet_proto.h +qmqp-source.o: ../../include/iostuff.h +qmqp-source.o: ../../include/mail_date.h +qmqp-source.o: ../../include/mail_version.h +qmqp-source.o: ../../include/msg.h +qmqp-source.o: ../../include/msg_vstream.h +qmqp-source.o: ../../include/myaddrinfo.h +qmqp-source.o: ../../include/mymalloc.h +qmqp-source.o: ../../include/netstring.h +qmqp-source.o: ../../include/qmqp_proto.h +qmqp-source.o: ../../include/sane_connect.h +qmqp-source.o: ../../include/split_at.h +qmqp-source.o: ../../include/sys_defs.h +qmqp-source.o: ../../include/valid_hostname.h +qmqp-source.o: ../../include/valid_mailhost_addr.h +qmqp-source.o: ../../include/vbuf.h +qmqp-source.o: ../../include/vstream.h +qmqp-source.o: ../../include/vstring.h +qmqp-source.o: qmqp-source.c +smtp-sink.o: ../../include/check_arg.h +smtp-sink.o: ../../include/chroot_uid.h +smtp-sink.o: ../../include/events.h +smtp-sink.o: ../../include/get_hostname.h +smtp-sink.o: ../../include/htable.h +smtp-sink.o: ../../include/inet_proto.h +smtp-sink.o: ../../include/iostuff.h +smtp-sink.o: ../../include/listen.h +smtp-sink.o: ../../include/mail_date.h +smtp-sink.o: ../../include/mail_version.h +smtp-sink.o: ../../include/make_dirs.h +smtp-sink.o: ../../include/msg.h +smtp-sink.o: ../../include/msg_vstream.h +smtp-sink.o: ../../include/myaddrinfo.h +smtp-sink.o: ../../include/mymalloc.h +smtp-sink.o: ../../include/myrand.h +smtp-sink.o: ../../include/sane_accept.h +smtp-sink.o: ../../include/smtp_stream.h +smtp-sink.o: ../../include/stringops.h +smtp-sink.o: ../../include/sys_defs.h +smtp-sink.o: ../../include/vbuf.h +smtp-sink.o: ../../include/vstream.h +smtp-sink.o: ../../include/vstring.h +smtp-sink.o: ../../include/vstring_vstream.h +smtp-sink.o: smtp-sink.c +smtp-source.o: ../../include/check_arg.h +smtp-source.o: ../../include/compat_va_copy.h +smtp-source.o: ../../include/connect.h +smtp-source.o: ../../include/events.h +smtp-source.o: ../../include/get_hostname.h +smtp-source.o: ../../include/host_port.h +smtp-source.o: ../../include/inet_proto.h +smtp-source.o: ../../include/iostuff.h +smtp-source.o: ../../include/mail_date.h +smtp-source.o: ../../include/mail_version.h +smtp-source.o: ../../include/msg.h +smtp-source.o: ../../include/msg_vstream.h +smtp-source.o: ../../include/myaddrinfo.h +smtp-source.o: ../../include/mymalloc.h +smtp-source.o: ../../include/sane_connect.h +smtp-source.o: ../../include/smtp_stream.h +smtp-source.o: ../../include/split_at.h +smtp-source.o: ../../include/sys_defs.h +smtp-source.o: ../../include/valid_hostname.h +smtp-source.o: ../../include/valid_mailhost_addr.h +smtp-source.o: ../../include/vbuf.h +smtp-source.o: ../../include/vstream.h +smtp-source.o: ../../include/vstring.h +smtp-source.o: ../../include/vstring_vstream.h +smtp-source.o: smtp-source.c diff --git a/src/smtpstone/hashed-deferred b/src/smtpstone/hashed-deferred new file mode 100644 index 0000000..93f0e12 --- /dev/null +++ b/src/smtpstone/hashed-deferred @@ -0,0 +1,37 @@ +Delivering 1000 deferred messages over the loopback transport, +outbound concurrency 10. smtp-sink pipelining disabled. Machine is +P230, BSD/OS 3.1, 64MB memory. + +hashing is 16 directories per level + +flat deferred queue + + start: Sun Feb 21 16:42:37 EST 1999 + done: Feb 21 16:44:35 + time: 1:58 = 118 seconds + + start: Sun Feb 21 16:48:01 EST 1999 + done: Feb 21 16:49:51 + time: 1:50 = 110 seconds + +hashed deferred queue, depth=1 (16 directories) + + start: Sun Feb 21 17:29:36 EST 1999 + done: Feb 21 17:31:32 + time: 1:56 = 116 seconds + + start: Sun Feb 21 17:33:36 EST 1999 + done: Feb 21 17:35:24 + time: 1:48 = 108 seconds + + start: Sun Feb 21 17:37:08 EST 1999 + done: Feb 21 17:39:02 + time: 1:52 = 112 seconds + +Hashing does not slow down deliveries. + +However the problem is scanning an empty deferred queue. On an idle +machine, it takes some 5 seconds to scan an empty depth=2 deferred +queue unless the blocks happen to be cached. During those 5 seconds +the queue manager will not pay attention to I/O from delivery +agents, which is bad. diff --git a/src/smtpstone/hashed-incoming b/src/smtpstone/hashed-incoming new file mode 100644 index 0000000..7bb1993 --- /dev/null +++ b/src/smtpstone/hashed-incoming @@ -0,0 +1,48 @@ +Sending 1000 messages through postfix over the loopback transport, +inbound concurrency 10. smtp-sink pipelining disabled. Machine is +P230, BSD/OS 3.1 + +accepted = time for postfix to accept all messages +moved = number of messages delivered by postfix when all mail accepted +done = time for postfix to deliver last message + +hashing is 16 directories per level + +flat incoming queue + + start: Sun Feb 21 15:12:44 EST 1999 + accepted: 66.10 real 0.50 user 1.56 sys + moved: 122 + done: Feb 21 15:15:19 + + total time: 2:35 = 155 seconds + + start: Sun Feb 21 15:30:44 EST 1999 + accepted: 67.47 real 0.48 user 1.60 sys + moved: + done: Feb 21 15:33:10 + + total time: 2:26 = 146 seconds + +hashed incoming queue, depth=1 (16 subdirs) + + start: Sun Feb 21 15:39:43 EST 1999 + accepted: 84.42 real 0.49 user 1.66 sys + moved: 144 + done: Feb 21 15:42:27 + + total time: 2:44 = 164 seconds + + start: Sun Feb 21 15:42:43 EST 1999 + accepted: 84.57 real 0.46 user 1.67 sys + moved: + done: Feb 21 15:45:34 + + total time: 2:51 = 171 seconds + + start: Sun Feb 21 15:47:11 EST 1999 + accepted: 84.11 real 0.34 user 1.72 sys + moved: + done: Feb 21 15:49:54 + + total time: 2:43 = 163 seconds diff --git a/src/smtpstone/mx-deliver b/src/smtpstone/mx-deliver new file mode 100644 index 0000000..a4f44a0 --- /dev/null +++ b/src/smtpstone/mx-deliver @@ -0,0 +1,20 @@ +MX needs 24 seconds to deliver 100 SMTP messages to one local user. +smtpd/local-delivery process limit = 100 + +/usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@fist.porcupine.org fist + 10.47 real 0.12 user 0.16 sys +Jun 8 14:45:25 fist mx:smtpd[19432]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:45:49 fist mx:local[19444]: 085788: to=, relay=l +Total time: 24 seconds + +/usr/bin/time ./smtp-source -s 10 -m 100 -t wietse@fist.porcupine.org fist + 9.10 real 0.06 user 0.26 sys +Jun 8 14:46:42 fist mx:smtpd[19443]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:47:06 fist mx:local[19446]: 085792: to=, relay=l +Total time: 24 seconds + +/usr/bin/time ./smtp-source -s 20 -m 100 -t wietse@fist.porcupine.org fist + 9.84 real 0.09 user 0.28 sys +Jun 8 14:48:03 fist mx:smtpd[19458]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:48:28 fist mx:local[19479]: 085795: to=, relay=l +Total time: 25 seconds diff --git a/src/smtpstone/mx-explode b/src/smtpstone/mx-explode new file mode 100644 index 0000000..e997147 --- /dev/null +++ b/src/smtpstone/mx-explode @@ -0,0 +1,33 @@ +MX needs 12 seconds per 1000 different remote destinations. +smtp process limit = 100, bundle_recipients = 0. + +/usr/bin/time ./smtp-source -r 1000 fist + 1.13 real 0.07 user 0.27 sys +Jun 8 13:32:18 fist mx:smtpd[18174]: connect from spike.porcupine.org(168.100.1 +Jun 8 13:32:31 fist mx:smtp[18209]: 085688: to=<544foo@spike.porcupine.org>, re +Total time: 13 seconds + +/usr/bin/time ./smtp-source -r 2000 fist + 2.55 real 0.21 user 0.48 sys +Jun 8 13:33:23 fist mx:smtpd[18174]: connect from spike.porcupine.org(168.100.1 +Jun 8 13:33:48 fist mx:smtp[18184]: 085693: to=<1041foo@spike.porcupine.org>, r +Total time: 25 seconds + +/usr/bin/time ./smtp-source -r 5000 fist +[test generating machine ran out of resources while receiving mail] + +/usr/bin/time ./smtp-source -r 1000 fist + 1.38 real 0.17 user 0.16 sys +Jun 8 15:20:33 fist mx:smtpd[27695]: connect from spike.porcupine.org(168.100.1 +Jun 8 15:20:46 fist mx:smtp[27724]: 085687: to=<493foo@spike.porcupine.org>, re +Total time: 13 seconds + +/usr/bin/time ./smtp-source -r 2000 fist + 2.64 real 0.23 user 0.46 sys +Jun 8 15:20:52 fist mx:smtpd[27695]: connect from spike.porcupine.org(168.100.1 +Jun 8 15:21:16 fist mx:smtp[27743]: 085687: to=<1086foo@spike.porcupine.org>, r +Total time: 24 seconds + +/usr/bin/time ./smtp-source -r 5000 fist +[test generating machine ran out of resources while receiving mail] + diff --git a/src/smtpstone/mx-relay b/src/smtpstone/mx-relay new file mode 100644 index 0000000..a5930cb --- /dev/null +++ b/src/smtpstone/mx-relay @@ -0,0 +1,20 @@ +MX needs 19 seconds to relay 100 messages with one recipient. +smtp/smtpd process limit = 100, bundle_recipients = 0. + +/usr/bin/time ./smtp-source -s 5 -m 100 fist + 9.56 real 0.07 user 0.20 sys +Jun 8 14:33:19 fist mx:smtpd[19366]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:33:36 fist mx:smtp[19382]: 085781: to=, relay +Total time: 17 seconds + +/usr/bin/time ./smtp-source -s 10 -m 100 fist + 8.95 real 0.12 user 0.19 sys +Jun 8 14:34:22 fist mx:smtpd[19377]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:34:41 fist mx:smtp[19378]: 085792: to=, relay +Total time: 19 seconds + +/usr/bin/time ./smtp-source -s 20 -m 100 fist + 9.79 real 0.11 user 0.27 sys +Jun 8 14:35:18 fist mx:smtpd[19377]: connect from spike.porcupine.org(168.100.1 +Jun 8 14:35:38 fist mx:smtp[19382]: 085794: to=, relay +Total time: 20 seconds diff --git a/src/smtpstone/performance b/src/smtpstone/performance new file mode 100644 index 0000000..805676f --- /dev/null +++ b/src/smtpstone/performance @@ -0,0 +1,28 @@ +List performance: time to forward one SMTP message with 1000, 2000 +and 5000 different remote destinations. Outbound SMTP concurrency += 100. + +dests 1000 2000 5000 time per 1000 +============================================= +qmail 15 32 80 16 +mx 13 25 (*) 13 + +(*) message sink host saturated under the load + +Local delivery performance: time to deliver 100 SMTP messages to +one recipient. Outbound SMTP concurrency = 100, inbound SMTP +concurrency = 5, 10, 20. + +concur 5 10 20 average time +============================================ +qmail 62 59 58 60 +mx 24 24 25 24 + +Relay performance: time to forward 100 SMTP messages with one +recipient. Outbound SMTP concurrency = 100, inbound SMTP concurrency += 5, 10, 20. + +concur 5 10 20 average time +============================================ +qmail 56 54 54 55 +mx 17 19 20 19 diff --git a/src/smtpstone/qmail-deliver b/src/smtpstone/qmail-deliver new file mode 100644 index 0000000..e00ab39 --- /dev/null +++ b/src/smtpstone/qmail-deliver @@ -0,0 +1,20 @@ +Qmail needs 59 seconds to deliver 100 SMTP messages to one local recipient. +Default configuration, concurrencyremote = 100. + +/usr/bin/time ./smtp-source -s 5 -m 100 -t wietse fist + 41.45 real 0.07 user 0.21 sys +Jun 8 12:17:20 fist qmail: 865786640.494072 new msg 39901 +Jun 8 12:18:22 fist qmail: 865786702.301087 end msg 39982 +Total time: 62 sec + +/usr/bin/time ./smtp-source -s 10 -m 100 -t wietse fist + 26.50 real 0.10 user 0.22 sys +Jun 8 12:14:49 fist qmail: 865786489.089492 new msg 39901 +Jun 8 12:15:48 fist qmail: 865786548.316898 end msg 39928 +Total time: 59 sec + +/usr/bin/time ./smtp-source -s 20 -m 100 -t wietse fist + 21.15 real 0.08 user 0.30 sys +Jun 8 12:19:18 fist qmail: 865786758.939755 new msg 39903 +Jun 8 12:20:16 fist qmail: 865786816.739912 end msg 40031 +Total time: 58 sec diff --git a/src/smtpstone/qmail-explode b/src/smtpstone/qmail-explode new file mode 100644 index 0000000..ed40e2c --- /dev/null +++ b/src/smtpstone/qmail-explode @@ -0,0 +1,20 @@ +Qmail needs 16 seconds per 1000 destinations. +Default configuration, concurrencyremote = 100. + +/usr/bin/time ./smtp-source -r 1000 fist + 1.16 real 0.09 user 0.24 sys +Jun 8 14:57:17 fist qmail: 865796237.334359 new msg 39906 +Jun 8 14:57:32 fist qmail: 865796252.632756 delivery 2154: success: 168.100.189 +Total time: 15 seconds + +/usr/bin/time ./smtp-source -r 2000 fist + 1.99 real 0.23 user 0.45 sys +Jun 8 14:58:11 fist qmail: 865796291.817523 new msg 39907 +Jun 8 14:58:43 fist qmail: 865796323.174117 delivery 4116: success: 168.100.189 +Total time: 32 seconds + +/usr/bin/time ./smtp-source -r 5000 fist + 4.63 real 0.58 user 1.10 sys +Jun 8 14:59:23 fist qmail: 865796363.346735 new msg 39908 +Jun 8 15:00:43 fist qmail: 865796443.209168 delivery 9153: success: 168.100.189 +Total time: 80 seconds diff --git a/src/smtpstone/qmail-relay b/src/smtpstone/qmail-relay new file mode 100644 index 0000000..8718e5e --- /dev/null +++ b/src/smtpstone/qmail-relay @@ -0,0 +1,20 @@ +Qmail needs 54 seconds to relay 100 messages with one recipient. +Default configuration, concurrencyremote = 100. + +spike_4% /usr/bin/time ./smtp-source -s 5 -m 100 fist + 43.77 real 0.05 user 0.23 sys +Jun 8 12:08:36 fist qmail: 865786116.744366 new msg 39901 +Jun 8 12:09:32 fist qmail: 865786172.791473 end msg 39921 +Total time: 56 sec + +/usr/bin/time ./smtp-source -s 10 -m 100 fist + 26.66 real 0.06 user 0.26 sys +Jun 8 12:06:20 fist qmail: 865785980.185885 new msg 39901 +Jun 8 12:07:14 fist qmail: 865786034.306429 end msg 39920 +Total time: 54 sec + +spike_8% /usr/bin/time ./smtp-source -s 20 -m 100 fist + 20.94 real 0.11 user 0.27 sys +Jun 8 12:10:52 fist qmail: 865786252.412648 new msg 39901 +Jun 8 12:11:46 fist qmail: 865786306.080605 end msg 39962 +Total time: 54 sec diff --git a/src/smtpstone/qmqp-sink.c b/src/smtpstone/qmqp-sink.c new file mode 100644 index 0000000..2fcbdd1 --- /dev/null +++ b/src/smtpstone/qmqp-sink.c @@ -0,0 +1,325 @@ +/*++ +/* NAME +/* qmqp-sink 1 +/* SUMMARY +/* parallelized QMQP test server +/* SYNOPSIS +/* .fi +/* \fBqmqp-sink\fR [\fB-46cv\fR] [\fB-x \fItime\fR] +/* [\fBinet:\fR][\fIhost\fR]:\fIport\fR \fIbacklog\fR +/* +/* \fBqmqp-sink\fR [\fB-46cv\fR] [\fB-x \fItime\fR] +/* \fBunix:\fR\fIpathname\fR \fIbacklog\fR +/* DESCRIPTION +/* \fBqmqp-sink\fR listens on the named host (or address) and port. +/* It receives messages from the network and throws them away. +/* The purpose is to measure QMQP client performance, not protocol +/* compliance. +/* Connections can be accepted on IPv4 or IPv6 endpoints, or on +/* UNIX-domain sockets. +/* IPv4 and IPv6 are the default. +/* This program is the complement of the \fBqmqp-source\fR(1) program. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Support IPv4 only. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Support IPv6 only. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP \fB-c\fR +/* Display a running counter that is updated whenever a delivery +/* is completed. +/* .IP \fB-v\fR +/* Increase verbosity. Specify \fB-v -v\fR to see some of the QMQP +/* conversation. +/* .IP "\fB-x \fItime\fR" +/* Terminate after \fItime\fR seconds. This is to facilitate memory +/* leak testing. +/* SEE ALSO +/* qmqp-source(1), QMQP message generator +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* Application-specific. */ + +typedef struct { + VSTREAM *stream; /* client connection */ + int count; /* bytes to go */ +} SINK_STATE; + +static int var_tmout; +static VSTRING *buffer; +static void disconnect(SINK_STATE *); +static int count_deliveries; +static int counter; + +/* send_reply - finish conversation */ + +static void send_reply(SINK_STATE *state) +{ + vstring_sprintf(buffer, "%cOk", QMQP_STAT_OK); + NETSTRING_PUT_BUF(state->stream, buffer); + netstring_fflush(state->stream); + if (count_deliveries) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + disconnect(state); +} + +/* read_data - read over-all netstring data */ + +static void read_data(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + int fd = vstream_fileno(state->stream); + int count; + + /* + * Refill the VSTREAM buffer, if necessary. + */ + if (VSTREAM_GETC(state->stream) == VSTREAM_EOF) + netstring_except(state->stream, vstream_ftimeout(state->stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + state->count--; + + /* + * Flush the VSTREAM buffer. As documented, vstream_fseek() discards + * unread input. + */ + if ((count = vstream_peek(state->stream)) > 0) { + state->count -= count; + if (state->count <= 0) { + send_reply(state); + return; + } + vstream_fpurge(state->stream, VSTREAM_PURGE_BOTH); + } + + /* + * Do not block while waiting for the arrival of more data. + */ + event_disable_readwrite(fd); + event_enable_read(fd, read_data, context); +} + +/* read_length - read over-all netstring length */ + +static void read_length(int event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown error reading input"); + + case NETSTRING_ERR_TIME: + msg_panic("attempt to read non-readable socket"); + /* NOTREACHED */ + + case NETSTRING_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case NETSTRING_ERR_FORMAT: + msg_warn("netstring format error"); + disconnect(state); + return; + + case NETSTRING_ERR_SIZE: + msg_warn("netstring size error"); + disconnect(state); + return; + + /* + * Include the netstring terminator in the read byte count. This + * violates abstractions. + */ + case 0: + state->count = netstring_get_length(state->stream) + 1; + read_data(event, context); + return; + } +} + +/* disconnect - handle disconnection events */ + +static void disconnect(SINK_STATE *state) +{ + event_disable_readwrite(vstream_fileno(state->stream)); + vstream_fclose(state->stream); + myfree((void *) state); +} + +/* connect_event - handle connection events */ + +static void connect_event(int unused_event, void *context) +{ + int sock = CAST_ANY_PTR_TO_INT(context); + struct sockaddr_storage ss; + SOCKADDR_SIZE len = sizeof(ss); + struct sockaddr *sa = (struct sockaddr *) &ss; + SINK_STATE *state; + int fd; + + if ((fd = accept(sock, sa, &len)) >= 0) { + if (msg_verbose) + msg_info("connect (%s)", +#ifdef AF_LOCAL + sa->sa_family == AF_LOCAL ? "AF_LOCAL" : +#else + sa->sa_family == AF_UNIX ? "AF_UNIX" : +#endif + sa->sa_family == AF_INET ? "AF_INET" : +#ifdef AF_INET6 + sa->sa_family == AF_INET6 ? "AF_INET6" : +#endif + "unknown protocol family"); + non_blocking(fd, NON_BLOCKING); + state = (SINK_STATE *) mymalloc(sizeof(*state)); + state->stream = vstream_fdopen(fd, O_RDWR); + vstream_tweak_sock(state->stream); + netstring_setup(state->stream, var_tmout); + event_enable_read(fd, read_length, (void *) state); + } +} + +/* terminate - voluntary exit */ + +static void terminate(int unused_event, void *unused_context) +{ + exit(0); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s [-cv] [-x time] [host]:port backlog", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + int sock; + int backlog; + int ch; + int ttl; + const char *protocols = INET_PROTO_NAME_ALL; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Fix 20051207. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Initialize diagnostics. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "46cvx:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case 'c': + count_deliveries++; + break; + case 'v': + msg_verbose++; + break; + case 'x': + if ((ttl = atoi(optarg)) <= 0) + usage(argv[0]); + event_request_timer(terminate, (void *) 0, ttl); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 2) + usage(argv[0]); + if ((backlog = atoi(argv[optind + 1])) <= 0) + usage(argv[0]); + + /* + * Initialize. + */ + (void) inet_proto_init("protocols", protocols); + buffer = vstring_alloc(1024); + if (strncmp(argv[optind], "unix:", 5) == 0) { + sock = unix_listen(argv[optind] + 5, backlog, BLOCKING); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + sock = inet_listen(argv[optind], backlog, BLOCKING); + } + + /* + * Start the event handler. + */ + event_enable_read(sock, connect_event, CAST_INT_TO_VOID_PTR(sock)); + for (;;) + event_loop(-1); +} diff --git a/src/smtpstone/qmqp-source.c b/src/smtpstone/qmqp-source.c new file mode 100644 index 0000000..9231aec --- /dev/null +++ b/src/smtpstone/qmqp-source.c @@ -0,0 +1,673 @@ +/*++ +/* NAME +/* qmqp-source 1 +/* SUMMARY +/* parallelized QMQP test generator +/* SYNOPSIS +/* .fi +/* \fBqmqp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* +/* \fBqmqp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR +/* DESCRIPTION +/* \fBqmqp-source\fR connects to the named host and TCP port (default 628) +/* and sends one or more messages to it, either sequentially +/* or in parallel. The program speaks the QMQP protocol. +/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers. +/* IPv4 and IPv6 are the default. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Connect to the server with IPv4. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Connect to the server with IPv6. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP \fB-c\fR +/* Display a running counter that is incremented each time +/* a delivery completes. +/* .IP "\fB-C \fIcount\fR" +/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times +/* before giving up. The default count is 1. Specify a larger count in +/* order to work around a problem with TCP/IP stacks that send RESET +/* when the listen queue is full. +/* .IP "\fB-f \fIfrom\fR" +/* Use the specified sender address (default: ). +/* .IP "\fB-l \fIlength\fR" +/* Send \fIlength\fR bytes as message payload. The length +/* includes the message headers. +/* .IP "\fB-m \fImessage_count\fR" +/* Send the specified number of messages (default: 1). +/* .IP "\fB-M \fImyhostname\fR" +/* Use the specified hostname or [address] in the default +/* sender and recipient addresses, instead of the machine +/* hostname. +/* .IP "\fB-r \fIrecipient_count\fR" +/* Send the specified number of recipients per transaction (default: 1). +/* Recipient names are generated by prepending a number to the +/* recipient address. +/* .IP "\fB-s \fIsession_count\fR" +/* Run the specified number of QMQP sessions in parallel (default: 1). +/* .IP "\fB-t \fIto\fR" +/* Use the specified recipient address (default: ). +/* .IP "\fB-R \fIinterval\fR" +/* Wait for a random period of time 0 <= n <= interval between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP \fB-v\fR +/* Make the program more verbose, for debugging purposes. +/* .IP "\fB-w \fIinterval\fR" +/* Wait a fixed time between messages. +/* Suspending one thread does not affect other delivery threads. +/* SEE ALSO +/* qmqp-sink(1), QMQP message dump +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#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 +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + + /* + * Per-session data structure with state. + * + * This software can maintain multiple parallel connections to the same QMQP + * server. However, it makes no more than one connection request at a time + * to avoid overwhelming the server with SYN packets and having to back off. + * Back-off would screw up the benchmark. Pending connection requests are + * kept in a linear list. + */ +typedef struct SESSION { + int xfer_count; /* # of xfers in session */ + int rcpt_done; /* # of recipients done */ + int rcpt_count; /* # of recipients to go */ + VSTREAM *stream; /* open connection */ + int connect_count; /* # of connect()s to retry */ + struct SESSION *next; /* connect() queue linkage */ +} SESSION; + +static SESSION *last_session; /* connect() queue tail */ + +static VSTRING *buffer; +static int var_line_limit = 10240; +static int var_timeout = 300; +static const char *var_myhostname; +static int session_count; +static int message_count = 1; +static struct sockaddr_storage ss; + +#undef sun +static struct sockaddr_un sun; +static struct sockaddr *sa; +static int sa_length; +static int recipients = 1; +static char *defaddr; +static char *recipient; +static char *sender; +static int message_length = 1024; +static int count = 0; +static int counter = 0; +static int connect_count = 1; +static int random_delay = 0; +static int fixed_delay = 0; +static const char *mydate; +static int mypid; + +static void enqueue_connect(SESSION *); +static void start_connect(SESSION *); +static void connect_done(int, void *); + +static void send_data(SESSION *); +static void receive_reply(int, void *); + +static VSTRING *message_buffer; +static VSTRING *sender_buffer; +static VSTRING *recipient_buffer; + +/* Silly little macros. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* random_interval - generate a random value in 0 .. (small) interval */ + +static int random_interval(int interval) +{ + return (rand() % (interval + 1)); +} + +/* socket_error - look up and reset the last socket error */ + +static int socket_error(int sock) +{ + int error; + SOCKOPT_SIZE error_len; + + /* + * Some Solaris 2 versions have getsockopt() itself return the error, + * instead of returning it via the parameter list. + */ + error = 0; + error_len = sizeof(error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0) + return (-1); + if (error) { + errno = error; + return (-1); + } + + /* + * No problems. + */ + return (0); +} + +/* exception_text - translate exceptions from the netstring module */ + +static char *exception_text(int except) +{ + ; + + switch (except) { + case NETSTRING_ERR_EOF: + return ("lost connection"); + case NETSTRING_ERR_TIME: + return ("timeout"); + case NETSTRING_ERR_FORMAT: + return ("netstring format error"); + case NETSTRING_ERR_SIZE: + return ("netstring size exceeds limit"); + default: + msg_panic("exception_text: unknown exception %d", except); + } + /* NOTREACHED */ +} + +/* startup - connect to server but do not wait */ + +static void startup(SESSION *session) +{ + if (message_count-- <= 0) { + myfree((void *) session); + session_count--; + return; + } + enqueue_connect(session); +} + +/* start_event - invoke startup from timer context */ + +static void start_event(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + startup(session); +} + +/* start_another - start another session */ + +static void start_another(SESSION *session) +{ + if (random_delay > 0) { + event_request_timer(start_event, (void *) session, + random_interval(random_delay)); + } else if (fixed_delay > 0) { + event_request_timer(start_event, (void *) session, fixed_delay); + } else { + startup(session); + } +} + +/* enqueue_connect - queue a connection request */ + +static void enqueue_connect(SESSION *session) +{ + session->next = 0; + if (last_session == 0) { + last_session = session; + start_connect(session); + } else { + last_session->next = session; + last_session = session; + } +} + +/* dequeue_connect - connection request completed */ + +static void dequeue_connect(SESSION *session) +{ + if (session == last_session) { + if (session->next != 0) + msg_panic("dequeue_connect: queue ends after last"); + last_session = 0; + } else { + if (session->next == 0) + msg_panic("dequeue_connect: queue ends before last"); + start_connect(session->next); + } +} + +/* fail_connect - handle failed startup */ + +static void fail_connect(SESSION *session) +{ + if (session->connect_count-- == 1) + msg_fatal("connect: %m"); + msg_warn("connect: %m"); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; +#ifdef MISSING_USLEEP + doze(10); +#else + usleep(10); +#endif + start_connect(session); +} + +/* start_connect - start TCP handshake */ + +static void start_connect(SESSION *session) +{ + int fd; + struct linger linger; + + /* + * Some systems don't set the socket error when connect() fails early + * (loopback) so we must deal with the error immediately, rather than + * retrieving it later with getsockopt(). We can't use MSG_PEEK to + * distinguish between server disconnect and connection refused. + */ + if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("socket: %m"); + (void) non_blocking(fd, NON_BLOCKING); + linger.l_onoff = 1; + linger.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger, + sizeof(linger)) < 0) + msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger); + session->stream = vstream_fdopen(fd, O_RDWR); + event_enable_write(fd, connect_done, (void *) session); + netstring_setup(session->stream, var_timeout); + if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS) + fail_connect(session); +} + +/* connect_done - send message sender info */ + +static void connect_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int fd = vstream_fileno(session->stream); + + /* + * Try again after some delay when the connection failed, in case they + * run a Mickey Mouse protocol stack. + */ + if (socket_error(fd) < 0) { + fail_connect(session); + } else { + dequeue_connect(session); + non_blocking(fd, BLOCKING); + event_disable_readwrite(fd); + /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) + vstream_tweak_tcp(session->stream); + send_data(session); + } +} + +/* send_data - send message+sender+recipients */ + +static void send_data(SESSION *session) +{ + int fd = vstream_fileno(session->stream); + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + + /* + * Send the message content, by wrapping three netstrings into an + * over-all netstring. + * + * XXX This should be done more carefully to avoid blocking when sending + * large messages over slow networks. + */ + netstring_put_multi(session->stream, + STR(message_buffer), LEN(message_buffer), + STR(sender_buffer), LEN(sender_buffer), + STR(recipient_buffer), LEN(recipient_buffer), + (char *) 0); + netstring_fflush(session->stream); + + /* + * Wake me up when the server replies or when something bad happens. + */ + event_enable_read(fd, receive_reply, (void *) session); +} + +/* receive_reply - read server reply */ + +static void receive_reply(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while receiving server reply", exception_text(except)); + + /* + * Receive and process the server reply. + */ + netstring_get(session->stream, buffer, var_line_limit); + if (msg_verbose) + vstream_printf("<< %.*s\n", (int) LEN(buffer), STR(buffer)); + if (STR(buffer)[0] != QMQP_STAT_OK) + msg_fatal("%s error: %.*s", + STR(buffer)[0] == QMQP_STAT_RETRY ? "recoverable" : + STR(buffer)[0] == QMQP_STAT_HARD ? "unrecoverable" : + "unknown", (int) LEN(buffer) - 1, STR(buffer) + 1); + + /* + * Update the optional running counter. + */ + if (count) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + + /* + * Finish this session. QMQP sends only one message per session. + */ + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s -cv -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -R delay -w delay host[:port]", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - parse JCL and start the machine */ + +int main(int argc, char **argv) +{ + SESSION *session; + char *host; + char *port; + char *path; + int path_len; + int sessions = 1; + int ch; + ssize_t len; + int n; + int i; + char *buf; + const char *parse_err; + struct addrinfo *res; + int aierr; + const char *protocols = INET_PROTO_NAME_ALL; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + signal(SIGPIPE, SIG_IGN); + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "46cC:f:l:m:M:r:R:s:t:vw:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case 'c': + count++; + break; + case 'C': + if ((connect_count = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'f': + sender = optarg; + break; + case 'l': + if ((message_length = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'm': + if ((message_count = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'M': + if (*optarg == '[') { + if (!valid_mailhost_literal(optarg, DO_GRIPE)) + msg_fatal("bad address literal: %s", optarg); + } else { + if (!valid_hostname(optarg, DO_GRIPE)) + msg_fatal("bad hostname: %s", optarg); + } + var_myhostname = optarg; + break; + case 'r': + if ((recipients = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'R': + if (fixed_delay > 0 || (random_delay = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 's': + if ((sessions = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 't': + recipient = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'w': + if (random_delay > 0 || (fixed_delay = atoi(optarg)) <= 0) + usage(argv[0]); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 1) + usage(argv[0]); + + if (random_delay > 0) + srand(getpid()); + + /* + * Translate endpoint address to internal form. + */ + (void) inet_proto_init("protocols", protocols); + if (strncmp(argv[optind], "unix:", 5) == 0) { + path = argv[optind] + 5; + path_len = strlen(path); + if (path_len >= (int) sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", path); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len); + sa = (struct sockaddr *) &sun; + sa_length = sizeof(sun); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + buf = mystrdup(argv[optind]); + if ((parse_err = host_port(buf, &host, (char *) 0, &port, "628")) != 0) + msg_fatal("%s: %s", argv[optind], parse_err); + if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) + msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr)); + myfree(buf); + sa = (struct sockaddr *) &ss; + if (res->ai_addrlen > sizeof(ss)) + msg_fatal("address length %d > buffer length %d", + (int) res->ai_addrlen, (int) sizeof(ss)); + memcpy((void *) sa, res->ai_addr, res->ai_addrlen); + sa_length = res->ai_addrlen; +#ifdef HAS_SA_LEN + sa->sa_len = sa_length; +#endif + freeaddrinfo(res); + } + + /* + * Allocate space for temporary buffer. + */ + buffer = vstring_alloc(100); + + /* + * Make sure we have sender and recipient addresses. + */ + if (var_myhostname == 0) + var_myhostname = get_hostname(); + if (sender == 0 || recipient == 0) { + vstring_sprintf(buffer, "foo@%s", var_myhostname); + defaddr = mystrdup(vstring_str(buffer)); + if (sender == 0) + sender = defaddr; + if (recipient == 0) + recipient = defaddr; + } + + /* + * Prepare some results that may be used multiple times: the message + * content netstring, the sender netstring, and the recipient netstrings. + */ + mydate = mail_date(time((time_t *) 0)); + mypid = getpid(); + + message_buffer = vstring_alloc(message_length + 200); + vstring_sprintf(buffer, + "From: <%s>\nTo: <%s>\nDate: %s\nMessage-Id: <%d@%s>\n\n", + sender, recipient, mydate, mypid, var_myhostname); + for (n = 1; LEN(buffer) < message_length; n++) { + for (i = 0; i < n && i < 79; i++) + VSTRING_ADDCH(buffer, 'X'); + VSTRING_ADDCH(buffer, '\n'); + } + STR(buffer)[message_length - 1] = '\n'; + netstring_memcpy(message_buffer, STR(buffer), message_length); + + len = strlen(sender); + sender_buffer = vstring_alloc(len); + netstring_memcpy(sender_buffer, sender, len); + + if (recipients == 1) { + len = strlen(recipient); + recipient_buffer = vstring_alloc(len); + netstring_memcpy(recipient_buffer, recipient, len); + } else { + recipient_buffer = vstring_alloc(100); + for (n = 0; n < recipients; n++) { + vstring_sprintf(buffer, "%d%s", n, recipient); + netstring_memcat(recipient_buffer, STR(buffer), LEN(buffer)); + } + } + + /* + * Start sessions. + */ + while (sessions-- > 0) { + session = (SESSION *) mymalloc(sizeof(*session)); + session->stream = 0; + session->xfer_count = 0; + session->connect_count = connect_count; + session->next = 0; + session_count++; + startup(session); + } + for (;;) { + event_loop(-1); + if (session_count <= 0 && message_count <= 0) { + if (count) { + VSTREAM_PUTC('\n', VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + } + exit(0); + } + } +} diff --git a/src/smtpstone/smtp-sink.c b/src/smtpstone/smtp-sink.c new file mode 100644 index 0000000..c34f21d --- /dev/null +++ b/src/smtpstone/smtp-sink.c @@ -0,0 +1,1643 @@ +/*++ +/* NAME +/* smtp-sink 1 +/* SUMMARY +/* parallelized SMTP/LMTP test server +/* SYNOPSIS +/* .fi +/* \fBsmtp-sink\fR [\fIoptions\fR] [\fBinet:\fR][\fIhost\fR]:\fIport\fR +/* \fIbacklog\fR +/* +/* \fBsmtp-sink\fR [\fIoptions\fR] \fBunix:\fR\fIpathname\fR \fIbacklog\fR +/* DESCRIPTION +/* \fBsmtp-sink\fR listens on the named host (or address) and port. +/* It takes SMTP messages from the network and throws them away. +/* The purpose is to measure client performance, not protocol +/* compliance. +/* +/* \fBsmtp-sink\fR may also be configured to capture each mail +/* delivery transaction to file. Since disk latencies are large +/* compared to network delays, this mode of operation can +/* reduce the maximal performance by several orders of magnitude. +/* +/* Connections can be accepted on IPv4 or IPv6 endpoints, or on +/* UNIX-domain sockets. +/* IPv4 and IPv6 are the default. +/* This program is the complement of the \fBsmtp-source\fR(1) program. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Support IPv4 only. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Support IPv6 only. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP \fB-8\fR +/* Do not announce 8BITMIME support. +/* .IP \fB-a\fR +/* Do not announce SASL authentication support. +/* .IP "\fB-A \fIdelay\fR" +/* Wait \fIdelay\fR seconds after responding to DATA, then +/* abort prematurely with a 550 reply status. Do not read +/* further input from the client; this is an attempt to block +/* the client before it sends ".". Specify a zero delay value +/* to abort immediately. +/* .IP "\fB-b \fIsoft-bounce-reply\fR" +/* Use \fIsoft-bounce-reply\fR for soft reject responses. The +/* default reply is "450 4.3.0 Error: command failed". +/* .IP "\fB-B \fIhard-bounce-reply\fR" +/* Use \fIhard-bounce-reply\fR for hard reject responses. The +/* default reply is "500 5.3.0 Error: command failed". +/* .IP \fB-c\fR +/* Display running counters that are updated whenever an SMTP +/* session ends, a QUIT command is executed, or when "." is +/* received. +/* .IP \fB-C\fR +/* Disable XCLIENT support. +/* .IP "\fB-d \fIdump-template\fR" +/* Dump each mail transaction to a single-message file whose +/* name is created by expanding the \fIdump-template\fR via +/* strftime(3) and appending a pseudo-random hexadecimal number +/* (example: "%Y%m%d%H/%M." expands into "2006081203/05.809a62e3"). +/* If the template contains "/" characters, missing directories +/* are created automatically. The message dump format is +/* described below. +/* .sp +/* Note: this option keeps one capture file open for every +/* mail transaction in progress. +/* .IP "\fB-D \fIdump-template\fR" +/* Append mail transactions to a multi-message dump file whose +/* name is created by expanding the \fIdump-template\fR via +/* strftime(3). +/* If the template contains "/" characters, missing directories +/* are created automatically. The message dump format is +/* described below. +/* .sp +/* Note: this option keeps one capture file open for every +/* mail transaction in progress. +/* .IP \fB-e\fR +/* Do not announce ESMTP support. +/* .IP \fB-E\fR +/* Do not announce ENHANCEDSTATUSCODES support. +/* .IP "\fB-f \fIcommand,command,...\fR" +/* Reject the specified commands with a hard (5xx) error code. +/* This option implies \fB-p\fR. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP \fB-F\fR +/* Disable XFORWARD support. +/* .IP "\fB-h\fI hostname\fR" +/* Use \fIhostname\fR in the SMTP greeting, in the HELO response, +/* and in the EHLO response. The default hostname is "smtp-sink". +/* .IP "\fB-H\fI delay\fR" +/* Delay the first read operation after receiving DATA (time +/* in seconds). Combine with a large test message and a small +/* TCP window size (see the \fB-T\fR option) to test the Postfix +/* client write_wait() implementation. +/* .IP \fB-L\fR +/* Enable LMTP instead of SMTP. +/* .IP "\fB-m \fIcount\fR (default: 256)" +/* An upper bound on the maximal number of simultaneous +/* connections that \fBsmtp-sink\fR will handle. This prevents +/* the process from running out of file descriptors. Excess +/* connections will stay queued in the TCP/IP stack. +/* .IP "\fB-M \fIcount\fR" +/* Terminate after receiving \fIcount\fR messages. +/* .IP "\fB-n \fIcount\fR" +/* Terminate after \fIcount\fR sessions. +/* .IP \fB-N\fR +/* Do not announce support for DSN. +/* .IP \fB-p\fR +/* Do not announce support for ESMTP command pipelining. +/* .IP \fB-P\fR +/* Change the server greeting so that it appears to come through +/* a CISCO PIX system. Implies \fB-e\fR. +/* .IP "\fB-q \fIcommand,command,...\fR" +/* Disconnect (without replying) after receiving one of the +/* specified commands. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-Q \fIcommand,command,...\fR" +/* Send a 421 reply and disconnect after receiving one +/* of the specified commands. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-r \fIcommand,command,...\fR" +/* Reject the specified commands with a soft (4xx) error code. +/* This option implies \fB-p\fR. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-R \fIroot-directory\fR" +/* Change the process root directory to the specified location. +/* This option requires super-user privileges. See also the +/* \fB-u\fR option. +/* .IP "\fB-s \fIcommand,command,...\fR" +/* Log the named commands to syslogd. +/* .sp +/* Examples of commands are CONNECT, HELO, EHLO, LHLO, MAIL, RCPT, VRFY, +/* DATA, ., RSET, NOOP, and QUIT. Separate command names by +/* white space or commas, and use quotes to protect white space +/* from the shell. Command names are case-insensitive. +/* .IP "\fB-S start-string\fR" +/* An optional string that is prepended to each message that is +/* written to a dump file (see the dump file format description +/* below). The following C escape sequences are supported: \ea +/* (bell), \eb (backspace), \ef (formfeed), \en (newline), \er +/* (carriage return), \et (horizontal tab), \ev (vertical tab), +/* \e\fIddd\fR (up to three octal digits) and \e\e (the backslash +/* character). +/* .IP "\fB-t \fItimeout\fR (default: 100)" +/* Limit the time for receiving a command or sending a response. +/* The time limit is specified in seconds. +/* .IP "\fB-T \fIwindowsize\fR" +/* Override the default TCP window size. To work around +/* broken TCP window scaling implementations, specify a +/* value > 0 and < 65536. +/* .IP "\fB-u \fIusername\fR" +/* Switch to the specified user privileges after opening the +/* network socket and optionally changing the process root +/* directory. This option is required when the process runs +/* with super-user privileges. See also the \fB-R\fR option. +/* .IP \fB-v\fR +/* Show the SMTP conversations. +/* .IP "\fB-w \fIdelay\fR" +/* Wait \fIdelay\fR seconds before responding to a DATA command. +/* .IP "\fB-W \fIcommand:delay[:odds]\fR" +/* Wait \fIdelay\fR seconds before responding to \fIcommand\fR. +/* If \fIodds\fR is also specified (a number between 1-99 +/* inclusive), wait for a random multiple of \fIdelay\fR. The +/* random multiplier is equal to the number of times the program +/* needs to roll a dice with a range of 0..99 inclusive, before +/* the dice produces a result greater than or equal to \fIodds\fR. +/* .IP [\fBinet:\fR][\fIhost\fR]:\fIport\fR +/* Listen on network interface \fIhost\fR (default: any interface) +/* TCP port \fIport\fR. Both \fIhost\fR and \fIport\fR may be +/* specified in numeric or symbolic form. +/* .IP \fBunix:\fR\fIpathname\fR +/* Listen on the UNIX-domain socket at \fIpathname\fR. +/* .IP \fIbacklog\fR +/* The maximum length of the queue of pending connections, +/* as defined by the \fBlisten\fR(2) system call. +/* DUMP FILE FORMAT +/* .ad +/* .fi +/* Each dumped message contains a sequence of text lines, +/* terminated with the newline character. The sequence of +/* information is as follows: +/* .IP \(bu +/* The optional string specified with the \fB-S\fR option. +/* .IP \(bu +/* The \fBsmtp-sink\fR generated headers as documented below. +/* .IP \(bu +/* The message header and body as received from the SMTP client. +/* .IP \(bu +/* An empty line. +/* .PP +/* The format of the \fBsmtp-sink\fR generated headers is as +/* follows: +/* .IP "\fBX-Client-Addr: \fItext\fR" +/* The client IP address without enclosing []. An IPv6 address +/* is prefixed with "ipv6:". This record is always present. +/* .IP "\fBX-Client-Proto: \fItext\fR" +/* The client protocol: SMTP, ESMTP or LMTP. This record is +/* always present. +/* .IP "\fBX-Helo-Args: \fItext\fR" +/* The arguments of the last HELO or EHLO command before this +/* mail delivery transaction. This record is present only if +/* the client sent a recognizable HELO or EHLO command before +/* the DATA command. +/* .IP "\fBX-Mail-Args: \fItext\fR" +/* The arguments of the MAIL command that started this mail +/* delivery transaction. This record is present exactly once. +/* .IP "\fBX-Rcpt-Args: \fItext\fR" +/* The arguments of an RCPT command within this mail delivery +/* transaction. There is one record for each RCPT command, and +/* they are in the order as sent by the client. +/* .IP "\fBReceived: \fItext\fR" +/* A message header for compatibility with mail processing +/* software. This three-line header marks the end of the headers +/* provided by \fBsmtp-sink\fR, and is formatted as follows: +/* .RS +/* .IP "\fBfrom \fIhelo\fR ([\fIaddr\fR])" +/* The HELO or EHLO command argument and client IP address. +/* If the client did not send HELO or EHLO, the client IP +/* address is used instead. +/* .IP "\fBby \fIhost\fB (smtp-sink) with \fIproto\fB id \fIrandom\fB;\fR" +/* The hostname specified with the \fB-h\fR option, the client +/* protocol (see \fBX-Client-Proto\fR above), and the pseudo-random +/* portion of the per-message capture file name. +/* .IP \fItime-stamp\fR +/* A time stamp as defined in RFC 2822. +/* .RE +/* SEE ALSO +/* smtp-source(1), SMTP/LMTP message generator +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + +typedef struct SINK_STATE { + VSTREAM *stream; + VSTRING *buffer; + int data_state; + int (*read_fn) (struct SINK_STATE *); + int in_mail; + int rcpts; + char *push_back_ptr; + /* Capture file information for fake Received: header */ + MAI_HOSTADDR_STR client_addr; /* IP address */ + char *addr_prefix; /* ipv6: or empty */ + char *helo_args; /* text after HELO or EHLO */ + const char *client_proto; /* SMTP, ESMTP, LMTP */ + time_t start_time; /* MAIL command time */ + int id; /* pseudo-random */ + VSTREAM *dump_file; /* dump file or null */ + void (*delayed_response) (struct SINK_STATE *state, const char *); + char *delayed_args; +} SINK_STATE; + +#define ST_ANY 0 +#define ST_CR 1 +#define ST_CR_LF 2 +#define ST_CR_LF_DOT 3 +#define ST_CR_LF_DOT_CR 4 +#define ST_CR_LF_DOT_CR_LF 5 + +#define PUSH_BACK_PEEK(state) (*(state)->push_back_ptr != 0) +#define PUSH_BACK_GET(state) (*(state)->push_back_ptr++) +#define PUSH_BACK_SET(state, text) ((state)->push_back_ptr = (text)) + +#ifndef DEF_MAX_CLIENT_COUNT +#define DEF_MAX_CLIENT_COUNT 256 +#endif + +#define SOFT_ERROR_RESP "450 4.3.0 Error: command failed" +#define HARD_ERROR_RESP "500 5.3.0 Error: command failed" + + /* + * We can't rely on vstream auto-flushing, so we have to prepare for the + * next read request. + */ +#define SMTP_FLUSH(fp) do { \ + if (vstream_peek(fp) <= 0 && readable(vstream_fileno(fp)) <= 0) \ + smtp_flush(fp); \ + } while (0) + +static int var_tmout = 100; +static int var_max_line_length = 2048; +static char *var_myhostname; +static char *soft_error_resp = SOFT_ERROR_RESP; +static char *hard_error_resp = HARD_ERROR_RESP; +static int command_read(SINK_STATE *); +static int data_read(SINK_STATE *); +static void disconnect(SINK_STATE *); +static void read_timeout(int, void *); +static void read_event(int, void *); +static int show_count; +static int sess_count; +static int quit_count; +static int mesg_count; +static int max_quit_count; +static int max_msg_quit_count; +static int disable_pipelining; +static int disable_8bitmime; +static int disable_esmtp; +static int enable_lmtp; +static int pretend_pix; +static int disable_saslauth; +static int disable_xclient; +static int disable_xforward; +static int disable_enh_status; +static int disable_dsn; +static int max_client_count = DEF_MAX_CLIENT_COUNT; +static int client_count; +static int sock; +static int abort_delay = -1; +static int data_read_delay = 0; + +static char *single_template; /* individual template */ +static char *shared_template; /* shared template */ +static VSTRING *start_string; /* dump content prefix */ + +static const INET_PROTO_INFO *proto_info; + +#define STR(x) vstring_str(x) + +/* do_stats - show counters */ + +static void do_stats(void) +{ + vstream_printf("sess=%d quit=%d mesg=%d\r", + sess_count, quit_count, mesg_count); + vstream_fflush(VSTREAM_OUT); +} + +/* hard_err_resp - generic hard error response */ + +static void hard_err_resp(SINK_STATE *state) +{ + smtp_printf(state->stream, "%s", hard_error_resp); + SMTP_FLUSH(state->stream); +} + +/* soft_err_resp - generic soft error response */ + +static void soft_err_resp(SINK_STATE *state) +{ + smtp_printf(state->stream, "%s", soft_error_resp); + SMTP_FLUSH(state->stream); +} + +/* exp_path_template - expand template pathname, static result */ + +static VSTRING *exp_path_template(const char *template, time_t start_time) +{ + static VSTRING *path_buf = 0; + struct tm *lt; + + if (path_buf == 0) + path_buf = vstring_alloc(100); + else + VSTRING_RESET(path_buf); + lt = localtime(&start_time); + while (strftime(STR(path_buf), vstring_avail(path_buf), template, lt) == 0) + VSTRING_SPACE(path_buf, vstring_avail(path_buf) + 100); + VSTRING_SKIP(path_buf); + return (path_buf); +} + +/* make_parent_dir - create parent directory or bust */ + +static void make_parent_dir(const char *path, mode_t mode) +{ + const char *parent; + + parent = sane_dirname((VSTRING *) 0, path); + if (make_dirs(parent, mode) < 0) + msg_fatal("mkdir %s: %m", parent); +} + +/* mail_file_open - open mail capture file */ + +static void mail_file_open(SINK_STATE *state) +{ + const char *myname = "mail_file_open"; + VSTRING *path_buf; + ssize_t len; + int tries = 0; + + /* + * Save the start time for later. + */ + time(&(state->start_time)); + + /* + * Expand the per-message dumpfile pathname template. + */ + path_buf = exp_path_template(single_template, state->start_time); + + /* + * Append a random hexadecimal string to the pathname and create a new + * file. Retry with a different path if the file already exists. Create + * intermediate directories on the fly when the template specifies + * multiple pathname segments. + */ +#define ID_FORMAT "%08x" + + for (len = VSTRING_LEN(path_buf); /* void */ ; vstring_truncate(path_buf, len)) { + if (++tries > 100) + msg_fatal("%s: something is looping", myname); + state->id = myrand(); + vstring_sprintf_append(path_buf, ID_FORMAT, state->id); + if ((state->dump_file = vstream_fopen(STR(path_buf), + O_RDWR | O_CREAT | O_EXCL, + 0644)) != 0) { + break; + } else if (errno == EEXIST) { + continue; + } else if (errno == ENOENT) { + make_parent_dir(STR(path_buf), 0755); + continue; + } else { + msg_fatal("open %s: %m", STR(path_buf)); + } + } + + /* + * Don't leave temporary files behind. + */ + if (shared_template != 0 && unlink(STR(path_buf)) < 0) + msg_fatal("unlink %s: %m", STR(path_buf)); + + /* + * Do initial header records. + */ + if (start_string) + vstream_fprintf(state->dump_file, "%s", STR(start_string)); + vstream_fprintf(state->dump_file, "X-Client-Addr: %s%s\n", + state->addr_prefix, state->client_addr.buf); + vstream_fprintf(state->dump_file, "X-Client-Proto: %s\n", state->client_proto); + if (state->helo_args) + vstream_fprintf(state->dump_file, "X-Helo-Args: %s\n", state->helo_args); + /* Note: there may be more than one recipient. */ +} + +/* mail_file_finish_header - do final smtp-sink generated header records */ + +static void mail_file_finish_header(SINK_STATE *state) +{ + if (state->helo_args) + vstream_fprintf(state->dump_file, "Received: from %s ([%s%s])\n", + state->helo_args, state->addr_prefix, + state->client_addr.buf); + else + vstream_fprintf(state->dump_file, "Received: from [%s%s] ([%s%s])\n", + state->addr_prefix, state->client_addr.buf, + state->addr_prefix, state->client_addr.buf); + vstream_fprintf(state->dump_file, "\tby %s (smtp-sink)" + " with %s id " ID_FORMAT ";\n", + var_myhostname, state->client_proto, state->id); + vstream_fprintf(state->dump_file, "\t%s\n", mail_date(state->start_time)); +} + +/* mail_file_cleanup - common cleanup for capture file */ + +static void mail_file_cleanup(SINK_STATE *state) +{ + (void) vstream_fclose(state->dump_file); + state->dump_file = 0; +} + +/* mail_file_finish - handle message completion for capture file */ + +static void mail_file_finish(SINK_STATE *state) +{ + + /* + * Optionally append the captured message to a shared dumpfile. + */ + if (shared_template) { + const char *out_path; + VSTREAM *out_fp; + ssize_t count; + + /* + * Expand the shared dumpfile pathname template. + */ + out_path = STR(exp_path_template(shared_template, state->start_time)); + + /* + * Open the shared dump file. + */ +#define OUT_OPEN_FLAGS (O_WRONLY | O_CREAT | O_APPEND) +#define OUT_OPEN_MODE 0644 + + if ((out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE)) + == 0 && errno == ENOENT) { + make_parent_dir(out_path, 0755); + out_fp = vstream_fopen(out_path, OUT_OPEN_FLAGS, OUT_OPEN_MODE); + } + if (out_fp == 0) + msg_fatal("open %s: %m", out_path); + + /* + * Append message content from single-message dump file. + */ + if (vstream_fseek(state->dump_file, 0L, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(state->dump_file)); + VSTRING_RESET(state->buffer); + for (;;) { + count = vstream_fread(state->dump_file, STR(state->buffer), + vstring_avail(state->buffer)); + if (count <= 0) + break; + if (vstream_fwrite(out_fp, STR(state->buffer), count) != count) + msg_fatal("append file %s: %m", out_path); + } + if (vstream_ferror(state->dump_file)) + msg_fatal("read file %s: %m", VSTREAM_PATH(state->dump_file)); + if (vstream_fclose(out_fp)) + msg_fatal("append file %s: %m", out_path); + } + mail_file_cleanup(state); +} + +/* mail_file_reset - abort mail to capture file */ + +static void mail_file_reset(SINK_STATE *state) +{ + if (shared_template == 0 + && unlink(VSTREAM_PATH(state->dump_file)) < 0 + && errno != ENOENT) + msg_fatal("unlink %s: %m", VSTREAM_PATH(state->dump_file)); + mail_file_cleanup(state); +} + +/* mail_cmd_reset - reset mail transaction information */ + +static void mail_cmd_reset(SINK_STATE *state) +{ + state->in_mail = 0; + /* Not: state->rcpts = 0. This breaks the DOT reply with LMTP. */ + if (state->dump_file) + mail_file_reset(state); +} + +/* ehlo_response - respond to EHLO command */ + +static void ehlo_response(SINK_STATE *state, const char *args) +{ +#define SKIP(cp, cond) do { \ + for (/* void */; *cp && (cond); cp++) \ + /* void */; \ + } while (0) + + /* EHLO aborts a mail transaction in progress. */ + mail_cmd_reset(state); + if (enable_lmtp == 0) + state->client_proto = "ESMTP"; + smtp_printf(state->stream, "250-%s", var_myhostname); + if (!disable_pipelining) + smtp_printf(state->stream, "250-PIPELINING"); + if (!disable_8bitmime) + smtp_printf(state->stream, "250-8BITMIME"); + if (!disable_saslauth) + smtp_printf(state->stream, "250-AUTH PLAIN LOGIN"); + if (!disable_xclient) + smtp_printf(state->stream, "250-XCLIENT NAME HELO"); + if (!disable_xforward) + smtp_printf(state->stream, "250-XFORWARD NAME ADDR PROTO HELO"); + if (!disable_enh_status) + smtp_printf(state->stream, "250-ENHANCEDSTATUSCODES"); + if (!disable_dsn) + smtp_printf(state->stream, "250-DSN"); + /* RFC 821/2821/5321: Format is replycodeoptional-text */ + smtp_printf(state->stream, "250 "); + SMTP_FLUSH(state->stream); + if (single_template) { + if (state->helo_args) + myfree(state->helo_args); + SKIP(args, ISSPACE(*args)); + state->helo_args = mystrdup(args); + } +} + +/* helo_response - respond to HELO command */ + +static void helo_response(SINK_STATE *state, const char *args) +{ + /* HELO aborts a mail transaction in progress. */ + mail_cmd_reset(state); + state->client_proto = "SMTP"; + smtp_printf(state->stream, "250 %s", var_myhostname); + SMTP_FLUSH(state->stream); + if (single_template) { + if (state->helo_args) + myfree(state->helo_args); + SKIP(args, ISSPACE(*args)); + state->helo_args = mystrdup(args); + } +} + +/* ok_response - send 250 OK */ + +static void ok_response(SINK_STATE *state, const char *unused_args) +{ + smtp_printf(state->stream, "250 2.0.0 Ok"); + SMTP_FLUSH(state->stream); +} + +/* rset_response - reset, send 250 OK */ + +static void rset_response(SINK_STATE *state, const char *unused_args) +{ + mail_cmd_reset(state); + smtp_printf(state->stream, "250 2.1.0 Ok"); + SMTP_FLUSH(state->stream); +} + +/* mail_response - reset recipient count, send 250 OK */ + +static void mail_response(SINK_STATE *state, const char *args) +{ + if (state->in_mail) { + smtp_printf(state->stream, "503 5.5.1 Error: nested MAIL command"); + SMTP_FLUSH(state->stream); + return; + } + state->in_mail++; + state->rcpts = 0; + smtp_printf(state->stream, "250 2.1.0 Ok"); + SMTP_FLUSH(state->stream); + if (single_template) { + mail_file_open(state); + SKIP(args, *args != ':'); + SKIP(args, *args == ':'); + SKIP(args, ISSPACE(*args)); + vstream_fprintf(state->dump_file, "X-Mail-Args: %s\n", args); + } +} + +/* rcpt_response - bump recipient count, send 250 OK */ + +static void rcpt_response(SINK_STATE *state, const char *args) +{ + if (state->in_mail == 0) { + smtp_printf(state->stream, "503 5.5.1 Error: need MAIL command"); + SMTP_FLUSH(state->stream); + return; + } + state->rcpts++; + smtp_printf(state->stream, "250 2.1.5 Ok"); + SMTP_FLUSH(state->stream); + /* Note: there may be more than one recipient per mail transaction. */ + if (state->dump_file) { + SKIP(args, *args != ':'); + SKIP(args, *args == ':'); + SKIP(args, ISSPACE(*args)); + vstream_fprintf(state->dump_file, "X-Rcpt-Args: %s\n", args); + } +} + +/* abort_event - delayed abort after DATA command */ + +static void abort_event(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + smtp_printf(state->stream, "550 This violates SMTP"); + SMTP_FLUSH(state->stream); + disconnect(state); +} + +/* delay_read_event - resume input event handling */ + +static void delay_read_event(int event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + if (event != EVENT_TIME) + msg_panic("delay_read_event: non-timer event %d", event); + + event_enable_read(vstream_fileno(state->stream), read_event, (void *) state); + event_request_timer(read_timeout, (void *) state, var_tmout); +} + +/* delay_read - temporarily suspend input event handling */ + +static void delay_read(SINK_STATE *state, int delay) +{ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_timeout, (void *) state); + event_request_timer(delay_read_event, (void *) state, delay); +} + +/* data_response - respond to DATA command */ + +static void data_response(SINK_STATE *state, const char *unused_args) +{ + if (state->in_mail == 0 || state->rcpts == 0) { + smtp_printf(state->stream, "503 5.5.1 Error: need RCPT command"); + SMTP_FLUSH(state->stream); + return; + } + /* Not: ST_ANY. */ + state->data_state = ST_CR_LF; + smtp_printf(state->stream, "354 End data with ."); + SMTP_FLUSH(state->stream); + if (abort_delay < 0) { + state->read_fn = data_read; + /* Todo: move into code that invokes the command response function. */ + if (data_read_delay > 0) + delay_read(state, data_read_delay); + } else { + /* Stop reading, send premature 550, and disconnect. */ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_event, (void *) state); + event_request_timer(abort_event, (void *) state, abort_delay); + } + if (state->dump_file) + mail_file_finish_header(state); +} + +/* dot_resp_hard - hard error response to . command */ + +static void dot_resp_hard(SINK_STATE *state) +{ + if (enable_lmtp) { + while (state->rcpts-- > 0) /* XXX this could block */ + smtp_printf(state->stream, "%s", hard_error_resp); + } else { + smtp_printf(state->stream, "%s", hard_error_resp); + } + SMTP_FLUSH(state->stream); +} + +/* dot_resp_soft - soft error response to . command */ + +static void dot_resp_soft(SINK_STATE *state) +{ + if (enable_lmtp) { + while (state->rcpts-- > 0) /* XXX this could block */ + smtp_printf(state->stream, "%s", soft_error_resp); + } else { + smtp_printf(state->stream, "%s", soft_error_resp); + } + SMTP_FLUSH(state->stream); +} + +/* dot_response - response to . command */ + +static void dot_response(SINK_STATE *state, const char *unused_args) +{ + if (enable_lmtp) { + while (state->rcpts-- > 0) /* XXX this could block */ + smtp_printf(state->stream, "250 2.2.0 Ok"); + } else { + smtp_printf(state->stream, "250 2.0.0 Ok"); + } + SMTP_FLUSH(state->stream); +} + +/* quit_response - respond to QUIT command */ + +static void quit_response(SINK_STATE *state, const char *unused_args) +{ + smtp_printf(state->stream, "221 Bye"); + smtp_flush(state->stream); /* not: SMTP_FLUSH */ + if (show_count) + quit_count++; +} + +/* conn_response - respond to connect command */ + +static void conn_response(SINK_STATE *state, const char *unused_args) +{ + if (pretend_pix) + smtp_printf(state->stream, "220 ********"); + else if (disable_esmtp) + smtp_printf(state->stream, "220 %s", var_myhostname); + else + smtp_printf(state->stream, "220 %s ESMTP", var_myhostname); + SMTP_FLUSH(state->stream); +} + +/* delay_event - delayed command response */ + +static void delay_event(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown read/write error"); + /* NOTREACHED */ + + case SMTP_ERR_TIME: + msg_warn("write timeout"); + disconnect(state); + return; + + case SMTP_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case 0: + state->delayed_response(state, state->delayed_args); + myfree(state->delayed_args); + state->delayed_args = 0; + break; + } + + if (state->delayed_response == quit_response) { + disconnect(state); + return; + } + state->delayed_response = 0; + + /* Resume input event handling after the delayed response. */ + event_enable_read(vstream_fileno(state->stream), read_event, (void *) state); + event_request_timer(read_timeout, (void *) state, var_tmout); +} + +/* data_read - read data from socket */ + +static int data_read(SINK_STATE *state) +{ + int ch; + struct data_trans { + int state; + int want; + int next_state; + }; + static struct data_trans data_trans[] = { + ST_ANY, '\r', ST_CR, + ST_CR, '\n', ST_CR_LF, + ST_CR_LF, '.', ST_CR_LF_DOT, + ST_CR_LF_DOT, '\r', ST_CR_LF_DOT_CR, + ST_CR_LF_DOT_CR, '\n', ST_CR_LF_DOT_CR_LF, + }; + struct data_trans *dp; + + /* + * A read may result in EOF, but is never supposed to time out - a time + * out means that we were trying to read when no data was available. + */ + for (;;) { + if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF) + return (-1); + for (dp = data_trans; dp->state != state->data_state; dp++) + /* void */ ; + + /* + * Try to match the current character desired by the state machine. + * If that fails, try to restart the machine with a match for its + * first state. This covers the case of a CR/LF/CR/LF sequence + * (empty line) right before the end of the message data. + */ + if (ch == dp->want) + state->data_state = dp->next_state; + else if (ch == data_trans[0].want) + state->data_state = data_trans[0].next_state; + else + state->data_state = ST_ANY; + if (state->dump_file) { + if (ch != '\r' && state->data_state != ST_CR_LF_DOT) + VSTREAM_PUTC(ch, state->dump_file); + if (vstream_ferror(state->dump_file)) + msg_fatal("append file %s: %m", VSTREAM_PATH(state->dump_file)); + } + if (state->data_state == ST_CR_LF_DOT_CR_LF) { + PUSH_BACK_SET(state, ".\r\n"); + state->read_fn = command_read; + state->data_state = ST_ANY; + if (state->dump_file) + mail_file_finish(state); + mail_cmd_reset(state); + if (show_count || max_msg_quit_count > 0) { + mesg_count++; + if (show_count) + do_stats(); + if (max_msg_quit_count > 0 && mesg_count >= max_msg_quit_count) + exit(0); + } + break; + } + + /* + * We must avoid blocking I/O, so get out of here as soon as both the + * VSTREAM and kernel read buffers dry up. + */ + if (vstream_peek(state->stream) <= 0 + && readable(vstream_fileno(state->stream)) <= 0) + return (0); + } + return (0); +} + + /* + * The table of all SMTP commands that we can handle. + */ +typedef struct SINK_COMMAND { + const char *name; + void (*response) (SINK_STATE *, const char *); + void (*hard_response) (SINK_STATE *); + void (*soft_response) (SINK_STATE *); + int flags; + int delay; + int delay_odds; +} SINK_COMMAND; + +#define FLAG_ENABLE (1<<0) /* command is enabled */ +#define FLAG_SYSLOG (1<<1) /* log the command */ +#define FLAG_HARD_ERR (1<<2) /* report hard error */ +#define FLAG_SOFT_ERR (1<<3) /* report soft error */ +#define FLAG_DISCONNECT (1<<4) /* disconnect */ +#define FLAG_CLOSE (1<<5) /* say goodbye and disconnect */ + +static SINK_COMMAND command_table[] = { + "connect", conn_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "helo", helo_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "ehlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "lhlo", ehlo_response, hard_err_resp, soft_err_resp, 0, 0, 0, + "xclient", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "xforward", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "auth", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "mail", mail_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "rcpt", rcpt_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "data", data_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + ".", dot_response, dot_resp_hard, dot_resp_soft, FLAG_ENABLE, 0, 0, + "rset", rset_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "noop", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "vrfy", ok_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + "quit", quit_response, hard_err_resp, soft_err_resp, FLAG_ENABLE, 0, 0, + 0, +}; + +/* reset_cmd_flags - reset per-command command flags */ + +static void reset_cmd_flags(const char *cmd, int flags) +{ + SINK_COMMAND *cmdp; + + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(cmd, cmdp->name) == 0) + break; + if (cmdp->name == 0) + msg_fatal("unknown command: %s", cmd); + cmdp->flags &= ~flags; +} + +/* set_cmd_flags - set per-command command flags */ + +static void set_cmd_flags(const char *cmd, int flags) +{ + SINK_COMMAND *cmdp; + + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(cmd, cmdp->name) == 0) + break; + if (cmdp->name == 0) + msg_fatal("unknown command: %s", cmd); + cmdp->flags |= flags; +} + +/* set_cmds_flags - set per-command flags for multiple commands */ + +static void set_cmds_flags(const char *cmds, int flags) +{ + char *saved_cmds; + char *cp; + char *cmd; + + saved_cmds = cp = mystrdup(cmds); + while ((cmd = mystrtok(&cp, CHARS_COMMA_SP)) != 0) + set_cmd_flags(cmd, flags); + myfree(saved_cmds); +} + +/* set_cmd_delay - set per-command delay */ + +static void set_cmd_delay(const char *cmd, int delay, int odds) +{ + SINK_COMMAND *cmdp; + + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(cmd, cmdp->name) == 0) + break; + if (cmdp->name == 0) + msg_fatal("unknown command: %s", cmd); + + if (delay <= 0) + msg_fatal("non-positive '%s' delay", cmd); + if (odds < 0 || odds > 99) + msg_fatal("delay odds for '%s' out of range", cmd); + + cmdp->delay = delay; + cmdp->delay_odds = odds; +} + +/* set_cmd_delay_arg - set per-command delay from option argument */ + +static void set_cmd_delay_arg(char *arg) +{ + char *cp; + char *saved_arg; + char *cmd; + char *delay; + char *odds; + + saved_arg = cp = mystrdup(arg); + cmd = mystrtok(&cp, ":"); + delay = mystrtok(&cp, ":"); + if (cmd == 0 || delay == 0) + msg_fatal("invalid command delay argument: %s", arg); + odds = mystrtok(&cp, ""); + set_cmd_delay(cmd, atoi(delay), odds ? atoi(odds) : 0); + myfree(saved_arg); +} + +/* command_resp - respond to command */ + +static int command_resp(SINK_STATE *state, SINK_COMMAND *cmdp, + const char *command, const char *args) +{ + /* We use raw syslog. Sanitize data content and length. */ + if (cmdp->flags & FLAG_SYSLOG) + syslog(LOG_INFO, "%s %.100s", command, args); + if (cmdp->flags & FLAG_DISCONNECT) + return (-1); + if (cmdp->flags & FLAG_CLOSE) { + smtp_printf(state->stream, "421 4.0.0 Server closing connection"); + return (-1); + } + if (cmdp->flags & FLAG_HARD_ERR) { + cmdp->hard_response(state); + return (0); + } + if (cmdp->flags & FLAG_SOFT_ERR) { + cmdp->soft_response(state); + return (0); + } + if (cmdp->delay > 0) { + int delay = cmdp->delay; + + if (cmdp->delay_odds > 0) + for (delay = 0; + ((int) (100.0 * rand() / (RAND_MAX + 1.0))) < cmdp->delay_odds; + delay += cmdp->delay) + /* NOP */ ; + /* Suspend input event handling while delaying the command response. */ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_timeout, (void *) state); + event_request_timer(delay_event, (void *) state, delay); + state->delayed_response = cmdp->response; + state->delayed_args = mystrdup(args); + } else { + cmdp->response(state, args); + if (cmdp->response == quit_response) + return (-1); + } + return (0); +} + +/* command_read - talk the SMTP protocol, server side */ + +static int command_read(SINK_STATE *state) +{ + char *command; + SINK_COMMAND *cmdp; + int ch; + struct cmd_trans { + int state; + int want; + int next_state; + }; + static struct cmd_trans cmd_trans[] = { + ST_ANY, '\r', ST_CR, + ST_CR, '\n', ST_CR_LF, + 0, 0, 0, + }; + struct cmd_trans *cp; + char *ptr; + + /* + * A read may result in EOF, but is never supposed to time out - a time + * out means that we were trying to read when no data was available. + */ +#define NEXT_CHAR(state) \ + (PUSH_BACK_PEEK(state) ? PUSH_BACK_GET(state) : VSTREAM_GETC(state->stream)) + + if (state->data_state == ST_CR_LF) + state->data_state = ST_ANY; /* XXX */ + for (;;) { + if ((ch = NEXT_CHAR(state)) == VSTREAM_EOF) + return (-1); + + /* + * Sanity check. We don't want to store infinitely long commands. + */ + if (VSTRING_LEN(state->buffer) >= var_max_line_length) { + msg_warn("command line too long"); + return (-1); + } + VSTRING_ADDCH(state->buffer, ch); + + /* + * Try to match the current character desired by the state machine. + * If that fails, try to restart the machine with a match for its + * first state. + */ + for (cp = cmd_trans; cp->state != state->data_state; cp++) + if (cp->want == 0) + msg_panic("command_read: unknown state: %d", state->data_state); + if (ch == cp->want) + state->data_state = cp->next_state; + else if (ch == cmd_trans[0].want) + state->data_state = cmd_trans[0].next_state; + else + state->data_state = ST_ANY; + if (state->data_state == ST_CR_LF) + break; + + /* + * We must avoid blocking I/O, so get out of here as soon as both the + * VSTREAM and kernel read buffers dry up. + * + * XXX Solaris non-blocking read() may fail on a socket when ioctl + * FIONREAD reports there is unread data. Diagnosis by Max Pashkov. + * As a workaround we use readable() (which uses poll or select()) + * instead of peek_fd() (which uses ioctl FIONREAD). Workaround added + * 20020604. + */ + if (PUSH_BACK_PEEK(state) == 0 && vstream_peek(state->stream) <= 0 + && readable(vstream_fileno(state->stream)) <= 0) + return (0); + } + + /* + * Properly terminate the result, and reset the buffer write pointer for + * reading the next command. This is ugly, but not as ugly as trying to + * deal with all the early returns below. + */ + vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2); + VSTRING_TERMINATE(state->buffer); + state->data_state = ST_CR_LF; + VSTRING_RESET(state->buffer); + + /* + * Got a complete command line. Parse it. + */ + ptr = vstring_str(state->buffer); + if (msg_verbose) + msg_info("%s", ptr); + if ((command = mystrtok(&ptr, " \t")) == 0) { + smtp_printf(state->stream, "500 5.5.2 Error: unknown command"); + SMTP_FLUSH(state->stream); + return (0); + } + for (cmdp = command_table; cmdp->name != 0; cmdp++) + if (strcasecmp(command, cmdp->name) == 0) + break; + if (cmdp->name == 0 || (cmdp->flags & FLAG_ENABLE) == 0) { + smtp_printf(state->stream, "500 5.5.1 Error: unknown command"); + SMTP_FLUSH(state->stream); + return (0); + } + return (command_resp(state, cmdp, command, printable(ptr, '?'))); +} + +/* read_timeout - handle timer event */ + +static void read_timeout(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + /* + * We don't send anything to the client, because we would have to set up + * an smtp_stream exception handler first. And that is just too much + * trouble. + */ + msg_warn("read timeout"); + disconnect(state); +} + +/* read_event - handle command or data read events */ + +static void read_event(int unused_event, void *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + /* + * The input reading routine not only reads input (with vstream calls) + * but also produces output (with smtp_stream calls). Because the output + * routines can raise timeout or EOF exceptions with vstream_longjmp(), + * the input reading routine needs to set up corresponding exception + * handlers with vstream_setjmp(). Guarding the input operations in the + * same manner is not useful: we must read input in non-blocking mode, so + * we never get called when the socket stays unreadable too long. And EOF + * is already trivial to detect with the vstream calls. + */ + do { + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown read/write error"); + /* NOTREACHED */ + + case SMTP_ERR_TIME: + msg_warn("write timeout"); + disconnect(state); + return; + + case SMTP_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case 0: + if (state->read_fn(state) < 0) { + if (msg_verbose) + msg_info("disconnect"); + disconnect(state); + return; + } + } + } while (PUSH_BACK_PEEK(state) != 0 || vstream_peek(state->stream) > 0); + + /* + * Reset the idle timer. Wait until the next input event, or until the + * idle timer goes off. + */ + event_request_timer(read_timeout, (void *) state, var_tmout); +} + +static void connect_event(int, void *); + +/* disconnect - handle disconnection events */ + +static void disconnect(SINK_STATE *state) +{ + event_disable_readwrite(vstream_fileno(state->stream)); + event_cancel_timer(read_timeout, (void *) state); + if (show_count) { + sess_count++; + do_stats(); + } + vstream_fclose(state->stream); + vstring_free(state->buffer); + /* Clean up file capture attributes. */ + if (state->helo_args) + myfree(state->helo_args); + /* Delete incomplete mail transaction. */ + mail_cmd_reset(state); + if (state->delayed_args) + myfree(state->delayed_args); + myfree((void *) state); + if (max_quit_count > 0 && quit_count >= max_quit_count) + exit(0); + if (client_count-- == max_client_count) + event_enable_read(sock, connect_event, (void *) 0); +} + +/* connect_event - handle connection events */ + +static void connect_event(int unused_event, void *unused_context) +{ + struct sockaddr_storage ss; + SOCKADDR_SIZE len = sizeof(ss); + struct sockaddr *sa = (struct sockaddr *) &ss; + SINK_STATE *state; + int fd; + + if ((fd = sane_accept(sock, sa, &len)) >= 0) { + /* Safety: limit the number of open sockets and capture files. */ + if (++client_count == max_client_count) + event_disable_readwrite(sock); + state = (SINK_STATE *) mymalloc(sizeof(*state)); + if (strchr((char *) proto_info->sa_family_list, sa->sa_family)) + SOCKADDR_TO_HOSTADDR(sa, len, &state->client_addr, + (MAI_SERVPORT_STR *) 0, sa->sa_family); + else + strncpy(state->client_addr.buf, "local", sizeof("local") + 0); + if (msg_verbose) + msg_info("connect (%s %s)", +#ifdef AF_LOCAL + sa->sa_family == AF_LOCAL ? "AF_LOCAL" : +#else + sa->sa_family == AF_UNIX ? "AF_UNIX" : +#endif + sa->sa_family == AF_INET ? "AF_INET" : +#ifdef AF_INET6 + sa->sa_family == AF_INET6 ? "AF_INET6" : +#endif + "unknown protocol family", + state->client_addr.buf); + non_blocking(fd, NON_BLOCKING); + state->stream = vstream_fdopen(fd, O_RDWR); + vstream_tweak_sock(state->stream); + state->buffer = vstring_alloc(1024); + state->read_fn = command_read; + state->data_state = ST_ANY; + PUSH_BACK_SET(state, ""); + smtp_timeout_setup(state->stream, var_tmout); + state->in_mail = 0; + state->rcpts = 0; + state->delayed_response = 0; + state->delayed_args = 0; + /* Initialize file capture attributes. */ +#ifdef AF_INET6 + if (sa->sa_family == AF_INET6) + state->addr_prefix = "ipv6:"; + else +#endif + state->addr_prefix = ""; + + state->helo_args = 0; + state->client_proto = enable_lmtp ? "LMTP" : "SMTP"; + state->start_time = 0; + state->id = 0; + state->dump_file = 0; + + /* + * We use the smtp_stream module to produce output. That module + * throws an exception via vstream_longjmp() in case of a timeout or + * lost connection error. Therefore we must prepare to handle these + * exceptions with vstream_setjmp(). + */ + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown read/write error"); + /* NOTREACHED */ + + case SMTP_ERR_TIME: + msg_warn("write timeout"); + disconnect(state); + return; + + case SMTP_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case 0: + if (command_resp(state, command_table, "connect", "") < 0) + disconnect(state); + else if (command_table->delay == 0) { + event_enable_read(fd, read_event, (void *) state); + event_request_timer(read_timeout, (void *) state, var_tmout); + } + } + } +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s [-468acCeEFLpPv] [-A abort_delay] [-b soft_bounce_reply] [-B hard_bounce_reply] [-d dump-template] [-D dump-template] [-f commands] [-h hostname] [-m max_concurrency] [-M message_quit_count] [-n quit_count] [-q commands] [-r commands] [-R root-dir] [-s commands] [-S start-string] [-u user_privs] [-w delay] [host]:port backlog", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + int backlog; + int ch; + int delay; + const char *protocols = INET_PROTO_NAME_ALL; + const char *root_dir = 0; + const char *user_privs = 0; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Fix 20051207. + */ + signal(SIGPIPE, SIG_IGN); + + /* + * Initialize diagnostics. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "468aA:b:B:cCd:D:eEf:Fh:H:Ln:m:M:NpPq:Q:r:R:s:S:t:T:u:vw:W:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case '8': + disable_8bitmime = 1; + break; + case 'a': + disable_saslauth = 1; + break; + case 'A': + if (!alldig(optarg) || (abort_delay = atoi(optarg)) < 0) + usage(argv[0]); + break; + case 'b': + if (optarg[0] != '4' || strspn(optarg, "0123456789") != 3) { + msg_error("bad soft error reply: %s", optarg); + usage(argv[0]); + } else + soft_error_resp = optarg; + break; + case 'B': + if (optarg[0] != '5' || strspn(optarg, "0123456789") != 3) { + msg_error("bad hard error reply: %s", optarg); + usage(argv[0]); + } else + hard_error_resp = optarg; + break; + case 'c': + show_count++; + break; + case 'C': + disable_xclient = 1; + reset_cmd_flags("xclient", FLAG_ENABLE); + break; + case 'd': + single_template = optarg; + break; + case 'D': + shared_template = optarg; + break; + case 'e': + disable_esmtp = 1; + break; + case 'E': + disable_enh_status = 1; + break; + case 'f': + set_cmds_flags(optarg, FLAG_HARD_ERR); + disable_pipelining = 1; + break; + case 'F': + disable_xforward = 1; + reset_cmd_flags("xforward", FLAG_ENABLE); + break; + case 'h': + var_myhostname = optarg; + break; + case 'H': + if ((data_read_delay = atoi(optarg)) <= 0) + msg_fatal("bad data read delay: %s", optarg); + break; + case 'L': + enable_lmtp = 1; + break; + case 'm': + if ((max_client_count = atoi(optarg)) <= 0) + msg_fatal("bad concurrency limit: %s", optarg); + break; + case 'M': + if ((max_msg_quit_count = atoi(optarg)) <= 0) + msg_fatal("bad message quit count: %s", optarg); + break; + case 'n': + if ((max_quit_count = atoi(optarg)) <= 0) + msg_fatal("bad quit count: %s", optarg); + break; + case 'N': + disable_dsn = 1; + break; + case 'p': + disable_pipelining = 1; + break; + case 'P': + pretend_pix = 1; + disable_esmtp = 1; + break; + case 'q': + set_cmds_flags(optarg, FLAG_DISCONNECT); + break; + case 'Q': + set_cmds_flags(optarg, FLAG_CLOSE); + break; + case 'r': + set_cmds_flags(optarg, FLAG_SOFT_ERR); + disable_pipelining = 1; + break; + case 'R': + root_dir = optarg; + break; + case 's': + openlog(basename(argv[0]), LOG_PID, LOG_MAIL); + set_cmds_flags(optarg, FLAG_SYSLOG); + break; + case 'S': + start_string = vstring_alloc(10); + unescape(start_string, optarg); + break; + case 't': + if ((var_tmout = atoi(optarg)) <= 0) + msg_fatal("bad timeout: %s", optarg); + break; + case 'T': + if ((inet_windowsize = atoi(optarg)) <= 0) + msg_fatal("bad TCP window size: %s", optarg); + break; + case 'u': + user_privs = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'w': + if ((delay = atoi(optarg)) <= 0) + usage(argv[0]); + set_cmd_delay("data", delay, 0); + break; + case 'W': + set_cmd_delay_arg(optarg); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 2) + usage(argv[0]); + if ((backlog = atoi(argv[optind + 1])) <= 0) + usage(argv[0]); + if (single_template && shared_template) + msg_fatal("use only one of -d or -D, but not both"); + if (geteuid() == 0 && user_privs == 0) + msg_fatal("-u option is required if running as root"); + + /* + * Initialize. + */ + if (var_myhostname == 0) + var_myhostname = "smtp-sink"; + set_cmds_flags(enable_lmtp ? "lhlo" : + disable_esmtp ? "helo" : + "helo, ehlo", FLAG_ENABLE); + proto_info = inet_proto_init("protocols", protocols); + if (strncmp(argv[optind], "unix:", 5) == 0) { + sock = unix_listen(argv[optind] + 5, backlog, BLOCKING); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + sock = inet_listen(argv[optind], backlog, BLOCKING); + } + if (user_privs) + chroot_uid(root_dir, user_privs); + + if (single_template) + mysrand((int) time((time_t *) 0)); + else if (shared_template) + single_template = shared_template; + + /* + * Start the event handler. + */ + event_enable_read(sock, connect_event, (void *) 0); + for (;;) + event_loop(-1); +} diff --git a/src/smtpstone/smtp-source.c b/src/smtpstone/smtp-source.c new file mode 100644 index 0000000..be388d6 --- /dev/null +++ b/src/smtpstone/smtp-source.c @@ -0,0 +1,1188 @@ +/*++ +/* NAME +/* smtp-source 1 +/* SUMMARY +/* parallelized SMTP/LMTP test generator +/* SYNOPSIS +/* .fi +/* \fBsmtp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* +/* \fBsmtp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR +/* DESCRIPTION +/* \fBsmtp-source\fR connects to the named \fIhost\fR and TCP \fIport\fR +/* (default: port 25) +/* and sends one or more messages to it, either sequentially +/* or in parallel. The program speaks either SMTP (default) or +/* LMTP. +/* Connections can be made to UNIX-domain and IPv4 or IPv6 servers. +/* IPv4 and IPv6 are the default. +/* +/* Note: this is an unsupported test program. No attempt is made +/* to maintain compatibility between successive versions. +/* +/* Arguments: +/* .IP \fB-4\fR +/* Connect to the server with IPv4. This option has no effect when +/* Postfix is built without IPv6 support. +/* .IP \fB-6\fR +/* Connect to the server with IPv6. This option is not available when +/* Postfix is built without IPv6 support. +/* .IP "\fB-A\fR" +/* Don't abort when the server sends something other than the +/* expected positive reply code. +/* .IP \fB-c\fR +/* Display a running counter that is incremented each time +/* an SMTP DATA command completes. +/* .IP "\fB-C \fIcount\fR" +/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times +/* before giving up. The default count is 1. Specify a larger count in +/* order to work around a problem with TCP/IP stacks that send RESET +/* when the listen queue is full. +/* .IP \fB-d\fR +/* Don't disconnect after sending a message; send the next +/* message over the same connection. +/* .IP "\fB-f \fIfrom\fR" +/* Use the specified sender address (default: ). +/* .IP "\fB-F \fIfile\fR" +/* Send the pre-formatted message header and body in the +/* specified \fIfile\fR, while prepending '.' before lines that +/* begin with '.', and while appending CRLF after each line. +/* .IP "\fB-l \fIlength\fR" +/* Send \fIlength\fR bytes as message payload. The length does not +/* include message headers. +/* .IP \fB-L\fR +/* Speak LMTP rather than SMTP. +/* .IP "\fB-m \fImessage_count\fR" +/* Send the specified number of messages (default: 1). +/* .IP "\fB-M \fImyhostname\fR" +/* Use the specified hostname or [address] in the HELO command +/* and in the default sender and recipient addresses, instead +/* of the machine hostname. +/* .IP "\fB-N\fR" +/* Prepend a non-repeating sequence number to each recipient +/* address. This avoids the artificial 100% hit rate in the +/* resolve and rewrite client caches and exercises the +/* trivial-rewrite daemon, better approximating Postfix +/* performance under real-life work-loads. +/* .IP \fB-o\fR +/* Old mode: don't send HELO, and don't send message headers. +/* .IP "\fB-r \fIrecipient_count\fR" +/* Send the specified number of recipients per transaction (default: 1). +/* Recipient names are generated by prepending a number to the +/* recipient address. +/* .IP "\fB-R \fIinterval\fR" +/* Wait for a random period of time 0 <= n <= interval between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP "\fB-s \fIsession_count\fR" +/* Run the specified number of SMTP sessions in parallel (default: 1). +/* .IP "\fB-S \fIsubject\fR" +/* Send mail with the named subject line (default: none). +/* .IP "\fB-t \fIto\fR" +/* Use the specified recipient address (default: ). +/* .IP "\fB-T \fIwindowsize\fR" +/* Override the default TCP window size. To work around +/* broken TCP window scaling implementations, specify a +/* value > 0 and < 65536. +/* .IP \fB-v\fR +/* Make the program more verbose, for debugging purposes. +/* .IP "\fB-w \fIinterval\fR" +/* Wait a fixed time between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* Connect via TCP to host \fIhost\fR, port \fIport\fR. The default +/* port is \fBsmtp\fR. +/* .IP \fBunix:\fIpathname\fR +/* Connect to the UNIX-domain socket at \fIpathname\fR. +/* BUGS +/* No SMTP command pipelining support. +/* SEE ALSO +/* smtp-sink(1), SMTP/LMTP message dump +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#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 +#include +#include +#include + +/* Global library. */ + +#include +#include +#include + +/* Application-specific. */ + + /* + * Per-session data structure with state. + * + * This software can maintain multiple parallel connections to the same SMTP + * server. However, it makes no more than one connection request at a time + * to avoid overwhelming the server with SYN packets and having to back off. + * Back-off would screw up the benchmark. Pending connection requests are + * kept in a linear list. + */ +typedef struct SESSION { + int xfer_count; /* # of xfers in session */ + int rcpt_done; /* # of recipients done */ + int rcpt_count; /* # of recipients to go */ + int rcpt_accepted; /* # of recipients accepted */ + VSTREAM *stream; /* open connection */ + int connect_count; /* # of connect()s to retry */ + struct SESSION *next; /* connect() queue linkage */ +} SESSION; + +static SESSION *last_session; /* connect() queue tail */ + + /* + * Structure with broken-up SMTP server response. + */ +typedef struct { /* server response */ + int code; /* status */ + char *str; /* text */ + VSTRING *buf; /* origin of text */ +} RESPONSE; + +static VSTRING *buffer; +static int var_line_limit = 10240; +static int var_timeout = 300; +static const char *var_myhostname; +static int session_count; +static int message_count = 1; +static struct sockaddr_storage ss; + +#undef sun +static struct sockaddr_un sun; +static struct sockaddr *sa; +static int sa_length; +static int recipients = 1; +static char *defaddr; +static char *recipient; +static char *sender; +static char *message_data; +static int message_length; +static int disconnect = 1; +static int count = 0; +static int counter = 0; +static int send_helo_first = 1; +static int send_headers = 1; +static int connect_count = 1; +static int random_delay = 0; +static int fixed_delay = 0; +static int talk_lmtp = 0; +static char *subject = 0; +static int number_rcpts = 0; +static int allow_reject = 0; + +static void enqueue_connect(SESSION *); +static void start_connect(SESSION *); +static void connect_done(int, void *); +static void read_banner(int, void *); +static void send_helo(SESSION *); +static void helo_done(int, void *); +static void send_mail(SESSION *); +static void mail_done(int, void *); +static void send_rcpt(int, void *); +static void rcpt_done(int, void *); +static void send_data(int, void *); +static void data_done(int, void *); +static void dot_done(int, void *); +static void send_rset(int, void *); +static void rset_done(int, void *); +static void send_quit(SESSION *); +static void quit_done(int, void *); +static void close_session(SESSION *); + +/* random_interval - generate a random value in 0 .. (small) interval */ + +static int random_interval(int interval) +{ + return (rand() % (interval + 1)); +} + +/* command - send an SMTP command */ + +static void command(VSTREAM *stream, char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + + /* + * Optionally, log the command before actually sending, so we can see + * what the program is trying to do. + */ + if (msg_verbose) { + va_list ap2; + + VA_COPY(ap2, ap); + vmsg_info(fmt, ap2); + va_end(ap2); + } + smtp_vprintf(stream, fmt, ap); + va_end(ap); + smtp_flush(stream); +} + +/* socket_error - look up and reset the last socket error */ + +static int socket_error(int sock) +{ + int error; + SOCKOPT_SIZE error_len; + + /* + * Some Solaris 2 versions have getsockopt() itself return the error, + * instead of returning it via the parameter list. + */ + error = 0; + error_len = sizeof(error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0) + return (-1); + if (error) { + errno = error; + return (-1); + } + + /* + * No problems. + */ + return (0); +} + +/* response - read and process SMTP server response */ + +static RESPONSE *response(VSTREAM *stream, VSTRING *buf) +{ + static RESPONSE rdata; + int more; + char *cp; + + /* + * Initialize the response data buffer. smtp_get() defends against a + * denial of service attack by limiting the amount of single-line text, + * and the loop below limits the amount of multi-line text that we are + * willing to store. + */ + if (rdata.buf == 0) + rdata.buf = vstring_alloc(100); + + /* + * Censor out non-printable characters in server responses. Concatenate + * multi-line server responses. Separate the status code from the text. + * Leave further parsing up to the application. + */ +#define BUF ((char *) vstring_str(buf)) + VSTRING_RESET(rdata.buf); + for (;;) { + smtp_get(buf, stream, var_line_limit, SMTP_GET_FLAG_SKIP); + for (cp = BUF; *cp != 0; cp++) + if (!ISPRINT(*cp) && !ISSPACE(*cp)) + *cp = '?'; + cp = BUF; + if (msg_verbose) + msg_info("<<< %s", cp); + while (ISDIGIT(*cp)) + cp++; + rdata.code = (cp - BUF == 3 ? atoi(BUF) : 0); + if ((more = (*cp == '-')) != 0) + cp++; + while (ISSPACE(*cp)) + cp++; + if (VSTRING_LEN(rdata.buf) < var_line_limit) + vstring_strcat(rdata.buf, cp); + if (more == 0) + break; + if (VSTRING_LEN(rdata.buf) < var_line_limit) + VSTRING_ADDCH(rdata.buf, '\n'); + } + VSTRING_TERMINATE(rdata.buf); + rdata.str = vstring_str(rdata.buf); + return (&rdata); +} + +/* exception_text - translate exceptions from the smtp_stream module */ + +static char *exception_text(int except) +{ + switch (except) { + case SMTP_ERR_EOF: + return ("lost connection"); + case SMTP_ERR_TIME: + return ("timeout"); + default: + msg_panic("exception_text: unknown exception %d", except); + } + /* NOTREACHED */ +} + +/* startup - connect to server but do not wait */ + +static void startup(SESSION *session) +{ + if (message_count-- <= 0) { + myfree((void *) session); + session_count--; + return; + } + if (session->stream == 0) { + enqueue_connect(session); + } else { + send_mail(session); + } +} + +/* start_event - invoke startup from timer context */ + +static void start_event(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + startup(session); +} + +/* start_another - start another session */ + +static void start_another(SESSION *session) +{ + if (random_delay > 0) { + event_request_timer(start_event, (void *) session, + random_interval(random_delay)); + } else if (fixed_delay > 0) { + event_request_timer(start_event, (void *) session, fixed_delay); + } else { + startup(session); + } +} + +/* enqueue_connect - queue a connection request */ + +static void enqueue_connect(SESSION *session) +{ + session->next = 0; + if (last_session == 0) { + last_session = session; + start_connect(session); + } else { + last_session->next = session; + last_session = session; + } +} + +/* dequeue_connect - connection request completed */ + +static void dequeue_connect(SESSION *session) +{ + if (session == last_session) { + if (session->next != 0) + msg_panic("dequeue_connect: queue ends after last"); + last_session = 0; + } else { + if (session->next == 0) + msg_panic("dequeue_connect: queue ends before last"); + start_connect(session->next); + } +} + +/* fail_connect - handle failed startup */ + +static void fail_connect(SESSION *session) +{ + if (session->connect_count-- == 1) + msg_fatal("connect: %m"); + msg_warn("connect: %m"); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; +#ifdef MISSING_USLEEP + doze(10); +#else + usleep(10); +#endif + start_connect(session); +} + +/* start_connect - start TCP handshake */ + +static void start_connect(SESSION *session) +{ + int fd; + struct linger linger; + + /* + * Some systems don't set the socket error when connect() fails early + * (loopback) so we must deal with the error immediately, rather than + * retrieving it later with getsockopt(). We can't use MSG_PEEK to + * distinguish between server disconnect and connection refused. + */ + if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("socket: %m"); + (void) non_blocking(fd, NON_BLOCKING); + linger.l_onoff = 1; + linger.l_linger = 0; + if (setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *) &linger, + sizeof(linger)) < 0) + msg_warn("setsockopt SO_LINGER %d: %m", linger.l_linger); + session->stream = vstream_fdopen(fd, O_RDWR); + event_enable_write(fd, connect_done, (void *) session); + smtp_timeout_setup(session->stream, var_timeout); + if (inet_windowsize > 0) + set_inet_windowsize(fd, inet_windowsize); + if (sane_connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS) + fail_connect(session); +} + +/* connect_done - send message sender info */ + +static void connect_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int fd = vstream_fileno(session->stream); + + /* + * Try again after some delay when the connection failed, in case they + * run a Mickey Mouse protocol stack. + */ + if (socket_error(fd) < 0) { + fail_connect(session); + } else { + non_blocking(fd, BLOCKING); + /* Disable write events. */ + event_disable_readwrite(fd); + event_enable_read(fd, read_banner, (void *) session); + dequeue_connect(session); + /* Avoid poor performance when TCP MSS > VSTREAM_BUFSIZE. */ + if (sa->sa_family == AF_INET +#ifdef AF_INET6 + || sa->sa_family == AF_INET6 +#endif + ) + vstream_tweak_tcp(session->stream); + } +} + +/* read_banner - receive SMTP server greeting */ + +static void read_banner(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while reading server greeting", exception_text(except)); + + /* + * Read and parse the server's SMTP greeting banner. + */ + if (((resp = response(session->stream, buffer))->code / 100) == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("rejected at server banner: %d %s", resp->code, resp->str); + } else { + msg_fatal("rejected at server banner: %d %s", resp->code, resp->str); + } + + /* + * Send helo or send the envelope sender address. + */ + if (send_helo_first) + send_helo(session); + else + send_mail(session); +} + +/* send_helo - send hostname */ + +static void send_helo(SESSION *session) +{ + int except; + const char *NOCLOBBER protocol = (talk_lmtp ? "LHLO" : "HELO"); + + /* + * Send the standard greeting with our hostname + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending %s", exception_text(except), protocol); + + command(session->stream, "%s %s", protocol, var_myhostname); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), helo_done, (void *) session); +} + +/* helo_done - handle HELO response */ + +static void helo_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + const char *protocol = (talk_lmtp ? "LHLO" : "HELO"); + + /* + * Get response to HELO command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending %s", exception_text(except), protocol); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("%s rejected: %d %s", protocol, resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("%s rejected: %d %s", protocol, resp->code, resp->str); + } + + send_mail(session); +} + +/* send_mail - send envelope sender */ + +static void send_mail(SESSION *session) +{ + int except; + + /* + * Send the envelope sender address. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending sender", exception_text(except)); + + command(session->stream, "MAIL FROM:<%s>", sender); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), mail_done, (void *) session); +} + +/* mail_done - handle MAIL response */ + +static void mail_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to MAIL command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending sender", exception_text(except)); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + session->rcpt_count = recipients; + session->rcpt_done = 0; + session->rcpt_accepted = 0; + send_rcpt(unused, context); + } else if (allow_reject) { + msg_warn("sender rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + send_rset(unused, context); + } else { + msg_fatal("sender rejected: %d %s", resp->code, resp->str); + } +} + +/* send_rcpt - send recipient address */ + +static void send_rcpt(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Send envelope recipient address. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending recipient", exception_text(except)); + + if (session->rcpt_count > 1 || number_rcpts > 0) + command(session->stream, "RCPT TO:<%d%s>", + number_rcpts ? number_rcpts++ : session->rcpt_count, + recipient); + else + command(session->stream, "RCPT TO:<%s>", recipient); + session->rcpt_count--; + session->rcpt_done++; + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), rcpt_done, (void *) session); +} + +/* rcpt_done - handle RCPT completion */ + +static void rcpt_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to RCPT command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending recipient", exception_text(except)); + + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + session->rcpt_accepted++; + } else if (allow_reject) { + msg_warn("recipient rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("recipient rejected: %d %s", resp->code, resp->str); + } + + /* + * Send another RCPT command or send DATA. + */ + if (session->rcpt_count > 0) + send_rcpt(unused, context); + else if (session->rcpt_accepted > 0) + send_data(unused, context); + else + send_rset(unused, context); +} + +/* send_data - send DATA command */ + +static void send_data(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Request data transmission. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending DATA command", exception_text(except)); + command(session->stream, "DATA"); + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), data_done, (void *) session); +} + +/* data_done - send message content */ + +static void data_done(int unused, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + static const char *mydate; + static int mypid; + + /* + * Get response to DATA command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending DATA command", exception_text(except)); + if ((resp = response(session->stream, buffer))->code == 354) { + /* see below */ ; + } else if (allow_reject) { + msg_warn("data rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + send_rset(unused, context); + return; + } else { + msg_fatal("data rejected: %d %s", resp->code, resp->str); + } + + /* + * Send basic header to keep mailers that bother to examine them happy. + */ + if (send_headers) { + if (mydate == 0) { + mydate = mail_date(time((time_t *) 0)); + mypid = getpid(); + } + smtp_printf(session->stream, "From: <%s>", sender); + smtp_printf(session->stream, "To: <%s>", recipient); + smtp_printf(session->stream, "Date: %s", mydate); + smtp_printf(session->stream, "Message-Id: <%04x.%04x.%04x@%s>", + mypid, vstream_fileno(session->stream), message_count, var_myhostname); + if (subject) + smtp_printf(session->stream, "Subject: %s", subject); + smtp_fputs("", 0, session->stream); + } + + /* + * Send some garbage. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + if (message_length == 0) { + smtp_fputs("La de da de da 1.", 17, session->stream); + smtp_fputs("La de da de da 2.", 17, session->stream); + smtp_fputs("La de da de da 3.", 17, session->stream); + smtp_fputs("La de da de da 4.", 17, session->stream); + } else { + + /* + * XXX This may cause the process to block with message content + * larger than VSTREAM_BUFIZ bytes. + */ + smtp_fputs(message_data, message_length, session->stream); + } + + /* + * Send end of message and process the server response. + */ + command(session->stream, "."); + + /* + * Update the running counter. + */ + if (count) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + + /* + * Prepare for the next event. + */ + event_enable_read(vstream_fileno(session->stream), dot_done, (void *) session); +} + +/* dot_done - send QUIT or start another transaction */ + +static void dot_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to "." command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + do { /* XXX this could block */ + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ ; + } else if (allow_reject) { + msg_warn("end of data rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("end of data rejected: %d %s", resp->code, resp->str); + } + } while (talk_lmtp && --session->rcpt_done > 0); + session->xfer_count++; + + /* + * Say goodbye or send the next message. + */ + if (disconnect || message_count < 1) { + send_quit(session); + } else { + event_disable_readwrite(vstream_fileno(session->stream)); + start_another(session); + } +} + +/* send_rset - send RSET command */ + +static void send_rset(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + command(session->stream, "RSET"); + event_enable_read(vstream_fileno(session->stream), rset_done, (void *) session); +} + +/* rset_done - handle RSET reply */ + +static void rset_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + RESPONSE *resp; + int except; + + /* + * Get response to RSET command. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + if ((resp = response(session->stream, buffer))->code / 100 == 2) { + /* void */ + } else if (allow_reject) { + msg_warn("rset rejected: %d %s", resp->code, resp->str); + if (resp->code == 421 || resp->code == 521) { + close_session(session); + return; + } + } else { + msg_fatal("rset rejected: %d %s", resp->code, resp->str); + } + + /* + * Say goodbye or send the next message. + */ + if (disconnect || message_count < 1) { + send_quit(session); + } else { + event_disable_readwrite(vstream_fileno(session->stream)); + start_another(session); + } +} + +/* send_quit - send QUIT command */ + +static void send_quit(SESSION *session) +{ + command(session->stream, "QUIT"); + event_enable_read(vstream_fileno(session->stream), quit_done, (void *) session); +} + +/* quit_done - disconnect */ + +static void quit_done(int unused_event, void *context) +{ + SESSION *session = (SESSION *) context; + + (void) response(session->stream, buffer); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* close_session - disconnect, for example after 421 or 521 reply */ + +static void close_session(SESSION *session) +{ + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s -cdLNov -s sess -l msglen -m msgs -C count -M myhostname -f from -t to -r rcptcount -R delay -w delay host[:port]", myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - parse JCL and start the machine */ + +int main(int argc, char **argv) +{ + SESSION *session; + char *host; + char *port; + char *path; + int path_len; + int sessions = 1; + int ch; + int i; + char *buf; + const char *parse_err; + struct addrinfo *res; + int aierr; + const char *protocols = INET_PROTO_NAME_ALL; + char *message_file = 0; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + signal(SIGPIPE, SIG_IGN); + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "46AcC:df:F:l:Lm:M:Nor:R:s:S:t:T:vw:")) > 0) { + switch (ch) { + case '4': + protocols = INET_PROTO_NAME_IPV4; + break; + case '6': + protocols = INET_PROTO_NAME_IPV6; + break; + case 'A': + allow_reject = 1; + break; + case 'c': + count++; + break; + case 'C': + if ((connect_count = atoi(optarg)) <= 0) + msg_fatal("bad connection count: %s", optarg); + break; + case 'd': + disconnect = 0; + break; + case 'f': + sender = optarg; + break; + case 'F': + if (message_file == 0 && message_length > 0) + msg_fatal("-l option cannot be used with -F"); + message_file = optarg; + break; + case 'l': + if (message_file != 0) + msg_fatal("-l option cannot be used with -F"); + if ((message_length = atoi(optarg)) <= 0) + msg_fatal("bad message length: %s", optarg); + break; + case 'L': + talk_lmtp = 1; + break; + case 'm': + if ((message_count = atoi(optarg)) <= 0) + msg_fatal("bad message count: %s", optarg); + break; + case 'M': + if (*optarg == '[') { + if (!valid_mailhost_literal(optarg, DO_GRIPE)) + msg_fatal("bad address literal: %s", optarg); + } else { + if (!valid_hostname(optarg, DO_GRIPE)) + msg_fatal("bad hostname: %s", optarg); + } + var_myhostname = optarg; + break; + case 'N': + number_rcpts = 1; + break; + case 'o': + send_helo_first = 0; + send_headers = 0; + break; + case 'r': + if ((recipients = atoi(optarg)) <= 0) + msg_fatal("bad recipient count: %s", optarg); + break; + case 'R': + if (fixed_delay > 0) + msg_fatal("do not use -w and -R options at the same time"); + if ((random_delay = atoi(optarg)) <= 0) + msg_fatal("bad random delay: %s", optarg); + break; + case 's': + if ((sessions = atoi(optarg)) <= 0) + msg_fatal("bad session count: %s", optarg); + break; + case 'S': + subject = optarg; + break; + case 't': + recipient = optarg; + break; + case 'T': + if ((inet_windowsize = atoi(optarg)) <= 0) + msg_fatal("bad TCP window size: %s", optarg); + break; + case 'v': + msg_verbose++; + break; + case 'w': + if (random_delay > 0) + msg_fatal("do not use -w and -R options at the same time"); + if ((fixed_delay = atoi(optarg)) <= 0) + msg_fatal("bad fixed delay: %s", optarg); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 1) + usage(argv[0]); + + if (random_delay > 0) + srand(getpid()); + + /* + * Initialize the message content, SMTP encoded. smtp_fputs() will append + * another \r\n but we don't care. + */ + if (message_file != 0) { + VSTREAM *fp; + VSTRING *buf = vstring_alloc(100); + VSTRING *msg = vstring_alloc(100); + + if ((fp = vstream_fopen(message_file, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", message_file); + while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) { + if (*vstring_str(buf) == '.') + VSTRING_ADDCH(msg, '.'); + vstring_memcat(msg, vstring_str(buf), VSTRING_LEN(buf)); + vstring_memcat(msg, "\r\n", 2); + } + if (vstream_ferror(fp)) + msg_fatal("read %s: %m", message_file); + vstream_fclose(fp); + vstring_free(buf); + message_length = VSTRING_LEN(msg); + message_data = vstring_export(msg); + send_headers = 0; + } else if (message_length > 0) { + message_data = mymalloc(message_length); + memset(message_data, 'X', message_length); + for (i = 80; i < message_length; i += 80) { + message_data[i - 80] = "0123456789"[(i / 80) % 10]; + message_data[i - 2] = '\r'; + message_data[i - 1] = '\n'; + } + } + + /* + * Translate endpoint address to internal form. + */ + (void) inet_proto_init("protocols", protocols); + if (strncmp(argv[optind], "unix:", 5) == 0) { + path = argv[optind] + 5; + path_len = strlen(path); + if (path_len >= (int) sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", path); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len); + sa = (struct sockaddr *) &sun; + sa_length = sizeof(sun); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + buf = mystrdup(argv[optind]); + if ((parse_err = host_port(buf, &host, (char *) 0, &port, "smtp")) != 0) + msg_fatal("%s: %s", argv[optind], parse_err); + if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) + msg_fatal("%s: %s", argv[optind], MAI_STRERROR(aierr)); + myfree(buf); + sa = (struct sockaddr *) &ss; + if (res->ai_addrlen > sizeof(ss)) + msg_fatal("address length %d > buffer length %d", + (int) res->ai_addrlen, (int) sizeof(ss)); + memcpy((void *) sa, res->ai_addr, res->ai_addrlen); + sa_length = res->ai_addrlen; +#ifdef HAS_SA_LEN + sa->sa_len = sa_length; +#endif + freeaddrinfo(res); + } + + /* + * smtp_get() makes sure the SMTP server cannot run us out of memory by + * sending never-ending lines of text. + */ + if (buffer == 0) + buffer = vstring_alloc(100); + + /* + * Make sure we have sender and recipient addresses. + */ + if (var_myhostname == 0) + var_myhostname = get_hostname(); + if (sender == 0 || recipient == 0) { + vstring_sprintf(buffer, "foo@%s", var_myhostname); + defaddr = mystrdup(vstring_str(buffer)); + if (sender == 0) + sender = defaddr; + if (recipient == 0) + recipient = defaddr; + } + + /* + * Start sessions. + */ + while (sessions-- > 0) { + session = (SESSION *) mymalloc(sizeof(*session)); + session->stream = 0; + session->xfer_count = 0; + session->connect_count = connect_count; + session->next = 0; + session_count++; + startup(session); + } + for (;;) { + event_loop(-1); + if (session_count <= 0 && message_count <= 0) { + if (count) { + VSTREAM_PUTC('\n', VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + } + exit(0); + } + } +} diff --git a/src/smtpstone/throughput b/src/smtpstone/throughput new file mode 100644 index 0000000..4853d75 --- /dev/null +++ b/src/smtpstone/throughput @@ -0,0 +1,28 @@ +Host: P233 BSD/OS 3.1 smtp-source and smtp-sink on the same host, +100 msgs in 10 sessions. + +send = time to send 100 msgs into postfix +rest = time for Postfix to finish +total = total elapsed time + +19990627 + +send rest total +14 10 25 +10 8 18 + 9 10 19 + 9 17 26 + 8 11 19 + 8 9 17 + +19990906 + +send rest total +10 15 25 + 9 10 19 + 8 9 17 + 9 8 17 + 8 9 17 + 9 8 17 + 8 9 17 + 8 8 16 diff --git a/src/smtpstone/vmail-local b/src/smtpstone/vmail-local new file mode 100644 index 0000000..84269b2 --- /dev/null +++ b/src/smtpstone/vmail-local @@ -0,0 +1,43 @@ +fist% date; /usr/bin/time ./smtp-source -s 5 -m 100 -t wietse@nipple nipple +Sat May 9 22:08:27 EDT 1998 + 6.29 real 0.08 user 0.17 sys +May 9 22:08:27 nipple wietse[100]: postfix/smtpd[2248]: connect from fist.porcu +May 9 22:08:58 nipple postfix/local[2330]: 5082D53AD0: to=$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +spawn.o: ../../include/argv.h +spawn.o: ../../include/check_arg.h +spawn.o: ../../include/dict.h +spawn.o: ../../include/htable.h +spawn.o: ../../include/mail_conf.h +spawn.o: ../../include/mail_params.h +spawn.o: ../../include/mail_parm_split.h +spawn.o: ../../include/mail_server.h +spawn.o: ../../include/mail_version.h +spawn.o: ../../include/msg.h +spawn.o: ../../include/myflock.h +spawn.o: ../../include/mymalloc.h +spawn.o: ../../include/set_eugid.h +spawn.o: ../../include/spawn_command.h +spawn.o: ../../include/split_at.h +spawn.o: ../../include/sys_defs.h +spawn.o: ../../include/timed_wait.h +spawn.o: ../../include/vbuf.h +spawn.o: ../../include/vstream.h +spawn.o: ../../include/vstring.h +spawn.o: spawn.c diff --git a/src/spawn/spawn.c b/src/spawn/spawn.c new file mode 100644 index 0000000..686b0da --- /dev/null +++ b/src/spawn/spawn.c @@ -0,0 +1,373 @@ +/*++ +/* NAME +/* spawn 8 +/* SUMMARY +/* Postfix external command spawner +/* SYNOPSIS +/* \fBspawn\fR [generic Postfix daemon options] command_attributes... +/* DESCRIPTION +/* The \fBspawn\fR(8) daemon provides the Postfix equivalent +/* of \fBinetd\fR. +/* It listens on a port as specified in the Postfix \fBmaster.cf\fR file +/* and spawns an external command whenever a connection is established. +/* The connection can be made over local IPC (such as UNIX-domain +/* sockets) or over non-local IPC (such as TCP sockets). +/* The command's standard input, output and error streams are connected +/* directly to the communication endpoint. +/* +/* This daemon expects to be run from the \fBmaster\fR(8) process +/* manager. +/* COMMAND ATTRIBUTE SYNTAX +/* .ad +/* .fi +/* The external command attributes are given in the \fBmaster.cf\fR +/* file at the end of a service definition. The syntax is as follows: +/* .IP "\fBuser\fR=\fIusername\fR (required)" +/* .IP "\fBuser\fR=\fIusername\fR:\fIgroupname\fR" +/* The external command is executed with the rights of the +/* specified \fIusername\fR. The software refuses to execute +/* commands with root privileges, or with the privileges of the +/* mail system owner. If \fIgroupname\fR is specified, the +/* corresponding group ID is used instead of the group ID +/* of \fIusername\fR. +/* .IP "\fBargv\fR=\fIcommand\fR... (required)" +/* The command to be executed. This must be specified as the +/* last command attribute. +/* The command is executed directly, i.e. without interpretation of +/* shell meta characters by a shell command interpreter. +/* BUGS +/* In order to enforce standard Postfix process resource controls, +/* the \fBspawn\fR(8) daemon runs only one external command at a time. +/* As such, it presents a noticeable overhead by wasting precious +/* process resources. The \fBspawn\fR(8) daemon is expected to be +/* replaced by a more structural solution. +/* DIAGNOSTICS +/* The \fBspawn\fR(8) daemon reports abnormal child exits. +/* Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8). +/* SECURITY +/* .fi +/* .ad +/* This program needs root privilege in order to execute external +/* commands as the specified user. It is therefore security sensitive. +/* However the \fBspawn\fR(8) daemon does not talk to the external command +/* and thus is not vulnerable to data-driven attacks. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically as \fBspawn\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* +/* In the text below, \fItransport\fR is the first field of the entry +/* in the \fBmaster.cf\fR file. +/* RESOURCE AND RATE CONTROL +/* .ad +/* .fi +/* .IP "\fBtransport_time_limit ($command_time_limit)\fR" +/* A transport-specific override for the command_time_limit parameter +/* value, where \fItransport\fR is the master.cf name of the message +/* delivery transport. +/* MISCELLANEOUS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBexport_environment (see 'postconf -d' output)\fR" +/* The list of environment variables that a Postfix process will export +/* to non-Postfix processes. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmail_owner (postfix)\fR" +/* The UNIX system account that owns the Postfix queue and most Postfix +/* daemon processes. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* postconf(5), configuration parameters +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Single server skeleton. */ + +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * Tunable parameters. Values are taken from the config file, after + * prepending the service name to _name, and so on. + */ +int var_command_maxtime; /* system-wide */ + + /* + * For convenience. Instead of passing around lists of parameters, bundle + * them up in convenient structures. + */ +typedef struct { + char **argv; /* argument vector */ + uid_t uid; /* command privileges */ + gid_t gid; /* command privileges */ + int time_limit; /* per-service time limit */ +} SPAWN_ATTR; + +/* get_service_attr - get service attributes */ + +static void get_service_attr(SPAWN_ATTR *attr, char *service, char **argv) +{ + const char *myname = "get_service_attr"; + struct passwd *pwd; + struct group *grp; + char *user; /* user name */ + char *group; /* group name */ + + /* + * Initialize. + */ + user = 0; + group = 0; + attr->argv = 0; + + /* + * Figure out the command time limit for this transport. + */ + attr->time_limit = + get_mail_conf_time2(service, _MAXTIME, var_command_maxtime, 's', 1, 0); + + /* + * Iterate over the command-line attribute list. + */ + for ( /* void */ ; *argv != 0; argv++) { + + /* + * user=username[:groupname] + */ + if (strncasecmp("user=", *argv, sizeof("user=") - 1) == 0) { + user = *argv + sizeof("user=") - 1; + if ((group = split_at(user, ':')) != 0) /* XXX clobbers argv */ + if (*group == 0) + group = 0; + if ((pwd = getpwnam(user)) == 0) + msg_fatal("unknown user name: %s", user); + attr->uid = pwd->pw_uid; + if (group != 0) { + if ((grp = getgrnam(group)) == 0) + msg_fatal("unknown group name: %s", group); + attr->gid = grp->gr_gid; + } else { + attr->gid = pwd->pw_gid; + } + } + + /* + * argv=command... + */ + else if (strncasecmp("argv=", *argv, sizeof("argv=") - 1) == 0) { + *argv += sizeof("argv=") - 1; /* XXX clobbers argv */ + attr->argv = argv; + break; + } + + /* + * Bad. + */ + else + msg_fatal("unknown attribute name: %s", *argv); + } + + /* + * Sanity checks. Verify that every member has an acceptable value. + */ + if (user == 0) + msg_fatal("missing user= attribute"); + if (attr->argv == 0) + msg_fatal("missing argv= attribute"); + if (attr->uid == 0) + msg_fatal("request to deliver as root"); + if (attr->uid == var_owner_uid) + msg_fatal("request to deliver as mail system owner"); + if (attr->gid == 0) + msg_fatal("request to use privileged group id %ld", (long) attr->gid); + if (attr->gid == var_owner_gid) + msg_fatal("request to use mail system owner group id %ld", (long) attr->gid); + if (attr->uid == (uid_t) (-1)) + msg_fatal("user must not have user ID -1"); + if (attr->gid == (gid_t) (-1)) + msg_fatal("user must not have group ID -1"); + + /* + * Give the poor tester a clue of what is going on. + */ + if (msg_verbose) + msg_info("%s: uid %ld, gid %ld; time %d", + myname, (long) attr->uid, (long) attr->gid, attr->time_limit); +} + +/* spawn_service - perform service for client */ + +static void spawn_service(VSTREAM *client_stream, char *service, char **argv) +{ + const char *myname = "spawn_service"; + static SPAWN_ATTR attr; + WAIT_STATUS_T status; + ARGV *export_env; + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * dedicated to running an external command. + */ + if (msg_verbose) + msg_info("%s: service=%s, command=%s...", myname, service, argv[0]); + + /* + * Look up service attributes and config information only once. This is + * safe since the information comes from a trusted source. + */ + if (attr.argv == 0) { + get_service_attr(&attr, service, argv); + } + + /* + * Execute the command. + */ + export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ); + status = spawn_command(CA_SPAWN_CMD_STDIN(vstream_fileno(client_stream)), + CA_SPAWN_CMD_STDOUT(vstream_fileno(client_stream)), + CA_SPAWN_CMD_STDERR(vstream_fileno(client_stream)), + CA_SPAWN_CMD_UID(attr.uid), + CA_SPAWN_CMD_GID(attr.gid), + CA_SPAWN_CMD_ARGV(attr.argv), + CA_SPAWN_CMD_TIME_LIMIT(attr.time_limit), + CA_SPAWN_CMD_EXPORT(export_env->argv), + CA_SPAWN_CMD_END); + argv_free(export_env); + + /* + * Warn about unsuccessful completion. + */ + if (!NORMAL_EXIT_STATUS(status)) { + if (WIFEXITED(status)) + msg_warn("command %s exit status %d", + attr.argv[0], WEXITSTATUS(status)); + if (WIFSIGNALED(status)) + msg_warn("command %s killed by signal %d", + attr.argv[0], WTERMSIG(status)); + } +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* drop_privileges - drop privileges most of the time */ + +static void drop_privileges(char *unused_name, char **unused_argv) +{ + set_eugid(var_owner_uid, var_owner_gid); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_COMMAND_MAXTIME, DEF_COMMAND_MAXTIME, &var_command_maxtime, 1, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, spawn_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_POST_INIT(drop_privileges), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_PRIVILEGED, + 0); +} diff --git a/src/tls/.indent.pro b/src/tls/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/tls/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/tls/Makefile.in b/src/tls/Makefile.in new file mode 100644 index 0000000..948afab --- /dev/null +++ b/src/tls/Makefile.in @@ -0,0 +1,601 @@ +SHELL = /bin/sh +SRCS = tls_prng_dev.c tls_prng_egd.c tls_prng_file.c tls_fprint.c \ + tls_prng_exch.c tls_stream.c tls_bio_ops.c tls_misc.c tls_dh.c \ + tls_verify.c tls_dane.c tls_certkey.c tls_session.c \ + tls_client.c tls_server.c tls_scache.c tls_mgr.c tls_seed.c \ + tls_level.c \ + tls_proxy_clnt.c tls_proxy_context_print.c tls_proxy_context_scan.c \ + tls_proxy_client_init_print.c tls_proxy_client_init_scan.c \ + tls_proxy_server_init_print.c tls_proxy_server_init_scan.c \ + tls_proxy_client_start_print.c tls_proxy_client_start_scan.c \ + tls_proxy_server_start_print.c tls_proxy_server_start_scan.c \ + tls_proxy_client_misc.c +OBJS = tls_prng_dev.o tls_prng_egd.o tls_prng_file.o tls_fprint.o \ + tls_prng_exch.o tls_stream.o tls_bio_ops.o tls_misc.o tls_dh.o \ + tls_verify.o tls_dane.o tls_certkey.o tls_session.o \ + tls_client.o tls_server.o tls_scache.o tls_mgr.o tls_seed.o \ + tls_level.o \ + tls_proxy_clnt.o tls_proxy_context_print.o tls_proxy_context_scan.o \ + tls_proxy_client_print.o tls_proxy_client_scan.o \ + tls_proxy_server_print.o tls_proxy_server_scan.o \ + tls_proxy_client_misc.o +HDRS = tls.h tls_prng.h tls_scache.h tls_mgr.h tls_proxy.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +INCL = +LIB = lib$(LIB_PREFIX)tls$(LIB_SUFFIX) +TESTPROG= tls_dh tls_mgr tls_dane tls_certkey + +LIBS = ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) +LIB_DIR = ../../lib +INC_DIR = ../../include +MAKES = + +.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c + +all: $(LIB) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: tls_certkey_tests + +tls_certkey_tests: test + @echo Testing loading of keys and certs + @for pem in goodchains.pem; do \ + $(SHLIB_ENV) $(VALGRIND) ./tls_certkey $$pem > $$pem.out 2>&1 || exit 1; \ + diff $$pem.ref $$pem.out || exit 1; \ + echo " $$pem: OK"; \ + done; \ + for pem in *-mixed-*.pem ; do \ + $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -m $$pem > $$pem.out 2>&1 || exit 1; \ + diff $$pem.ref $$pem.out || exit 1; \ + echo " $$pem: OK"; \ + $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -k $$pem $$pem > $$pem.out 2>&1 || exit 1; \ + diff $$pem.ref $$pem.out || exit 1; \ + echo " $$pem (with key in $$pem): OK"; \ + case $$pem in good-*) \ + ln -sf $$pem tmpkey.pem; \ + $(SHLIB_ENV) $(VALGRIND) ./tls_certkey -k tmpkey.pem $$pem > $$pem.out 2>&1 || exit 1; \ + diff $$pem.ref $$pem.out || exit 1; \ + echo " $$pem (with key in tmpkey.pem): OK"; \ + rm -f tmpkey.pem;; \ + esac; \ + done; \ + for pem in bad-*.pem; do \ + $(SHLIB_ENV) $(VALGRIND) ./tls_certkey $$pem > $$pem.out 2>&1 && exit 1 || : ok; \ + egrep -v 'TLS library problem' $$pem.out | diff $$pem.ref - || \ + exit 1; \ + echo " $$pem: OK"; \ + done + +root_tests: + +$(LIB): $(OBJS) + $(AR) $(ARFL) $(LIB) $? + $(RANLIB) $(LIB) + $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS) + +$(LIB_DIR)/$(LIB): $(LIB) + cp $(LIB) $(LIB_DIR) + $(RANLIB) $(LIB_DIR)/$(LIB) + +update: $(LIB_DIR)/$(LIB) $(HDRS) + -for i in $(HDRS); \ + do \ + cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \ + done + cd $(INC_DIR); chmod 644 $(HDRS) + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o $(LIB) *core $(TESTPROG) junk *.pem.out + rm -rf printfck + +tidy: clean + +tls_dh: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +tls_mgr: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +tls_dane: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +tls_certkey: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +tls_bio_ops.o: ../../include/argv.h +tls_bio_ops.o: ../../include/check_arg.h +tls_bio_ops.o: ../../include/dns.h +tls_bio_ops.o: ../../include/iostuff.h +tls_bio_ops.o: ../../include/msg.h +tls_bio_ops.o: ../../include/myaddrinfo.h +tls_bio_ops.o: ../../include/name_code.h +tls_bio_ops.o: ../../include/name_mask.h +tls_bio_ops.o: ../../include/sock_addr.h +tls_bio_ops.o: ../../include/sys_defs.h +tls_bio_ops.o: ../../include/vbuf.h +tls_bio_ops.o: ../../include/vstream.h +tls_bio_ops.o: ../../include/vstring.h +tls_bio_ops.o: tls.h +tls_bio_ops.o: tls_bio_ops.c +tls_certkey.o: ../../include/argv.h +tls_certkey.o: ../../include/check_arg.h +tls_certkey.o: ../../include/dns.h +tls_certkey.o: ../../include/mail_params.h +tls_certkey.o: ../../include/msg.h +tls_certkey.o: ../../include/myaddrinfo.h +tls_certkey.o: ../../include/name_code.h +tls_certkey.o: ../../include/name_mask.h +tls_certkey.o: ../../include/sock_addr.h +tls_certkey.o: ../../include/sys_defs.h +tls_certkey.o: ../../include/vbuf.h +tls_certkey.o: ../../include/vstream.h +tls_certkey.o: ../../include/vstring.h +tls_certkey.o: tls.h +tls_certkey.o: tls_certkey.c +tls_client.o: ../../include/argv.h +tls_client.o: ../../include/check_arg.h +tls_client.o: ../../include/dict.h +tls_client.o: ../../include/dns.h +tls_client.o: ../../include/iostuff.h +tls_client.o: ../../include/mail_params.h +tls_client.o: ../../include/midna_domain.h +tls_client.o: ../../include/msg.h +tls_client.o: ../../include/myaddrinfo.h +tls_client.o: ../../include/myflock.h +tls_client.o: ../../include/mymalloc.h +tls_client.o: ../../include/name_code.h +tls_client.o: ../../include/name_mask.h +tls_client.o: ../../include/sock_addr.h +tls_client.o: ../../include/stringops.h +tls_client.o: ../../include/sys_defs.h +tls_client.o: ../../include/vbuf.h +tls_client.o: ../../include/vstream.h +tls_client.o: ../../include/vstring.h +tls_client.o: tls.h +tls_client.o: tls_client.c +tls_client.o: tls_mgr.h +tls_client.o: tls_scache.h +tls_dane.o: ../../include/argv.h +tls_dane.o: ../../include/check_arg.h +tls_dane.o: ../../include/ctable.h +tls_dane.o: ../../include/dns.h +tls_dane.o: ../../include/events.h +tls_dane.o: ../../include/hex_code.h +tls_dane.o: ../../include/mail_params.h +tls_dane.o: ../../include/midna_domain.h +tls_dane.o: ../../include/msg.h +tls_dane.o: ../../include/myaddrinfo.h +tls_dane.o: ../../include/mymalloc.h +tls_dane.o: ../../include/name_code.h +tls_dane.o: ../../include/name_mask.h +tls_dane.o: ../../include/safe_ultostr.h +tls_dane.o: ../../include/sock_addr.h +tls_dane.o: ../../include/split_at.h +tls_dane.o: ../../include/stringops.h +tls_dane.o: ../../include/sys_defs.h +tls_dane.o: ../../include/timecmp.h +tls_dane.o: ../../include/vbuf.h +tls_dane.o: ../../include/vstream.h +tls_dane.o: ../../include/vstring.h +tls_dane.o: tls.h +tls_dane.o: tls_dane.c +tls_dh.o: ../../include/argv.h +tls_dh.o: ../../include/check_arg.h +tls_dh.o: ../../include/dns.h +tls_dh.o: ../../include/mail_params.h +tls_dh.o: ../../include/msg.h +tls_dh.o: ../../include/myaddrinfo.h +tls_dh.o: ../../include/mymalloc.h +tls_dh.o: ../../include/name_code.h +tls_dh.o: ../../include/name_mask.h +tls_dh.o: ../../include/sock_addr.h +tls_dh.o: ../../include/stringops.h +tls_dh.o: ../../include/sys_defs.h +tls_dh.o: ../../include/vbuf.h +tls_dh.o: ../../include/vstream.h +tls_dh.o: ../../include/vstring.h +tls_dh.o: tls.h +tls_dh.o: tls_dh.c +tls_fprint.o: ../../include/argv.h +tls_fprint.o: ../../include/check_arg.h +tls_fprint.o: ../../include/dns.h +tls_fprint.o: ../../include/mail_params.h +tls_fprint.o: ../../include/msg.h +tls_fprint.o: ../../include/myaddrinfo.h +tls_fprint.o: ../../include/mymalloc.h +tls_fprint.o: ../../include/name_code.h +tls_fprint.o: ../../include/name_mask.h +tls_fprint.o: ../../include/sock_addr.h +tls_fprint.o: ../../include/stringops.h +tls_fprint.o: ../../include/sys_defs.h +tls_fprint.o: ../../include/vbuf.h +tls_fprint.o: ../../include/vstream.h +tls_fprint.o: ../../include/vstring.h +tls_fprint.o: tls.h +tls_fprint.o: tls_fprint.c +tls_level.o: ../../include/argv.h +tls_level.o: ../../include/check_arg.h +tls_level.o: ../../include/dns.h +tls_level.o: ../../include/myaddrinfo.h +tls_level.o: ../../include/name_code.h +tls_level.o: ../../include/name_mask.h +tls_level.o: ../../include/sock_addr.h +tls_level.o: ../../include/sys_defs.h +tls_level.o: ../../include/vbuf.h +tls_level.o: ../../include/vstream.h +tls_level.o: ../../include/vstring.h +tls_level.o: tls.h +tls_level.o: tls_level.c +tls_mgr.o: ../../include/argv.h +tls_mgr.o: ../../include/attr.h +tls_mgr.o: ../../include/attr_clnt.h +tls_mgr.o: ../../include/check_arg.h +tls_mgr.o: ../../include/dict.h +tls_mgr.o: ../../include/htable.h +tls_mgr.o: ../../include/iostuff.h +tls_mgr.o: ../../include/mail_params.h +tls_mgr.o: ../../include/mail_proto.h +tls_mgr.o: ../../include/msg.h +tls_mgr.o: ../../include/myflock.h +tls_mgr.o: ../../include/mymalloc.h +tls_mgr.o: ../../include/nvtable.h +tls_mgr.o: ../../include/stringops.h +tls_mgr.o: ../../include/sys_defs.h +tls_mgr.o: ../../include/vbuf.h +tls_mgr.o: ../../include/vstream.h +tls_mgr.o: ../../include/vstring.h +tls_mgr.o: tls_mgr.c +tls_mgr.o: tls_mgr.h +tls_mgr.o: tls_scache.h +tls_misc.o: ../../include/argv.h +tls_misc.o: ../../include/check_arg.h +tls_misc.o: ../../include/dict.h +tls_misc.o: ../../include/dns.h +tls_misc.o: ../../include/mail_conf.h +tls_misc.o: ../../include/mail_params.h +tls_misc.o: ../../include/maps.h +tls_misc.o: ../../include/msg.h +tls_misc.o: ../../include/myaddrinfo.h +tls_misc.o: ../../include/myflock.h +tls_misc.o: ../../include/mymalloc.h +tls_misc.o: ../../include/name_code.h +tls_misc.o: ../../include/name_mask.h +tls_misc.o: ../../include/sock_addr.h +tls_misc.o: ../../include/stringops.h +tls_misc.o: ../../include/sys_defs.h +tls_misc.o: ../../include/valid_hostname.h +tls_misc.o: ../../include/vbuf.h +tls_misc.o: ../../include/vstream.h +tls_misc.o: ../../include/vstring.h +tls_misc.o: tls.h +tls_misc.o: tls_misc.c +tls_prng_dev.o: ../../include/connect.h +tls_prng_dev.o: ../../include/iostuff.h +tls_prng_dev.o: ../../include/msg.h +tls_prng_dev.o: ../../include/mymalloc.h +tls_prng_dev.o: ../../include/sys_defs.h +tls_prng_dev.o: tls_prng.h +tls_prng_dev.o: tls_prng_dev.c +tls_prng_egd.o: ../../include/connect.h +tls_prng_egd.o: ../../include/iostuff.h +tls_prng_egd.o: ../../include/msg.h +tls_prng_egd.o: ../../include/mymalloc.h +tls_prng_egd.o: ../../include/sys_defs.h +tls_prng_egd.o: tls_prng.h +tls_prng_egd.o: tls_prng_egd.c +tls_prng_exch.o: ../../include/iostuff.h +tls_prng_exch.o: ../../include/msg.h +tls_prng_exch.o: ../../include/myflock.h +tls_prng_exch.o: ../../include/mymalloc.h +tls_prng_exch.o: ../../include/sys_defs.h +tls_prng_exch.o: tls_prng.h +tls_prng_exch.o: tls_prng_exch.c +tls_prng_file.o: ../../include/connect.h +tls_prng_file.o: ../../include/iostuff.h +tls_prng_file.o: ../../include/msg.h +tls_prng_file.o: ../../include/mymalloc.h +tls_prng_file.o: ../../include/sys_defs.h +tls_prng_file.o: tls_prng.h +tls_prng_file.o: tls_prng_file.c +tls_proxy_client_misc.o: ../../include/argv.h +tls_proxy_client_misc.o: ../../include/attr.h +tls_proxy_client_misc.o: ../../include/check_arg.h +tls_proxy_client_misc.o: ../../include/dns.h +tls_proxy_client_misc.o: ../../include/htable.h +tls_proxy_client_misc.o: ../../include/mail_params.h +tls_proxy_client_misc.o: ../../include/msg.h +tls_proxy_client_misc.o: ../../include/myaddrinfo.h +tls_proxy_client_misc.o: ../../include/mymalloc.h +tls_proxy_client_misc.o: ../../include/name_code.h +tls_proxy_client_misc.o: ../../include/name_mask.h +tls_proxy_client_misc.o: ../../include/nvtable.h +tls_proxy_client_misc.o: ../../include/sock_addr.h +tls_proxy_client_misc.o: ../../include/sys_defs.h +tls_proxy_client_misc.o: ../../include/vbuf.h +tls_proxy_client_misc.o: ../../include/vstream.h +tls_proxy_client_misc.o: ../../include/vstring.h +tls_proxy_client_misc.o: tls.h +tls_proxy_client_misc.o: tls_proxy.h +tls_proxy_client_misc.o: tls_proxy_client_misc.c +tls_proxy_client_print.o: ../../include/argv.h +tls_proxy_client_print.o: ../../include/argv_attr.h +tls_proxy_client_print.o: ../../include/attr.h +tls_proxy_client_print.o: ../../include/check_arg.h +tls_proxy_client_print.o: ../../include/dns.h +tls_proxy_client_print.o: ../../include/htable.h +tls_proxy_client_print.o: ../../include/mail_params.h +tls_proxy_client_print.o: ../../include/msg.h +tls_proxy_client_print.o: ../../include/myaddrinfo.h +tls_proxy_client_print.o: ../../include/mymalloc.h +tls_proxy_client_print.o: ../../include/name_code.h +tls_proxy_client_print.o: ../../include/name_mask.h +tls_proxy_client_print.o: ../../include/nvtable.h +tls_proxy_client_print.o: ../../include/sock_addr.h +tls_proxy_client_print.o: ../../include/sys_defs.h +tls_proxy_client_print.o: ../../include/vbuf.h +tls_proxy_client_print.o: ../../include/vstream.h +tls_proxy_client_print.o: ../../include/vstring.h +tls_proxy_client_print.o: tls.h +tls_proxy_client_print.o: tls_proxy.h +tls_proxy_client_print.o: tls_proxy_client_print.c +tls_proxy_client_scan.o: ../../include/argv.h +tls_proxy_client_scan.o: ../../include/argv_attr.h +tls_proxy_client_scan.o: ../../include/attr.h +tls_proxy_client_scan.o: ../../include/check_arg.h +tls_proxy_client_scan.o: ../../include/dns.h +tls_proxy_client_scan.o: ../../include/htable.h +tls_proxy_client_scan.o: ../../include/mail_params.h +tls_proxy_client_scan.o: ../../include/msg.h +tls_proxy_client_scan.o: ../../include/myaddrinfo.h +tls_proxy_client_scan.o: ../../include/mymalloc.h +tls_proxy_client_scan.o: ../../include/name_code.h +tls_proxy_client_scan.o: ../../include/name_mask.h +tls_proxy_client_scan.o: ../../include/nvtable.h +tls_proxy_client_scan.o: ../../include/sock_addr.h +tls_proxy_client_scan.o: ../../include/sys_defs.h +tls_proxy_client_scan.o: ../../include/vbuf.h +tls_proxy_client_scan.o: ../../include/vstream.h +tls_proxy_client_scan.o: ../../include/vstring.h +tls_proxy_client_scan.o: tls.h +tls_proxy_client_scan.o: tls_proxy.h +tls_proxy_client_scan.o: tls_proxy_client_scan.c +tls_proxy_clnt.o: ../../include/argv.h +tls_proxy_clnt.o: ../../include/attr.h +tls_proxy_clnt.o: ../../include/check_arg.h +tls_proxy_clnt.o: ../../include/connect.h +tls_proxy_clnt.o: ../../include/dns.h +tls_proxy_clnt.o: ../../include/htable.h +tls_proxy_clnt.o: ../../include/iostuff.h +tls_proxy_clnt.o: ../../include/mail_params.h +tls_proxy_clnt.o: ../../include/mail_proto.h +tls_proxy_clnt.o: ../../include/msg.h +tls_proxy_clnt.o: ../../include/myaddrinfo.h +tls_proxy_clnt.o: ../../include/mymalloc.h +tls_proxy_clnt.o: ../../include/name_code.h +tls_proxy_clnt.o: ../../include/name_mask.h +tls_proxy_clnt.o: ../../include/nvtable.h +tls_proxy_clnt.o: ../../include/sock_addr.h +tls_proxy_clnt.o: ../../include/stringops.h +tls_proxy_clnt.o: ../../include/sys_defs.h +tls_proxy_clnt.o: ../../include/vbuf.h +tls_proxy_clnt.o: ../../include/vstream.h +tls_proxy_clnt.o: ../../include/vstring.h +tls_proxy_clnt.o: tls.h +tls_proxy_clnt.o: tls_proxy.h +tls_proxy_clnt.o: tls_proxy_clnt.c +tls_proxy_context_print.o: ../../include/argv.h +tls_proxy_context_print.o: ../../include/attr.h +tls_proxy_context_print.o: ../../include/check_arg.h +tls_proxy_context_print.o: ../../include/dns.h +tls_proxy_context_print.o: ../../include/htable.h +tls_proxy_context_print.o: ../../include/myaddrinfo.h +tls_proxy_context_print.o: ../../include/mymalloc.h +tls_proxy_context_print.o: ../../include/name_code.h +tls_proxy_context_print.o: ../../include/name_mask.h +tls_proxy_context_print.o: ../../include/nvtable.h +tls_proxy_context_print.o: ../../include/sock_addr.h +tls_proxy_context_print.o: ../../include/sys_defs.h +tls_proxy_context_print.o: ../../include/vbuf.h +tls_proxy_context_print.o: ../../include/vstream.h +tls_proxy_context_print.o: ../../include/vstring.h +tls_proxy_context_print.o: tls.h +tls_proxy_context_print.o: tls_proxy.h +tls_proxy_context_print.o: tls_proxy_context_print.c +tls_proxy_context_scan.o: ../../include/argv.h +tls_proxy_context_scan.o: ../../include/attr.h +tls_proxy_context_scan.o: ../../include/check_arg.h +tls_proxy_context_scan.o: ../../include/dns.h +tls_proxy_context_scan.o: ../../include/htable.h +tls_proxy_context_scan.o: ../../include/msg.h +tls_proxy_context_scan.o: ../../include/myaddrinfo.h +tls_proxy_context_scan.o: ../../include/mymalloc.h +tls_proxy_context_scan.o: ../../include/name_code.h +tls_proxy_context_scan.o: ../../include/name_mask.h +tls_proxy_context_scan.o: ../../include/nvtable.h +tls_proxy_context_scan.o: ../../include/sock_addr.h +tls_proxy_context_scan.o: ../../include/sys_defs.h +tls_proxy_context_scan.o: ../../include/vbuf.h +tls_proxy_context_scan.o: ../../include/vstream.h +tls_proxy_context_scan.o: ../../include/vstring.h +tls_proxy_context_scan.o: tls.h +tls_proxy_context_scan.o: tls_proxy.h +tls_proxy_context_scan.o: tls_proxy_context_scan.c +tls_proxy_server_print.o: ../../include/argv.h +tls_proxy_server_print.o: ../../include/attr.h +tls_proxy_server_print.o: ../../include/check_arg.h +tls_proxy_server_print.o: ../../include/dns.h +tls_proxy_server_print.o: ../../include/htable.h +tls_proxy_server_print.o: ../../include/myaddrinfo.h +tls_proxy_server_print.o: ../../include/mymalloc.h +tls_proxy_server_print.o: ../../include/name_code.h +tls_proxy_server_print.o: ../../include/name_mask.h +tls_proxy_server_print.o: ../../include/nvtable.h +tls_proxy_server_print.o: ../../include/sock_addr.h +tls_proxy_server_print.o: ../../include/sys_defs.h +tls_proxy_server_print.o: ../../include/vbuf.h +tls_proxy_server_print.o: ../../include/vstream.h +tls_proxy_server_print.o: ../../include/vstring.h +tls_proxy_server_print.o: tls.h +tls_proxy_server_print.o: tls_proxy.h +tls_proxy_server_print.o: tls_proxy_server_print.c +tls_proxy_server_scan.o: ../../include/argv.h +tls_proxy_server_scan.o: ../../include/attr.h +tls_proxy_server_scan.o: ../../include/check_arg.h +tls_proxy_server_scan.o: ../../include/dns.h +tls_proxy_server_scan.o: ../../include/htable.h +tls_proxy_server_scan.o: ../../include/myaddrinfo.h +tls_proxy_server_scan.o: ../../include/mymalloc.h +tls_proxy_server_scan.o: ../../include/name_code.h +tls_proxy_server_scan.o: ../../include/name_mask.h +tls_proxy_server_scan.o: ../../include/nvtable.h +tls_proxy_server_scan.o: ../../include/sock_addr.h +tls_proxy_server_scan.o: ../../include/sys_defs.h +tls_proxy_server_scan.o: ../../include/vbuf.h +tls_proxy_server_scan.o: ../../include/vstream.h +tls_proxy_server_scan.o: ../../include/vstring.h +tls_proxy_server_scan.o: tls.h +tls_proxy_server_scan.o: tls_proxy.h +tls_proxy_server_scan.o: tls_proxy_server_scan.c +tls_rsa.o: tls_rsa.c +tls_scache.o: ../../include/argv.h +tls_scache.o: ../../include/check_arg.h +tls_scache.o: ../../include/dict.h +tls_scache.o: ../../include/hex_code.h +tls_scache.o: ../../include/msg.h +tls_scache.o: ../../include/myflock.h +tls_scache.o: ../../include/mymalloc.h +tls_scache.o: ../../include/stringops.h +tls_scache.o: ../../include/sys_defs.h +tls_scache.o: ../../include/timecmp.h +tls_scache.o: ../../include/vbuf.h +tls_scache.o: ../../include/vstream.h +tls_scache.o: ../../include/vstring.h +tls_scache.o: tls_scache.c +tls_scache.o: tls_scache.h +tls_seed.o: ../../include/argv.h +tls_seed.o: ../../include/check_arg.h +tls_seed.o: ../../include/dict.h +tls_seed.o: ../../include/dns.h +tls_seed.o: ../../include/msg.h +tls_seed.o: ../../include/myaddrinfo.h +tls_seed.o: ../../include/myflock.h +tls_seed.o: ../../include/name_code.h +tls_seed.o: ../../include/name_mask.h +tls_seed.o: ../../include/sock_addr.h +tls_seed.o: ../../include/sys_defs.h +tls_seed.o: ../../include/vbuf.h +tls_seed.o: ../../include/vstream.h +tls_seed.o: ../../include/vstring.h +tls_seed.o: tls.h +tls_seed.o: tls_mgr.h +tls_seed.o: tls_scache.h +tls_seed.o: tls_seed.c +tls_server.o: ../../include/argv.h +tls_server.o: ../../include/check_arg.h +tls_server.o: ../../include/dict.h +tls_server.o: ../../include/dns.h +tls_server.o: ../../include/hex_code.h +tls_server.o: ../../include/iostuff.h +tls_server.o: ../../include/mail_params.h +tls_server.o: ../../include/msg.h +tls_server.o: ../../include/myaddrinfo.h +tls_server.o: ../../include/myflock.h +tls_server.o: ../../include/mymalloc.h +tls_server.o: ../../include/name_code.h +tls_server.o: ../../include/name_mask.h +tls_server.o: ../../include/sock_addr.h +tls_server.o: ../../include/stringops.h +tls_server.o: ../../include/sys_defs.h +tls_server.o: ../../include/vbuf.h +tls_server.o: ../../include/vstream.h +tls_server.o: ../../include/vstring.h +tls_server.o: tls.h +tls_server.o: tls_mgr.h +tls_server.o: tls_scache.h +tls_server.o: tls_server.c +tls_session.o: ../../include/argv.h +tls_session.o: ../../include/check_arg.h +tls_session.o: ../../include/dns.h +tls_session.o: ../../include/mail_params.h +tls_session.o: ../../include/msg.h +tls_session.o: ../../include/myaddrinfo.h +tls_session.o: ../../include/mymalloc.h +tls_session.o: ../../include/name_code.h +tls_session.o: ../../include/name_mask.h +tls_session.o: ../../include/sock_addr.h +tls_session.o: ../../include/sys_defs.h +tls_session.o: ../../include/vbuf.h +tls_session.o: ../../include/vstream.h +tls_session.o: ../../include/vstring.h +tls_session.o: tls.h +tls_session.o: tls_session.c +tls_stream.o: ../../include/argv.h +tls_stream.o: ../../include/check_arg.h +tls_stream.o: ../../include/dns.h +tls_stream.o: ../../include/iostuff.h +tls_stream.o: ../../include/msg.h +tls_stream.o: ../../include/myaddrinfo.h +tls_stream.o: ../../include/name_code.h +tls_stream.o: ../../include/name_mask.h +tls_stream.o: ../../include/sock_addr.h +tls_stream.o: ../../include/sys_defs.h +tls_stream.o: ../../include/vbuf.h +tls_stream.o: ../../include/vstream.h +tls_stream.o: ../../include/vstring.h +tls_stream.o: tls.h +tls_stream.o: tls_stream.c +tls_verify.o: ../../include/argv.h +tls_verify.o: ../../include/check_arg.h +tls_verify.o: ../../include/dns.h +tls_verify.o: ../../include/msg.h +tls_verify.o: ../../include/myaddrinfo.h +tls_verify.o: ../../include/mymalloc.h +tls_verify.o: ../../include/name_code.h +tls_verify.o: ../../include/name_mask.h +tls_verify.o: ../../include/sock_addr.h +tls_verify.o: ../../include/stringops.h +tls_verify.o: ../../include/sys_defs.h +tls_verify.o: ../../include/vbuf.h +tls_verify.o: ../../include/vstream.h +tls_verify.o: ../../include/vstring.h +tls_verify.o: tls.h +tls_verify.o: tls_verify.c diff --git a/src/tls/bad-back-to-back-keys.pem b/src/tls/bad-back-to-back-keys.pem new file mode 100644 index 0000000..b1895ec --- /dev/null +++ b/src/tls/bad-back-to-back-keys.pem @@ -0,0 +1,64 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA +lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz +Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny +K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE +pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE +9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2 +tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/ +0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU +TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7 +ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE +8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU +ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc +5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv +rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/ +QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz +CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W +2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe +RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC +LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc +kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP +9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM +8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo ++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC +yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9 +Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc +Okn/aJneEclcdzv8JHfVMcdNxA== +-----END PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH +S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo +XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3 +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1 +ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV +BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO +wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al +VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa +XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK +BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7 +CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD +DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M +ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs +G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD +VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U +WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD +DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo +e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z +UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud +IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI +KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X +AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o= +-----END CERTIFICATE----- diff --git a/src/tls/bad-back-to-back-keys.pem.ref b/src/tls/bad-back-to-back-keys.pem.ref new file mode 100644 index 0000000..192e9ea --- /dev/null +++ b/src/tls/bad-back-to-back-keys.pem.ref @@ -0,0 +1,2 @@ +unknown: warning: error loading certificate chain: key at index 1 in bad-back-to-back-keys.pem not followed by a certificate +unknown: warning: error loading private keys and certificates from: bad-back-to-back-keys.pem: disabling TLS support diff --git a/src/tls/bad-ec-cert-before-key.pem b/src/tls/bad-ec-cert-before-key.pem new file mode 100644 index 0000000..520927a --- /dev/null +++ b/src/tls/bad-ec-cert-before-key.pem @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1 +ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV +BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO +wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al +VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa +XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK +BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7 +CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH +S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo +XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3 +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD +DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M +ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs +G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD +VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U +WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD +DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo +e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z +UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud +IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI +KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X +AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o= +-----END CERTIFICATE----- diff --git a/src/tls/bad-ec-cert-before-key.pem.ref b/src/tls/bad-ec-cert-before-key.pem.ref new file mode 100644 index 0000000..71e5f78 --- /dev/null +++ b/src/tls/bad-ec-cert-before-key.pem.ref @@ -0,0 +1,2 @@ +unknown: warning: error loading chain from bad-ec-cert-before-key.pem: key not first +unknown: warning: error loading private keys and certificates from: bad-ec-cert-before-key.pem: disabling TLS support diff --git a/src/tls/bad-key-cert-mismatch.pem b/src/tls/bad-key-cert-mismatch.pem new file mode 100644 index 0000000..1e78833 --- /dev/null +++ b/src/tls/bad-key-cert-mismatch.pem @@ -0,0 +1,36 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC +rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy +b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1 +ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV +BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO +wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al +VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa +XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK +BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7 +CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD +DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M +ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs +G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD +VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U +WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD +DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo +e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z +UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud +IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI +KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X +AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o= +-----END CERTIFICATE----- diff --git a/src/tls/bad-key-cert-mismatch.pem.ref b/src/tls/bad-key-cert-mismatch.pem.ref new file mode 100644 index 0000000..a32b657 --- /dev/null +++ b/src/tls/bad-key-cert-mismatch.pem.ref @@ -0,0 +1,2 @@ +unknown: warning: key at index 1 in bad-key-cert-mismatch.pem does not match next certificate +unknown: warning: error loading private keys and certificates from: bad-key-cert-mismatch.pem: disabling TLS support diff --git a/src/tls/bad-rsa-key-last.pem b/src/tls/bad-rsa-key-last.pem new file mode 100644 index 0000000..de15808 --- /dev/null +++ b/src/tls/bad-rsa-key-last.pem @@ -0,0 +1,64 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH +S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo +XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3 +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1 +ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV +BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO +wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al +VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa +XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK +BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7 +CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD +DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M +ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs +G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD +VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U +WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD +DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo +e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z +UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud +IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI +KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X +AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o= +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA +lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz +Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny +K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE +pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE +9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2 +tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/ +0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU +TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7 +ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE +8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU +ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc +5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv +rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/ +QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz +CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W +2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe +RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC +LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc +kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP +9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM +8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo ++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC +yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9 +Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc +Okn/aJneEclcdzv8JHfVMcdNxA== +-----END PRIVATE KEY----- diff --git a/src/tls/bad-rsa-key-last.pem.ref b/src/tls/bad-rsa-key-last.pem.ref new file mode 100644 index 0000000..a72c0d9 --- /dev/null +++ b/src/tls/bad-rsa-key-last.pem.ref @@ -0,0 +1,2 @@ +unknown: warning: No certs for key at index 5 in bad-rsa-key-last.pem +unknown: warning: error loading private keys and certificates from: bad-rsa-key-last.pem: disabling TLS support diff --git a/src/tls/ecca-cert.pem b/src/tls/ecca-cert.pem new file mode 100644 index 0000000..54418f1 --- /dev/null +++ b/src/tls/ecca-cert.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD +DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M +ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs +G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD +VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U +WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7 +-----END CERTIFICATE----- diff --git a/src/tls/ecca-pkey.pem b/src/tls/ecca-pkey.pem new file mode 100644 index 0000000..2d95f45 --- /dev/null +++ b/src/tls/ecca-pkey.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC +rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy +b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV +-----END PRIVATE KEY----- diff --git a/src/tls/ecee-cert.pem b/src/tls/ecee-cert.pem new file mode 100644 index 0000000..1afb2f5 --- /dev/null +++ b/src/tls/ecee-cert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1 +ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV +BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO +wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al +VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa +XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK +BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7 +CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg== +-----END CERTIFICATE----- diff --git a/src/tls/ecee-pkey.pem b/src/tls/ecee-pkey.pem new file mode 100644 index 0000000..ffec716 --- /dev/null +++ b/src/tls/ecee-pkey.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH +S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo +XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3 +-----END PRIVATE KEY----- diff --git a/src/tls/ecroot-cert.pem b/src/tls/ecroot-cert.pem new file mode 100644 index 0000000..4f5041c --- /dev/null +++ b/src/tls/ecroot-cert.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD +DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo +e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z +UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud +IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI +KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X +AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o= +-----END CERTIFICATE----- diff --git a/src/tls/ecroot-pkey.pem b/src/tls/ecroot-pkey.pem new file mode 100644 index 0000000..0d36dc4 --- /dev/null +++ b/src/tls/ecroot-pkey.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg85iR6mrXjfV9O38h +9/KivPeTKxdjXHQ6j2VCWD8K8KahRANCAAR8MXvjqDNXBKh79SxW0WpJfUBIptgx +1xwT2k4HkP98rlo+pmzL39gpIT1P78VwKBlrq4RE7JHnzdlQzJNAWgdG +-----END PRIVATE KEY----- diff --git a/src/tls/good-mixed-keyfirst.pem b/src/tls/good-mixed-keyfirst.pem new file mode 100644 index 0000000..c7ba743 --- /dev/null +++ b/src/tls/good-mixed-keyfirst.pem @@ -0,0 +1,45 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH +S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo +XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3 +-----END PRIVATE KEY----- +subject=/CN=mx1.example.com +issuer=/CN=EC issuer CA +-----BEGIN CERTIFICATE----- +MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1 +ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV +BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO +wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al +VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa +XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK +BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7 +CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg== +-----END CERTIFICATE----- + +subject=/CN=EC issuer CA +issuer=/CN=EC root CA +-----BEGIN CERTIFICATE----- +MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD +DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M +ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs +G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD +VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U +WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7 +-----END CERTIFICATE----- + +subject=/CN=EC root CA +issuer=/CN=EC root CA +-----BEGIN CERTIFICATE----- +MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD +DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo +e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z +UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud +IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI +KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X +AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o= +-----END CERTIFICATE----- + diff --git a/src/tls/good-mixed-keyfirst.pem.ref b/src/tls/good-mixed-keyfirst.pem.ref new file mode 100644 index 0000000..95502d8 --- /dev/null +++ b/src/tls/good-mixed-keyfirst.pem.ref @@ -0,0 +1,12 @@ +depth = 0 +issuer = /CN=EC issuer CA +subject = /CN=mx1.example.com + +depth = 1 +issuer = /CN=EC root CA +subject = /CN=EC issuer CA + +depth = 2 +issuer = /CN=EC root CA +subject = /CN=EC root CA + diff --git a/src/tls/good-mixed-keylast.pem b/src/tls/good-mixed-keylast.pem new file mode 100644 index 0000000..6ce758e --- /dev/null +++ b/src/tls/good-mixed-keylast.pem @@ -0,0 +1,45 @@ +subject=/CN=mx1.example.com +issuer=/CN=EC issuer CA +-----BEGIN CERTIFICATE----- +MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1 +ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV +BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO +wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al +VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa +XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK +BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7 +CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg== +-----END CERTIFICATE----- + +subject=/CN=EC issuer CA +issuer=/CN=EC root CA +-----BEGIN CERTIFICATE----- +MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD +DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M +ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs +G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD +VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U +WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7 +-----END CERTIFICATE----- + +subject=/CN=EC root CA +issuer=/CN=EC root CA +-----BEGIN CERTIFICATE----- +MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD +DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo +e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z +UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud +IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI +KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X +AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o= +-----END CERTIFICATE----- + +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH +S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo +XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3 +-----END PRIVATE KEY----- diff --git a/src/tls/good-mixed-keylast.pem.ref b/src/tls/good-mixed-keylast.pem.ref new file mode 100644 index 0000000..95502d8 --- /dev/null +++ b/src/tls/good-mixed-keylast.pem.ref @@ -0,0 +1,12 @@ +depth = 0 +issuer = /CN=EC issuer CA +subject = /CN=mx1.example.com + +depth = 1 +issuer = /CN=EC root CA +subject = /CN=EC issuer CA + +depth = 2 +issuer = /CN=EC root CA +subject = /CN=EC root CA + diff --git a/src/tls/good-mixed-keymiddle.pem b/src/tls/good-mixed-keymiddle.pem new file mode 100644 index 0000000..b3dda42 --- /dev/null +++ b/src/tls/good-mixed-keymiddle.pem @@ -0,0 +1,45 @@ +subject=/CN=mx1.example.com +issuer=/CN=EC issuer CA +-----BEGIN CERTIFICATE----- +MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1 +ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV +BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO +wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al +VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa +XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK +BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7 +CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg== +-----END CERTIFICATE----- + +subject=/CN=EC issuer CA +issuer=/CN=EC root CA +-----BEGIN CERTIFICATE----- +MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD +DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M +ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs +G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD +VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U +WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7 +-----END CERTIFICATE----- + +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH +S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo +XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3 +-----END PRIVATE KEY----- + +subject=/CN=EC root CA +issuer=/CN=EC root CA +-----BEGIN CERTIFICATE----- +MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD +DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo +e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z +UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud +IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI +KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X +AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o= +-----END CERTIFICATE----- diff --git a/src/tls/good-mixed-keymiddle.pem.ref b/src/tls/good-mixed-keymiddle.pem.ref new file mode 100644 index 0000000..95502d8 --- /dev/null +++ b/src/tls/good-mixed-keymiddle.pem.ref @@ -0,0 +1,12 @@ +depth = 0 +issuer = /CN=EC issuer CA +subject = /CN=mx1.example.com + +depth = 1 +issuer = /CN=EC root CA +subject = /CN=EC issuer CA + +depth = 2 +issuer = /CN=EC root CA +subject = /CN=EC root CA + diff --git a/src/tls/goodchains.pem b/src/tls/goodchains.pem new file mode 100644 index 0000000..81b2b32 --- /dev/null +++ b/src/tls/goodchains.pem @@ -0,0 +1,121 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA +lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz +Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny +K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE +pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE +9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2 +tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/ +0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU +TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7 +ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE +8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU +ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc +5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv +rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/ +QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz +CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W +2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe +RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC +LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc +kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP +9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM +8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo ++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC +yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9 +Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc +Okn/aJneEclcdzv8JHfVMcdNxA== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDLTCCAhWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1SU0Eg +aXNzdWVyIENBMCAXDTE5MDEwODA2NDQzMloYDzIxMTkwMTA5MDY0NDMyWjAaMRgw +FgYDVQQDDA9teDEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCu99Z6AB3EA2tAlWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxl +BNHTk4c8HkobGqMTRU0x0qpzMnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX ++5ZnqS9gWbLZyjkCCqVQR/nyK0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakH +ZIB8TYI6p6hanNQyz4NWq1PEpWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJki +Hka24JzNCLx3GD/Q0GNGFGoE9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIo +W0/dbG0KaKTTeHf9LegFXez2tJeLl+vpAgMBAAGjfjB8MB0GA1UdDgQWBBTCrBLC +itbyxruhJFbJEaGYdG4NRjAfBgNVHSMEGDAWgBTCbuRdkXOuqXbXqJJnH5iQPhkT +JTAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214 +MS5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAYOgZ5NaFJhmjT6V+GI5m +cuTsMC7WB5nQ8nbf2wxw052jgBvnZWAgnshtgWvrK0T6402VcTOzHt8VKS0KwLwk +FESvj2rdoDGvGqUcw6DhCns98PRuts2nMTDlKYJTYWab2Q08PPYxTtRt1Y+A2sp7 +et+bxMjYr2mdO0wkqK7guUA9H2bZkGYJck5bbna67hpzx17YomBjDUjTtMiagXKs +E5QzOXXqCQxiSunlxsMqDwt/HRxGvQy+BHuNq4epuZfeMOndIjl2ZyKdN2ABy88G +dYqRO6nBHUO2/ZxlAZf11xpueVZZ61SueimPfzbsw0B7ZvPSi9KdbPIGBbD4cCwt +JA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC/jCCAeagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg +cm9vdCBDQTAgFw0xOTAxMDgwNjQyMjRaGA8yMTE5MDEwOTA2NDIyNFowGDEWMBQG +A1UEAwwNUlNBIGlzc3VlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3tLZ4hc8RmIX0Yd0uj1Z31mK94KMcVN//L0iiR/ZOFr39ma2Ys7AL0UQeV +H9s4hUaMM91n0fkwVL0r8NUsCkuPS/MvJClWxTbN3rEzzaajD+BdyW1Y1QfdEI5t +AI2wSSieF3GtiTmxH43lDHTeZz0qhwh8NNzh+j09DwYS+TfQnO70LdmR5Vks0SoF +DmxTs2/DRup/iCWGd853C3T/lfixdhkdVwGNuxtuF6/xSEQirT2OUjjnVIcCPGq0 +Zbz3hFJ0xHoqiwNBv9PytfLk4BNf+VWTANFXnzNvZqWrOO8yQWS+RHIdf2+a96G2 +EFIgWz/rwo4WP9tozETlPzQc6P8CAwEAAaNTMFEwHQYDVR0OBBYEFMJu5F2Rc66p +dteokmcfmJA+GRMlMB8GA1UdIwQYMBaAFJGpciw0EYdhbO3y0sM62cWnHTtiMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD6ngGSUb9grVkhlb8M5 +geC4SEhBiysoXoxHleFW6FFe4k1v5dg/xbCbNwysToEG+2S498f2FfyNzKWVDSM7 +5al+4jYrKlwTbX6W9krH6iGhE0Sdz/YHWYdPCDYssdOvS46/+MGj16WPiOdj5UYY +U0+TVKlqLOcpqkPKrz/9X7sM8bXi8FQHPme8ksvnB9qMdwcru/BN62tUOw1JHnmh +CCUbI3TU5xE8fUCl4F43vsLq73EfaHs4TO9hhk2hBW7K0QOinNcFlcNy8AF7mF/T +kY4FWI/kMfw6cXkg+F44Oy3j3KUF3hgByhWzTQimY2oRpk7U5tGuC8Xgh6eHPbRG +Kck= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIC/DCCAeSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg +cm9vdCBDQTAgFw0xOTAxMDgwNjE5NDJaGA8yMTE5MDEwOTA2MTk0MlowFjEUMBIG +A1UEAwwLUlNBIHJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCoZoGzTiMbZcztQVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65An +K2vHNrE37DDRl2YTmP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6 +yuO07nut+rNFMSSjgnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0S +oxQVSkyOIr/phPAkLWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2 +X8lMaz8ooT9OI/4ln/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu +1ViLHIsm+55CJj5OI676Q4JJAgMBAAGjUzBRMB0GA1UdDgQWBBSRqXIsNBGHYWzt +8tLDOtnFpx07YjAfBgNVHSMEGDAWgBSRqXIsNBGHYWzt8tLDOtnFpx07YjAPBgNV +HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCHEQ8DRqrWz8xibaNEv3Id +SEJvgfRRsMA7jk2HAnE84bO1ZLkXF9HX7oqXbIMRw5IZRUrBXO0fUwcxz3DwTYZj +ojRPP1yNfylmc8uxyd2goMoG4OgTdt2/Ewu8bli0S9NapH9fdBYbSDUBw3QiH+Wy +ze9BiDVLwM7eERHGj1XKlC3iUaTZTOFWrr4y5eRjCD8oMzCB/73CWMRwBGPyF+Wg +uP8FfF7i05B9rrKELAzCr4/hqYPDYNX8mv8QzcwXbn93PY35VN5y/wBuccggv0af +iK5NfoNxvmTp9SVD4wA2prlkq1lbjMuqoAxrKpCGG0I0x8Dzn3bTaTNETal61Wt/ +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH +S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo +XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3 +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1 +ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV +BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO +wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al +VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa +XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK +BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7 +CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD +DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M +ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs +G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD +VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U +WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD +DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo +e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z +UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud +IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI +KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X +AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o= +-----END CERTIFICATE----- diff --git a/src/tls/goodchains.pem.ref b/src/tls/goodchains.pem.ref new file mode 100644 index 0000000..ade7562 --- /dev/null +++ b/src/tls/goodchains.pem.ref @@ -0,0 +1,24 @@ +depth = 0 +issuer = /CN=RSA issuer CA +subject = /CN=mx1.example.com + +depth = 1 +issuer = /CN=RSA root CA +subject = /CN=RSA issuer CA + +depth = 2 +issuer = /CN=RSA root CA +subject = /CN=RSA root CA + +depth = 0 +issuer = /CN=EC issuer CA +subject = /CN=mx1.example.com + +depth = 1 +issuer = /CN=EC root CA +subject = /CN=EC issuer CA + +depth = 2 +issuer = /CN=EC root CA +subject = /CN=EC root CA + diff --git a/src/tls/mkcert.sh b/src/tls/mkcert.sh new file mode 100644 index 0000000..c15f4e1 --- /dev/null +++ b/src/tls/mkcert.sh @@ -0,0 +1,264 @@ +#! /bin/bash +# +# Copyright (c) 2016 Viktor Dukhovni . +# All rights reserved. +# +# Licensed under the terms of the Postfix SECURE MAILER license +# included with the Postfix source code. +# +# This file is dual-licensed and is also available under other terms. +# Please contact the author. + +# 100 years should be enough for now +if [ -z "$DAYS" ]; then + DAYS=36525 +fi + +if [ -z "$OPENSSL_SIGALG" ]; then + OPENSSL_SIGALG=sha256 +fi + +case ${OPENSSL_SIGALG} in + null) dopts=();; + *) dopts=(-"${OPENSSL_SIGALG}");; +esac + +if [ -z "$REQMASK" ]; then + REQMASK=utf8only +fi + +stderr_onerror() { + ( + err=$("$@" >&3 2>&1) || { + printf "%s\n" "$err" >&2 + exit 1 + } + ) 3>&1 +} + +key() { + local key=$1; shift + + local alg=rsa + if [ -n "$OPENSSL_KEYALG" ]; then + alg=$OPENSSL_KEYALG + fi + + local bits=2048 + if [ -n "$OPENSSL_KEYBITS" ]; then + bits=$OPENSSL_KEYBITS + fi + + if [ ! -f "${key}.pem" ]; then + args=(-algorithm "$alg") + case $alg in + rsa) args=("${args[@]}" -pkeyopt rsa_keygen_bits:$bits );; + ec) args=("${args[@]}" -pkeyopt "ec_paramgen_curve:$bits") + args=("${args[@]}" -pkeyopt ec_param_enc:named_curve);; + dsa) args=(-paramfile "$bits");; + ed25519) ;; + ed448) ;; + *) printf "Unsupported key algorithm: %s\n" "$alg" >&2; return 1;; + esac + stderr_onerror \ + openssl genpkey "${args[@]}" -out "${key}.pem" + fi +} + +# Usage: $0 req keyname dn1 dn2 ... +req() { + local key=$1; shift + + key "$key" + local errs + + stderr_onerror \ + openssl req "${dopts[@]}" -new -key "${key}.pem" \ + -config <(printf "string_mask=%s\n[req]\n%s\n%s\n[dn]\n" \ + "$REQMASK" "prompt = no" "distinguished_name = dn" + for dn in "$@"; do echo "$dn"; done) +} + +req_nocn() { + local key=$1; shift + + key "$key" + stderr_onerror \ + openssl req "${dopts[@]}" -new -subj / -key "${key}.pem" \ + -config <(printf "[req]\n%s\n[dn]\nCN_default =\n" \ + "distinguished_name = dn") +} + +cert() { + local cert=$1; shift + local exts=$1; shift + + stderr_onerror \ + openssl x509 "${dopts[@]}" -req -out "${cert}.pem" \ + -extfile <(printf "%s\n" "$exts") "$@" +} + +genroot() { + local cn=$1; shift + local key=$1; shift + local cert=$1; shift + local skid="subjectKeyIdentifier = hash" + local akid="authorityKeyIdentifier = keyid" + + exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = critical,CA:true") + for eku in "$@" + do + exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku") + done + csr=$(req "$key" "CN = $cn") || return 1 + echo "$csr" | + cert "$cert" "$exts" -signkey "${key}.pem" -set_serial 1 -days "${DAYS}" +} + +genca() { + local cn=$1; shift + local key=$1; shift + local cert=$1; shift + local cakey=$1; shift + local cacert=$1; shift + local skid="subjectKeyIdentifier = hash" + local akid="authorityKeyIdentifier = keyid" + + exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = critical,CA:true") + for eku in "$@" + do + exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku") + done + if [ -n "$NC" ]; then + exts=$(printf "%s\nnameConstraints = %s\n" "$exts" "$NC") + fi + csr=$(req "$key" "CN = $cn") || return 1 + echo "$csr" | + cert "$cert" "$exts" -CA "${cacert}.pem" -CAkey "${cakey}.pem" \ + -set_serial 2 -days "${DAYS}" +} + +gen_nonbc_ca() { + local cn=$1; shift + local key=$1; shift + local cert=$1; shift + local cakey=$1; shift + local cacert=$1; shift + local skid="subjectKeyIdentifier = hash" + local akid="authorityKeyIdentifier = keyid" + + exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid") + exts=$(printf "%s\nkeyUsage = %s\n" "$exts" "keyCertSign, cRLSign") + for eku in "$@" + do + exts=$(printf "%s\nextendedKeyUsage = %s\n" "$exts" "$eku") + done + csr=$(req "$key" "CN = $cn") || return 1 + echo "$csr" | + cert "$cert" "$exts" -CA "${cacert}.pem" -CAkey "${cakey}.pem" \ + -set_serial 2 -days "${DAYS}" +} + +# Usage: $0 genpc keyname certname eekeyname eecertname pcext1 pcext2 ... +# +# Note: takes csr on stdin, so must be used with $0 req like this: +# +# $0 req keyname dn | $0 genpc keyname certname eekeyname eecertname pcext ... +genpc() { + local key=$1; shift + local cert=$1; shift + local cakey=$1; shift + local ca=$1; shift + + exts=$(printf "%s\n%s\n%s\n%s\n" \ + "subjectKeyIdentifier = hash" \ + "authorityKeyIdentifier = keyid, issuer:always" \ + "basicConstraints = CA:false" \ + "proxyCertInfo = critical, @pcexts"; + echo "[pcexts]"; + for x in "$@"; do echo $x; done) + cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \ + -set_serial 2 -days "${DAYS}" +} + +# Usage: $0 geneealt keyname certname eekeyname eecertname alt1 alt2 ... +# +# Note: takes csr on stdin, so must be used with $0 req like this: +# +# $0 req keyname dn | $0 geneealt keyname certname eekeyname eecertname alt ... +geneealt() { + local key=$1; shift + local cert=$1; shift + local cakey=$1; shift + local ca=$1; shift + + exts=$(printf "%s\n%s\n%s\n%s\n" \ + "subjectKeyIdentifier = hash" \ + "authorityKeyIdentifier = keyid" \ + "basicConstraints = CA:false" \ + "subjectAltName = @alts"; + echo "[alts]"; + for x in "$@"; do echo $x; done) + cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \ + -set_serial 2 -days "${DAYS}" +} + +genee() { + local OPTIND=1 + local purpose=serverAuth + + while getopts p: o + do + case $o in + p) purpose="$OPTARG";; + *) echo "Usage: $0 genee [-p EKU] cn keyname certname cakeyname cacertname" >&2 + return 1;; + esac + done + + shift $((OPTIND - 1)) + local cn=$1; shift + local key=$1; shift + local cert=$1; shift + local cakey=$1; shift + local ca=$1; shift + + exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \ + "subjectKeyIdentifier = hash" \ + "authorityKeyIdentifier = keyid, issuer" \ + "basicConstraints = CA:false" \ + "extendedKeyUsage = $purpose" \ + "subjectAltName = @alts" "DNS=${cn}") + csr=$(req "$key" "CN = $cn") || return 1 + echo "$csr" | + cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \ + -set_serial 2 -days "${DAYS}" "$@" +} + +genss() { + local cn=$1; shift + local key=$1; shift + local cert=$1; shift + + exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \ + "subjectKeyIdentifier = hash" \ + "authorityKeyIdentifier = keyid, issuer" \ + "basicConstraints = CA:false" \ + "extendedKeyUsage = serverAuth" \ + "subjectAltName = @alts" "DNS=${cn}") + csr=$(req "$key" "CN = $cn") || return 1 + echo "$csr" | + cert "$cert" "$exts" -signkey "${key}.pem" \ + -set_serial 1 -days "${DAYS}" "$@" +} + +gennocn() { + local key=$1; shift + local cert=$1; shift + + csr=$(req_nocn "$key") || return 1 + echo "$csr" | + cert "$cert" "" -signkey "${key}.pem" -set_serial 1 -days -1 "$@" +} + +"$@" diff --git a/src/tls/rsaca-cert.pem b/src/tls/rsaca-cert.pem new file mode 100644 index 0000000..1be736a --- /dev/null +++ b/src/tls/rsaca-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIC/jCCAeagAwIBAgIBAjANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg +cm9vdCBDQTAgFw0xOTAxMDgwNjQyMjRaGA8yMTE5MDEwOTA2NDIyNFowGDEWMBQG +A1UEAwwNUlNBIGlzc3VlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAL3tLZ4hc8RmIX0Yd0uj1Z31mK94KMcVN//L0iiR/ZOFr39ma2Ys7AL0UQeV +H9s4hUaMM91n0fkwVL0r8NUsCkuPS/MvJClWxTbN3rEzzaajD+BdyW1Y1QfdEI5t +AI2wSSieF3GtiTmxH43lDHTeZz0qhwh8NNzh+j09DwYS+TfQnO70LdmR5Vks0SoF +DmxTs2/DRup/iCWGd853C3T/lfixdhkdVwGNuxtuF6/xSEQirT2OUjjnVIcCPGq0 +Zbz3hFJ0xHoqiwNBv9PytfLk4BNf+VWTANFXnzNvZqWrOO8yQWS+RHIdf2+a96G2 +EFIgWz/rwo4WP9tozETlPzQc6P8CAwEAAaNTMFEwHQYDVR0OBBYEFMJu5F2Rc66p +dteokmcfmJA+GRMlMB8GA1UdIwQYMBaAFJGpciw0EYdhbO3y0sM62cWnHTtiMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAD6ngGSUb9grVkhlb8M5 +geC4SEhBiysoXoxHleFW6FFe4k1v5dg/xbCbNwysToEG+2S498f2FfyNzKWVDSM7 +5al+4jYrKlwTbX6W9krH6iGhE0Sdz/YHWYdPCDYssdOvS46/+MGj16WPiOdj5UYY +U0+TVKlqLOcpqkPKrz/9X7sM8bXi8FQHPme8ksvnB9qMdwcru/BN62tUOw1JHnmh +CCUbI3TU5xE8fUCl4F43vsLq73EfaHs4TO9hhk2hBW7K0QOinNcFlcNy8AF7mF/T +kY4FWI/kMfw6cXkg+F44Oy3j3KUF3hgByhWzTQimY2oRpk7U5tGuC8Xgh6eHPbRG +Kck= +-----END CERTIFICATE----- diff --git a/src/tls/rsaca-pkey.pem b/src/tls/rsaca-pkey.pem new file mode 100644 index 0000000..2381ae6 --- /dev/null +++ b/src/tls/rsaca-pkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC97S2eIXPEZiF9 +GHdLo9Wd9ZiveCjHFTf/y9Iokf2Tha9/ZmtmLOwC9FEHlR/bOIVGjDPdZ9H5MFS9 +K/DVLApLj0vzLyQpVsU2zd6xM82mow/gXcltWNUH3RCObQCNsEkonhdxrYk5sR+N +5Qx03mc9KocIfDTc4fo9PQ8GEvk30Jzu9C3ZkeVZLNEqBQ5sU7Nvw0bqf4glhnfO +dwt0/5X4sXYZHVcBjbsbbhev8UhEIq09jlI451SHAjxqtGW894RSdMR6KosDQb/T +8rXy5OATX/lVkwDRV58zb2alqzjvMkFkvkRyHX9vmvehthBSIFs/68KOFj/baMxE +5T80HOj/AgMBAAECggEBAIY9Ch4XRMzO5uKVFVRoEwcXXHjBNAkqTS9F719veEv5 +lEY2rLhGDfY0msUCOMboVwK6+7mEtNsstugSE6GIBCrNuH/ElQmG49NNhRW4KKWb +4Q/TGhhoTgHF1PrlvhtnOv4zZxyY7EHTmBrhhoFf5JZN5a0wpOht7EG2U1UWugEg +/fOXp9XUsmJRxwp1jukqIi71GDVtoq3XCK7clluvNdeSlBn7D8dZ8cYUnMkqqaZA +YYamyP3qF6UdxblW3xzGC3/0sLQvmHZOlMBuSpWUgr6NDsQPG4qXq10XePeteViw +WnUqu11ILp8Ivoc20wJkokj1dCN3MOO0MyGcCxEX+5ECgYEA/P+Y+0AXq1j9yjNM +aaXT3EZwSSAP/5MtZRiomFVwCgYbH6/hUV+OVih20Yhsl3MA5vgQTpSThZmmcBPC +c3bn53OXVi6jnSzNzuQW28R8pSGoNnGT6TzG+J+my7ZxA0NTCAa72qhDgbI0dqd/ +sjk1E6hRtOkEI8fD5/IipkvxCpcCgYEAwC4FA0AA6Oy2YOAi1KeACZpIKIDcOFJz +TLfuxhwnNi5C/1i99d5W0doCzaneECyadhGKPbv0DlVS2fe9Xm06PQLD2Y/+vkag +iQ7V31JfwvoTQNTPVqnGcRu2ZmzI8Nja4/BcEEoxpPbMVbQ1ualkqcwpYu66ZlbD +yWpCLSGFadkCgYEAvAkoZYzkSqjwr4jjAR6L0QrVR7Q5z8VOlvX10Iqno/uXyzxI +Zdd0jdqzPNZ9hy6lfATg8daBsmlZh7FX88NrZt3Fm/s8BYSYTm2+A4cM8RqL0DMo +MNDIPV9Dc+LcKgWuv6dplYE78zhEv++L/CWCqmKOn7wUJJfDpi+Tyy9kLm8CgYAy ++gIKYqfbIS8fc1TJ48Rqx6nsVIIVzokXCJMlqcIc9RiAcyGwXlHZSGMF+tEUqUAv +oWdyCLEsPCXF+5kXuxF/rYQV6cRA5Ksgr/a7TjZomb0RrWFyM4aX6inv8Vs7x8oI +PHGvQH76qxx4f1zg6rXw9F7mBz0aeFlmy/DR19pzwQKBgQCJWwlv7zisuTDXAuG/ +KF4VV6D2Y00pEHsxVx/RGeUXm+3HmqrZbcnccgivxyNMIAfuE5+7s2wfnjG38nnw +DbZtp+rEcKX1h+95QnOlFxiowdlfX/ueOC2J59+Dc+R7UoDxqkQrSbdE5advHIl9 +Xb64SvwMhLzJYSYacv+fogoAPw== +-----END PRIVATE KEY----- diff --git a/src/tls/rsaee-cert.pem b/src/tls/rsaee-cert.pem new file mode 100644 index 0000000..9f923b4 --- /dev/null +++ b/src/tls/rsaee-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDLTCCAhWgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1SU0Eg +aXNzdWVyIENBMCAXDTE5MDEwODA2NDQzMloYDzIxMTkwMTA5MDY0NDMyWjAaMRgw +FgYDVQQDDA9teDEuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCu99Z6AB3EA2tAlWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxl +BNHTk4c8HkobGqMTRU0x0qpzMnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX ++5ZnqS9gWbLZyjkCCqVQR/nyK0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakH +ZIB8TYI6p6hanNQyz4NWq1PEpWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJki +Hka24JzNCLx3GD/Q0GNGFGoE9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIo +W0/dbG0KaKTTeHf9LegFXez2tJeLl+vpAgMBAAGjfjB8MB0GA1UdDgQWBBTCrBLC +itbyxruhJFbJEaGYdG4NRjAfBgNVHSMEGDAWgBTCbuRdkXOuqXbXqJJnH5iQPhkT +JTAJBgNVHRMEAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214 +MS5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAYOgZ5NaFJhmjT6V+GI5m +cuTsMC7WB5nQ8nbf2wxw052jgBvnZWAgnshtgWvrK0T6402VcTOzHt8VKS0KwLwk +FESvj2rdoDGvGqUcw6DhCns98PRuts2nMTDlKYJTYWab2Q08PPYxTtRt1Y+A2sp7 +et+bxMjYr2mdO0wkqK7guUA9H2bZkGYJck5bbna67hpzx17YomBjDUjTtMiagXKs +E5QzOXXqCQxiSunlxsMqDwt/HRxGvQy+BHuNq4epuZfeMOndIjl2ZyKdN2ABy88G +dYqRO6nBHUO2/ZxlAZf11xpueVZZ61SueimPfzbsw0B7ZvPSi9KdbPIGBbD4cCwt +JA== +-----END CERTIFICATE----- diff --git a/src/tls/rsaee-pkey.pem b/src/tls/rsaee-pkey.pem new file mode 100644 index 0000000..aa4e498 --- /dev/null +++ b/src/tls/rsaee-pkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCu99Z6AB3EA2tA +lWlJLPtsF2Ed81Hy2c5HYPmE8cGZJgp6YuzF+qxlBNHTk4c8HkobGqMTRU0x0qpz +Mnltkq1JuIwjku4GzoEPcBPqJxSNVZ96pmrVWuxX+5ZnqS9gWbLZyjkCCqVQR/ny +K0/tXij/1s7Kkol1lPOqgBhzSwgOqDLqfipxiakHZIB8TYI6p6hanNQyz4NWq1PE +pWK9yhBJdeZJwK6K5VEMzqr//t+9dIT/dC8RoJkiHka24JzNCLx3GD/Q0GNGFGoE +9Fcz28BelGpigWyleheqzuGPXYquEBHDAHaanRIoW0/dbG0KaKTTeHf9LegFXez2 +tJeLl+vpAgMBAAECggEBAIsJKoe7++s9BEdH82hQtGIKmgPgzKf8aCzUZLM+ISd/ +0Se74IbaUY/cqphETVx6fUzYd+niUhtbWlM8Mr3dppJEcif6o+cIxgnseAbDk1NU +TOx7vb1ZCSJJLBWnnZwk6ziht3dl2kPXTkkgHd1zy4Sxv5W9qDmlaZN9QvCrGPX7 +ZkJnZg2QcLvjhaBCIXTYIpbTSWZjHBzecjmPrIb+NvO4/gbYgfAj9sJAPi5H3LQE +8hZ30ylFZ03+2MKJoS+JrUZQllXyd92AmtXXv9jEFgLQ2mNETCXhsh9BK7qC8YKU +ImZHGHgGQOA30PGEltstzgNvROKb/U2tPgVOo9v9L+ECgYEA1zLokvYwEyuN+9xc +5L7cf4LA7k5WHNdFeERPudGO2RedfQkoZ8sP0WjCeg+LF/iWh4NfLMoGUNgxRQtv +rv5GjDRvlvPXj6YHZo/uMgGLLeeZ6tMJ8NvnrZxNTIDPw+XTd5doiEmnrDrmJZH/ +QeT6HlBkIURjIuYpzFrBxzSvKu0CgYEA0CRAp+zP0QC75aHgvSVPB+7JU0xIEuGz +CdYxWh7rHAztvxxdDFFwBjPEnczDmMF8o5lZwH4L1RFQ2b9UAZy451dEIVqxKp0W +2mOemjohi3DR3nZfpS/Cpattwd61/KGSUUS0k3Lpp7ACCybqNl1wIcX50mbLOQGe +RMu+2+NnmW0CgYBWyhpQQueqo5M0s2/ndS46YsJqmb9TDGLhGTPKLkDdPw/uAIaC +LbwoaYadca1YMpKK/qmgx616Z2afgPg+7CAJpZAnohoavgwYCg43rrWyAsjpkslc +kWPDedkW9JBWYldB0ReAd6we4vY0ysfWjIFvPl7Tp+APkCHlX+js7UuEFQKBgQDP +9Eh8/kcrpDYW2cM2dz4cyOBwzvf7lXBR1aT9S4LPRcOLe4Ujt4HbUIGSv3B1AGvM +8HwRrF2aBXDn/Rarfa/nwvKY+Iml78lTbNCbepTlQlUMlOw+mBc7eqlwV1kwDSWo ++KIJ53e3SEziY4EBzB0qQSi2pGlKjWlZhs8r1mo9iQKBgQCoZNYakj717J5FnpWC +yhkuGYmbBItYBwL36TQqTD6Kq0oJcrBRtMxWWqMmlDV7w8BP4/A2cRXHJz2MLhm9 +Jc5OPh8OkFY4pT8NTpVYqq1pb2d5fzwRX68MKWtVBZzJqzK8w0t4QZLqqeAwQCrc +Okn/aJneEclcdzv8JHfVMcdNxA== +-----END PRIVATE KEY----- diff --git a/src/tls/rsaroot-cert.pem b/src/tls/rsaroot-cert.pem new file mode 100644 index 0000000..3911451 --- /dev/null +++ b/src/tls/rsaroot-cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC/DCCAeSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtSU0Eg +cm9vdCBDQTAgFw0xOTAxMDgwNjE5NDJaGA8yMTE5MDEwOTA2MTk0MlowFjEUMBIG +A1UEAwwLUlNBIHJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQCoZoGzTiMbZcztQVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65An +K2vHNrE37DDRl2YTmP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6 +yuO07nut+rNFMSSjgnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0S +oxQVSkyOIr/phPAkLWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2 +X8lMaz8ooT9OI/4ln/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu +1ViLHIsm+55CJj5OI676Q4JJAgMBAAGjUzBRMB0GA1UdDgQWBBSRqXIsNBGHYWzt +8tLDOtnFpx07YjAfBgNVHSMEGDAWgBSRqXIsNBGHYWzt8tLDOtnFpx07YjAPBgNV +HRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCHEQ8DRqrWz8xibaNEv3Id +SEJvgfRRsMA7jk2HAnE84bO1ZLkXF9HX7oqXbIMRw5IZRUrBXO0fUwcxz3DwTYZj +ojRPP1yNfylmc8uxyd2goMoG4OgTdt2/Ewu8bli0S9NapH9fdBYbSDUBw3QiH+Wy +ze9BiDVLwM7eERHGj1XKlC3iUaTZTOFWrr4y5eRjCD8oMzCB/73CWMRwBGPyF+Wg +uP8FfF7i05B9rrKELAzCr4/hqYPDYNX8mv8QzcwXbn93PY35VN5y/wBuccggv0af +iK5NfoNxvmTp9SVD4wA2prlkq1lbjMuqoAxrKpCGG0I0x8Dzn3bTaTNETal61Wt/ +-----END CERTIFICATE----- diff --git a/src/tls/rsaroot-pkey.pem b/src/tls/rsaroot-pkey.pem new file mode 100644 index 0000000..a5509b7 --- /dev/null +++ b/src/tls/rsaroot-pkey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCoZoGzTiMbZczt +QVknoNmPtN7m/kDOfG95MWGSLHIDJlOI0OzpcfhiOUVh65AnK2vHNrE37DDRl2YT +mP3oBw8xn6BG6Ybe++kTR7g6xEnqR1kw6AStxo0OYE06NWp6yuO07nut+rNFMSSj +gnjS4aj0fSiMSR7TuIzHbGvvJJXZpvYXxtnBgYQAAPJQuD0SoxQVSkyOIr/phPAk +LWg5/d8prQDyEoMvo6NMPet/6/buqXaAvLXqUBeHgkEe+Zm2X8lMaz8ooT9OI/4l +n/psk+jg90GTiMTAZFrKruHwBpNbVx0AyxuisiTqhv6M++fu1ViLHIsm+55CJj5O +I676Q4JJAgMBAAECggEBAKLmzk7KpnFpb+yPC5SnJ+65M+tWRxC4FQmyuEUz03Ky +j5pJKPTGeFVkO3b27fLGMTN798E2LR+DCo6or+3VbmA9n0kZvItWOuiYt2G54hrM +vD3wJB6KdIdUp0BIzeFNBStQi7oIS4UCfgPiQckV3F/t9tyGG1kKLLz5aAvlY0Qw +iXylE9h6dDZyufLPlkq0KVVuZA2Em1oa6UaqSYgKPX5KN0Oa4cVmzqXVt0BGq1tB +SW80CAtKIH2AhCvHQ98ZlWbt1XgzFeP/3idZdjF2H0y2cg9rBDwRmFBwVTceQSbY +Ezrh5WW4okFpldb/qxjURuhwkPpf3vMe/jOIBUoXjRECgYEA2/Xoz4Z8RmhgPjh2 +QOXwSuCWyKVpOXbCoP9hH9stVbUaxDuU7/ywSVW0P+x7hImUXnnKDp6pJ+NzO96b +u6nBuUH6tNP+GTyWFgwHJf//6rEQg9WGIK0srX8Epg+S0wCRlvxggIwXWQe3HY9P +KWFJJdBJ165AMoc/yeYHEsK536UCgYEAw/3xTrrOqNnstLwRJw2uKYj8BDTMgCA4 +ZW96t6hYI/Pwweuu4MXVCaCnt4tzyrYVP6FcYXK5Oa0G8yTF32mk45Nmro9Q4ZhX +Wyavk/P37W0Ep+vSVtbJ7/zgRLTGVh8LPn3T7dVcoXx/JSvEW8Ofp49GXHKq5l2V +0luXyZmfVtUCgYEAw0Ijnf1TWqkTLoiuqOO1kLKYB5uWshUzpvms/Ttyng+7qBEA +IJ3e2+rBrLE/4KLE260faiT8IlWtmKr+8fM67jqc1GMPwNVgokehHOGJC4yNDYrB +m0Y/T/Bebw+KFdb+Ztq4y8QQgc7whcQO0Lv01CV3N4gOowwe2xpgkw3bNKUCgYAf +a/gjAiodwgqEE17Anx3cBN06o2hh5kiEYrIO/ctbwEFKJcn8uVrlVz9sWswupLCV +af5QlT8C8y2ZD701i09nOPuOYuW5tV3T/EjL9KI8C21iqpknWPo5IpamDUF7DzET +TMMMb1eRgE82G2U4vQ08pOjH645groJVnl+gb6OvHQKBgCurbKUMy0kPGECptAJ9 +dgqC9fricGX63bMPIcnz0V7uqvYbalNPhlmhwvMCkPNebWgzMnLQM3zrh+VnmOR2 +43MfgCm/+hqhRIyt3PQcgKS7shzrf2xPFZRMZVJFRk+sbDaD0MhUxXQ4g34tHtFz +ozsnCRsyRaJNG8hC49dTTrlh +-----END PRIVATE KEY----- diff --git a/src/tls/tls.h b/src/tls/tls.h new file mode 100644 index 0000000..b1fdfea --- /dev/null +++ b/src/tls/tls.h @@ -0,0 +1,728 @@ +#ifndef _TLS_H_INCLUDED_ +#define _TLS_H_INCLUDED_ + +/*++ +/* NAME +/* tls 3h +/* SUMMARY +/* libtls internal interfaces +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * TLS enforcement levels. Non-sentinel values may also be used to indicate + * the actual security level of a session. + * + * XXX TLS_LEV_NOTFOUND no longer belongs in this list. The SMTP client will + * have to use something else to report that policy table lookup failed. + * + * The order of levels matters, but we hide most of the details in macros. + * + * "dane" vs. "fingerprint", both must lie between "encrypt" and "verify". + * + * - With "may" and higher, TLS is enabled. + * + * - With "encrypt" and higher, TLS encryption must be applied. + * + * - Strictly above "encrypt", the peer certificate must match. + * + * - At "dane" and higher, the peer certificate must also be trusted. With + * "dane" the trust may be self-asserted, so we only log trust verification + * errors when TA associations are involved. + */ +#define TLS_LEV_INVALID -2 /* sentinel */ +#define TLS_LEV_NOTFOUND -1 /* XXX not in policy table */ +#define TLS_LEV_NONE 0 /* plain-text only */ +#define TLS_LEV_MAY 1 /* wildcard */ +#define TLS_LEV_ENCRYPT 2 /* encrypted connection */ +#define TLS_LEV_FPRINT 3 /* "peer" CA-less verification */ +#define TLS_LEV_HALF_DANE 4 /* DANE TLSA MX host, insecure MX RR */ +#define TLS_LEV_DANE 5 /* Opportunistic TLSA policy */ +#define TLS_LEV_DANE_ONLY 6 /* Required TLSA policy */ +#define TLS_LEV_VERIFY 7 /* certificate verified */ +#define TLS_LEV_SECURE 8 /* "secure" verification */ + +#define TLS_REQUIRED(l) ((l) > TLS_LEV_MAY) +#define TLS_MUST_MATCH(l) ((l) > TLS_LEV_ENCRYPT) +#define TLS_MUST_PKIX(l) ((l) >= TLS_LEV_VERIFY) +#define TLS_OPPORTUNISTIC(l) ((l) == TLS_LEV_MAY || (l) == TLS_LEV_DANE) +#define TLS_DANE_BASED(l) \ + ((l) >= TLS_LEV_HALF_DANE && (l) <= TLS_LEV_DANE_ONLY) +#define TLS_NEVER_SECURED(l) ((l) == TLS_LEV_HALF_DANE) + +extern int tls_level_lookup(const char *); +extern const char *str_tls_level(int); + +#ifdef USE_TLS + + /* + * OpenSSL library. + */ +#include +#include +#include +#include +#include +#include +#include +#include /* Legacy SSLEAY_VERSION_NUMBER */ +#include /* New OpenSSL 3.0 EVP_PKEY APIs */ +#include /* OPENSSL_VERSION_NUMBER */ +#include +#include + + /* Appease indent(1) */ +#define x509_stack_t STACK_OF(X509) +#define general_name_stack_t STACK_OF(GENERAL_NAME) +#define ssl_cipher_stack_t STACK_OF(SSL_CIPHER) +#define ssl_comp_stack_t STACK_OF(SSL_COMP) + +/*- + * Official way to check minimum OpenSSL API version from 3.0 onward. + * We simply define it false for all prior versions, where we typically also + * need the patch level to determine API compatibility. + */ +#ifndef OPENSSL_VERSION_PREREQ +#define OPENSSL_VERSION_PREREQ(m,n) 0 +#endif + +#if (OPENSSL_VERSION_NUMBER < 0x1010100fUL) +#error "OpenSSL releases prior to 1.1.1 are no longer supported" +#endif + + /*- + * Backwards compatibility with OpenSSL < 1.1.1a. + * + * In OpenSSL 1.1.1a the client-only interface SSL_get_server_tmp_key() was + * updated to work on both the client and the server, and was renamed to + * SSL_get_peer_tmp_key(), with the original name left behind as an alias. We + * use the new name when available. + */ +#if OPENSSL_VERSION_NUMBER < 0x1010101fUL +#undef SSL_get_signature_nid +#define SSL_get_signature_nid(ssl, pnid) (NID_undef) +#define tls_get_peer_dh_pubkey SSL_get_server_tmp_key +#else +#define tls_get_peer_dh_pubkey SSL_get_peer_tmp_key +#endif + +#if OPENSSL_VERSION_PREREQ(3,0) +#define TLS_PEEK_PEER_CERT(ssl) SSL_get0_peer_certificate(ssl) +#define TLS_FREE_PEER_CERT(x) ((void) 0) +#define tls_set_bio_callback BIO_set_callback_ex +#else +#define TLS_PEEK_PEER_CERT(ssl) SSL_get_peer_certificate(ssl) +#define TLS_FREE_PEER_CERT(x) X509_free(x) +#define tls_set_bio_callback BIO_set_callback +#endif + + /* + * Utility library. + */ +#include +#include +#include + + /* + * TLS library. + */ +#include + + /* + * TLS role, presently for logging. + */ +typedef enum { + TLS_ROLE_CLIENT, TLS_ROLE_SERVER, +} TLS_ROLE; + +typedef enum { + TLS_USAGE_NEW, TLS_USAGE_USED, +} TLS_USAGE; + + /* + * Names of valid tlsmgr(8) session caches. + */ +#define TLS_MGR_SCACHE_SMTPD "smtpd" +#define TLS_MGR_SCACHE_SMTP "smtp" +#define TLS_MGR_SCACHE_LMTP "lmtp" + + /* + * RFC 6698, 7671, 7672 DANE + */ +#define TLS_DANE_TA 0 /* Match trust-anchor digests */ +#define TLS_DANE_EE 1 /* Match end-entity digests */ + +#define TLS_DANE_CERT 0 /* Match the certificate digest */ +#define TLS_DANE_PKEY 1 /* Match the public key digest */ + +#define TLS_DANE_FLAG_NORRS (1<<0) /* Nothing found in DNS */ +#define TLS_DANE_FLAG_EMPTY (1<<1) /* Nothing usable found in DNS */ +#define TLS_DANE_FLAG_ERROR (1<<2) /* TLSA record lookup error */ + +#define tls_dane_unusable(dane) ((dane)->flags & TLS_DANE_FLAG_EMPTY) +#define tls_dane_notfound(dane) ((dane)->flags & TLS_DANE_FLAG_NORRS) + +#define TLS_DANE_CACHE_TTL_MIN 1 /* A lot can happen in ~2 seconds */ +#define TLS_DANE_CACHE_TTL_MAX 100 /* Comparable to max_idle */ + + /* + * Certificate and public key digests (typically from TLSA RRs), grouped by + * algorithm. + */ +typedef struct TLS_TLSA { + uint8_t usage; /* DANE certificate usage */ + uint8_t selector; /* DANE selector */ + uint8_t mtype; /* Algorithm for this digest list */ + uint16_t length; /* Length of associated data */ + unsigned char *data; /* Associated data */ + struct TLS_TLSA *next; /* Chain to next algorithm */ +} TLS_TLSA; + +typedef struct TLS_DANE { + TLS_TLSA *tlsa; /* TLSA records */ + char *base_domain; /* Base domain of TLSA RRset */ + int flags; /* Lookup status */ + time_t expires; /* Expiration time of this record */ + int refs; /* Reference count */ +} TLS_DANE; + + /* + * tls_dane.c + */ +extern int tls_dane_avail(void); +extern void tls_dane_loglevel(const char *, const char *); +extern void tls_dane_flush(void); +extern TLS_DANE *tls_dane_alloc(void); +extern void tls_tlsa_free(TLS_TLSA *); +extern void tls_dane_free(TLS_DANE *); +extern void tls_dane_add_fpt_digests(TLS_DANE *, const char *, const char *, + int); +extern TLS_DANE *tls_dane_resolve(unsigned, const char *, DNS_RR *, int); +extern int tls_dane_load_trustfile(TLS_DANE *, const char *); + + /* + * TLS session context, also used by the VSTREAM call-back routines for SMTP + * input/output, and by OpenSSL call-back routines for key verification. + * + * Only some members are (read-only) accessible by the public. + */ +#define CCERT_BUFSIZ 256 + +typedef struct { + /* Public, read-only. */ + char *peer_CN; /* Peer Common Name */ + char *issuer_CN; /* Issuer Common Name */ + char *peer_sni; /* SNI sent to or by the peer */ + char *peer_cert_fprint; /* ASCII certificate fingerprint */ + char *peer_pkey_fprint; /* ASCII public key fingerprint */ + int level; /* Effective security level */ + int peer_status; /* Certificate and match status */ + const char *protocol; + const char *cipher_name; + int cipher_usebits; + int cipher_algbits; + const char *kex_name; /* shared key-exchange algorithm */ + const char *kex_curve; /* shared key-exchange ECDHE curve */ + int kex_bits; /* shared FFDHE key exchange bits */ + const char *clnt_sig_name; /* client's signature key algorithm */ + const char *clnt_sig_curve; /* client's ECDSA curve name */ + int clnt_sig_bits; /* client's RSA signature key bits */ + const char *clnt_sig_dgst; /* client's signature digest */ + const char *srvr_sig_name; /* server's signature key algorithm */ + const char *srvr_sig_curve; /* server's ECDSA curve name */ + int srvr_sig_bits; /* server's RSA signature key bits */ + const char *srvr_sig_dgst; /* server's signature digest */ + /* Private. */ + SSL *con; + char *cache_type; /* tlsmgr(8) cache type if enabled */ + int ticketed; /* Session ticket issued */ + char *serverid; /* unique server identifier */ + char *namaddr; /* nam[addr] for logging */ + int log_mask; /* What to log */ + int session_reused; /* this session was reused */ + int am_server; /* Are we an SSL server or client? */ + const char *mdalg; /* default message digest algorithm */ + /* Built-in vs external SSL_accept/read/write/shutdown support. */ + VSTREAM *stream; /* Blocking-mode SMTP session */ + /* DANE TLSA trust input and verification state */ + const TLS_DANE *dane; /* DANE TLSA digests */ + X509 *errorcert; /* Error certificate closest to leaf */ + int errordepth; /* Chain depth of error cert */ + int errorcode; /* First error at error depth */ + int must_fail; /* Failed to load trust settings */ +} TLS_SESS_STATE; + + /* + * Peer status bits. TLS_CERT_FLAG_MATCHED implies TLS_CERT_FLAG_TRUSTED + * only in the case of a hostname match. + */ +#define TLS_CERT_FLAG_PRESENT (1<<0) +#define TLS_CERT_FLAG_ALTNAME (1<<1) +#define TLS_CERT_FLAG_TRUSTED (1<<2) +#define TLS_CERT_FLAG_MATCHED (1<<3) +#define TLS_CERT_FLAG_SECURED (1<<4) + +#define TLS_CERT_IS_PRESENT(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_PRESENT)) +#define TLS_CERT_IS_ALTNAME(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_ALTNAME)) +#define TLS_CERT_IS_TRUSTED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_TRUSTED)) +#define TLS_CERT_IS_MATCHED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_MATCHED)) +#define TLS_CERT_IS_SECURED(c) ((c) && ((c)->peer_status&TLS_CERT_FLAG_SECURED)) + + /* + * Opaque client context handle. + */ +typedef struct TLS_APPL_STATE TLS_APPL_STATE; + +#ifdef TLS_INTERNAL + + /* + * Log mask details are internal to the library. + */ +extern int tls_log_mask(const char *, const char *); + + /* + * What to log. + */ +#define TLS_LOG_NONE (1<<0) +#define TLS_LOG_SUMMARY (1<<1) +#define TLS_LOG_UNTRUSTED (1<<2) +#define TLS_LOG_PEERCERT (1<<3) +#define TLS_LOG_CERTMATCH (1<<4) +#define TLS_LOG_VERBOSE (1<<5) +#define TLS_LOG_CACHE (1<<6) +#define TLS_LOG_DEBUG (1<<7) +#define TLS_LOG_TLSPKTS (1<<8) +#define TLS_LOG_ALLPKTS (1<<9) +#define TLS_LOG_DANE (1<<10) + + /* + * Client and Server application contexts + */ +struct TLS_APPL_STATE { + SSL_CTX *ssl_ctx; + SSL_CTX *sni_ctx; + int log_mask; + char *cache_type; +}; + + /* + * tls_misc.c Application-context update and disposal. + */ +extern void tls_update_app_logmask(TLS_APPL_STATE *, int); +extern void tls_free_app_context(TLS_APPL_STATE *); + + /* + * tls_misc.c + */ +extern void tls_param_init(void); +extern int tls_library_init(void); + + /* + * Protocol selection. + */ +#define TLS_PROTOCOL_INVALID (~0) /* All protocol bits masked */ + +#ifdef SSL_TXT_SSLV2 +#define TLS_PROTOCOL_SSLv2 (1<<0) /* SSLv2 */ +#else +#define SSL_TXT_SSLV2 "SSLv2" +#define TLS_PROTOCOL_SSLv2 0 /* Unknown */ +#undef SSL_OP_NO_SSLv2 +#define SSL_OP_NO_SSLv2 0L /* Noop */ +#endif + +#ifdef SSL_TXT_SSLV3 +#define TLS_PROTOCOL_SSLv3 (1<<1) /* SSLv3 */ +#else +#define SSL_TXT_SSLV3 "SSLv3" +#define TLS_PROTOCOL_SSLv3 0 /* Unknown */ +#undef SSL_OP_NO_SSLv3 +#define SSL_OP_NO_SSLv3 0L /* Noop */ +#endif + +#ifdef SSL_TXT_TLSV1 +#define TLS_PROTOCOL_TLSv1 (1<<2) /* TLSv1 */ +#else +#define SSL_TXT_TLSV1 "TLSv1" +#define TLS_PROTOCOL_TLSv1 0 /* Unknown */ +#undef SSL_OP_NO_TLSv1 +#define SSL_OP_NO_TLSv1 0L /* Noop */ +#endif + +#ifdef SSL_TXT_TLSV1_1 +#define TLS_PROTOCOL_TLSv1_1 (1<<3) /* TLSv1_1 */ +#else +#define SSL_TXT_TLSV1_1 "TLSv1.1" +#define TLS_PROTOCOL_TLSv1_1 0 /* Unknown */ +#undef SSL_OP_NO_TLSv1_1 +#define SSL_OP_NO_TLSv1_1 0L /* Noop */ +#endif + +#ifdef SSL_TXT_TLSV1_2 +#define TLS_PROTOCOL_TLSv1_2 (1<<4) /* TLSv1_2 */ +#else +#define SSL_TXT_TLSV1_2 "TLSv1.2" +#define TLS_PROTOCOL_TLSv1_2 0 /* Unknown */ +#undef SSL_OP_NO_TLSv1_2 +#define SSL_OP_NO_TLSv1_2 0L /* Noop */ +#endif + + /* + * OpenSSL 1.1.1 does not define a TXT macro for TLS 1.3, so we roll our + * own. + */ +#define TLS_PROTOCOL_TXT_TLSV1_3 "TLSv1.3" + +#if defined(TLS1_3_VERSION) && defined(SSL_OP_NO_TLSv1_3) +#define TLS_PROTOCOL_TLSv1_3 (1<<5) /* TLSv1_3 */ +#else +#define TLS_PROTOCOL_TLSv1_3 0 /* Unknown */ +#undef SSL_OP_NO_TLSv1_3 +#define SSL_OP_NO_TLSv1_3 0L /* Noop */ +#endif + +/* + * Always used when defined, SMTP has no truncation attacks. + */ +#ifndef SSL_OP_IGNORE_UNEXPECTED_EOF +#define SSL_OP_IGNORE_UNEXPECTED_EOF 0L +#endif + +#define TLS_KNOWN_PROTOCOLS \ + ( TLS_PROTOCOL_SSLv2 | TLS_PROTOCOL_SSLv3 | TLS_PROTOCOL_TLSv1 \ + | TLS_PROTOCOL_TLSv1_1 | TLS_PROTOCOL_TLSv1_2 | TLS_PROTOCOL_TLSv1_3 ) +#define TLS_SSL_OP_PROTOMASK(m) \ + ((((m) & TLS_PROTOCOL_SSLv2) ? SSL_OP_NO_SSLv2 : 0L) \ + | (((m) & TLS_PROTOCOL_SSLv3) ? SSL_OP_NO_SSLv3 : 0L) \ + | (((m) & TLS_PROTOCOL_TLSv1) ? SSL_OP_NO_TLSv1 : 0L) \ + | (((m) & TLS_PROTOCOL_TLSv1_1) ? SSL_OP_NO_TLSv1_1 : 0L) \ + | (((m) & TLS_PROTOCOL_TLSv1_2) ? SSL_OP_NO_TLSv1_2 : 0L) \ + | (((m) & TLS_PROTOCOL_TLSv1_3) ? SSL_OP_NO_TLSv1_3 : 0L)) + +/* + * SSL options that are managed via dedicated Postfix features, rather than + * just exposed via hex codes or named elements of tls_ssl_options. + */ +#define TLS_SSL_OP_MANAGED_BITS \ + (SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_IGNORE_UNEXPECTED_EOF | \ + TLS_SSL_OP_PROTOMASK(~0)) + +extern int tls_proto_mask_lims(const char *, int *, int *); + + /* + * Cipher grade selection. + */ +#define TLS_CIPHER_NONE 0 +#define TLS_CIPHER_NULL 1 +#define TLS_CIPHER_EXPORT 2 +#define TLS_CIPHER_LOW 3 +#define TLS_CIPHER_MEDIUM 4 +#define TLS_CIPHER_HIGH 5 + +extern const NAME_CODE tls_cipher_grade_table[]; + +#define tls_cipher_grade(str) \ + name_code(tls_cipher_grade_table, NAME_CODE_FLAG_NONE, (str)) +#define str_tls_cipher_grade(gr) \ + str_name_code(tls_cipher_grade_table, (gr)) + + /* + * Cipher lists with exclusions. + */ +extern const char *tls_set_ciphers(TLS_SESS_STATE *, const char *, + const char *); + + /* + * Populate TLS context with TLS 1.3-related signature parameters. + */ +extern void tls_get_signature_params(TLS_SESS_STATE *); + +#endif /* TLS_INTERNAL */ + + /* + * tls_client.c + */ +typedef struct { + const char *log_param; + const char *log_level; + int verifydepth; + const char *cache_type; + const char *chain_files; + const char *cert_file; + const char *key_file; + const char *dcert_file; + const char *dkey_file; + const char *eccert_file; + const char *eckey_file; + const char *CAfile; + const char *CApath; + const char *mdalg; /* default message digest algorithm */ +} TLS_CLIENT_INIT_PROPS; + +typedef struct { + TLS_APPL_STATE *ctx; + VSTREAM *stream; + int fd; /* Event-driven file descriptor */ + int timeout; + int tls_level; /* Security level */ + const char *nexthop; /* destination domain */ + const char *host; /* MX hostname */ + const char *namaddr; /* nam[addr] for logging */ + const char *sni; /* optional SNI name when not DANE */ + const char *serverid; /* Session cache key */ + const char *helo; /* Server name from EHLO response */ + const char *protocols; /* Enabled protocols */ + const char *cipher_grade; /* Minimum cipher grade */ + const char *cipher_exclusions; /* Ciphers to exclude */ + const ARGV *matchargv; /* Cert match patterns */ + const char *mdalg; /* default message digest algorithm */ + const TLS_DANE *dane; /* DANE TLSA verification */ +} TLS_CLIENT_START_PROPS; + +extern TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *); +extern TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *); +extern TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *, + const TLS_CLIENT_START_PROPS *); + +#define tls_client_stop(ctx, stream, timeout, failure, TLScontext) \ + tls_session_stop(ctx, (stream), (timeout), (failure), (TLScontext)) + +#define TLS_CLIENT_INIT_ARGS(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \ + a10, a11, a12, a13, a14) \ + (((props)->a1), ((props)->a2), ((props)->a3), \ + ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ + ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ + ((props)->a12), ((props)->a13), ((props)->a14), (props)) + +#define TLS_CLIENT_INIT(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \ + a10, a11, a12, a13, a14) \ + tls_client_init(TLS_CLIENT_INIT_ARGS(props, a1, a2, a3, a4, a5, \ + a6, a7, a8, a9, a10, a11, a12, a13, a14)) + +#define TLS_CLIENT_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \ + a10, a11, a12, a13, a14, a15, a16, a17) \ + tls_client_start((((props)->a1), ((props)->a2), ((props)->a3), \ + ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ + ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ + ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \ + ((props)->a16), ((props)->a17), (props))) + + /* + * tls_server.c + */ +typedef struct { + const char *log_param; + const char *log_level; + int verifydepth; + const char *cache_type; + int set_sessid; + const char *chain_files; + const char *cert_file; + const char *key_file; + const char *dcert_file; + const char *dkey_file; + const char *eccert_file; + const char *eckey_file; + const char *CAfile; + const char *CApath; + const char *protocols; + const char *eecdh_grade; + const char *dh1024_param_file; + const char *dh512_param_file; + int ask_ccert; + const char *mdalg; /* default message digest algorithm */ +} TLS_SERVER_INIT_PROPS; + +typedef struct { + TLS_APPL_STATE *ctx; /* TLS application context */ + VSTREAM *stream; /* Client stream */ + int fd; /* Event-driven file descriptor */ + int timeout; /* TLS handshake timeout */ + int requirecert; /* Insist on client cert? */ + const char *serverid; /* Server instance (salt cache key) */ + const char *namaddr; /* Client nam[addr] for logging */ + const char *cipher_grade; + const char *cipher_exclusions; + const char *mdalg; /* default message digest algorithm */ +} TLS_SERVER_START_PROPS; + +extern TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *); +extern TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props); +extern TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *); + +#define tls_server_stop(ctx, stream, timeout, failure, TLScontext) \ + tls_session_stop(ctx, (stream), (timeout), (failure), (TLScontext)) + +#define TLS_SERVER_INIT(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, \ + a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) \ + tls_server_init((((props)->a1), ((props)->a2), ((props)->a3), \ + ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ + ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ + ((props)->a12), ((props)->a13), ((props)->a14), ((props)->a15), \ + ((props)->a16), ((props)->a17), ((props)->a18), ((props)->a19), \ + ((props)->a20), (props))) + +#define TLS_SERVER_START(props, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \ + tls_server_start((((props)->a1), ((props)->a2), ((props)->a3), \ + ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ + ((props)->a8), ((props)->a9), ((props)->a10), (props))) + + /* + * tls_session.c + */ +extern void tls_session_stop(TLS_APPL_STATE *, VSTREAM *, int, int, TLS_SESS_STATE *); + + /* + * tls_misc.c + */ +extern const char *tls_compile_version(void); +extern const char *tls_run_version(void); +extern const char **tls_pkey_algorithms(void); +extern void tls_log_summary(TLS_ROLE, TLS_USAGE, TLS_SESS_STATE *); +extern void tls_pre_jail_init(TLS_ROLE); + +#ifdef TLS_INTERNAL + +#include + +extern VSTRING *tls_session_passivate(SSL_SESSION *); +extern SSL_SESSION *tls_session_activate(const char *, int); + + /* + * tls_stream.c. + */ +extern void tls_stream_start(VSTREAM *, TLS_SESS_STATE *); +extern void tls_stream_stop(VSTREAM *); + + /* + * tls_bio_ops.c: a generic multi-personality driver that retries SSL + * operations until they are satisfied or until a hard error happens. + * Because of its ugly multi-personality user interface we invoke it via + * not-so-ugly single-personality wrappers. + */ +extern int tls_bio(int, int, TLS_SESS_STATE *, + int (*) (SSL *), /* handshake */ + int (*) (SSL *, void *, int), /* read */ + int (*) (SSL *, const void *, int), /* write */ + void *, int); + +#define tls_bio_connect(fd, timeout, context) \ + tls_bio((fd), (timeout), (context), SSL_connect, \ + NULL, NULL, NULL, 0) +#define tls_bio_accept(fd, timeout, context) \ + tls_bio((fd), (timeout), (context), SSL_accept, \ + NULL, NULL, NULL, 0) +#define tls_bio_shutdown(fd, timeout, context) \ + tls_bio((fd), (timeout), (context), SSL_shutdown, \ + NULL, NULL, NULL, 0) +#define tls_bio_read(fd, buf, len, timeout, context) \ + tls_bio((fd), (timeout), (context), NULL, \ + SSL_read, NULL, (buf), (len)) +#define tls_bio_write(fd, buf, len, timeout, context) \ + tls_bio((fd), (timeout), (context), NULL, \ + NULL, SSL_write, (buf), (len)) + + /* + * tls_dh.c + */ +extern void tls_set_dh_from_file(const char *); +extern void tls_tmp_dh(SSL_CTX *, int); +extern void tls_auto_eecdh_curves(SSL_CTX *, const char *); + + /* + * tls_verify.c + */ +extern char *tls_peer_CN(X509 *, const TLS_SESS_STATE *); +extern char *tls_issuer_CN(X509 *, const TLS_SESS_STATE *); +extern int tls_verify_certificate_callback(int, X509_STORE_CTX *); +extern void tls_log_verify_error(TLS_SESS_STATE *); + + /* + * tls_dane.c + */ +extern void tls_dane_log(TLS_SESS_STATE *); +extern void tls_dane_digest_init(SSL_CTX *, const EVP_MD *); +extern int tls_dane_enable(TLS_SESS_STATE *); +extern TLS_TLSA *tlsa_prepend(TLS_TLSA *, uint8_t, uint8_t, uint8_t, + const unsigned char *, uint16_t); + + /* + * tls_fprint.c + */ +extern const EVP_MD *tls_digest_byname(const char *, EVP_MD_CTX **); +extern char *tls_digest_encode(const unsigned char *, int); +extern char *tls_cert_fprint(X509 *, const char *); +extern char *tls_pkey_fprint(X509 *, const char *); +extern char *tls_serverid_digest(TLS_SESS_STATE *, + const TLS_CLIENT_START_PROPS *, const char *); + + /* + * tls_certkey.c + */ +extern int tls_set_ca_certificate_info(SSL_CTX *, const char *, const char *); +extern int tls_load_pem_chain(SSL *, const char *, const char *); +extern int tls_set_my_certificate_key_info(SSL_CTX *, /* All */ const char *, + /* RSA */ const char *, const char *, + /* DSA */ const char *, const char *, + /* ECDSA */ const char *, const char *); + + /* + * tls_misc.c + */ +extern int TLScontext_index; + +extern TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *, SSL_CTX *, int); +extern TLS_SESS_STATE *tls_alloc_sess_context(int, const char *); +extern void tls_free_context(TLS_SESS_STATE *); +extern void tls_check_version(void); +extern long tls_bug_bits(void); +extern void tls_print_errors(void); +extern void tls_info_callback(const SSL *, int, int); + +#if OPENSSL_VERSION_PREREQ(3,0) +extern long tls_bio_dump_cb(BIO *, int, const char *, size_t, int, long, + int, size_t *); + +#else +extern long tls_bio_dump_cb(BIO *, int, const char *, int, long, long); + +#endif +extern const EVP_MD *tls_validate_digest(const char *); + + /* + * tls_seed.c + */ +extern void tls_int_seed(void); +extern int tls_ext_seed(int); + +#endif /* TLS_INTERNAL */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +#endif /* USE_TLS */ +#endif /* _TLS_H_INCLUDED_ */ diff --git a/src/tls/tls_bio_ops.c b/src/tls/tls_bio_ops.c new file mode 100644 index 0000000..9b66195 --- /dev/null +++ b/src/tls/tls_bio_ops.c @@ -0,0 +1,296 @@ +/*++ +/* NAME +/* tls_bio_ops 3 +/* SUMMARY +/* TLS network basic I/O management +/* SYNOPSIS +/* #define TLS_INTERNAL +/* #include +/* +/* int tls_bio_connect(fd, timeout, context) +/* int fd; +/* int timeout; +/* TLS_SESS_STATE *context; +/* +/* int tls_bio_accept(fd, timeout, context) +/* int fd; +/* int timeout; +/* TLS_SESS_STATE *context; +/* +/* int tls_bio_shutdown(fd, timeout, context) +/* int fd; +/* int timeout; +/* TLS_SESS_STATE *context; +/* +/* int tls_bio_read(fd, buf, len, timeout, context) +/* int fd; +/* void *buf; +/* int len; +/* int timeout; +/* TLS_SESS_STATE *context; +/* +/* int tls_bio_write(fd, buf, len, timeout, context) +/* int fd; +/* void *buf; +/* int len; +/* int timeout; +/* TLS_SESS_STATE *context; +/* DESCRIPTION +/* This module enforces VSTREAM-style timeouts on non-blocking +/* I/O while performing TLS handshake or input/output operations. +/* +/* The Postfix VSTREAM read/write routines invoke the +/* tls_bio_read/write routines to send and receive plain-text +/* data. In addition, this module provides tls_bio_connect/accept +/* routines that trigger the initial TLS handshake. The +/* tls_bio_xxx routines invoke the corresponding SSL routines +/* that translate the requests into TLS protocol messages. +/* +/* Whenever an SSL operation indicates that network input (or +/* output) needs to happen, the tls_bio_xxx routines wait for +/* the network to become readable (or writable) within the +/* timeout limit, then retry the SSL operation. This works +/* because the network socket is in non-blocking mode. +/* +/* tls_bio_connect() performs the SSL_connect() operation. +/* +/* tls_bio_accept() performs the SSL_accept() operation. +/* +/* tls_bio_shutdown() performs the SSL_shutdown() operation. +/* +/* tls_bio_read() performs the SSL_read() operation. +/* +/* tls_bio_write() performs the SSL_write() operation. +/* +/* Arguments: +/* .IP fd +/* Network socket. +/* .IP buf +/* Read/write buffer. +/* .IP len +/* Read/write request size. +/* .IP timeout +/* Read/write timeout. +/* .IP TLScontext +/* TLS session state. +/* DIAGNOSTICS +/* A result value > 0 means successful completion. +/* +/* A result value < 0 means that the requested operation did +/* not complete due to TLS protocol failure, system call +/* failure, or for any reason described under "in addition" +/* below. +/* +/* A result value of 0 from tls_bio_shutdown() means that the +/* operation is in progress. A result value of 0 from other +/* tls_bio_ops(3) operations means that the remote party either +/* closed the network connection or that it sent a TLS shutdown +/* request. +/* +/* Upon return from the tls_bio_ops(3) routines the global +/* errno value is non-zero when the requested operation did not +/* complete due to system call failure. +/* +/* In addition, the result value is set to -1, and the global +/* errno value is set to ETIMEDOUT, when some network read/write +/* operation did not complete within the time limit. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Originally written by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include +#include + +#ifndef timersub +/* res = a - b */ +#define timersub(a, b, res) do { \ + (res)->tv_sec = (a)->tv_sec - (b)->tv_sec; \ + (res)->tv_usec = (a)->tv_usec - (b)->tv_usec; \ + if ((res)->tv_usec < 0) { \ + (res)->tv_sec--; \ + (res)->tv_usec += 1000000; \ + } \ + } while (0) +#endif + +#ifdef USE_TLS + +/* Utility library. */ + +#include +#include + +/* TLS library. */ + +#define TLS_INTERNAL +#include + +/* tls_bio - perform SSL input/output operation with extreme prejudice */ + +int tls_bio(int fd, int timeout, TLS_SESS_STATE *TLScontext, + int (*hsfunc) (SSL *), + int (*rfunc) (SSL *, void *, int), + int (*wfunc) (SSL *, const void *, int), + void *buf, int num) +{ + const char *myname = "tls_bio"; + int status; + int err; + int enable_deadline; + struct timeval time_left; /* amount of time left */ + struct timeval time_deadline; /* time of deadline */ + struct timeval time_now; /* time after SSL_mumble() call */ + + /* + * Compensation for interface mis-match: With VSTREAMs, timeout <= 0 + * means wait forever; with the read/write_wait() calls below, we need to + * specify timeout < 0 instead. + * + * Safety: no time limit means no deadline. + */ + if (timeout <= 0) { + timeout = -1; + enable_deadline = 0; + } + + /* + * Deadline management is simpler than with VSTREAMs, because we don't + * need to decrement a per-stream time limit. We just work within the + * budget that is available for this tls_bio() call. + */ + else { + enable_deadline = + vstream_fstat(TLScontext->stream, VSTREAM_FLAG_DEADLINE); + if (enable_deadline) { + GETTIMEOFDAY(&time_deadline); + time_deadline.tv_sec += timeout; + } + } + + /* + * If necessary, retry the SSL handshake or read/write operation after + * handling any pending network I/O. + */ + for (;;) { + + /* + * Flush the per-thread SSL error queue. Otherwise, errors from other + * code that also uses TLS may confuse SSL_get_error(3). + */ + ERR_clear_error(); + + if (hsfunc) + status = hsfunc(TLScontext->con); + else if (rfunc) + status = rfunc(TLScontext->con, buf, num); + else if (wfunc) + status = wfunc(TLScontext->con, buf, num); + else + msg_panic("%s: nothing to do here", myname); + err = SSL_get_error(TLScontext->con, status); + + /* + * Correspondence between SSL_ERROR_* error codes and tls_bio_(read, + * write, accept, connect, shutdown) return values (for brevity: + * retval). + * + * SSL_ERROR_NONE corresponds with retval > 0. With SSL_(read, write) + * this is the number of plaintext bytes sent or received. With + * SSL_(accept, connect, shutdown) this means that the operation was + * completed successfully. + * + * SSL_ERROR_WANT_(WRITE, READ) start a new loop iteration, or force + * (retval = -1, errno = ETIMEDOUT) when the time limit is exceeded. + * + * All other SSL_ERROR_* cases correspond with retval <= 0. With + * SSL_(read, write, accept, connect) retval == 0 means that the + * remote party either closed the network connection or that it + * requested TLS shutdown; with SSL_shutdown() retval == 0 means that + * our own shutdown request is in progress. With all operations + * retval < 0 means that there was an error. In the latter case, + * SSL_ERROR_SYSCALL means that error details are returned via the + * errno value. + * + * Find out if we must retry the operation and/or if there is pending + * network I/O. + * + * XXX If we're the first to invoke SSL_shutdown(), then the operation + * isn't really complete when the call returns. We could hide that + * anomaly here and repeat the call. + */ + switch (err) { + case SSL_ERROR_WANT_WRITE: + case SSL_ERROR_WANT_READ: + if (enable_deadline) { + GETTIMEOFDAY(&time_now); + timersub(&time_deadline, &time_now, &time_left); + timeout = time_left.tv_sec + (time_left.tv_usec > 0); + if (timeout <= 0) { + errno = ETIMEDOUT; + return (-1); + } + } + if (err == SSL_ERROR_WANT_WRITE) { + if (write_wait(fd, timeout) < 0) + return (-1); /* timeout error */ + } else { + if (read_wait(fd, timeout) < 0) + return (-1); /* timeout error */ + } + break; + + /* + * Unhandled cases: SSL_ERROR_WANT_(ACCEPT, CONNECT, X509_LOOKUP) + * etc. Historically, Postfix silently treated these as ordinary + * I/O errors so we don't really know how common they are. For + * now, we just log a warning. + */ + default: + msg_warn("%s: unexpected SSL_ERROR code %d", myname, err); + /* FALLTHROUGH */ + + /* + * With tls_timed_read() and tls_timed_write() the caller is the + * VSTREAM library module which is unaware of TLS, so we log the + * TLS error stack here. In a better world, each VSTREAM I/O + * object would provide an error reporting method in addition to + * the timed_read and timed_write methods, so that we would not + * need to have ad-hoc code like this. + */ + case SSL_ERROR_SSL: + if (rfunc || wfunc) + tls_print_errors(); + /* FALLTHROUGH */ + case SSL_ERROR_ZERO_RETURN: + case SSL_ERROR_NONE: + errno = 0; /* avoid bogus warnings */ + /* FALLTHROUGH */ + case SSL_ERROR_SYSCALL: + return (status); + } + } +} + +#endif diff --git a/src/tls/tls_certkey.c b/src/tls/tls_certkey.c new file mode 100644 index 0000000..09a35e0 --- /dev/null +++ b/src/tls/tls_certkey.c @@ -0,0 +1,721 @@ +/*++ +/* NAME +/* tls_certkey 3 +/* SUMMARY +/* public key certificate and private key loader +/* SYNOPSIS +/* #define TLS_INTERNAL +/* #include +/* +/* int tls_set_ca_certificate_info(ctx, CAfile, CApath) +/* SSL_CTX *ctx; +/* const char *CAfile; +/* const char *CApath; +/* +/* int tls_set_my_certificate_key_info(ctx, chain_files, +/* cert_file, key_file, +/* dcert_file, dkey_file, +/* eccert_file, eckey_file) +/* SSL_CTX *ctx; +/* const char *chain_files; +/* const char *cert_file; +/* const char *key_file; +/* const char *dcert_file; +/* const char *dkey_file; +/* const char *eccert_file; +/* const char *eckey_file; +/* +/* int tls_load_pem_chain(ssl, pem, origin); +/* SSL *ssl; +/* const char *pem; +/* const char *origin; +/* DESCRIPTION +/* OpenSSL supports two options to specify CA certificates: +/* either one file CAfile that contains all CA certificates, +/* or a directory CApath with separate files for each +/* individual CA, with symbolic links named after the hash +/* values of the certificates. The second option is not +/* convenient with a chrooted process. +/* +/* tls_set_ca_certificate_info() loads the CA certificate +/* information for the specified TLS server or client context. +/* The result is -1 on failure, 0 on success. +/* +/* tls_set_my_certificate_key_info() loads the public key +/* certificates and private keys for the specified TLS server +/* or client context. Up to 3 pairs of key pairs (RSA, DSA and +/* ECDSA) may be specified; each certificate and key pair must +/* match. The chain_files argument makes it possible to load +/* keys and certificates for more than 3 algorithms, via either +/* a single file, or a list of multiple files. The result is -1 +/* on failure, 0 on success. +/* +/* tls_load_pem_chain() loads one or more (key, cert, [chain]) +/* triples from an in-memory PEM blob. The "origin" argument +/* is used for error logging, to identify the provenance of the +/* PEM blob. "ssl" must be non-zero, and the keys and certificates +/* will be loaded into that object. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Originally written by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +#ifdef USE_TLS + +/* Utility library. */ + +#include + +/* Global library. */ + +#include + +/* TLS library. */ + +#define TLS_INTERNAL +#include + +#define PEM_LOAD_STATE_NOGO -2 /* Unusable object or sequence */ +#define PEM_LOAD_STATE_FAIL -1 /* Error in libcrypto */ +#define PEM_LOAD_STATE_DONE 0 /* End of PEM file, return value only */ +#define PEM_LOAD_STATE_INIT 1 /* No PEM objects seen */ +#define PEM_LOAD_STATE_PKEY 2 /* Last object was a private key */ +#define PEM_LOAD_STATE_CERT 3 /* Last object was a certificate */ +#define PEM_LOAD_STATE_BOTH 4 /* Unordered, key + first cert seen */ + +#define PEM_LOAD_READ_LAST 0 /* Reading last file */ +#define PEM_LOAD_READ_MORE 1 /* More files to be read */ + +typedef struct pem_load_state_t { + const char *origin; /* PEM chain origin description */ + const char *source; /* PEM BIO origin description */ + const char *keysrc; /* Source of last key */ + BIO *pembio; /* PEM input stream */ + SSL_CTX *ctx; /* SSL connection factory */ + SSL *ssl; /* SSL connection handle */ + EVP_PKEY *pkey; /* current key */ + X509 *cert; /* current certificate */ + x509_stack_t *chain; /* current chain */ + int keynum; /* Index of last key */ + int objnum; /* Index in current source */ + int state; /* Current state, never "DONE" */ + int mixed; /* Single file with key anywhere */ +} pem_load_state_t; + +/* init_pem_load_state - fill in initial pem_load_state structure */ + +static void init_pem_load_state(pem_load_state_t *st, SSL_CTX *ctx, SSL *ssl, + const char *origin) +{ + st->origin = origin; + st->source = origin; + st->keysrc = 0; + st->pembio = 0; + st->ctx = ctx; + st->ssl = ssl; + st->pkey = 0; + st->cert = 0; + st->chain = 0; + st->keynum = 0; + st->objnum = 0; + st->state = PEM_LOAD_STATE_INIT; + st->mixed = 0; +} + +/* use_chain - load cert, key and chain into ctx or ssl */ + +static int use_chain(pem_load_state_t *st) +{ + int ret; + int replace = 0; + + /* + * With replace == 0, an error is returned if the algorithm slot is + * already taken, and a previous key + chain of the same type would be + * clobbered. + */ + if (st->ctx) + ret = SSL_CTX_use_cert_and_key(st->ctx, st->cert, st->pkey, st->chain, + replace); + else + ret = SSL_use_cert_and_key(st->ssl, st->cert, st->pkey, st->chain, + replace); + + /* + * SSL_[CTX_]_use_cert_key() uprefs all the objects in question, so we + * must free ours. + */ + X509_free(st->cert); + st->cert = 0; + EVP_PKEY_free(st->pkey); + st->pkey = 0; + sk_X509_pop_free(st->chain, X509_free); + st->chain = 0; + + return ret; +} + +/* load_cert - decode and load a DER-encoded X509 certificate */ + +static void load_cert(pem_load_state_t *st, unsigned char *buf, + long buflen) +{ + const unsigned char *p = buf; + X509 *cert = d2i_X509(0, &p, buflen); + + /* + * When expecting one or more keys, each key must precede the associated + * certificate (chain). + */ + if (!st->mixed && st->state == PEM_LOAD_STATE_INIT) { + msg_warn("error loading chain from %s: key not first", st->source); + if (cert) + X509_free(cert); + st->state = PEM_LOAD_STATE_NOGO; + return; + } + if (!cert) { + msg_warn("error loading certificate (PEM object number %d) from %s", + st->objnum, st->source); + st->state = PEM_LOAD_STATE_FAIL; + return; + } + if (p - buf != buflen) { + msg_warn("error loading certificate (PEM object number %d) from %s:" + " excess data", st->objnum, st->source); + X509_free(cert); + st->state = PEM_LOAD_STATE_NOGO; + return; + } + + /* + * The first certificate after a new key becomes the leaf certificate for + * that key. Subsequent certificates are added to the issuer chain. + * + * In "mixed" mode, the first certificate is either after the key, or else + * comes first. + */ + switch (st->state) { + case PEM_LOAD_STATE_PKEY: + st->cert = cert; + st->state = st->mixed ? PEM_LOAD_STATE_BOTH : PEM_LOAD_STATE_CERT; + return; + case PEM_LOAD_STATE_INIT: + st->cert = cert; + st->state = PEM_LOAD_STATE_CERT; + return; + case PEM_LOAD_STATE_CERT: + case PEM_LOAD_STATE_BOTH: + if ((!st->chain && (st->chain = sk_X509_new_null()) == 0) + || !sk_X509_push(st->chain, cert)) { + X509_free(cert); + st->state = PEM_LOAD_STATE_FAIL; + } + return; + } +} + +/* load_pkey - decode and load a DER-encoded private key */ + +static void load_pkey(pem_load_state_t *st, int pkey_type, + unsigned char *buf, long buflen) +{ + const char *myname = "load_pkey"; + const unsigned char *p = buf; + PKCS8_PRIV_KEY_INFO *p8; + EVP_PKEY *pkey = 0; + + /* + * Keys are either algorithm-specific, or else (ideally) algorithm + * agnostic, in which case they are wrapped as PKCS#8 objects with an + * algorithm OID. + */ + if (pkey_type != NID_undef) { + pkey = d2i_PrivateKey(pkey_type, 0, &p, buflen); + } else { + p8 = d2i_PKCS8_PRIV_KEY_INFO(NULL, &p, buflen); + if (p8) { + pkey = EVP_PKCS82PKEY(p8); + PKCS8_PRIV_KEY_INFO_free(p8); + } + } + + /* + * Except in "mixed" mode, where a single key appears anywhere in a file + * with multiple certificates, a given key is either at the first object + * we process, or occurs after a previous key and one or more associated + * certificates. Thus, encountering a key in a state other than "INIT" + * or "CERT" is an error, except in "mixed" mode where a second key is + * ignored with a warning. + */ + switch (st->state) { + case PEM_LOAD_STATE_CERT: + + /* + * When processing the key of a "next" chain, we're in the "CERT" + * state, and first complete the processing of the previous chain. + */ + if (!st->mixed && !use_chain(st)) { + msg_warn("error loading certificate chain: " + "key at index %d in %s does not match the certificate", + st->keynum, st->keysrc); + st->state = PEM_LOAD_STATE_FAIL; + return; + } + /* FALLTHROUGH */ + case PEM_LOAD_STATE_INIT: + + if (!pkey) { + msg_warn("error loading private key (PEM object number %d) from %s", + st->objnum, st->source); + st->state = PEM_LOAD_STATE_FAIL; + return; + } + /* Reject unexpected data beyond the end of the DER-encoded object */ + if (p - buf != buflen) { + msg_warn("error loading private key (PEM object number %d) from" + " %s: excess data", st->objnum, st->source); + EVP_PKEY_free(pkey); + st->state = PEM_LOAD_STATE_NOGO; + return; + } + /* All's well, update the state */ + st->pkey = pkey; + if (st->state == PEM_LOAD_STATE_INIT) + st->state = PEM_LOAD_STATE_PKEY; + else if (st->mixed) + st->state = PEM_LOAD_STATE_BOTH; + else + st->state = PEM_LOAD_STATE_PKEY; + return; + + case PEM_LOAD_STATE_PKEY: + case PEM_LOAD_STATE_BOTH: + if (pkey) + EVP_PKEY_free(pkey); + + /* XXX: Legacy behavior was silent, should we stay silent? */ + if (st->mixed) { + msg_warn("ignoring 2nd key at index %d in %s after 1st at %d", + st->objnum, st->source, st->keynum); + return; + } + /* else back-to-back keys */ + msg_warn("error loading certificate chain: " + "key at index %d in %s not followed by a certificate", + st->keynum, st->keysrc); + st->state = PEM_LOAD_STATE_NOGO; + return; + + default: + msg_error("%s: internal error: bad state: %d", myname, st->state); + st->state = PEM_LOAD_STATE_NOGO; + return; + } +} + +/* load_pem_object - load next pkey or cert from open BIO */ + +static int load_pem_object(pem_load_state_t *st) +{ + char *name = 0; + char *header = 0; + unsigned char *buf = 0; + long buflen; + int pkey_type = NID_undef; + + if (!PEM_read_bio(st->pembio, &name, &header, &buf, &buflen)) { + if (ERR_GET_REASON(ERR_peek_last_error()) != PEM_R_NO_START_LINE) + return (st->state = PEM_LOAD_STATE_FAIL); + + ERR_clear_error(); + /* Clean EOF, preserve stored state for any next input file */ + return (PEM_LOAD_STATE_DONE); + } + if (strcmp(name, PEM_STRING_X509) == 0 + || strcmp(name, PEM_STRING_X509_OLD) == 0) { + load_cert(st, buf, buflen); + } else if (strcmp(name, PEM_STRING_PKCS8INF) == 0 + || ((pkey_type = EVP_PKEY_RSA) != NID_undef + && strcmp(name, PEM_STRING_RSA) == 0) + || ((pkey_type = EVP_PKEY_EC) != NID_undef + && strcmp(name, PEM_STRING_ECPRIVATEKEY) == 0) + || ((pkey_type = EVP_PKEY_DSA) != NID_undef + && strcmp(name, PEM_STRING_DSA) == 0)) { + load_pkey(st, pkey_type, buf, buflen); + } else if (!st->mixed) { + msg_warn("loading %s: ignoring PEM type: %s", st->source, name); + } + OPENSSL_free(name); + OPENSSL_free(header); + OPENSSL_free(buf); + return (st->state); +} + +/* load_pem_bio - load all key/certs from bio and free the bio */ + +static int load_pem_bio(pem_load_state_t *st, int more) +{ + int state = st->state; + + /* Don't report old news */ + ERR_clear_error(); + + /* + * When "more" is PEM_LOAD_READ_MORE, more files will be loaded after the + * current file, and final processing for the last key and chain is + * deferred. + * + * When "more" is PEM_LOAD_READ_LAST, this is the last file in the list, and + * we validate the final chain. + * + * When st->mixed is true, this is the only file, and its key can occur at + * any location. In this case we load at most one key. + */ + for (st->objnum = 1; state > PEM_LOAD_STATE_DONE; ++st->objnum) { + state = load_pem_object(st); + if ((st->mixed && st->keynum == 0 && + (state == PEM_LOAD_STATE_PKEY || state == PEM_LOAD_STATE_BOTH)) + || (!st->mixed && state == PEM_LOAD_STATE_PKEY)) { + /* Squirrel-away the current key location */ + st->keynum = st->objnum; + st->keysrc = st->source; + } + } + /* We're responsible for unconditionally freeing the BIO */ + BIO_free(st->pembio); + + /* Success with current file, go back for more? */ + if (more == PEM_LOAD_READ_MORE && state >= PEM_LOAD_STATE_DONE) + return 0; + + /* + * If all is well so far, complete processing for the final chain. + */ + switch (st->state) { + case PEM_LOAD_STATE_FAIL: + tls_print_errors(); + break; + default: + break; + case PEM_LOAD_STATE_INIT: + msg_warn("No PEM data in %s", st->origin); + break; + case PEM_LOAD_STATE_PKEY: + msg_warn("No certs for key at index %d in %s", st->keynum, st->keysrc); + break; + case PEM_LOAD_STATE_CERT: + if (st->mixed) { + msg_warn("No private key found in %s", st->origin); + break; + } + /* FALLTHROUGH */ + case PEM_LOAD_STATE_BOTH: + /* use_chain() frees the key and certs, and zeroes the pointers */ + if (use_chain(st)) + return (0); + msg_warn("key at index %d in %s does not match next certificate", + st->keynum, st->keysrc); + tls_print_errors(); + break; + } + /* Free any left-over unused keys and certs */ + EVP_PKEY_free(st->pkey); + X509_free(st->cert); + sk_X509_pop_free(st->chain, X509_free); + + msg_warn("error loading private keys and certificates from: %s: %s", + st->origin, st->ctx ? "disabling TLS support" : + "aborting TLS handshake"); + return (-1); +} + +/* load_chain_files - load sequence of (key, cert, [chain]) from files */ + +static int load_chain_files(SSL_CTX *ctx, const char *chain_files) +{ + pem_load_state_t st; + ARGV *files = argv_split(chain_files, CHARS_COMMA_SP); + char **filep; + int ret = 0; + int more; + + init_pem_load_state(&st, ctx, 0, chain_files); + for (filep = files->argv; ret == 0 && *filep; ++filep) { + st.source = *filep; + if ((st.pembio = BIO_new_file(st.source, "r")) == NULL) { + msg_warn("error opening chain file: %s: %m", st.source); + st.state = PEM_LOAD_STATE_NOGO; + break; + } + more = filep[1] ? PEM_LOAD_READ_MORE : PEM_LOAD_READ_LAST; + /* load_pem_bio() frees the BIO */ + ret = load_pem_bio(&st, more); + } + argv_free(files); + return (ret); +} + +/* load_mixed_file - load certs with single key anywhere in the file */ + +static int load_mixed_file(SSL_CTX *ctx, const char *file) +{ + pem_load_state_t st; + + init_pem_load_state(&st, ctx, 0, file); + if ((st.pembio = BIO_new_file(st.source, "r")) == NULL) { + msg_warn("error opening chain file: %s: %m", st.source); + return (-1); + } + st.mixed = 1; + /* load_pem_bio() frees the BIO */ + return load_pem_bio(&st, PEM_LOAD_READ_LAST); +} + +/* tls_set_ca_certificate_info - load Certification Authority certificates */ + +int tls_set_ca_certificate_info(SSL_CTX *ctx, const char *CAfile, + const char *CApath) +{ + if (*CAfile == 0) + CAfile = 0; + if (*CApath == 0) + CApath = 0; + +#define CA_PATH_FMT "%s%s%s" +#define CA_PATH_ARGS(var, nextvar) \ + var ? #var "=\"" : "", \ + var ? var : "", \ + var ? (nextvar ? "\", " : "\"") : "" + + if (CAfile || CApath) { + if (!SSL_CTX_load_verify_locations(ctx, CAfile, CApath)) { + msg_info("cannot load Certification Authority data, " + CA_PATH_FMT CA_PATH_FMT ": disabling TLS support", + CA_PATH_ARGS(CAfile, CApath), + CA_PATH_ARGS(CApath, 0)); + tls_print_errors(); + return (-1); + } + if (var_tls_append_def_CA && !SSL_CTX_set_default_verify_paths(ctx)) { + msg_info("cannot set default OpenSSL certificate verification " + "paths: disabling TLS support"); + tls_print_errors(); + return (-1); + } + } + return (0); +} + +/* set_cert_stuff - specify certificate and key information */ + +static int set_cert_stuff(SSL_CTX *ctx, const char *cert_type, + const char *cert_file, + const char *key_file) +{ + + /* + * When the certfile and keyfile are one and the same, load both in a + * single pass, avoiding potential race conditions during key rollover. + */ + if (strcmp(cert_file, key_file) == 0) + return (load_mixed_file(ctx, cert_file) == 0); + + /* + * We need both the private key (in key_file) and the public key + * certificate (in cert_file). + * + * Code adapted from OpenSSL apps/s_cb.c. + */ + ERR_clear_error(); + if (SSL_CTX_use_certificate_chain_file(ctx, cert_file) <= 0) { + msg_warn("cannot get %s certificate from file \"%s\": " + "disabling TLS support", cert_type, cert_file); + tls_print_errors(); + return (0); + } + if (SSL_CTX_use_PrivateKey_file(ctx, key_file, SSL_FILETYPE_PEM) <= 0) { + msg_warn("cannot get %s private key from file \"%s\": " + "disabling TLS support", cert_type, key_file); + tls_print_errors(); + return (0); + } + + /* + * Sanity check. + */ + if (!SSL_CTX_check_private_key(ctx)) { + msg_warn("%s private key in %s does not match public key in %s: " + "disabling TLS support", cert_type, key_file, cert_file); + return (0); + } + return (1); +} + +/* tls_set_my_certificate_key_info - load client or server certificates/keys */ + +int tls_set_my_certificate_key_info(SSL_CTX *ctx, const char *chain_files, + const char *cert_file, + const char *key_file, + const char *dcert_file, + const char *dkey_file, + const char *eccert_file, + const char *eckey_file) +{ + + /* The "chain_files" parameter overrides all the legacy parameters */ + if (chain_files && *chain_files) + return load_chain_files(ctx, chain_files); + + /* + * Lack of certificates is fine so long as we are prepared to use + * anonymous ciphers. + */ + if (*cert_file && !set_cert_stuff(ctx, "RSA", cert_file, key_file)) + return (-1); /* logged */ + if (*dcert_file && !set_cert_stuff(ctx, "DSA", dcert_file, dkey_file)) + return (-1); /* logged */ +#ifndef OPENSSL_NO_ECDH + if (*eccert_file && !set_cert_stuff(ctx, "ECDSA", eccert_file, eckey_file)) + return (-1); /* logged */ +#else + if (*eccert_file) + msg_warn("ECDSA not supported. Ignoring ECDSA certificate file \"%s\"", + eccert_file); +#endif + return (0); +} + +/* tls_load_pem_chain - load in-memory PEM client or server chain */ + +int tls_load_pem_chain(SSL *ssl, const char *pem, const char *origin) +{ + static VSTRING *obuf; + pem_load_state_t st; + + if (!obuf) + obuf = vstring_alloc(100); + vstring_sprintf(obuf, "SNI data for %s", origin); + init_pem_load_state(&st, 0, ssl, vstring_str(obuf)); + + if ((st.pembio = BIO_new_mem_buf(pem, -1)) == NULL) { + msg_warn("error opening memory BIO for %s", st.origin); + tls_print_errors(); + return (-1); + } + /* load_pem_bio() frees the BIO */ + return (load_pem_bio(&st, PEM_LOAD_READ_LAST)); +} + +#ifdef TEST + +static NORETURN usage(void) +{ + fprintf(stderr, "usage: tls_certkey [-m] \n"); + exit(1); +} + +int main(int argc, char *argv[]) +{ + int ch; + int mixed = 0; + int ret; + char *key_file = 0; + SSL_CTX *ctx; + + if (!(ctx = SSL_CTX_new(TLS_client_method()))) { + tls_print_errors(); + exit(1); + } + while ((ch = GETOPT(argc, argv, "mk:")) > 0) { + switch (ch) { + case 'k': + key_file = optarg; + break; + case 'm': + mixed = 1; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + + if (key_file) + ret = set_cert_stuff(ctx, "any", argv[0], key_file) == 0; + else if (mixed) + ret = load_mixed_file(ctx, argv[0]); + else + ret = load_chain_files(ctx, argv[0]); + + if (ret != 0) + exit(1); + + if (SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_FIRST) != 1) { + fprintf(stderr, "error selecting first certificate\n"); + tls_print_errors(); + exit(1); + } + do { + STACK_OF(X509) *chain; + int i; + + if (SSL_CTX_get0_chain_certs(ctx, &chain) != 1) { + fprintf(stderr, "error locating certificate chain\n"); + tls_print_errors(); + exit(1); + } + for (i = 0; i <= sk_X509_num(chain); ++i) { + char buf[CCERT_BUFSIZ]; + X509 *cert; + + if (i > 0) + cert = sk_X509_value(chain, i - 1); + else + cert = SSL_CTX_get0_certificate(ctx); + + printf("depth = %d\n", i); + + X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf)); + printf("issuer = %s\n", buf); + + X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf)); + printf("subject = %s\n\n", buf); + } + } while (SSL_CTX_set_current_cert(ctx, SSL_CERT_SET_NEXT) != 0); + + exit(0); +} + +#endif + +#endif diff --git a/src/tls/tls_client.c b/src/tls/tls_client.c new file mode 100644 index 0000000..68f9255 --- /dev/null +++ b/src/tls/tls_client.c @@ -0,0 +1,1250 @@ +/*++ +/* NAME +/* tls_client +/* SUMMARY +/* client-side TLS engine +/* SYNOPSIS +/* #include +/* +/* TLS_APPL_STATE *tls_client_init(init_props) +/* const TLS_CLIENT_INIT_PROPS *init_props; +/* +/* TLS_SESS_STATE *tls_client_start(start_props) +/* const TLS_CLIENT_START_PROPS *start_props; +/* +/* TLS_SESS_STATE *tls_client_post_connect(TLScontext, start_props) +/* TLS_SESS_STATE *TLScontext; +/* const TLS_CLIENT_START_PROPS *start_props; +/* +/* void tls_client_stop(app_ctx, stream, failure, TLScontext) +/* TLS_APPL_STATE *app_ctx; +/* VSTREAM *stream; +/* int failure; +/* TLS_SESS_STATE *TLScontext; +/* DESCRIPTION +/* This module is the interface between Postfix TLS clients, +/* the OpenSSL library and the TLS entropy and cache manager. +/* +/* The SMTP client will attempt to verify the server hostname +/* against the names listed in the server certificate. When +/* a hostname match is required, the verification fails +/* on certificate verification or hostname mis-match errors. +/* When no hostname match is required, hostname verification +/* failures are logged but they do not affect the TLS handshake +/* or the SMTP session. +/* +/* The rules for peer name wild-card matching differ between +/* RFC 2818 (HTTP over TLS) and RFC 2830 (LDAP over TLS), while +/* RFC RFC3207 (SMTP over TLS) does not specify a rule at all. +/* Postfix uses a restrictive match algorithm. One asterisk +/* ('*') is allowed as the left-most component of a wild-card +/* certificate name; it matches the left-most component of +/* the peer hostname. +/* +/* Another area where RFCs aren't always explicit is the +/* handling of dNSNames in peer certificates. RFC 3207 (SMTP +/* over TLS) does not mention dNSNames. Postfix follows the +/* strict rules in RFC 2818 (HTTP over TLS), section 3.1: The +/* Subject Alternative Name/dNSName has precedence over +/* CommonName. If at least one dNSName is provided, Postfix +/* verifies those against the peer hostname and ignores the +/* CommonName, otherwise Postfix verifies the CommonName +/* against the peer hostname. +/* +/* tls_client_init() is called once when the SMTP client +/* initializes. +/* Certificate details are also decided during this phase, +/* so peer-specific certificate selection is not possible. +/* +/* tls_client_start() activates the TLS session over an established +/* stream. We expect that network buffers are flushed and +/* the TLS handshake can begin immediately. +/* +/* tls_client_stop() sends the "close notify" alert via +/* SSL_shutdown() to the peer and resets all connection specific +/* TLS data. As RFC2487 does not specify a separate shutdown, it +/* is assumed that the underlying TCP connection is shut down +/* immediately afterwards. Any further writes to the channel will +/* be discarded, and any further reads will report end-of-file. +/* If the failure flag is set, no SSL_shutdown() handshake is performed. +/* +/* Once the TLS connection is initiated, information about the TLS +/* state is available via the TLScontext structure: +/* .IP TLScontext->protocol +/* the protocol name (SSLv2, SSLv3, TLSv1), +/* .IP TLScontext->cipher_name +/* the cipher name (e.g. RC4/MD5), +/* .IP TLScontext->cipher_usebits +/* the number of bits actually used (e.g. 40), +/* .IP TLScontext->cipher_algbits +/* the number of bits the algorithm is based on (e.g. 128). +/* .PP +/* The last two values may differ from each other when export-strength +/* encryption is used. +/* +/* If the peer offered a certificate, part of the certificate data are +/* available as: +/* .IP TLScontext->peer_status +/* A bitmask field that records the status of the peer certificate +/* verification. This consists of one or more of TLS_CERT_FLAG_PRESENT, +/* TLS_CERT_FLAG_TRUSTED, TLS_CERT_FLAG_MATCHED and TLS_CERT_FLAG_SECURED. +/* .IP TLScontext->peer_CN +/* Extracted CommonName of the peer, or zero-length string if the +/* information could not be extracted. +/* .IP TLScontext->issuer_CN +/* Extracted CommonName of the issuer, or zero-length string if the +/* information could not be extracted. +/* .IP TLScontext->peer_cert_fprint +/* At the fingerprint security level, if the peer presented a certificate +/* the fingerprint of the certificate. +/* .PP +/* If no peer certificate is presented the peer_status is set to 0. +/* EVENT_DRIVEN APPLICATIONS +/* .ad +/* .fi +/* Event-driven programs manage multiple I/O channels. Such +/* programs cannot use the synchronous VSTREAM-over-TLS +/* implementation that the TLS library historically provides, +/* including tls_client_stop() and the underlying tls_stream(3) +/* and tls_bio_ops(3) routines. +/* +/* With the current TLS library implementation, this means +/* that an event-driven application is responsible for calling +/* and retrying SSL_connect(), SSL_read(), SSL_write() and +/* SSL_shutdown(). +/* +/* To maintain control over TLS I/O, an event-driven client +/* invokes tls_client_start() with a null VSTREAM argument and +/* with an fd argument that specifies the I/O file descriptor. +/* Then, tls_client_start() performs all the necessary +/* preparations before the TLS handshake and returns a partially +/* populated TLS context. The event-driven application is then +/* responsible for invoking SSL_connect(), and if successful, +/* for invoking tls_client_post_connect() to finish the work +/* that was started by tls_client_start(). In case of unrecoverable +/* failure, tls_client_post_connect() destroys the TLS context +/* and returns a null pointer value. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Originally written by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include + +#ifdef USE_TLS +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include /* non-blocking */ +#include + +/* Global library. */ + +#include + +/* TLS library. */ + +#include +#define TLS_INTERNAL +#include + +/* Application-specific. */ + +#define STR vstring_str +#define LEN VSTRING_LEN + +/* load_clnt_session - load session from client cache (non-callback) */ + +static SSL_SESSION *load_clnt_session(TLS_SESS_STATE *TLScontext) +{ + const char *myname = "load_clnt_session"; + SSL_SESSION *session = 0; + VSTRING *session_data = vstring_alloc(2048); + + /* + * Prepare the query. + */ + if (TLScontext->log_mask & TLS_LOG_CACHE) + /* serverid contains transport:addr:port information */ + msg_info("looking for session %s in %s cache", + TLScontext->serverid, TLScontext->cache_type); + + /* + * We only get here if the cache_type is not empty. This code is not + * called unless caching is enabled and the cache_type is stored in the + * server SSL context. + */ + if (TLScontext->cache_type == 0) + msg_panic("%s: null client session cache type in session lookup", + myname); + + /* + * Look up and activate the SSL_SESSION object. Errors are non-fatal, + * since caching is only an optimization. + */ + if (tls_mgr_lookup(TLScontext->cache_type, TLScontext->serverid, + session_data) == TLS_MGR_STAT_OK) { + session = tls_session_activate(STR(session_data), LEN(session_data)); + if (session) { + if (TLScontext->log_mask & TLS_LOG_CACHE) + /* serverid contains transport:addr:port information */ + msg_info("reloaded session %s from %s cache", + TLScontext->serverid, TLScontext->cache_type); + } + } + + /* + * Clean up. + */ + vstring_free(session_data); + + return (session); +} + +/* new_client_session_cb - name new session and save it to client cache */ + +static int new_client_session_cb(SSL *ssl, SSL_SESSION *session) +{ + const char *myname = "new_client_session_cb"; + TLS_SESS_STATE *TLScontext; + VSTRING *session_data; + + /* + * The cache name (if caching is enabled in tlsmgr(8)) and the cache ID + * string for this session are stored in the TLScontext. It cannot be + * null at this point. + */ + if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0) + msg_panic("%s: null TLScontext in new session callback", myname); + + /* + * We only get here if the cache_type is not empty. This callback is not + * set unless caching is enabled and the cache_type is stored in the + * server SSL context. + */ + if (TLScontext->cache_type == 0) + msg_panic("%s: null session cache type in new session callback", + myname); + + if (TLScontext->log_mask & TLS_LOG_CACHE) + /* serverid contains transport:addr:port information */ + msg_info("save session %s to %s cache", + TLScontext->serverid, TLScontext->cache_type); + + /* + * Passivate and save the session object. Errors are non-fatal, since + * caching is only an optimization. + */ + if ((session_data = tls_session_passivate(session)) != 0) { + tls_mgr_update(TLScontext->cache_type, TLScontext->serverid, + STR(session_data), LEN(session_data)); + vstring_free(session_data); + } + + /* + * Clean up. + */ + SSL_SESSION_free(session); /* 200502 */ + + return (1); +} + +/* uncache_session - remove session from the external cache */ + +static void uncache_session(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext) +{ + SSL_SESSION *session = SSL_get_session(TLScontext->con); + + SSL_CTX_remove_session(ctx, session); + if (TLScontext->cache_type == 0 || TLScontext->serverid == 0) + return; + + if (TLScontext->log_mask & TLS_LOG_CACHE) + /* serverid contains transport:addr:port information */ + msg_info("remove session %s from client cache", TLScontext->serverid); + + tls_mgr_delete(TLScontext->cache_type, TLScontext->serverid); +} + +/* verify_extract_name - verify peer name and extract peer information */ + +static void verify_extract_name(TLS_SESS_STATE *TLScontext, X509 *peercert, + const TLS_CLIENT_START_PROPS *props) +{ + int verbose; + + verbose = TLScontext->log_mask & + (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT); + + /* + * On exit both peer_CN and issuer_CN should be set. + */ + TLScontext->issuer_CN = tls_issuer_CN(peercert, TLScontext); + TLScontext->peer_CN = tls_peer_CN(peercert, TLScontext); + + /* + * Is the certificate trust chain trusted and matched? Any required name + * checks are now performed internally in OpenSSL. + */ + if (SSL_get_verify_result(TLScontext->con) == X509_V_OK) { + TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED; + if (TLScontext->must_fail) { + msg_panic("%s: cert valid despite trust init failure", + TLScontext->namaddr); + } else if (TLS_MUST_MATCH(TLScontext->level)) { + + /* + * Fully secured only if not insecure like half-dane. We use + * TLS_CERT_FLAG_MATCHED to satisfy policy, but + * TLS_CERT_FLAG_SECURED to log the effective security. + * + * Would ideally also exclude "verify" (as opposed to "secure") + * here, because that can be subject to insecure MX indirection, + * but that's rather incompatible (and not even the case with + * explicitly chosen non-default match patterns). Users have + * been warned. + */ + if (!TLS_NEVER_SECURED(TLScontext->level)) + TLScontext->peer_status |= TLS_CERT_FLAG_SECURED; + TLScontext->peer_status |= TLS_CERT_FLAG_MATCHED; + + if (verbose) { + const char *peername = SSL_get0_peername(TLScontext->con); + + if (peername) + msg_info("%s: matched peername: %s", + TLScontext->namaddr, peername); + tls_dane_log(TLScontext); + } + } + } + + /* + * Give them a clue. Problems with trust chain verification are logged + * when the session is first negotiated, before the session is stored + * into the cache. We don't want mystery failures, so log the fact the + * real problem is to be found in the past. + */ + if (!TLS_CERT_IS_MATCHED(TLScontext) + && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) { + if (TLScontext->session_reused == 0) + tls_log_verify_error(TLScontext); + else + msg_info("%s: re-using session with untrusted certificate, " + "look for details earlier in the log", props->namaddr); + } +} + +/* add_namechecks - tell OpenSSL what names to check */ + +static void add_namechecks(TLS_SESS_STATE *TLScontext, + const TLS_CLIENT_START_PROPS *props) +{ + SSL *ssl = TLScontext->con; + int namechecks_count = 0; + int i; + + /* RFC6125: No part-label 'foo*bar.example.com' wildcards for SMTP */ + SSL_set_hostflags(ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + + for (i = 0; i < props->matchargv->argc; ++i) { + const char *name = props->matchargv->argv[i]; + const char *aname; + int match_subdomain = 0; + + if (strcasecmp(name, "nexthop") == 0) { + name = props->nexthop; + } else if (strcasecmp(name, "dot-nexthop") == 0) { + name = props->nexthop; + match_subdomain = 1; + } else if (strcasecmp(name, "hostname") == 0) { + name = props->host; + } else { + if (*name == '.') { + if (*++name == 0) { + msg_warn("%s: ignoring invalid match name: \".\"", + TLScontext->namaddr); + continue; + } + match_subdomain = 1; + } +#ifndef NO_EAI + else { + + /* + * Besides U+002E (full stop) IDNA2003 allows labels to be + * separated by any of the Unicode variants U+3002 + * (ideographic full stop), U+FF0E (fullwidth full stop), and + * U+FF61 (halfwidth ideographic full stop). Their respective + * UTF-8 encodings are: E38082, EFBC8E and EFBDA1. + * + * IDNA2008 does not permit (upper) case and other variant + * differences in U-labels. The midna_domain_to_ascii() + * function, based on UTS46, normalizes such differences + * away. + * + * The IDNA to_ASCII conversion does not allow empty leading + * labels, so we handle these explicitly here. + */ + unsigned char *cp = (unsigned char *) name; + + if ((cp[0] == 0xe3 && cp[1] == 0x80 && cp[2] == 0x82) + || (cp[0] == 0xef && cp[1] == 0xbc && cp[2] == 0x8e) + || (cp[0] == 0xef && cp[1] == 0xbd && cp[2] == 0xa1)) { + if (name[3]) { + name = name + 3; + match_subdomain = 1; + } + } + } +#endif + } + + /* + * DNS subjectAltNames are required to be ASCII. + * + * Per RFC 6125 Section 6.4.4 Matching the CN-ID, follows the same rules + * (6.4.1, 6.4.2 and 6.4.3) that apply to subjectAltNames. In + * particular, 6.4.2 says that the reference identifier is coerced to + * ASCII, but no conversion is stated or implied for the CN-ID, so it + * seems it only matches if it is all ASCII. Otherwise, it is some + * other sort of name. + */ +#ifndef NO_EAI + if (!allascii(name) && (aname = midna_domain_to_ascii(name)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", name, aname); + name = aname; + } +#endif + + if (!match_subdomain) { + if (SSL_add1_host(ssl, name)) + ++namechecks_count; + else + msg_warn("%s: error loading match name: \"%s\"", + TLScontext->namaddr, name); + } else { + char *dot_name = concatenate(".", name, (char *) 0); + + if (SSL_add1_host(ssl, dot_name)) + ++namechecks_count; + else + msg_warn("%s: error loading match name: \"%s\"", + TLScontext->namaddr, dot_name); + myfree(dot_name); + } + } + + /* + * If we failed to add any names, OpenSSL will perform no namechecks, so + * we set the "must_fail" bit to avoid verification false-positives. + */ + if (namechecks_count == 0) { + msg_warn("%s: could not configure peer name checks", + TLScontext->namaddr); + TLScontext->must_fail = 1; + } +} + +/* tls_auth_enable - set up TLS authentication */ + +static int tls_auth_enable(TLS_SESS_STATE *TLScontext, + const TLS_CLIENT_START_PROPS *props) +{ + const char *sni = 0; + + if (props->sni && *props->sni) { +#ifndef NO_EAI + const char *aname; + +#endif + + /* + * MTA-STS policy plugin compatibility: with servername=hostname, + * Postfix must send the MX hostname (not CNAME expanded). + */ + if (strcmp(props->sni, "hostname") == 0) + sni = props->host; + else if (strcmp(props->sni, "nexthop") == 0) + sni = props->nexthop; + else + sni = props->sni; + + /* + * The SSL_set_tlsext_host_name() documentation does not promise that + * every implementation will convert U-label form to A-label form. + */ +#ifndef NO_EAI + if (!allascii(sni) && (aname = midna_domain_to_ascii(sni)) != 0) { + if (msg_verbose) + msg_info("%s asciified to %s", sni, aname); + sni = aname; + } +#endif + } + switch (TLScontext->level) { + case TLS_LEV_HALF_DANE: + case TLS_LEV_DANE: + case TLS_LEV_DANE_ONLY: + + /* + * With DANE sessions, send an SNI hint. We don't care whether the + * server reports finding a matching certificate or not, so no + * callback is required to process the server response. Our use of + * SNI is limited to giving servers that make use of SNI the best + * opportunity to find the certificate they promised via the + * associated TLSA RRs. + * + * Since the hostname is DNSSEC-validated, it must be a DNS FQDN and + * therefore valid for use with SNI. + */ + if (SSL_dane_enable(TLScontext->con, 0) <= 0) { + msg_warn("%s: error enabling DANE-based certificate validation", + TLScontext->namaddr); + tls_print_errors(); + return (0); + } + /* RFC7672 Section 3.1.1 specifies no name checks for DANE-EE(3) */ + SSL_dane_set_flags(TLScontext->con, DANE_FLAG_NO_DANE_EE_NAMECHECKS); + + /* Per RFC7672 the SNI name is the TLSA base domain */ + sni = props->dane->base_domain; + add_namechecks(TLScontext, props); + break; + + case TLS_LEV_FPRINT: + /* Synthetic DANE for fingerprint security */ + if (SSL_dane_enable(TLScontext->con, 0) <= 0) { + msg_warn("%s: error enabling fingerprint certificate validation", + props->namaddr); + tls_print_errors(); + return (0); + } + SSL_dane_set_flags(TLScontext->con, DANE_FLAG_NO_DANE_EE_NAMECHECKS); + break; + + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + if (TLScontext->dane != 0 && TLScontext->dane->tlsa != 0) { + /* Synthetic DANE for per-destination trust-anchors */ + if (SSL_dane_enable(TLScontext->con, NULL) <= 0) { + msg_warn("%s: error configuring local trust anchors", + props->namaddr); + tls_print_errors(); + return (0); + } + } + add_namechecks(TLScontext, props); + break; + default: + break; + } + + if (sni) { + if (strlen(sni) > TLSEXT_MAXLEN_host_name) { + msg_warn("%s: ignoring too long SNI hostname: %.100s", + props->namaddr, sni); + return (0); + } + + /* + * Failure to set a valid SNI hostname is a memory allocation error, + * and thus transient. Since we must not cache the session if we + * failed to send the SNI name, we have little choice but to abort. + */ + if (!SSL_set_tlsext_host_name(TLScontext->con, sni)) { + msg_warn("%s: error setting SNI hostname to: %s", props->namaddr, + sni); + return (0); + } + + /* + * The saved value is not presently used client-side, but could later + * be logged if acked by the server (requires new client-side + * callback to detect the ack). For now this just maintains symmetry + * with the server code, where do record the received SNI for + * logging. + */ + TLScontext->peer_sni = mystrdup(sni); + if (TLScontext->log_mask & TLS_LOG_DEBUG) + msg_info("%s: SNI hostname: %s", props->namaddr, sni); + } + return (1); +} + +/* tls_client_init - initialize client-side TLS engine */ + +TLS_APPL_STATE *tls_client_init(const TLS_CLIENT_INIT_PROPS *props) +{ + SSL_CTX *client_ctx; + TLS_APPL_STATE *app_ctx; + const EVP_MD *fpt_alg; + long off = 0; + int cachable; + int scache_timeout; + int log_mask; + + /* + * Convert user loglevel to internal logmask. + */ + log_mask = tls_log_mask(props->log_param, props->log_level); + + if (log_mask & TLS_LOG_VERBOSE) + msg_info("initializing the client-side TLS engine"); + + /* + * Load (mostly cipher related) TLS-library internal main.cf parameters. + */ + tls_param_init(); + + /* + * Detect mismatch between compile-time headers and run-time library. + */ + tls_check_version(); + + /* + * Initialize the OpenSSL library, possibly loading its configuration + * file. + */ + if (tls_library_init() == 0) + return (0); + + /* + * Create an application data index for SSL objects, so that we can + * attach TLScontext information; this information is needed inside + * tls_verify_certificate_callback(). + */ + if (TLScontext_index < 0) { + if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0) { + msg_warn("Cannot allocate SSL application data index: " + "disabling TLS support"); + return (0); + } + } + + /* + * If the administrator specifies an unsupported digest algorithm, fail + * now, rather than in the middle of a TLS handshake. + */ + if ((fpt_alg = tls_validate_digest(props->mdalg)) == 0) { + msg_warn("disabling TLS support"); + return (0); + } + + /* + * Initialize the PRNG (Pseudo Random Number Generator) with some seed + * from external and internal sources. Don't enable TLS without some real + * entropy. + */ + if (tls_ext_seed(var_tls_daemon_rand_bytes) < 0) { + msg_warn("no entropy for TLS key generation: disabling TLS support"); + return (0); + } + tls_int_seed(); + + /* + * The SSL/TLS specifications require the client to send a message in the + * oldest specification it understands with the highest level it + * understands in the message. RFC2487 is only specified for TLSv1, but + * we want to be as compatible as possible, so we will start off with a + * SSLv2 greeting allowing the best we can offer: TLSv1. We can restrict + * this with the options setting later, anyhow. + */ + ERR_clear_error(); + client_ctx = SSL_CTX_new(TLS_client_method()); + if (client_ctx == 0) { + msg_warn("cannot allocate client SSL_CTX: disabling TLS support"); + tls_print_errors(); + return (0); + } +#ifdef SSL_SECOP_PEER + /* Backwards compatible security as a base for opportunistic TLS. */ + SSL_CTX_set_security_level(client_ctx, 0); +#endif + + /* + * See the verify callback in tls_verify.c + */ + SSL_CTX_set_verify_depth(client_ctx, props->verifydepth + 1); + + /* + * This is a prerequisite for enabling DANE support in OpenSSL, but not a + * commitment to use DANE, thus suitable for both DANE and non-DANE TLS + * connections. Indeed we need this not just for DANE, but aslo for + * fingerprint and "tafile" support. Since it just allocates memory, it + * should never fail except when we're likely to fail anyway. Rather + * than try to run with crippled TLS support, just give up using TLS. + */ + if (SSL_CTX_dane_enable(client_ctx) <= 0) { + msg_warn("OpenSSL DANE initialization failed: disabling TLS support"); + tls_print_errors(); + return (0); + } + tls_dane_digest_init(client_ctx, fpt_alg); + + /* + * Presently we use TLS only with SMTP where truncation attacks are not + * possible as a result of application framing. If we ever use TLS in + * some other application protocol where truncation could be relevant, + * we'd need to disable truncation detection conditionally, or explicitly + * clear the option in that code path. + */ + off |= SSL_OP_IGNORE_UNEXPECTED_EOF; + + /* + * Protocol selection is destination dependent, so we delay the protocol + * selection options to the per-session SSL object. + */ + off |= tls_bug_bits(); + SSL_CTX_set_options(client_ctx, off); + + /* + * Set the call-back routine for verbose logging. + */ + if (log_mask & TLS_LOG_DEBUG) + SSL_CTX_set_info_callback(client_ctx, tls_info_callback); + + /* + * Load the CA public key certificates for both the client cert and for + * the verification of server certificates. As provided by OpenSSL we + * support two types of CA certificate handling: One possibility is to + * add all CA certificates to one large CAfile, the other possibility is + * a directory pointed to by CApath, containing separate files for each + * CA with softlinks named after the hash values of the certificate. The + * first alternative has the advantage that the file is opened and read + * at startup time, so that you don't have the hassle to maintain another + * copy of the CApath directory for chroot-jail. + */ + if (tls_set_ca_certificate_info(client_ctx, + props->CAfile, props->CApath) < 0) { + /* tls_set_ca_certificate_info() already logs a warning. */ + SSL_CTX_free(client_ctx); /* 200411 */ + return (0); + } + + /* + * We do not need a client certificate, so the certificates are only + * loaded (and checked) if supplied. A clever client would handle + * multiple client certificates and decide based on the list of + * acceptable CAs, sent by the server, which certificate to submit. + * OpenSSL does however not do this and also has no call-back hooks to + * easily implement it. + * + * Load the client public key certificate and private key from file and + * check whether the cert matches the key. We can use RSA certificates + * ("cert") DSA certificates ("dcert") or ECDSA certificates ("eccert"). + * All three can be made available at the same time. The CA certificates + * for all three are handled in the same setup already finished. Which + * one is used depends on the cipher negotiated (that is: the first + * cipher listed by the client which does match the server). The client + * certificate is presented after the server chooses the session cipher, + * so we will just present the right cert for the chosen cipher (if it + * uses certificates). + */ + if (tls_set_my_certificate_key_info(client_ctx, + props->chain_files, + props->cert_file, + props->key_file, + props->dcert_file, + props->dkey_file, + props->eccert_file, + props->eckey_file) < 0) { + /* tls_set_my_certificate_key_info() already logs a warning. */ + SSL_CTX_free(client_ctx); /* 200411 */ + return (0); + } + + /* + * With OpenSSL 1.0.2 and later the client EECDH curve list becomes + * configurable with the preferred curve negotiated via the supported + * curves extension. + */ + tls_auto_eecdh_curves(client_ctx, var_tls_eecdh_auto); + + /* + * Finally, the setup for the server certificate checking, done "by the + * book". + */ + SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE, + tls_verify_certificate_callback); + + /* + * Initialize the session cache. + * + * Since the client does not search an internal cache, we simply disable it. + * It is only useful for expiring old sessions, but we do that in the + * tlsmgr(8). + * + * This makes SSL_CTX_remove_session() not useful for flushing broken + * sessions from the external cache, so we must delete them directly (not + * via a callback). + */ + if (tls_mgr_policy(props->cache_type, &cachable, + &scache_timeout) != TLS_MGR_STAT_OK) + scache_timeout = 0; + if (scache_timeout <= 0) + cachable = 0; + + /* + * Allocate an application context, and populate with mandatory protocol + * and cipher data. + */ + app_ctx = tls_alloc_app_context(client_ctx, 0, log_mask); + + /* + * The external session cache is implemented by the tlsmgr(8) process. + */ + if (cachable) { + + app_ctx->cache_type = mystrdup(props->cache_type); + + /* + * OpenSSL does not use callbacks to load sessions from a client + * cache, so we must invoke that function directly. Apparently, + * OpenSSL does not provide a way to pass session names from here to + * call-back routines that do session lookup. + * + * OpenSSL can, however, automatically save newly created sessions for + * us by callback (we create the session name in the call-back + * function). + * + * XXX gcc 2.95 can't compile #ifdef .. #endif in the expansion of + * SSL_SESS_CACHE_CLIENT | SSL_SESS_CACHE_NO_INTERNAL_STORE | + * SSL_SESS_CACHE_NO_AUTO_CLEAR. + */ +#ifndef SSL_SESS_CACHE_NO_INTERNAL_STORE +#define SSL_SESS_CACHE_NO_INTERNAL_STORE 0 +#endif + + SSL_CTX_set_session_cache_mode(client_ctx, + SSL_SESS_CACHE_CLIENT | + SSL_SESS_CACHE_NO_INTERNAL_STORE | + SSL_SESS_CACHE_NO_AUTO_CLEAR); + SSL_CTX_sess_set_new_cb(client_ctx, new_client_session_cb); + + /* + * OpenSSL ignores timed-out sessions. We need to set the internal + * cache timeout at least as high as the external cache timeout. This + * applies even if no internal cache is used. We set the session to + * twice the cache lifetime. This way a session always lasts longer + * than its lifetime in the cache. + */ + SSL_CTX_set_timeout(client_ctx, 2 * scache_timeout); + } + return (app_ctx); +} + + /* + * This is the actual startup routine for the connection. We expect that the + * buffers are flushed and the "220 Ready to start TLS" was received by us, + * so that we can immediately start the TLS handshake process. + */ +TLS_SESS_STATE *tls_client_start(const TLS_CLIENT_START_PROPS *props) +{ + int sts; + int protomask; + int min_proto; + int max_proto; + const char *cipher_list; + SSL_SESSION *session = 0; + TLS_SESS_STATE *TLScontext; + TLS_APPL_STATE *app_ctx = props->ctx; + int log_mask = app_ctx->log_mask; + + /* + * When certificate verification is required, log trust chain validation + * errors even when disabled by default for opportunistic sessions. For + * DANE this only applies when using trust-anchor associations. + */ + if (TLS_MUST_MATCH(props->tls_level)) + log_mask |= TLS_LOG_UNTRUSTED; + + if (log_mask & TLS_LOG_VERBOSE) + msg_info("setting up TLS connection to %s", props->namaddr); + + /* + * First make sure we have valid protocol and cipher parameters + * + * Per-session protocol restrictions must be applied to the SSL connection, + * as restrictions in the global context cannot be cleared. + */ + protomask = tls_proto_mask_lims(props->protocols, &min_proto, &max_proto); + if (protomask == TLS_PROTOCOL_INVALID) { + /* tls_protocol_mask() logs no warning. */ + msg_warn("%s: Invalid TLS protocol list \"%s\": aborting TLS session", + props->namaddr, props->protocols); + return (0); + } + + /* + * Though RFC7672 set the floor at SSLv3, we really can and should + * require TLS 1.0, since e.g. we send SNI, which is a TLS 1.0 extension. + * No DANE domains have been observed to support only SSLv3. + * + * XXX: Would be nice to make that TLS 1.2 at some point. Users can choose + * to exclude TLS 1.0 and TLS 1.1 if they find they don't run into any + * problems doing that. + */ + if (TLS_DANE_BASED(props->tls_level)) + protomask |= TLS_PROTOCOL_SSLv2 | TLS_PROTOCOL_SSLv3; + + /* + * Allocate a new TLScontext for the new connection and get an SSL + * structure. Add the location of TLScontext to the SSL to later retrieve + * the information inside the tls_verify_certificate_callback(). + * + * If session caching was enabled when TLS was initialized, the cache type + * is stored in the client SSL context. + */ + TLScontext = tls_alloc_sess_context(log_mask, props->namaddr); + TLScontext->cache_type = app_ctx->cache_type; + TLScontext->level = props->tls_level; + + if ((TLScontext->con = SSL_new(app_ctx->ssl_ctx)) == NULL) { + msg_warn("Could not allocate 'TLScontext->con' with SSL_new()"); + tls_print_errors(); + tls_free_context(TLScontext); + return (0); + } + + /* + * Per session cipher selection for sessions with mandatory encryption + * + * The cipherlist is applied to the global SSL context, since it is likely + * to stay the same between connections, so we make use of a 1-element + * cache to return the same result for identical inputs. + */ + cipher_list = tls_set_ciphers(TLScontext, props->cipher_grade, + props->cipher_exclusions); + if (cipher_list == 0) { + /* already warned */ + tls_free_context(TLScontext); + return (0); + } + if (log_mask & TLS_LOG_VERBOSE) + msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list); + + TLScontext->stream = props->stream; + TLScontext->mdalg = props->mdalg; + + /* Alias DANE digest info from props */ + TLScontext->dane = props->dane; + + if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) { + msg_warn("Could not set application data for 'TLScontext->con'"); + tls_print_errors(); + tls_free_context(TLScontext); + return (0); + } +#define CARP_VERSION(which) do { \ + if (which##_proto != 0) \ + msg_warn("%s: error setting %simum TLS version to: 0x%04x", \ + TLScontext->namaddr, #which, which##_proto); \ + else \ + msg_warn("%s: error clearing %simum TLS version", \ + TLScontext->namaddr, #which); \ + } while (0) + + /* + * Apply session protocol restrictions. + */ + if (protomask != 0) + SSL_set_options(TLScontext->con, TLS_SSL_OP_PROTOMASK(protomask)); + if (!SSL_set_min_proto_version(TLScontext->con, min_proto)) + CARP_VERSION(min); + if (!SSL_set_max_proto_version(TLScontext->con, max_proto)) + CARP_VERSION(max); + + /* + * When applicable, configure DNS-based or synthetic (fingerprint or + * local trust anchor) DANE authentication, enable an appropriate SNI + * name and peer name matching. + * + * NOTE, this can change the effective security level, and needs to happen + * early. + */ + if (!tls_auth_enable(TLScontext, props)) { + tls_free_context(TLScontext); + return (0); + } + + /* + * Try to convey the configured TLSA records for this connection to the + * OpenSSL library. If none are "usable", we'll fall back to "encrypt" + * when authentication is not mandatory, otherwise we must arrange to + * ensure authentication failure. + */ + if (TLScontext->dane && TLScontext->dane->tlsa) { + int usable = tls_dane_enable(TLScontext); + int must_fail = usable <= 0; + + if (usable == 0) { + switch (TLScontext->level) { + case TLS_LEV_HALF_DANE: + case TLS_LEV_DANE: + msg_warn("%s: all TLSA records unusable, fallback to " + "unauthenticated TLS", TLScontext->namaddr); + must_fail = 0; + TLScontext->level = TLS_LEV_ENCRYPT; + break; + + case TLS_LEV_FPRINT: + msg_warn("%s: all fingerprints unusable", TLScontext->namaddr); + break; + case TLS_LEV_DANE_ONLY: + msg_warn("%s: all TLSA records unusable", TLScontext->namaddr); + break; + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + msg_warn("%s: all trust anchors unusable", TLScontext->namaddr); + break; + } + } + TLScontext->must_fail |= must_fail; + } + + /* + * We compute the policy digest after we compute the SNI name in + * tls_auth_enable() and possibly update the TLScontext security level. + * + * OpenSSL will ignore cached sessions that use the wrong protocol. So we do + * not need to filter out cached sessions with the "wrong" protocol, + * rather OpenSSL will simply negotiate a new session. + * + * We salt the session lookup key with the protocol list, so that sessions + * found in the cache are plausibly acceptable. + * + * By the time a TLS client is negotiating ciphers it has already offered to + * re-use a session, it is too late to renege on the offer. So we must + * not attempt to re-use sessions whose ciphers are too weak. We salt the + * session lookup key with the cipher list, so that sessions found in the + * cache are always acceptable. + * + * With DANE, (more generally any TLScontext where we specified explicit + * trust-anchor or end-entity certificates) the verification status of + * the SSL session depends on the specified list. Since we verify the + * certificate only during the initial handshake, we must segregate + * sessions with different TA lists. Note, that TA re-verification is + * not possible with cached sessions, since these don't hold the complete + * peer trust chain. Therefore, we compute a digest of the sorted TA + * parameters and append it to the serverid. + */ + TLScontext->serverid = + tls_serverid_digest(TLScontext, props, cipher_list); + + /* + * When authenticating the peer, use 80-bit plus OpenSSL security level + * + * XXX: We should perhaps use security level 1 also for mandatory + * encryption, with only "may" tolerating weaker algorithms. But that + * could mean no TLS 1.0 with OpenSSL >= 3.0 and encrypt, unless I get my + * patch in on time to conditionally re-enable SHA1 at security level 1, + * and we add code to make it so. + * + * That said, with "encrypt", we could reasonably require TLS 1.2? + */ + if (TLS_MUST_MATCH(TLScontext->level)) + SSL_set_security_level(TLScontext->con, 1); + + /* + * XXX To avoid memory leaks we must always call SSL_SESSION_free() after + * calling SSL_set_session(), regardless of whether or not the session + * will be reused. + */ + if (TLScontext->cache_type) { + session = load_clnt_session(TLScontext); + if (session) { + SSL_set_session(TLScontext->con, session); + SSL_SESSION_free(session); /* 200411 */ + } + } + + /* + * Before really starting anything, try to seed the PRNG a little bit + * more. + */ + tls_int_seed(); + (void) tls_ext_seed(var_tls_daemon_rand_bytes); + + /* + * Connect the SSL connection with the network socket. + */ + if (SSL_set_fd(TLScontext->con, props->stream == 0 ? props->fd : + vstream_fileno(props->stream)) != 1) { + msg_info("SSL_set_fd error to %s", props->namaddr); + tls_print_errors(); + uncache_session(app_ctx->ssl_ctx, TLScontext); + tls_free_context(TLScontext); + return (0); + } + + /* + * If the debug level selected is high enough, all of the data is dumped: + * TLS_LOG_TLSPKTS will dump the SSL negotiation, TLS_LOG_ALLPKTS will + * dump everything. + * + * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called? + * Well there is a BIO below the SSL routines that is automatically + * created for us, so we can use it for debugging purposes. + */ + if (log_mask & TLS_LOG_TLSPKTS) + tls_set_bio_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb); + + /* + * If we don't trigger the handshake in the library, leave control over + * SSL_connect/read/write/etc with the application. + */ + if (props->stream == 0) + return (TLScontext); + + /* + * Turn on non-blocking I/O so that we can enforce timeouts on network + * I/O. + */ + non_blocking(vstream_fileno(props->stream), NON_BLOCKING); + + /* + * Start TLS negotiations. This process is a black box that invokes our + * call-backs for certificate verification. + * + * Error handling: If the SSL handshake fails, we print out an error message + * and remove all TLS state concerning this session. + */ + sts = tls_bio_connect(vstream_fileno(props->stream), props->timeout, + TLScontext); + if (sts <= 0) { + if (ERR_peek_error() != 0) { + msg_info("SSL_connect error to %s: %d", props->namaddr, sts); + tls_print_errors(); + } else if (errno != 0) { + msg_info("SSL_connect error to %s: %m", props->namaddr); + } else { + msg_info("SSL_connect error to %s: lost connection", + props->namaddr); + } + uncache_session(app_ctx->ssl_ctx, TLScontext); + tls_free_context(TLScontext); + return (0); + } + return (tls_client_post_connect(TLScontext, props)); +} + +/* tls_client_post_connect - post-handshake processing */ + +TLS_SESS_STATE *tls_client_post_connect(TLS_SESS_STATE *TLScontext, + const TLS_CLIENT_START_PROPS *props) +{ + const SSL_CIPHER *cipher; + X509 *peercert; + + /* Turn off packet dump if only dumping the handshake */ + if ((TLScontext->log_mask & TLS_LOG_ALLPKTS) == 0) + tls_set_bio_callback(SSL_get_rbio(TLScontext->con), 0); + + /* + * The caller may want to know if this session was reused or if a new + * session was negotiated. + */ + TLScontext->session_reused = SSL_session_reused(TLScontext->con); + if ((TLScontext->log_mask & TLS_LOG_CACHE) && TLScontext->session_reused) + msg_info("%s: Reusing old session", TLScontext->namaddr); + + /* + * Do peername verification if requested and extract useful information + * from the certificate for later use. + */ + if ((peercert = TLS_PEEK_PEER_CERT(TLScontext->con)) != 0) { + TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT; + + /* + * Peer name or fingerprint verification as requested. + * Unconditionally set peer_CN, issuer_CN and peer_cert_fprint. Check + * fingerprint first, and avoid logging verified as untrusted in the + * call to verify_extract_name(). + */ + TLScontext->peer_cert_fprint = tls_cert_fprint(peercert, props->mdalg); + TLScontext->peer_pkey_fprint = tls_pkey_fprint(peercert, props->mdalg); + verify_extract_name(TLScontext, peercert, props); + + if (TLScontext->log_mask & + (TLS_LOG_CERTMATCH | TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) + msg_info("%s: subject_CN=%s, issuer_CN=%s, " + "fingerprint=%s, pkey_fingerprint=%s", props->namaddr, + TLScontext->peer_CN, TLScontext->issuer_CN, + TLScontext->peer_cert_fprint, + TLScontext->peer_pkey_fprint); + } else { + TLScontext->issuer_CN = mystrdup(""); + TLScontext->peer_CN = mystrdup(""); + TLScontext->peer_cert_fprint = mystrdup(""); + TLScontext->peer_pkey_fprint = mystrdup(""); + } + + /* + * Finally, collect information about protocol and cipher for logging + */ + TLScontext->protocol = SSL_get_version(TLScontext->con); + cipher = SSL_get_current_cipher(TLScontext->con); + TLScontext->cipher_name = SSL_CIPHER_get_name(cipher); + TLScontext->cipher_usebits = SSL_CIPHER_get_bits(cipher, + &(TLScontext->cipher_algbits)); + + /* + * The TLS engine is active. Switch to the tls_timed_read/write() + * functions and make the TLScontext available to those functions. + */ + if (TLScontext->stream != 0) + tls_stream_start(props->stream, TLScontext); + + /* + * With the handshake done, extract TLS 1.3 signature metadata. + */ + tls_get_signature_params(TLScontext); + + if (TLScontext->log_mask & TLS_LOG_SUMMARY) + tls_log_summary(TLS_ROLE_CLIENT, TLS_USAGE_NEW, TLScontext); + + tls_int_seed(); + + return (TLScontext); +} + +#endif /* USE_TLS */ diff --git a/src/tls/tls_dane.c b/src/tls/tls_dane.c new file mode 100644 index 0000000..a2b9b80 --- /dev/null +++ b/src/tls/tls_dane.c @@ -0,0 +1,1357 @@ +/*++ +/* NAME +/* tls_dane 3 +/* SUMMARY +/* Support for RFC 6698, 7671, 7672 (DANE) certificate matching +/* SYNOPSIS +/* #include +/* +/* void tls_dane_loglevel(log_param, log_level); +/* const char *log_param; +/* const char *log_level; +/* +/* int tls_dane_avail() +/* +/* void tls_dane_flush() +/* +/* TLS_DANE *tls_dane_alloc() +/* +/* void tls_tlsa_free(tlsa) +/* TLS_TLSA *tlsa; +/* +/* void tls_dane_free(dane) +/* TLS_DANE *dane; +/* +/* void tls_dane_add_fpt_digests(dane, digest, delim, smtp_mode) +/* TLS_DANE *dane; +/* const char *digest; +/* const char *delim; +/* int smtp_mode; +/* +/* TLS_TLSA *tlsa_prepend(tlsa, usage, selector, mtype, data, len) +/* TLS_TLSA *tlsa; +/* uint8_t usage; +/* uint8_t selector; +/* uint8_t mtype; +/* const unsigned char *data; +/* uint16_t length; +/* +/* int tls_dane_load_trustfile(dane, tafile) +/* TLS_DANE *dane; +/* const char *tafile; +/* +/* TLS_DANE *tls_dane_resolve(port, proto, hostrr, forcetlsa) +/* unsigned port; +/* const char *proto; +/* DNS_RR *hostrr; +/* int forcetlsa; +/* +/* void tls_dane_digest_init(ctx, fpt_alg) +/* SSL_CTX *ctx; +/* const EVP_MD *fpt_alg; +/* +/* void tls_dane_enable(TLScontext) +/* TLS_SESS_STATE *TLScontext; +/* +/* void tls_dane_log(TLScontext) +/* TLS_SESS_STATE *TLScontext; +/* +/* int tls_dane_unusable(dane) +/* const TLS_DANE *dane; +/* +/* int tls_dane_notfound(dane) +/* const TLS_DANE *dane; +/* DESCRIPTION +/* tls_dane_loglevel() allows the policy lookup functions in the DANE +/* library to examine the application's TLS loglevel in and possibly +/* produce a more detailed activity log. +/* +/* tls_dane_avail() returns true if the features required to support DANE +/* are present in libresolv. +/* +/* tls_dane_flush() flushes all entries from the cache, and deletes +/* the cache. +/* +/* tls_dane_alloc() returns a pointer to a newly allocated TLS_DANE +/* structure with null ta and ee digest sublists. +/* +/* tls_tlsa_free() frees a TLSA record linked list. +/* +/* tls_dane_free() frees the structure allocated by tls_dane_alloc(). +/* +/* tls_dane_digest_init() configures OpenSSL to support the configured +/* DANE TLSA digests and private-use fingerprint digest. +/* +/* tlsa_prepend() prepends a TLSA record to the head of a linked list +/* which may be null when the list is empty. The result value is the +/* new list head. +/* +/* tls_dane_add_fpt_digests() splits "digest" using the characters in +/* "delim" as delimiters and generates corresponding synthetic DANE TLSA +/* records with matching type 255 (private-use), which we associated with +/* the configured fingerprint digest algorithm. This is an incremental +/* interface, that builds a TLS_DANE structure outside the cache by +/* manually adding entries. +/* +/* tls_dane_load_trustfile() imports trust-anchor certificates and +/* public keys from a file (rather than DNS TLSA records). +/* +/* tls_dane_resolve() maps a (port, protocol, hostrr) tuple to a +/* corresponding TLS_DANE policy structure found in the DNS. The port +/* argument is in network byte order. A null pointer is returned when +/* the DNS query for the TLSA record tempfailed. In all other cases the +/* return value is a pointer to the corresponding TLS_DANE structure. +/* The caller must free the structure via tls_dane_free(). +/* +/* tls_dane_enable() enables DANE-style certificate checks for connections +/* that are configured with TLSA records. The TLSA records may be from +/* DNS (at the "dane", "dane-only" and "half-dane" security levels), or be +/* synthetic in support of either the "fingerprint" level or local trust +/* anchor based validation with the "secure" and "verify" levels. The +/* return value is the number of "usable" TLSA records loaded, or negative +/* if a record failed to load due to an internal OpenSSL problems, rather +/* than an issue with the record making that record "unusable". +/* +/* tls_dane_log() logs successful verification via DNS-based or +/* synthetic DANE TLSA RRs (fingerprint or "tafile"). +/* +/* tls_dane_unusable() checks whether a cached TLS_DANE record is +/* the result of a validated RRset, with no usable elements. In +/* this case, TLS is mandatory, but certificate verification is +/* not DANE-based. +/* +/* tls_dane_notfound() checks whether a cached TLS_DANE record is +/* the result of a validated DNS lookup returning NODATA. In +/* this case, TLS is not required by RFC, though users may elect +/* a mandatory TLS fallback policy. +/* +/* Arguments: +/* .IP ctx +/* SSL context to be configured with the chosen digest algorithms. +/* .IP fpt_alg +/* The OpenSSL EVP digest algorithm handle for the fingerprint digest. +/* .IP tlsa +/* TLSA record linked list head, initially NULL. +/* .IP usage +/* DANE TLSA certificate usage field. +/* .IP selector +/* DANE TLSA selector field. +/* .IP mtype +/* DANE TLSA matching type field +/* .IP data +/* DANE TLSA associated data field (raw binary form), copied for internal +/* use. The caller is responsible for freeing his own copy. +/* .IP length +/* Length of DANE TLSA associated DATA field. +/* .IP dane +/* Pointer to a TLS_DANE structure that lists the valid trust-anchor +/* and end-entity full-certificate and/or public-key digests. +/* .IP port +/* The TCP port in network byte order. +/* .IP proto +/* Almost certainly "tcp". +/* .IP hostrr +/* DNS_RR pointer to TLSA base domain data. +/* .IP forcetlsa +/* When true, TLSA lookups are performed even when the qname and rname +/* are insecure. This is only useful in the unlikely case that DLV is +/* used to secure the TLSA RRset in an otherwise insecure zone. +/* .IP log_param +/* The TLS log level parameter name whose value is the log_level argument. +/* .IP log_level +/* The application TLS log level, which may affect dane lookup verbosity. +/* .IP digest +/* The digest (or list of digests concatenated with characters from +/* "delim") to be added to the TLS_DANE record. +/* .IP delim +/* The set of delimiter characters used above. +/* .IP smtp_mode +/* Is the caller an SMTP client or an LMTP client? +/* .IP tafile; +/* A file with trust anchor certificates or public keys in PEM format. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Viktor Dukhovni +/*--*/ + +/* System library. */ + +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +#ifdef USE_TLS +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include /* event_time() */ +#include +#include +#include +#include +#include +#include + +#define STR(x) vstring_str(x) + +/* Global library */ + +#include + +/* DNS library. */ + +#include + +/* TLS library. */ + +#define TLS_INTERNAL +#include + +/* Application-specific. */ + +#undef DANE_TLSA_SUPPORT + +#if RES_USE_DNSSEC && RES_USE_EDNS0 +#define DANE_TLSA_SUPPORT +static int dane_tlsa_support = 1; + +#else +static int dane_tlsa_support = 0; + +#endif + +/* + * A NULL alg field disables the algorithm at the codepoint passed to the + * SSL_CTX_dane_mtype_set(3) function. The ordinals are used for digest + * agility, higher is "better" (presumed stronger). + */ +typedef struct dane_mtype { + const EVP_MD *alg; + uint8_t ord; +} dane_mtype; + +/* + * This is not intended to be a long-term cache of pre-parsed TLSA data, + * rather we primarily want to avoid fetching and parsing the TLSA records + * for a single multi-homed MX host more than once per delivery. Therefore, + * we keep the table reasonably small. + */ +#define CACHE_SIZE 20 +static CTABLE *dane_cache; + +static int log_mask; + +/* tls_dane_logmask - configure policy lookup logging */ + +void tls_dane_loglevel(const char *log_param, const char *log_level) +{ + log_mask = tls_log_mask(log_param, log_level); +} + +/* tls_dane_avail - check for availability of dane required digests */ + +int tls_dane_avail(void) +{ + return (dane_tlsa_support); +} + +/* tls_dane_alloc - allocate a TLS_DANE structure */ + +TLS_DANE *tls_dane_alloc(void) +{ + TLS_DANE *dane = (TLS_DANE *) mymalloc(sizeof(*dane)); + + dane->tlsa = 0; + dane->base_domain = 0; + dane->flags = 0; + dane->expires = 0; + dane->refs = 1; + return (dane); +} + +/* tls_tlsa_free - free a TLSA RR linked list */ + +void tls_tlsa_free(TLS_TLSA *tlsa) +{ + TLS_TLSA *next; + + for (; tlsa; tlsa = next) { + next = tlsa->next; + myfree(tlsa->data); + myfree(tlsa); + } +} + +/* tls_dane_free - free a TLS_DANE structure */ + +void tls_dane_free(TLS_DANE *dane) +{ + if (--dane->refs > 0) + return; + if (dane->base_domain) + myfree(dane->base_domain); + if (dane->tlsa) + tls_tlsa_free(dane->tlsa); + myfree((void *) dane); +} + +/* tlsa_prepend - Prepend internal-form TLSA record to the RRset linked list */ + +TLS_TLSA *tlsa_prepend(TLS_TLSA *tlsa, uint8_t usage, uint8_t selector, + uint8_t mtype, const unsigned char *data, + uint16_t data_len) +{ + TLS_TLSA *head; + + head = (TLS_TLSA *) mymalloc(sizeof(*head)); + head->usage = usage; + head->selector = selector; + head->mtype = mtype; + head->length = data_len; + head->data = (unsigned char *) mymemdup(data, data_len); + head->next = tlsa; + return (head); +} + +#define MAX_HEAD_BYTES 32 +#define MAX_TAIL_BYTES 32 +#define MAX_DUMP_BYTES (MAX_HEAD_BYTES + MAX_TAIL_BYTES) + +/* tlsa_info - log import of a particular TLSA record */ + +static void tlsa_info(const char *tag, const char *msg, + uint8_t u, uint8_t s, uint8_t m, + const unsigned char *data, ssize_t dlen) +{ + static VSTRING *top; + static VSTRING *bot; + + if (top == 0) + top = vstring_alloc(2 * MAX_HEAD_BYTES); + if (bot == 0) + bot = vstring_alloc(2 * MAX_TAIL_BYTES); + + if (dlen > MAX_DUMP_BYTES) { + hex_encode(top, (char *) data, MAX_HEAD_BYTES); + hex_encode(bot, (char *) data + dlen - MAX_TAIL_BYTES, MAX_TAIL_BYTES); + } else if (dlen > 0) { + hex_encode(top, (char *) data, dlen); + } else { + vstring_sprintf(top, "..."); + } + + msg_info("%s: %s: %u %u %u %s%s%s", tag, msg, u, s, m, STR(top), + dlen > MAX_DUMP_BYTES ? "..." : "", + dlen > MAX_DUMP_BYTES ? STR(bot) : ""); +} + +/* tlsa_carp - carp about a particular TLSA record */ + +static void tlsa_carp(const char *s1, const char *s2, const char *s3, + const char *s4, uint8_t u, uint8_t s, uint8_t m, + const unsigned char *data, ssize_t dlen) +{ + static VSTRING *top; + static VSTRING *bot; + + if (top == 0) + top = vstring_alloc(2 * MAX_HEAD_BYTES); + if (bot == 0) + bot = vstring_alloc(2 * MAX_TAIL_BYTES); + + if (dlen > MAX_DUMP_BYTES) { + hex_encode(top, (char *) data, MAX_HEAD_BYTES); + hex_encode(bot, (char *) data + dlen - MAX_TAIL_BYTES, MAX_TAIL_BYTES); + } else if (dlen > 0) { + hex_encode(top, (char *) data, dlen); + } else { + vstring_sprintf(top, "..."); + } + + msg_warn("%s%s%s %s: %u %u %u %s%s%s", s1, s2, s3, s4, u, s, m, STR(top), + dlen > MAX_DUMP_BYTES ? "..." : "", + dlen > MAX_DUMP_BYTES ? STR(bot) : ""); +} + +/* tls_dane_flush - flush the cache */ + +void tls_dane_flush(void) +{ + if (dane_cache) + ctable_free(dane_cache); + dane_cache = 0; +} + +/* dane_free - ctable style */ + +static void dane_free(void *dane, void *unused_context) +{ + tls_dane_free((TLS_DANE *) dane); +} + +/* tls_dane_add_fpt_digests - map fingerprint list to DANE TLSA RRset */ + +void tls_dane_add_fpt_digests(TLS_DANE *dane, const char *digest, + const char *delim, int smtp_mode) +{ + ARGV *values = argv_split(digest, delim); + ssize_t i; + + if (smtp_mode) { + if (warn_compat_break_smtp_tls_fpt_dgst) + msg_info("using backwards-compatible default setting " + VAR_SMTP_TLS_FPT_DGST "=md5 to compute certificate " + "fingerprints"); + } else { + if (warn_compat_break_lmtp_tls_fpt_dgst) + msg_info("using backwards-compatible default setting " + VAR_LMTP_TLS_FPT_DGST "=md5 to compute certificate " + "fingerprints"); + } + + for (i = 0; i < values->argc; ++i) { + const char *cp = values->argv[i]; + size_t ilen = strlen(cp); + VSTRING *raw; + + /* + * Decode optionally colon-separated hex-encoded string, the input + * value requires at most 3 bytes per byte of payload, which must not + * exceed the size of the widest supported hash function. + */ + if (ilen > 3 * EVP_MAX_MD_SIZE) { + msg_warn("malformed fingerprint value: %.100s...", + values->argv[i]); + continue; + } + raw = vstring_alloc(ilen / 2); + if (hex_decode_opt(raw, cp, ilen, HEX_DECODE_FLAG_ALLOW_COLON) == 0) { + myfree(raw); + msg_warn("malformed fingerprint value: %.384s", values->argv[i]); + continue; + } + + /* + * At the "fingerprint" security level certificate digests and public + * key digests are interchangeable. Each leaf certificate is matched + * via either the public key digest or full certificate digest. The + * DER encoding of a certificate is not a valid public key, and + * conversely, the DER encoding of a public key is not a valid + * certificate. An attacker would need a 2nd-preimage that is + * feasible across types (given cert digest == some pkey digest) and + * yet presumably difficult within a type (e.g. given cert digest == + * some other cert digest). No such attacks are known at this time, + * and it is expected that if any are found they would work within as + * well as across the cert/pkey data types. + * + * The private-use matching type "255" is mapped to the configured + * fingerprint digest, which may (harmlessly) coincide with one of + * the standard DANE digest algorithms. The private code point is + * however unconditionally enabled. + */ + if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE)) + tlsa_info("fingerprint", "digest as private-use TLSA record", + 3, 0, 255, (unsigned char *) STR(raw), VSTRING_LEN(raw)); + dane->tlsa = tlsa_prepend(dane->tlsa, 3, 0, 255, + (unsigned char *) STR(raw), VSTRING_LEN(raw)); + dane->tlsa = tlsa_prepend(dane->tlsa, 3, 1, 255, + (unsigned char *) STR(raw), VSTRING_LEN(raw)); + vstring_free(raw); + } + argv_free(values); +} + +/* parse_tlsa_rr - parse a validated TLSA RRset */ + +static int parse_tlsa_rr(TLS_DANE *dane, DNS_RR *rr) +{ + const uint8_t *ip; + uint8_t usage; + uint8_t selector; + uint8_t mtype; + ssize_t dlen; + unsigned const char *data; + int iscname = strcasecmp(rr->rname, rr->qname); + const char *q = iscname ? rr->qname : ""; + const char *a = iscname ? " -> " : ""; + const char *r = rr->rname; + + if (rr->type != T_TLSA) + msg_panic("%s%s%s: unexpected non-TLSA RR type: %u", + q, a, r, rr->type); + + /* Drop truncated records */ + if ((dlen = rr->data_len - 3) < 0) { + msg_warn("%s%s%s: truncated TLSA RR length == %u", + q, a, r, (unsigned) rr->data_len); + return (0); + } + ip = (const uint8_t *) rr->data; + usage = *ip++; + selector = *ip++; + mtype = *ip++; + data = (const unsigned char *) ip; + + /*- + * Drop unsupported usages. + * Note: NO SUPPORT for usages 0/1 which do not apply to SMTP. + */ + switch (usage) { + case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION: + case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE: + break; + default: + tlsa_carp(q, a, r, "unsupported TLSA certificate usage", + usage, selector, mtype, data, dlen); + return (0); + } + + /* + * Drop private-use matching type, reserved for fingerprint matching. + */ + if (mtype == 255) { + tlsa_carp(q, a, r, "reserved private-use matching type", + usage, selector, mtype, data, dlen); + return (0); + } + if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE)) + tlsa_info("DNSSEC-signed TLSA record", r, + usage, selector, mtype, data, dlen); + dane->tlsa = tlsa_prepend(dane->tlsa, usage, selector, mtype, data, dlen); + return (1); +} + +/* dane_lookup - TLSA record lookup, ctable style */ + +static void *dane_lookup(const char *tlsa_fqdn, void *unused_ctx) +{ + static VSTRING *why = 0; + DNS_RR *rrs = 0; + DNS_RR *rr; + TLS_DANE *dane = tls_dane_alloc(); + int ret; + + if (why == 0) + why = vstring_alloc(10); + + ret = dns_lookup(tlsa_fqdn, T_TLSA, RES_USE_DNSSEC, &rrs, 0, why); + + switch (ret) { + case DNS_OK: + if (TLS_DANE_CACHE_TTL_MIN && rrs->ttl < TLS_DANE_CACHE_TTL_MIN) + rrs->ttl = TLS_DANE_CACHE_TTL_MIN; + if (TLS_DANE_CACHE_TTL_MAX && rrs->ttl > TLS_DANE_CACHE_TTL_MAX) + rrs->ttl = TLS_DANE_CACHE_TTL_MAX; + + /* One more second to account for discrete time */ + dane->expires = 1 + event_time() + rrs->ttl; + + if (rrs->dnssec_valid) { + int n = 0; + + for (rr = rrs; rr != 0; rr = rr->next) + n += parse_tlsa_rr(dane, rr); + if (n == 0) + dane->flags |= TLS_DANE_FLAG_EMPTY; + } else + dane->flags |= TLS_DANE_FLAG_NORRS; + + if (rrs) + dns_rr_free(rrs); + break; + + case DNS_NOTFOUND: + dane->flags |= TLS_DANE_FLAG_NORRS; + dane->expires = 1 + event_time() + TLS_DANE_CACHE_TTL_MIN; + break; + + default: + msg_warn("DANE TLSA lookup problem: %s", STR(why)); + dane->flags |= TLS_DANE_FLAG_ERROR; + break; + } + + return (void *) dane; +} + +/* resolve_host - resolve TLSA RRs for hostname (rname or qname) */ + +static TLS_DANE *resolve_host(const char *host, const char *proto, + unsigned port) +{ + static VSTRING *query_domain; + TLS_DANE *dane; + + if (query_domain == 0) + query_domain = vstring_alloc(64); + + vstring_sprintf(query_domain, "_%u._%s.%s", ntohs(port), proto, host); + dane = (TLS_DANE *) ctable_locate(dane_cache, STR(query_domain)); + if (timecmp(event_time(), dane->expires) > 0) + dane = (TLS_DANE *) ctable_refresh(dane_cache, STR(query_domain)); + if (dane->base_domain == 0) + dane->base_domain = mystrdup(host); + /* Increment ref-count of cached entry */ + ++dane->refs; + return (dane); +} + +/* qname_secure - Lookup qname DNSSEC status */ + +static int qname_secure(const char *qname) +{ + static VSTRING *why; + int ret = 0; + DNS_RR *rrs; + + if (!why) + why = vstring_alloc(10); + + /* + * We assume that qname is already an fqdn, and does not need any + * suffixes from RES_DEFNAME or RES_DNSRCH. This is typically the name + * of an MX host, and must be a complete DNS name. DANE initialization + * code in the SMTP client is responsible for checking that the default + * resolver flags do not include RES_DEFNAME and RES_DNSRCH. + */ + ret = dns_lookup(qname, T_CNAME, RES_USE_DNSSEC, &rrs, 0, why); + if (ret == DNS_OK) { + ret = rrs->dnssec_valid; + dns_rr_free(rrs); + return (ret); + } + if (ret == DNS_NOTFOUND) + vstring_sprintf(why, "no longer a CNAME"); + msg_warn("DNSSEC status lookup error for %s: %s", qname, STR(why)); + return (-1); +} + +/* tls_dane_resolve - cached map: (name, proto, port) -> TLS_DANE */ + +TLS_DANE *tls_dane_resolve(unsigned port, const char *proto, DNS_RR *hostrr, + int forcetlsa) +{ + TLS_DANE *dane = 0; + int iscname = strcasecmp(hostrr->rname, hostrr->qname); + int isvalid = 1; + + if (!tls_dane_avail()) + return (0); /* Error */ + + /* + * By default suppress TLSA lookups for hosts in non-DNSSEC zones. If + * the host zone is not DNSSEC validated, the TLSA qname sub-domain is + * safely assumed to not be in a DNSSEC Look-aside Validation child zone. + */ + if (!forcetlsa && !hostrr->dnssec_valid) { + isvalid = iscname ? qname_secure(hostrr->qname) : 0; + if (isvalid < 0) + return (0); /* Error */ + } + if (!isvalid) { + dane = tls_dane_alloc(); + dane->flags = TLS_DANE_FLAG_NORRS; + } else { + if (!dane_cache) + dane_cache = ctable_create(CACHE_SIZE, dane_lookup, dane_free, 0); + + /* + * Try the rname first if secure, if nothing there, try the qname if + * different. Note, lookup errors are distinct from success with + * nothing found. If the rname lookup fails we don't try the qname. + */ + if (hostrr->dnssec_valid) { + dane = resolve_host(hostrr->rname, proto, port); + if (tls_dane_notfound(dane) && iscname) { + tls_dane_free(dane); + dane = 0; + } + } + if (!dane) + dane = resolve_host(hostrr->qname, proto, port); + if (dane->flags & TLS_DANE_FLAG_ERROR) { + /* We don't return this object. */ + tls_dane_free(dane); + dane = 0; + } + } + + return (dane); +} + +/* tls_dane_load_trustfile - load trust anchor certs or keys from file */ + +int tls_dane_load_trustfile(TLS_DANE *dane, const char *tafile) +{ + BIO *bp; + char *name = 0; + char *header = 0; + unsigned char *data = 0; + long len; + int tacount; + char *errtype = 0; /* if error: cert or pkey? */ + + /* nop */ + if (tafile == 0 || *tafile == 0) + return (1); + + /* + * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio, + * calls PEM_read_bio() and then frees the bio. It is just as easy to + * open a BIO as a stdio file, so we use BIOs and call PEM_read_bio() + * directly. + */ + if ((bp = BIO_new_file(tafile, "r")) == NULL) { + msg_warn("error opening trust anchor file: %s: %m", tafile); + return (0); + } + /* Don't report old news */ + ERR_clear_error(); + + /* + * OpenSSL implements DANE strictly, with DANE-TA(2) only matching issuer + * certificates, and never the leaf cert. We also allow the + * trust-anchors to directly match the leaf certificate or public key. + */ + for (tacount = 0; + errtype == 0 && PEM_read_bio(bp, &name, &header, &data, &len); + ++tacount) { + uint8_t daneta = DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION; + uint8_t daneee = DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE; + uint8_t mtype = DNS_TLSA_MATCHING_TYPE_NO_HASH_USED; + + if (strcmp(name, PEM_STRING_X509) == 0 + || strcmp(name, PEM_STRING_X509_OLD) == 0) { + uint8_t selector = DNS_TLSA_SELECTOR_FULL_CERTIFICATE; + + if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE)) + tlsa_info("TA cert as TLSA record", tafile, + daneta, selector, mtype, data, len); + dane->tlsa = + tlsa_prepend(dane->tlsa, daneta, selector, mtype, data, len); + dane->tlsa = + tlsa_prepend(dane->tlsa, daneee, selector, mtype, data, len); + } else if (strcmp(name, PEM_STRING_PUBLIC) == 0) { + uint8_t selector = DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO; + + if (log_mask & (TLS_LOG_VERBOSE | TLS_LOG_DANE)) + tlsa_info("TA pkey as TLSA record", tafile, + daneta, selector, mtype, data, len); + dane->tlsa = + tlsa_prepend(dane->tlsa, daneta, selector, mtype, data, len); + dane->tlsa = tlsa_prepend(dane->tlsa, daneee, selector, mtype, data, len); + } + + /* + * If any of these were null, PEM_read() would have failed. + */ + OPENSSL_free(name); + OPENSSL_free(header); + OPENSSL_free(data); + } + BIO_free(bp); + + if (errtype) { + tls_print_errors(); + msg_warn("error reading: %s: malformed trust-anchor %s", + tafile, errtype); + return (0); + } + if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) { + /* Reached end of PEM file */ + ERR_clear_error(); + return (tacount > 0); + } + /* Some other PEM read error */ + tls_print_errors(); + return (0); +} + +int tls_dane_enable(TLS_SESS_STATE *TLScontext) +{ + const TLS_DANE *dane = TLScontext->dane; + TLS_TLSA *tp; + SSL *ssl = TLScontext->con; + int usable = 0; + int ret; + + for (tp = dane->tlsa; tp != 0; tp = tp->next) { + ret = SSL_dane_tlsa_add(ssl, tp->usage, tp->selector, + tp->mtype, tp->data, tp->length); + if (ret > 0) { + ++usable; + continue; + } + if (ret == 0) { + tlsa_carp(TLScontext->namaddr, ":", "", "unusable TLSA RR", + tp->usage, tp->selector, tp->mtype, tp->data, + tp->length); + continue; + } + /* Internal problem in OpenSSL */ + tlsa_carp(TLScontext->namaddr, ":", "", "error loading trust settings", + tp->usage, tp->selector, tp->mtype, tp->data, tp->length); + tls_print_errors(); + return (-1); + } + return (usable); +} + +/* tls_dane_digest_init - configure supported DANE digests */ + +void tls_dane_digest_init(SSL_CTX *ctx, const EVP_MD *fpt_alg) +{ + dane_mtype mtypes[256]; + char *cp; + char *save; + char *algname; + uint8_t m; + uint8_t ord = 0; + uint8_t maxtype; + + memset((char *) mtypes, 0, sizeof(mtypes)); + + /* + * The DANE SHA2-256(1) and SHA2-512(2) algorithms are disabled, unless + * explicitly enabled. Other codepoints can be disabled explicitly by + * giving them an empty digest name, which also implicitly disables all + * smaller codepoints that are not explicitly assigned. + * + * We reserve the private-use code point (255) for use with fingerprint + * matching. It MUST NOT be accepted in DNS replies. + */ + mtypes[1].alg = NULL; + mtypes[2].alg = NULL; + mtypes[255].alg = fpt_alg; + maxtype = 2; + + save = cp = mystrdup(var_tls_dane_digests); + while ((algname = mystrtok(&cp, CHARS_COMMA_SP)) != 0) { + char *algcode = split_at(algname, '='); + int codepoint = -1; + + if (algcode && *algcode) { + unsigned long l; + char *endcp; + + /* + * XXX: safe_strtoul() does not flag empty or white-space only + * input. Since we get algcode by splitting white-space/comma + * delimited tokens, this is not a problem here. + */ + l = safe_strtoul(algcode, &endcp, 10); + if ((l == 0 && (errno == EINVAL || endcp == algcode)) + || l >= 255 || *endcp) { + msg_warn("Invalid matching type number in %s: %s=%s", + VAR_TLS_DANE_DIGESTS, algname, algcode); + continue; + } + if (l == 0 || l == 255) { + msg_warn("Reserved matching type number in %s: %s=%s", + VAR_TLS_DANE_DIGESTS, algname, algcode); + continue; + } + codepoint = l; + } + /* Disable any codepoint gaps */ + if (codepoint > maxtype) { + while (++maxtype < codepoint) + mtypes[codepoint].alg = NULL; + maxtype = codepoint; + } + /* Handle explicitly disabled codepoints */ + if (*algname == 0) { + /* Skip empty specifiers */ + if (codepoint < 0) + continue; + mtypes[codepoint].alg = NULL; + continue; + } + switch (codepoint) { + case -1: + if (strcasecmp(algname, LN_sha256) == 0) + codepoint = 1; /* SHA2-256(1) */ + else if (strcasecmp(algname, LN_sha512) == 0) + codepoint = 2; /* SHA2-512(2) */ + else { + msg_warn("%s: digest algorithm %s needs an explicit number", + VAR_TLS_DANE_DIGESTS, algname); + continue; + } + break; + case 1: + if (strcasecmp(algname, LN_sha256) != 0) { + msg_warn("%s: matching type 1 can only be %s", + VAR_TLS_DANE_DIGESTS, LN_sha256); + continue; + } + algname = LN_sha256; + break; + case 2: + if (strcasecmp(algname, LN_sha512) != 0) { + msg_warn("%s: matching type 2 can only be %s", + VAR_TLS_DANE_DIGESTS, LN_sha512); + continue; + } + algname = LN_sha512; + break; + default: + break; + } + + if (mtypes[codepoint].ord != 0) { + msg_warn("%s: matching type %d specified more than once", + VAR_TLS_DANE_DIGESTS, codepoint); + continue; + } + mtypes[codepoint].ord = ++ord; + + if ((mtypes[codepoint].alg = tls_digest_byname(algname, NULL)) == 0) { + msg_warn("%s: digest algorithm \"%s\"(%d) unknown", + VAR_TLS_DANE_DIGESTS, algname, codepoint); + continue; + } + } + myfree(save); + + for (m = 1; m != 0; m = m != maxtype ? m + 1 : 255) { + + /* + * In OpenSSL higher order ordinals are preferred, but we list the + * most preferred algorithms first, so the last ordinal becomes 1, + * next-to-last, 2, ... + * + * The ordinals of non-disabled algorithms are always positive, and the + * computed value cannot overflow 254 (the largest possible value of + * 'ord' after loading each valid codepoint at most once). + */ + if (SSL_CTX_dane_mtype_set(ctx, mtypes[m].alg, m, + ord - mtypes[m].ord + 1) <= 0) { + msg_warn("%s: error configuring matching type %d", + VAR_TLS_DANE_DIGESTS, m); + tls_print_errors(); + } + } +} + +/* tls_dane_log - log DANE-based verification success */ + +void tls_dane_log(TLS_SESS_STATE *TLScontext) +{ + static VSTRING *top; + static VSTRING *bot; + EVP_PKEY *mspki = 0; + int depth = SSL_get0_dane_authority(TLScontext->con, NULL, &mspki); + uint8_t u, s, m; + unsigned const char *data; + size_t dlen; + + if (depth < 0) + return; /* No DANE auth */ + + switch (TLScontext->level) { + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + msg_info("%s: Matched trust anchor at depth %d", + TLScontext->namaddr, depth); + return; + } + + if (top == 0) + top = vstring_alloc(2 * MAX_HEAD_BYTES); + if (bot == 0) + bot = vstring_alloc(2 * MAX_TAIL_BYTES); + + (void) SSL_get0_dane_tlsa(TLScontext->con, &u, &s, &m, &data, &dlen); + if (dlen > MAX_DUMP_BYTES) { + hex_encode(top, (char *) data, MAX_HEAD_BYTES); + hex_encode(bot, (char *) data + dlen - MAX_TAIL_BYTES, MAX_TAIL_BYTES); + } else { + hex_encode(top, (char *) data, dlen); + } + + switch (TLScontext->level) { + case TLS_LEV_FPRINT: + msg_info("%s: Matched fingerprint: %s%s%s", TLScontext->namaddr, + STR(top), dlen > MAX_DUMP_BYTES ? "..." : "", + dlen > MAX_DUMP_BYTES ? STR(bot) : ""); + return; + + default: + msg_info("%s: Matched DANE %s at depth %d: %u %u %u %s%s%s", + TLScontext->namaddr, mspki ? + "TA public key verified certificate" : depth ? + "TA certificate" : "EE certificate", depth, u, s, m, + STR(top), dlen > MAX_DUMP_BYTES ? "..." : "", + dlen > MAX_DUMP_BYTES ? STR(bot) : ""); + return; + } +} + +#ifdef TEST + +#include +#include + +#include +#include +#include + +static int verify_chain(SSL *ssl, x509_stack_t *chain, TLS_SESS_STATE *tctx) +{ + int ret; + X509 *cert; + X509_STORE_CTX *store_ctx; + SSL_CTX *ssl_ctx = SSL_get_SSL_CTX(ssl); + X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx); + int store_ctx_idx = SSL_get_ex_data_X509_STORE_CTX_idx(); + + cert = sk_X509_value(chain, 0); + if ((store_ctx = X509_STORE_CTX_new()) == NULL) { + SSLerr(SSL_F_SSL_VERIFY_CERT_CHAIN, ERR_R_MALLOC_FAILURE); + return 0; + } + if (!X509_STORE_CTX_init(store_ctx, store, cert, chain)) { + X509_STORE_CTX_free(store_ctx); + return 0; + } + X509_STORE_CTX_set_ex_data(store_ctx, store_ctx_idx, ssl); + + /* We're *verifying* a server chain */ + X509_STORE_CTX_set_default(store_ctx, "ssl_server"); + X509_VERIFY_PARAM_set1(X509_STORE_CTX_get0_param(store_ctx), + SSL_get0_param(ssl)); + X509_STORE_CTX_set0_dane(store_ctx, SSL_get0_dane(ssl)); + + ret = X509_verify_cert(store_ctx); + + SSL_set_verify_result(ssl, X509_STORE_CTX_get_error(store_ctx)); + X509_STORE_CTX_free(store_ctx); + + return (ret); +} + +static void load_tlsa_args(SSL *ssl, char *argv[]) +{ + const EVP_MD *md = 0; + X509 *cert = 0; + BIO *bp; + unsigned char *buf; + unsigned char *buf2; + int len; + uint8_t u = atoi(argv[1]); + uint8_t s = atoi(argv[2]); + uint8_t m = atoi(argv[3]); + EVP_PKEY *pkey; + + /* Unsupported usages are fatal */ + switch (u) { + case DNS_TLSA_USAGE_TRUST_ANCHOR_ASSERTION: + case DNS_TLSA_USAGE_DOMAIN_ISSUED_CERTIFICATE: + break; + default: + msg_fatal("unsupported certificate usage %u", u); + } + + /* Unsupported selectors are fatal */ + switch (s) { + case DNS_TLSA_SELECTOR_FULL_CERTIFICATE: + case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO: + break; + default: + msg_fatal("unsupported selector %u", s); + } + + /* Unsupported selectors are fatal */ + switch (m) { + case DNS_TLSA_MATCHING_TYPE_NO_HASH_USED: + case DNS_TLSA_MATCHING_TYPE_SHA256: + case DNS_TLSA_MATCHING_TYPE_SHA512: + break; + default: + msg_fatal("unsupported matching type %u", m); + } + + if ((bp = BIO_new_file(argv[4], "r")) == NULL) + msg_fatal("error opening %s: %m", argv[4]); + if (!PEM_read_bio_X509(bp, &cert, 0, 0)) { + tls_print_errors(); + msg_fatal("error loading certificate from %s: %m", argv[4]); + } + BIO_free(bp); + + /* + * Extract ASN.1 DER form of certificate or public key. + */ + switch (s) { + case DNS_TLSA_SELECTOR_FULL_CERTIFICATE: + len = i2d_X509(cert, NULL); + if (len > 0xffff) + msg_fatal("certificate too long: %d", len); + buf2 = buf = (unsigned char *) mymalloc(len); + i2d_X509(cert, &buf2); + break; + case DNS_TLSA_SELECTOR_SUBJECTPUBLICKEYINFO: + pkey = X509_get_pubkey(cert); + len = i2d_PUBKEY(pkey, NULL); + if (len > 0xffff) + msg_fatal("public key too long: %d", len); + buf2 = buf = (unsigned char *) mymalloc(len); + i2d_PUBKEY(pkey, &buf2); + EVP_PKEY_free(pkey); + break; + } + X509_free(cert); + OPENSSL_assert(buf2 - buf == len); + + switch (m) { + case 0: + break; + case 1: + if ((md = tls_digest_byname(LN_sha256, NULL)) == 0) + msg_fatal("Digest %s not found", LN_sha256); + break; + case 2: + if ((md = tls_digest_byname(LN_sha512, NULL)) == 0) + msg_fatal("Digest %s not found", LN_sha512); + break; + default: + msg_fatal("Unsupported DANE mtype: %d", m); + } + + if (md != 0) { + unsigned char mdbuf[EVP_MAX_MD_SIZE]; + unsigned int mdlen = sizeof(mdbuf); + + if (!EVP_Digest(buf, len, mdbuf, &mdlen, md, 0)) + msg_fatal("Digest failure for mtype: %d", m); + myfree(buf); + buf = (unsigned char *) mymemdup(mdbuf, len = mdlen); + } + SSL_dane_tlsa_add(ssl, u, s, m, buf, len); + myfree((void *) buf); +} + +static x509_stack_t *load_chain(const char *chainfile) +{ + BIO *bp; + char *name = 0; + char *header = 0; + unsigned char *data = 0; + long len; + int count; + char *errtype = 0; /* if error: cert or pkey? */ + x509_stack_t *chain; + typedef X509 *(*d2i_X509_t) (X509 **, const unsigned char **, long); + + if ((chain = sk_X509_new_null()) == 0) { + perror("malloc"); + exit(1); + } + + /* + * On each call, PEM_read() wraps a stdio file in a BIO_NOCLOSE bio, + * calls PEM_read_bio() and then frees the bio. It is just as easy to + * open a BIO as a stdio file, so we use BIOs and call PEM_read_bio() + * directly. + */ + if ((bp = BIO_new_file(chainfile, "r")) == NULL) { + fprintf(stderr, "error opening chainfile: %s: %m\n", chainfile); + exit(1); + } + /* Don't report old news */ + ERR_clear_error(); + + for (count = 0; + errtype == 0 && PEM_read_bio(bp, &name, &header, &data, &len); + ++count) { + const unsigned char *p = data; + + if (strcmp(name, PEM_STRING_X509) == 0 + || strcmp(name, PEM_STRING_X509_TRUSTED) == 0 + || strcmp(name, PEM_STRING_X509_OLD) == 0) { + d2i_X509_t d; + X509 *cert; + + d = strcmp(name, PEM_STRING_X509_TRUSTED) ? d2i_X509_AUX : d2i_X509; + if ((cert = d(0, &p, len)) == 0 || (p - data) != len) + errtype = "certificate"; + else if (sk_X509_push(chain, cert) == 0) { + perror("malloc"); + exit(1); + } + } else { + fprintf(stderr, "unexpected chain file object: %s\n", name); + exit(1); + } + + /* + * If any of these were null, PEM_read() would have failed. + */ + OPENSSL_free(name); + OPENSSL_free(header); + OPENSSL_free(data); + } + BIO_free(bp); + + if (errtype) { + tls_print_errors(); + fprintf(stderr, "error reading: %s: malformed %s", chainfile, errtype); + exit(1); + } + if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) { + /* Reached end of PEM file */ + ERR_clear_error(); + if (count > 0) + return chain; + fprintf(stderr, "no certificates found in: %s\n", chainfile); + exit(1); + } + /* Some other PEM read error */ + tls_print_errors(); + fprintf(stderr, "error reading: %s\n", chainfile); + exit(1); +} + +static void usage(const char *progname) +{ + fprintf(stderr, "Usage: %s certificate-usage selector matching-type" + " certfile \\\n\t\tCAfile chainfile hostname [certname ...]\n", + progname); + fprintf(stderr, " where, certificate-usage = TLSA certificate usage,\n"); + fprintf(stderr, "\t selector = TLSA selector,\n"); + fprintf(stderr, "\t matching-type = empty string or OpenSSL digest algorithm name,\n"); + fprintf(stderr, "\t PEM certfile provides certificate association data,\n"); + fprintf(stderr, "\t PEM CAfile contains any usage 0/1 trusted roots,\n"); + fprintf(stderr, "\t PEM chainfile = server chain file to verify\n"); + fprintf(stderr, "\t hostname = destination hostname,\n"); + fprintf(stderr, "\t each certname augments the hostname for name checks.\n"); + exit(1); +} + +static SSL_CTX *ctx_init(const char *CAfile) +{ + SSL_CTX *client_ctx; + + tls_param_init(); + tls_check_version(); + + if (!tls_validate_digest(LN_sha1)) + msg_fatal("%s digest algorithm not available", LN_sha1); + + if (TLScontext_index < 0) + if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0) + msg_fatal("Cannot allocate SSL application data index"); + + ERR_clear_error(); + if ((client_ctx = SSL_CTX_new(TLS_client_method())) == 0) + msg_fatal("cannot allocate client SSL_CTX"); + SSL_CTX_set_verify_depth(client_ctx, 5); + + /* Enable DANE support in OpenSSL */ + if (SSL_CTX_dane_enable(client_ctx) <= 0) { + tls_print_errors(); + msg_fatal("OpenSSL DANE initialization failed"); + } + if (tls_set_ca_certificate_info(client_ctx, CAfile, "") < 0) { + tls_print_errors(); + msg_fatal("cannot load CAfile: %s", CAfile); + } + SSL_CTX_set_verify(client_ctx, SSL_VERIFY_NONE, + tls_verify_certificate_callback); + return (client_ctx); +} + +int main(int argc, char *argv[]) +{ + SSL_CTX *ssl_ctx; + const EVP_MD *fpt_alg; + TLS_SESS_STATE *tctx; + x509_stack_t *chain; + int i; + + var_procname = mystrdup(basename(argv[0])); + set_mail_conf_str(VAR_PROCNAME, var_procname); + msg_vstream_init(var_procname, VSTREAM_OUT); + + if (argc < 8) + usage(argv[0]); + + ssl_ctx = ctx_init(argv[5]); + if (!tls_dane_avail()) + msg_fatal("DANE TLSA support not available"); + + tctx = tls_alloc_sess_context(TLS_LOG_NONE, argv[7]); + tctx->namaddr = argv[7]; + tctx->mdalg = LN_sha256; + tctx->dane = tls_dane_alloc(); + + if ((fpt_alg = tls_validate_digest(tctx->mdalg)) == 0) + msg_fatal("fingerprint digest algorithm %s not found", + tctx->mdalg); + tls_dane_digest_init(ssl_ctx, fpt_alg); + + if ((tctx->con = SSL_new(ssl_ctx)) == 0 + || !SSL_set_ex_data(tctx->con, TLScontext_index, tctx)) { + tls_print_errors(); + msg_fatal("Error allocating SSL connection"); + } + if (SSL_dane_enable(tctx->con, 0) <= 0) { + tls_print_errors(); + msg_fatal("Error enabling DANE for SSL handle"); + } + SSL_dane_set_flags(tctx->con, DANE_FLAG_NO_DANE_EE_NAMECHECKS); + SSL_dane_set_flags(tctx->con, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + for (i = 7; i < argc; ++i) + if (!SSL_add1_host(tctx->con, argv[i])) + msg_fatal("error adding hostname: %s", argv[i]); + load_tlsa_args(tctx->con, argv); + SSL_set_connect_state(tctx->con); + + /* Verify saved server chain */ + chain = load_chain(argv[6]); + i = verify_chain(tctx->con, chain, tctx); + tls_print_errors(); + + if (i > 0) { + const char *peername = SSL_get0_peername(tctx->con); + + if (peername == 0) + peername = argv[7]; + msg_info("Verified %s", peername); + } else { + i = SSL_get_verify_result(tctx->con); + msg_info("certificate verification failed for %s:%s: num=%d:%s", + argv[6], argv[7], i, X509_verify_cert_error_string(i)); + } + + return (i <= 0); +} + +#endif /* TEST */ + +#endif /* USE_TLS */ diff --git a/src/tls/tls_dane.sh b/src/tls/tls_dane.sh new file mode 100644 index 0000000..a950857 --- /dev/null +++ b/src/tls/tls_dane.sh @@ -0,0 +1,211 @@ +#! /bin/bash + +set -e + +DOMAIN=example.com +HOST=mail.${DOMAIN} +TEST=./tls_dane + +key() { + local key=$1; shift + + if [ ! -f "${key}.pem" ]; then + openssl genpkey 2>/dev/null \ + -paramfile <(openssl ecparam -name prime256v1) \ + -out "${key}.pem" + fi +} + +req() { + local key=$1; shift + local cn=$1; shift + + key "$key" + openssl req -new -sha256 -key "${key}.pem" 2>/dev/null \ + -config <(printf "[req]\n%s\n%s\n[dn]\nCN=%s\n" \ + "prompt = no" "distinguished_name = dn" "${cn}") +} + +req_nocn() { + local key=$1; shift + + key "$key" + openssl req -new -sha256 -subj / -key "${key}.pem" 2>/dev/null \ + -config <(printf "[req]\n%s\n[dn]\nCN_default =\n" \ + "distinguished_name = dn") +} + +cert() { + local cert=$1; shift + local exts=$1; shift + + openssl x509 -req -sha256 -out "${cert}.pem" 2>/dev/null \ + -extfile <(printf "%s\n" "$exts") "$@" +} + +genroot() { + local cn=$1; shift + local key=$1; shift + local cert=$1; shift + local skid=$1; shift + local akid=$1; shift + + exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = CA:true") + req "$key" "$cn" | + cert "$cert" "$exts" -signkey "${key}.pem" -set_serial 1 -days 30 +} + +genca() { + local cn=$1; shift + local key=$1; shift + local cert=$1; shift + local skid=$1; shift + local akid=$1; shift + local ca=$1; shift + local cakey=$1; shift + + exts=$(printf "%s\n%s\n%s\n" "$skid" "$akid" "basicConstraints = CA:true") + req "$key" "$cn" | + cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \ + -set_serial 2 -days 30 "$@" +} + +genee() { + local cn=$1; shift + local key=$1; shift + local cert=$1; shift + local ca=$1; shift + local cakey=$1; shift + + exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \ + "subjectKeyIdentifier = hash" \ + "authorityKeyIdentifier = keyid, issuer" \ + "basicConstraints = CA:false" \ + "extendedKeyUsage = serverAuth" \ + "subjectAltName = @alts" "DNS=${cn}") + req "$key" "$cn" | + cert "$cert" "$exts" -CA "${ca}.pem" -CAkey "${cakey}.pem" \ + -set_serial 2 -days 30 "$@" +} + +genss() { + local cn=$1; shift + local key=$1; shift + local cert=$1; shift + + exts=$(printf "%s\n%s\n%s\n%s\n%s\n[alts]\n%s\n" \ + "subjectKeyIdentifier = hash" \ + "authorityKeyIdentifier = keyid, issuer" \ + "basicConstraints = CA:true" \ + "extendedKeyUsage = serverAuth" \ + "subjectAltName = @alts" "DNS=${cn}") + req "$key" "$cn" | + cert "$cert" "$exts" -set_serial 1 -days 30 -signkey "${key}.pem" "$@" +} + +gennocn() { + local key=$1; shift + local cert=$1; shift + + req_nocn "$key" | + cert "$cert" "" -signkey "${key}.pem" -set_serial 1 -days -1 "$@" +} + +runtest() { + local desc=$1; shift + local usage=$1; shift + local selector=$1; shift + local mtype=$1; shift + local tlsa=$1; shift + local ca=$1; shift + local chain=$1; shift + local digest + + case $mtype in + 0) digest="";; + 1) digest=sha256;; + 2) digest=sha512;; + *) echo "bad mtype: $mtype"; exit 1;; + esac + + printf "%d %d %d %-24s %s: " "$usage" "$selector" "$mtype" "$tlsa" "$desc" + + if [ -n "$ca" ]; then ca="$ca.pem"; fi + "$TEST" "$usage" "$selector" "$digest" "$tlsa.pem" "$ca" "$chain.pem" \ + "$@" > /dev/null +} + +checkpass() { runtest "$@" && { echo pass; } || { echo fail; exit 1; }; } +checkfail() { runtest "$@" && { echo fail; exit 1; } || { echo pass; }; } + +#--------- + +genss "$HOST" sskey sscert +gennocn akey acert + +# Tests that might depend on akid/skid chaining +# +for rakid in "" \ + "authorityKeyIdentifier = keyid,issuer" \ + "authorityKeyIdentifier = issuer" \ + "authorityKeyIdentifier = keyid" +do +for cakid in "" \ + "authorityKeyIdentifier = keyid,issuer" \ + "authorityKeyIdentifier = issuer" \ + "authorityKeyIdentifier = keyid" +do +for rskid in "" "subjectKeyIdentifier = hash" +do +for caskid in "" "subjectKeyIdentifier = hash" +do + +genroot "Root CA" rootkey rootcert "$rskid" "$rakid" +genca "CA 1" cakey1 cacert1 "$caskid" "$cakid" rootcert rootkey +genca "CA 2" cakey2 cacert2 "$caskid" "$cakid" cacert1 cakey1 +genee "$HOST" eekey eecert cacert2 cakey2 +cat eecert.pem cacert2.pem cacert1.pem rootcert.pem > chain.pem +cat eecert.pem cacert2.pem cacert1.pem > chain1.pem + +for s in 0 1 +do + checkpass "OOB root TA" 2 "$s" 0 rootcert "" chain1 "$HOST" + checkpass "OOB TA" 2 "$s" 0 cacert2 "" eecert "$HOST" + checkpass "in-chain root TA" 2 "$s" 1 rootcert "" chain "$HOST" + + for m in 0 1 2 + do + checkpass "valid TA" 2 "$s" "$m" rootcert "" chain "$HOST" + for ca in "cacert1" "cacert2"; do + checkpass "valid TA" 2 "$s" "$m" "$ca" "" chain "$HOST" + checkpass "valid TA" 2 "$s" "$m" "$ca" "" chain1 "$HOST" + checkpass "valid TA+CA" 2 "$s" "$m" "$ca" rootcert chain1 "$HOST" + checkpass "sub-domain" 2 "$s" "$m" "$ca" "" chain1 whatever ".$DOMAIN" + checkfail "wrong name" 2 "$s" "$m" "$ca" "" chain1 "whatever" + done + done +done + +done +done +done +done + +# These tests don't depend in the akid/skid chaining: +# +for s in 0 1 +do + checkfail "missing TA" 2 "$s" 1 rootcert "" chain1 "$HOST" + for m in 0 1 2 + do + checkpass "depth 0 TA" 2 "$s" "$m" sscert "" sscert "$HOST" + checkfail "non-TA" 2 "$s" "$m" eecert rootcert chain "$HOST" + checkfail "depth 0 TA namecheck" 2 "$s" "$m" sscert sscert sscert whatever + + checkpass "valid EE" 3 "$s" "$m" eecert "" chain whatever + checkpass "key-only EE" 3 "$s" "$m" acert "" acert whatever + checkfail "wrong EE" 3 "$s" "$m" cacert2 "" chain whatever + done +done + +rm -f *.pem diff --git a/src/tls/tls_dh.c b/src/tls/tls_dh.c new file mode 100644 index 0000000..9a6bd67 --- /dev/null +++ b/src/tls/tls_dh.c @@ -0,0 +1,384 @@ +/*++ +/* NAME +/* tls_dh +/* SUMMARY +/* Diffie-Hellman parameter support +/* SYNOPSIS +/* #define TLS_INTERNAL +/* #include +/* +/* void tls_set_dh_from_file(path) +/* const char *path; +/* +/* void tls_auto_eecdh_curves(ctx, configured) +/* SSL_CTX *ctx; +/* char *configured; +/* +/* void tls_tmp_dh(ctx, useauto) +/* SSL_CTX *ctx; +/* int useauto; +/* DESCRIPTION +/* This module maintains parameters for Diffie-Hellman key generation. +/* +/* tls_tmp_dh() returns the configured or compiled-in FFDHE +/* group parameters. +/* +/* tls_set_dh_from_file() overrides compiled-in DH parameters +/* with those specified in the named files. The file format +/* is as expected by the PEM_read_DHparams() routine. +/* +/* tls_auto_eecdh_curves() enables negotiation of the most preferred curve +/* among the curves specified by the "configured" argument. The useauto +/* argument enables OpenSSL-builtin group selection in preference to our +/* own compiled-in group. This may interoperate better with overly strict +/* peers that accept only "standard" groups (bogus threat model). +/* DIAGNOSTICS +/* In case of error, tls_set_dh_from_file() logs a warning and +/* ignores the request. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Originally written by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +#ifdef USE_TLS +#include + +/* Utility library. */ + +#include +#include +#include + + /* + * Global library + */ +#include + +/* TLS library. */ + +#define TLS_INTERNAL +#include +#include +#ifndef OPENSSL_NO_ECDH +#include +#endif +#if OPENSSL_VERSION_PREREQ(3,0) +#include +#endif + +/* Application-specific. */ + + /* + * Compiled-in FFDHE (finite-field ephemeral Diffie-Hellman) parameters. + * Used when no parameters are explicitly loaded from a site-specific file. + * + * With OpenSSL 3.0 and later when no explicit parameter file is specified by + * the administrator (or the setting is "auto"), we delegate group selection + * to OpenSSL via SSL_CTX_set_dh_auto(3). + * + * Using an ASN.1 DER encoding avoids the need to explicitly manipulate the + * internal representation of DH parameter objects. + * + * The FFDHE group is now 2048-bit, as 1024 bits is increasingly considered to + * weak by clients. When greater security is required, use EECDH. + */ + + /*- + * Generated via: + * $ openssl dhparam -2 -outform DER 2048 2>/dev/null | + * hexdump -ve '/1 "0x%02x, "' | fmt -73 + * TODO: generate at compile-time. But that is no good for the majority of + * sites that install pre-compiled binaries, and breaks reproducible builds. + * Instead, generate at installation time and use main.cf configuration. + */ +static unsigned char builtin_der[] = { + 0x30, 0x82, 0x01, 0x08, 0x02, 0x82, 0x01, 0x01, 0x00, 0xec, 0x02, 0x7b, + 0x74, 0xc6, 0xd4, 0xb4, 0x89, 0x68, 0xfd, 0xbc, 0xe0, 0x82, 0xae, 0xd6, + 0xf1, 0x4d, 0x93, 0xaa, 0x47, 0x07, 0x84, 0x3d, 0x86, 0xf8, 0x47, 0xf7, + 0xdf, 0x08, 0x7b, 0xca, 0x04, 0xa4, 0x72, 0xec, 0x11, 0xe2, 0x38, 0x43, + 0xb7, 0x94, 0xab, 0xaf, 0xe2, 0x85, 0x59, 0x43, 0x4e, 0x71, 0x85, 0xfe, + 0x52, 0x0c, 0xe0, 0x1c, 0xb6, 0xc7, 0xb0, 0x1b, 0x06, 0xb3, 0x4d, 0x1b, + 0x4f, 0xf6, 0x4b, 0x45, 0xbd, 0x1d, 0xb8, 0xe4, 0xa4, 0x48, 0x09, 0x28, + 0x19, 0xd7, 0xce, 0xb1, 0xe5, 0x9a, 0xc4, 0x94, 0x55, 0xde, 0x4d, 0x86, + 0x0f, 0x4c, 0x5e, 0x25, 0x51, 0x6c, 0x96, 0xca, 0xfa, 0xe3, 0x01, 0x69, + 0x82, 0x6c, 0x8f, 0xf5, 0xe7, 0x0e, 0xb7, 0x8e, 0x52, 0xf1, 0xcf, 0x0b, + 0x67, 0x10, 0xd0, 0xb3, 0x77, 0x79, 0xa4, 0xc1, 0xd0, 0x0f, 0x3f, 0xf5, + 0x5c, 0x35, 0xf9, 0x46, 0xd2, 0xc7, 0xfb, 0x97, 0x6d, 0xd5, 0xbe, 0xe4, + 0x8b, 0x5a, 0xf2, 0x88, 0xfa, 0x47, 0xdc, 0xc2, 0x4a, 0x4d, 0x69, 0xd3, + 0x2a, 0xdf, 0x55, 0x6c, 0x5f, 0x71, 0x11, 0x1e, 0x87, 0x03, 0x68, 0xe1, + 0xf4, 0x21, 0x06, 0x63, 0xd9, 0x65, 0xd4, 0x0c, 0x4d, 0xa7, 0x1f, 0x15, + 0x53, 0x3a, 0x50, 0x1a, 0xf5, 0x9b, 0x50, 0x35, 0xe0, 0x16, 0xa1, 0xd7, + 0xe6, 0xbf, 0xd7, 0xd9, 0xd9, 0x53, 0xe5, 0x8b, 0xf8, 0x7b, 0x45, 0x46, + 0xb6, 0xac, 0x50, 0x16, 0x46, 0x42, 0xca, 0x76, 0x38, 0x4b, 0x8e, 0x83, + 0xc6, 0x73, 0x13, 0x9c, 0x03, 0xd1, 0x7a, 0x3d, 0x8d, 0x99, 0x34, 0x10, + 0x79, 0x67, 0x21, 0x23, 0xf9, 0x6f, 0x48, 0x9a, 0xa6, 0xde, 0xbf, 0x7f, + 0x9c, 0x16, 0x53, 0xff, 0xf7, 0x20, 0x96, 0xeb, 0x34, 0xcb, 0x5b, 0x85, + 0x2b, 0x7c, 0x98, 0x00, 0x23, 0x47, 0xce, 0xc2, 0x58, 0x12, 0x86, 0x2c, + 0x57, 0x02, 0x01, 0x02, +}; + +#if OPENSSL_VERSION_PREREQ(3,0) + +/* ------------------------------------- 3.0 API */ + +static EVP_PKEY *dhp = 0; + +/* load_builtin - load compile-time FFDHE group */ + +static void load_builtin(void) +{ + EVP_PKEY *tmp = 0; + OSSL_DECODER_CTX *d; + const unsigned char *endp = builtin_der; + size_t dlen = sizeof(builtin_der); + + d = OSSL_DECODER_CTX_new_for_pkey(&tmp, "DER", NULL, "DH", + OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + NULL, NULL); + /* Check decode succeeds and consumes all data (final dlen == 0) */ + if (d && OSSL_DECODER_from_data(d, &endp, &dlen) && tmp && !dlen) { + dhp = tmp; + } else { + EVP_PKEY_free(tmp); + msg_warn("error loading compiled-in DH parameters"); + tls_print_errors(); + } + OSSL_DECODER_CTX_free(d); +} + +/* tls_set_dh_from_file - set Diffie-Hellman parameters from file */ + +void tls_set_dh_from_file(const char *path) +{ + FILE *fp; + EVP_PKEY *tmp = 0; + OSSL_DECODER_CTX *d; + + /* + * This function is the first to set the DH parameters, but free any + * prior value just in case the call sequence changes some day. + */ + if (dhp) { + EVP_PKEY_free(dhp); + dhp = 0; + } + if (strcmp(path, "auto") == 0) + return; + + if ((fp = fopen(path, "r")) == 0) { + msg_warn("error opening DH parameter file \"%s\": %m" + " -- using compiled-in defaults", path); + return; + } + d = OSSL_DECODER_CTX_new_for_pkey(&tmp, "PEM", NULL, "DH", + OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS, + NULL, NULL); + if (!d || !OSSL_DECODER_from_fp(d, fp) || !tmp) { + msg_warn("error decoding DH parameters from file \"%s\"" + " -- using compiled-in defaults", path); + tls_print_errors(); + } else { + dhp = tmp; + } + OSSL_DECODER_CTX_free(d); + (void) fclose(fp); +} + +/* tls_tmp_dh - configure FFDHE group */ + +void tls_tmp_dh(SSL_CTX *ctx, int useauto) +{ + if (!dhp && !useauto) + load_builtin(); + if (!ctx) + return; + if (dhp) { + EVP_PKEY *tmp = EVP_PKEY_dup(dhp); + + if (tmp && SSL_CTX_set0_tmp_dh_pkey(ctx, tmp) > 0) + return; + EVP_PKEY_free(tmp); + msg_warn("error configuring explicit DH parameters"); + tls_print_errors(); + } else { + if (SSL_CTX_set_dh_auto(ctx, 1) > 0) + return; + msg_warn("error configuring auto DH parameters"); + tls_print_errors(); + } +} + +#else /* OPENSSL_VERSION_PREREQ(3,0) */ + +/* ------------------------------------- 1.1.1 API */ + +static DH *dhp = 0; + +static void load_builtin(void) +{ + DH *tmp = 0; + const unsigned char *endp = builtin_der; + + if (d2i_DHparams(&tmp, &endp, sizeof(builtin_der)) + && sizeof(builtin_der) == endp - builtin_der) { + dhp = tmp; + } else { + DH_free(tmp); + msg_warn("error loading compiled-in DH parameters"); + tls_print_errors(); + } +} + +/* tls_set_dh_from_file - set Diffie-Hellman parameters from file */ + +void tls_set_dh_from_file(const char *path) +{ + FILE *fp; + + /* + * This function is the first to set the DH parameters, but free any + * prior value just in case the call sequence changes some day. + */ + if (dhp) { + DH_free(dhp); + dhp = 0; + } + + /* + * Forwards compatibility, support "auto" by using the builtin group when + * OpenSSL is < 3.0 and does not support automatic FFDHE group selection. + */ + if (strcmp(path, "auto") == 0) + return; + + if ((fp = fopen(path, "r")) == 0) { + msg_warn("cannot load DH parameters from file %s: %m" + " -- using compiled-in defaults", path); + return; + } + if ((dhp = PEM_read_DHparams(fp, 0, 0, 0)) == 0) { + msg_warn("cannot load DH parameters from file %s" + " -- using compiled-in defaults", path); + tls_print_errors(); + } + (void) fclose(fp); +} + +/* tls_tmp_dh - configure FFDHE group */ + +void tls_tmp_dh(SSL_CTX *ctx, int useauto) +{ + if (!dhp) + load_builtin(); + if (!ctx || !dhp || SSL_CTX_set_tmp_dh(ctx, dhp) > 0) + return; + msg_warn("error configuring explicit DH parameters"); + tls_print_errors(); +} + +#endif /* OPENSSL_VERSION_PREREQ(3,0) */ + +/* ------------------------------------- Common API */ + +void tls_auto_eecdh_curves(SSL_CTX *ctx, const char *configured) +{ +#ifndef OPENSSL_NO_ECDH + SSL_CTX *tmpctx; + int *nids; + int space = 5; + int n = 0; + int unknown = 0; + char *save; + char *curves; + char *curve; + + if ((tmpctx = SSL_CTX_new(TLS_method())) == 0) { + msg_warn("cannot allocate temp SSL_CTX, using default ECDHE curves"); + tls_print_errors(); + return; + } + nids = mymalloc(space * sizeof(int)); + curves = save = mystrdup(configured); +#define RETURN do { \ + myfree(save); \ + myfree(nids); \ + SSL_CTX_free(tmpctx); \ + return; \ + } while (0) + + while ((curve = mystrtok(&curves, CHARS_COMMA_SP)) != 0) { + int nid = EC_curve_nist2nid(curve); + + if (nid == NID_undef) + nid = OBJ_sn2nid(curve); + if (nid == NID_undef) + nid = OBJ_ln2nid(curve); + if (nid == NID_undef) { + msg_warn("ignoring unknown ECDHE curve \"%s\"", + curve); + continue; + } + + /* + * Validate the NID by trying it as the sole EC curve for a + * throw-away SSL context. Silently skip unsupported code points. + * This way, we can list X25519 and X448 as soon as the nids are + * assigned, and before the supporting code is implemented. They'll + * be silently skipped when not yet supported. + */ + if (SSL_CTX_set1_curves(tmpctx, &nid, 1) <= 0) { + ++unknown; + continue; + } + if (++n > space) { + space *= 2; + nids = myrealloc(nids, space * sizeof(int)); + } + nids[n - 1] = nid; + } + + if (n == 0) { + if (unknown > 0) + msg_warn("none of the configured ECDHE curves are supported"); + RETURN; + } + if (SSL_CTX_set1_curves(ctx, nids, n) <= 0) { + msg_warn("failed to configure ECDHE curves"); + tls_print_errors(); + RETURN; + } + RETURN; +#endif +} + +#ifdef TEST + +int main(int unused_argc, char **unused_argv) +{ + tls_tmp_dh(0, 0); + return (dhp == 0); +} + +#endif + +#endif diff --git a/src/tls/tls_fprint.c b/src/tls/tls_fprint.c new file mode 100644 index 0000000..8021570 --- /dev/null +++ b/src/tls/tls_fprint.c @@ -0,0 +1,435 @@ +/*++ +/* NAME +/* tls_fprint 3 +/* SUMMARY +/* Digests fingerprints and all that. +/* SYNOPSIS +/* #include +/* +/* EVP_MD *tls_digest_byname(const char *mdalg, EVP_MD_CTX **mdctxPtr) +/* const char *mdalg; +/* EVP_MD_CTX **mdctxPtr; +/* +/* char *tls_serverid_digest(TLScontext, props, ciphers) +/* TLS_SESS_STATE *TLScontext; +/* const TLS_CLIENT_START_PROPS *props; +/* const char *ciphers; +/* +/* char *tls_digest_encode(md_buf, md_len) +/* const unsigned char *md_buf; +/* const char *md_len; +/* +/* char *tls_cert_fprint(peercert, mdalg) +/* X509 *peercert; +/* const char *mdalg; +/* +/* char *tls_pkey_fprint(peercert, mdalg) +/* X509 *peercert; +/* const char *mdalg; +/* DESCRIPTION +/* tls_digest_byname() constructs, and optionally returns, an EVP_MD_CTX +/* handle for performing digest operations with the algorithm named by the +/* mdalg parameter. The return value is non-null on success, and holds a +/* digest algorithm handle. If the mdctxPtr argument is non-null the +/* created context is returned to the caller, who is then responsible for +/* deleting it by calling EVP_MD_ctx_free() once it is no longer needed. +/* +/* tls_digest_encode() converts a binary message digest to a hex ASCII +/* format with ':' separators between each pair of hex digits. +/* The return value is dynamically allocated with mymalloc(), +/* and the caller must eventually free it with myfree(). +/* +/* tls_cert_fprint() returns a fingerprint of the given +/* certificate using the requested message digest, formatted +/* with tls_digest_encode(). Panics if the +/* (previously verified) digest algorithm is not found. The return +/* value is dynamically allocated with mymalloc(), and the caller +/* must eventually free it with myfree(). +/* +/* tls_pkey_fprint() returns a public-key fingerprint; in all +/* other respects the function behaves as tls_cert_fprint(). +/* The var_tls_bc_pkey_fprint variable enables an incorrect +/* algorithm that was used in Postfix versions 2.9.[0-5]. +/* The return value is dynamically allocated with mymalloc(), +/* and the caller must eventually free it with myfree(). +/* +/* tls_serverid_digest() suffixes props->serverid computed by the SMTP +/* client with "&" plus a digest of additional parameters needed to ensure +/* that re-used sessions are more likely to be reused and that they will +/* satisfy all protocol and security requirements. The return value is +/* dynamically allocated with mymalloc(), and the caller must eventually +/* free it with myfree(). +/* +/* Arguments: +/* .IP mdalg +/* A digest algorithm name, such as "sha256". +/* .IP peercert +/* Server or client X.509 certificate. +/* .IP md_buf +/* The raw binary digest. +/* .IP md_len +/* The digest length in bytes. +/* .IP mdalg +/* Name of a message digest algorithm suitable for computing secure +/* (1st pre-image resistant) message digests of certificates. For now, +/* md5, sha1, or member of SHA-2 family if supported by OpenSSL. +/* .IP mdctxPtr +/* Pointer to an (EVP_MD_CTX *) handle, or NULL if only probing for +/* algorithm support without immediate use in mind. +/* .IP buf +/* Input data for the message digest algorithm mdalg. +/* .IP len +/* The length of the input data. +/* .IP props +/* The client start properties for the session, which contains the +/* initial serverid from the SMTP client and the DANE verification +/* parameters. +/* .IP protomask +/* The mask of protocol exclusions. +/* .IP ciphers +/* The SSL client cipherlist. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Viktor Dukhovni +/*--*/ + +/* System library. */ + +#include +#include + +#ifdef USE_TLS +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* TLS library. */ + +#define TLS_INTERNAL +#include + +/* Application-specific. */ + +static const char hexcodes[] = "0123456789ABCDEF"; + +#define checkok(stillok) (ok = ok && (stillok)) +#define digest_object(p) digest_data((unsigned char *)(p), sizeof(*(p))) +#define digest_data(p, l) checkok(digest_bytes(mdctx, (p), (l))) +#define digest_string(s) checkok(digest_chars(mdctx, (s))) +#define digest_dane(tlsa) checkok(tls_digest_tlsa(mdctx, tlsa)) + +/* digest_bytes - hash octet string of given length */ + +static int digest_bytes(EVP_MD_CTX *ctx, const unsigned char *buf, size_t len) +{ + return (EVP_DigestUpdate(ctx, buf, len)); +} + +/* digest_chars - hash string including trailing NUL */ + +static int digest_chars(EVP_MD_CTX *ctx, const char *s) +{ + return (EVP_DigestUpdate(ctx, s, strlen(s) + 1)); +} + +/* tlsa_cmp - compare TLSA RRs for sorting to canonical order */ + +static int tlsa_cmp(const void *a, const void *b) +{ + TLS_TLSA *p = *(TLS_TLSA **) a; + TLS_TLSA *q = *(TLS_TLSA **) b; + int d; + + if ((d = (int) p->usage - (int) q->usage) != 0) + return d; + if ((d = (int) p->selector - (int) q->selector) != 0) + return d; + if ((d = (int) p->mtype - (int) q->mtype) != 0) + return d; + if ((d = (int) p->length - (int) q->length) != 0) + return d; + return (memcmp(p->data, q->data, p->length)); +} + +/* tls_digest_tlsa - fold in digest of TLSA records */ + +static int tls_digest_tlsa(EVP_MD_CTX *mdctx, TLS_TLSA *tlsa) +{ + TLS_TLSA *p; + TLS_TLSA **arr; + int ok = 1; + int n; + int i; + + for (n = 0, p = tlsa; p != 0; p = p->next) + ++n; + arr = (TLS_TLSA **) mymalloc(n * sizeof(*arr)); + for (i = 0, p = tlsa; p; p = p->next) + arr[i++] = (void *) p; + qsort(arr, n, sizeof(arr[0]), tlsa_cmp); + + digest_object(&n); + for (i = 0; i < n; ++i) { + digest_object(&arr[i]->usage); + digest_object(&arr[i]->selector); + digest_object(&arr[i]->mtype); + digest_object(&arr[i]->length); + digest_data(arr[i]->data, arr[i]->length); + } + myfree((void *) arr); + return (ok); +} + +/* tls_digest_byname - test availability or prepare to use digest */ + +const EVP_MD *tls_digest_byname(const char *mdalg, EVP_MD_CTX **mdctxPtr) +{ + const EVP_MD *md; + EVP_MD_CTX *mdctx = NULL; + int ok = 1; + + /* + * In OpenSSL 3.0, because of dynamically variable algorithm providers, + * there is a time-of-check/time-of-use issue that means that abstract + * algorithm handles returned by EVP_get_digestbyname() can (and not + * infrequently do) return ultimately unusable algorithms, to check for + * actual availability, one needs to use the new EVP_MD_fetch() API, or + * indirectly check usability by creating a concrete context. We take the + * latter approach here (works for 1.1.1 without #ifdef). + * + * Note that EVP_MD_CTX_{create,destroy} were renamed to, respectively, + * EVP_MD_CTX_{new,free} in OpenSSL 1.1.0. + */ + checkok(md = EVP_get_digestbyname(mdalg)); + + /* + * Sanity check: Newer shared libraries could (hypothetical ABI break) + * allow larger digests, we avoid such poison algorithms. + */ + checkok(EVP_MD_size(md) <= EVP_MAX_MD_SIZE); + checkok(mdctx = EVP_MD_CTX_new()); + checkok(EVP_DigestInit_ex(mdctx, md, NULL)); + + + if (ok && mdctxPtr != 0) + *mdctxPtr = mdctx; + else + EVP_MD_CTX_free(mdctx); + return (ok ? md : 0); +} + +/* tls_serverid_digest - suffix props->serverid with parameter digest */ + +char *tls_serverid_digest(TLS_SESS_STATE *TLScontext, + const TLS_CLIENT_START_PROPS *props, + const char *ciphers) +{ + EVP_MD_CTX *mdctx; + const char *mdalg; + unsigned char md_buf[EVP_MAX_MD_SIZE]; + unsigned int md_len; + int ok = 1; + int i; + long sslversion; + VSTRING *result; + + /* + * Try to use sha256: our serverid choice should be strong enough to + * resist 2nd-preimage attacks with a difficulty comparable to that of + * DANE TLSA digests. Failing that, we compute serverid digests with the + * default digest, but DANE requires sha256 and sha512, so if we must + * fall back to our default digest, DANE support won't be available. We + * panic if the fallback algorithm is not available, as it was verified + * available in tls_client_init() and must not simply vanish. Our + * provider set is not expected to change once the OpenSSL library is + * initialized. + */ + if (tls_digest_byname(mdalg = LN_sha256, &mdctx) == 0 + && tls_digest_byname(mdalg = props->mdalg, &mdctx) == 0) + msg_panic("digest algorithm \"%s\" not found", props->mdalg); + + /* Salt the session lookup key with the OpenSSL runtime version. */ + sslversion = OpenSSL_version_num(); + + digest_string(props->helo ? props->helo : ""); + digest_object(&sslversion); + digest_string(props->protocols); + digest_string(ciphers); + + /* + * Ensure separation of caches for sessions where DANE trust + * configuration succeeded from those where it did not. The latter + * should always see a certificate validation failure, both on initial + * handshake and on resumption. + */ + digest_object(&TLScontext->must_fail); + + /* + * DNS-based or synthetic DANE trust settings are potentially used at all + * levels above "encrypt". + */ + if (TLScontext->level > TLS_LEV_ENCRYPT + && props->dane && props->dane->tlsa) { + digest_dane(props->dane->tlsa); + } else { + int none = 0; /* Record a TLSA RR count of zero */ + + digest_object(&none); + } + + /* + * Include the chosen SNI name, which can affect server certificate + * selection. + */ + if (TLScontext->level > TLS_LEV_ENCRYPT && TLScontext->peer_sni) + digest_string(TLScontext->peer_sni); + else + digest_string(""); + + checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len)); + EVP_MD_CTX_destroy(mdctx); + if (!ok) + msg_fatal("error computing %s message digest", mdalg); + + /* Check for OpenSSL contract violation */ + if (md_len > EVP_MAX_MD_SIZE) + msg_panic("unexpectedly large %s digest size: %u", mdalg, md_len); + + /* + * Append the digest to the serverid. We don't compare this digest to + * any user-specified fingerprints. Therefore, we don't need to use a + * colon-separated format, which saves space in the TLS session cache and + * makes logging of session cache lookup keys more readable. + * + * This does however duplicate a few lines of code from the digest encoder + * for colon-separated cert and pkey fingerprints. If that is a + * compelling reason to consolidate, we could use that and append the + * result. + */ + result = vstring_alloc(strlen(props->serverid) + 1 + 2 * md_len); + vstring_strcpy(result, props->serverid); + VSTRING_ADDCH(result, '&'); + for (i = 0; i < md_len; i++) { + VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0xf0) >> 4U]); + VSTRING_ADDCH(result, hexcodes[(md_buf[i] & 0x0f)]); + } + VSTRING_TERMINATE(result); + return (vstring_export(result)); +} + +/* tls_digest_encode - encode message digest binary blob as xx:xx:... */ + +char *tls_digest_encode(const unsigned char *md_buf, int md_len) +{ + int i; + char *result = mymalloc(md_len * 3); + + /* Check for contract violation */ + if (md_len > EVP_MAX_MD_SIZE || md_len >= INT_MAX / 3) + msg_panic("unexpectedly large message digest size: %u", md_len); + + /* No risk of overruns, len is bounded by OpenSSL digest length */ + for (i = 0; i < md_len; i++) { + result[i * 3] = hexcodes[(md_buf[i] & 0xf0) >> 4U]; + result[(i * 3) + 1] = hexcodes[(md_buf[i] & 0x0f)]; + result[(i * 3) + 2] = (i + 1 != md_len) ? ':' : '\0'; + } + return (result); +} + +/* tls_data_fprint - compute and encode digest of binary object */ + +static char *tls_data_fprint(const unsigned char *buf, int len, const char *mdalg) +{ + EVP_MD_CTX *mdctx = NULL; + unsigned char md_buf[EVP_MAX_MD_SIZE]; + unsigned int md_len; + int ok = 1; + + /* Previously available in "init" routine. */ + if (tls_digest_byname(mdalg, &mdctx) == 0) + msg_panic("digest algorithm \"%s\" not found", mdalg); + + digest_data(buf, len); + checkok(EVP_DigestFinal_ex(mdctx, md_buf, &md_len)); + EVP_MD_CTX_destroy(mdctx); + if (!ok) + msg_fatal("error computing %s message digest", mdalg); + + return (tls_digest_encode(md_buf, md_len)); +} + +/* tls_cert_fprint - extract certificate fingerprint */ + +char *tls_cert_fprint(X509 *peercert, const char *mdalg) +{ + int len; + unsigned char *buf; + unsigned char *buf2; + char *result; + + len = i2d_X509(peercert, NULL); + buf2 = buf = mymalloc(len); + i2d_X509(peercert, &buf2); + if (buf2 - buf != len) + msg_panic("i2d_X509 invalid result length"); + + result = tls_data_fprint(buf, len, mdalg); + myfree(buf); + + return (result); +} + +/* tls_pkey_fprint - extract public key fingerprint from certificate */ + +char *tls_pkey_fprint(X509 *peercert, const char *mdalg) +{ + if (var_tls_bc_pkey_fprint) { + const char *myname = "tls_pkey_fprint"; + ASN1_BIT_STRING *key; + char *result; + + key = X509_get0_pubkey_bitstr(peercert); + if (key == 0) + msg_fatal("%s: error extracting legacy public-key fingerprint: %m", + myname); + + result = tls_data_fprint(key->data, key->length, mdalg); + return (result); + } else { + int len; + unsigned char *buf; + unsigned char *buf2; + char *result; + + len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), NULL); + buf2 = buf = mymalloc(len); + i2d_X509_PUBKEY(X509_get_X509_PUBKEY(peercert), &buf2); + if (buf2 - buf != len) + msg_panic("i2d_X509_PUBKEY invalid result length"); + + result = tls_data_fprint(buf, len, mdalg); + myfree(buf); + return (result); + } +} + +#endif diff --git a/src/tls/tls_level.c b/src/tls/tls_level.c new file mode 100644 index 0000000..eec15fd --- /dev/null +++ b/src/tls/tls_level.c @@ -0,0 +1,95 @@ +/*++ +/* NAME +/* tls_level 3 +/* SUMMARY +/* TLS security level conversion +/* SYNOPSIS +/* #include +/* +/* int tls_level_lookup(name) +/* const char *name; +/* +/* const char *str_tls_level(level) +/* int level; +/* DESCRIPTION +/* The functions in this module convert TLS levels from symbolic +/* name to internal form and vice versa. +/* +/* tls_level_lookup() converts a TLS level from symbolic name +/* to internal form. When an unknown level is specified, +/* tls_level_lookup() logs no warning, and returns TLS_LEV_INVALID. +/* +/* str_tls_level() converts a TLS level from internal form to +/* symbolic name. The result is a null pointer for an unknown +/* level. The "halfdane" level is not a valid user-selected TLS level, +/* it is generated internally and is only valid output for the +/* str_tls_level() function. +/* SEE ALSO +/* name_code(3) name to number mapping +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include + +/* TLS library. */ + +#include + +/* Application-specific. */ + + /* + * Numerical order of levels is critical (see tls.h): + * + * - With "may" and higher, TLS is enabled. + * + * - With "encrypt" and higher, TLS is required. + * + * - With "fingerprint" and higher, the peer certificate must match. + * + * - With "dane" and higher, the peer certificate must also be trusted, + * possibly via TLSA RRs that make it its own authority. + * + * The smtp(8) client will report trust failure in preference to reporting + * failure to match, so we make "dane" larger than "fingerprint". + */ +static const NAME_CODE tls_level_table[] = { + "none", TLS_LEV_NONE, + "may", TLS_LEV_MAY, + "encrypt", TLS_LEV_ENCRYPT, + "fingerprint", TLS_LEV_FPRINT, + "halfdane", TLS_LEV_HALF_DANE, /* output only */ + "dane", TLS_LEV_DANE, + "dane-only", TLS_LEV_DANE_ONLY, + "verify", TLS_LEV_VERIFY, + "secure", TLS_LEV_SECURE, + 0, TLS_LEV_INVALID, +}; + +int tls_level_lookup(const char *name) +{ + int level = name_code(tls_level_table, NAME_CODE_FLAG_NONE, name); + + return ((level != TLS_LEV_HALF_DANE) ? level : TLS_LEV_INVALID); +} + +const char *str_tls_level(int level) +{ + return (str_name_code(tls_level_table, level)); +} diff --git a/src/tls/tls_mgr.c b/src/tls/tls_mgr.c new file mode 100644 index 0000000..b69ddf6 --- /dev/null +++ b/src/tls/tls_mgr.c @@ -0,0 +1,486 @@ +/*++ +/* NAME +/* tls_mgr 3 +/* SUMMARY +/* tlsmgr client interface +/* SYNOPSIS +/* #include +/* +/* int tls_mgr_seed(buf, len) +/* VSTRING *buf; +/* int len; +/* +/* int tls_mgr_policy(cache_type, cachable, timeout) +/* const char *cache_type; +/* int *cachable; +/* int *timeout; +/* +/* int tls_mgr_update(cache_type, cache_id, buf, len) +/* const char *cache_type; +/* const char *cache_id; +/* const char *buf; +/* ssize_t len; +/* +/* int tls_mgr_lookup(cache_type, cache_id, buf) +/* const char *cache_type; +/* const char *cache_id; +/* VSTRING *buf; +/* +/* int tls_mgr_delete(cache_type, cache_id) +/* const char *cache_type; +/* const char *cache_id; +/* +/* TLS_TICKET_KEY *tls_mgr_key(keyname, timeout) +/* unsigned char *keyname; +/* int timeout; +/* DESCRIPTION +/* These routines communicate with the tlsmgr(8) server for +/* entropy and session cache management. Since these are +/* non-critical services, requests are allowed to fail without +/* disrupting Postfix. +/* +/* tls_mgr_seed() requests entropy from the tlsmgr(8) +/* Pseudo Random Number Generator (PRNG) pool. +/* +/* tls_mgr_policy() requests the session caching policy. +/* +/* tls_mgr_lookup() loads the specified session from +/* the specified session cache. +/* +/* tls_mgr_update() saves the specified session to +/* the specified session cache. +/* +/* tls_mgr_delete() removes specified session from +/* the specified session cache. +/* +/* tls_mgr_key() is used to retrieve the current TLS session ticket +/* encryption or decryption keys. +/* +/* Arguments: +/* .IP cache_type +/* One of TLS_MGR_SCACHE_SMTPD, TLS_MGR_SCACHE_SMTP or +/* TLS_MGR_SCACHE_LMTP. +/* .IP cachable +/* Pointer to int, set non-zero if the requested cache_type +/* is enabled. +/* .IP timeout +/* Pointer to int, returns the cache entry timeout. +/* .IP cache_id +/* The session cache lookup key. +/* .IP buf +/* The result or input buffer. +/* .IP len +/* The length of the input buffer, or the amount of data requested. +/* .IP keyname +/* Is null when requesting the current encryption keys. Otherwise, +/* keyname is a pointer to an array of TLS_TICKET_NAMELEN unsigned +/* chars (not NUL terminated) that is an identifier for a key +/* previously used to encrypt a session ticket. When encrypting +/* a null result indicates that session tickets are not supported, when +/* decrypting it indicates that no matching keys were found. +/* .IP timeout +/* The encryption key timeout. Once a key has been active for this many +/* seconds it is retired and used only for decrypting previously issued +/* session tickets for another timeout seconds, and is then destroyed. +/* The timeout must not be longer than half the SSL session lifetime. +/* DIAGNOSTICS +/* All client functions return one of the following status codes: +/* .IP TLS_MGR_STAT_OK +/* The request completed, and the requested operation was +/* successful (for example, the requested session was found, +/* or the specified session was saved or removed). +/* .IP TLS_MGR_STAT_ERR +/* The request completed, but the requested operation failed +/* (for example, the requested object was not found or the +/* specified session was not saved or removed). +/* .IP TLS_MGR_STAT_FAIL +/* The request could not complete (the client could not +/* communicate with the tlsmgr(8) server). +/* SEE ALSO +/* tlsmgr(8) TLS session and PRNG management +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +#ifdef USE_TLS + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* TLS library. */ +#include + +/* Application-specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +static ATTR_CLNT *tls_mgr; + +/* tls_mgr_handshake - receive server protocol announcement */ + +static int tls_mgr_handshake(VSTREAM *stream) +{ + return (attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSMGR), + ATTR_TYPE_END)); +} + +/* tls_mgr_open - create client handle */ + +static void tls_mgr_open(void) +{ + char *service; + + /* + * Sanity check. + */ + if (tls_mgr != 0) + msg_panic("tls_mgr_open: multiple initialization"); + + /* + * Use whatever IPC is preferred for internal use: UNIX-domain sockets or + * Solaris streams. + */ + service = concatenate("local:" TLS_MGR_CLASS "/", var_tls_mgr_service, + (char *) 0); + tls_mgr = attr_clnt_create(service, var_ipc_timeout, + var_ipc_idle_limit, var_ipc_ttl_limit); + myfree(service); + + attr_clnt_control(tls_mgr, + ATTR_CLNT_CTL_PROTO, attr_vprint, attr_vscan, + ATTR_CLNT_CTL_HANDSHAKE, tls_mgr_handshake, + ATTR_CLNT_CTL_END); +} + +/* tls_mgr_seed - request PRNG seed */ + +int tls_mgr_seed(VSTRING *buf, int len) +{ + int status; + + /* + * Create the tlsmgr client handle. + */ + if (tls_mgr == 0) + tls_mgr_open(); + + /* + * Request seed. + */ + if (attr_clnt_request(tls_mgr, + ATTR_FLAG_NONE, /* Request attributes */ + SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_SEED), + SEND_ATTR_INT(TLS_MGR_ATTR_SIZE, len), + ATTR_TYPE_END, + ATTR_FLAG_MISSING, /* Reply attributes */ + RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status), + RECV_ATTR_DATA(TLS_MGR_ATTR_SEED, buf), + ATTR_TYPE_END) != 2) + status = TLS_MGR_STAT_FAIL; + return (status); +} + +/* tls_mgr_policy - request caching policy */ + +int tls_mgr_policy(const char *cache_type, int *cachable, int *timeout) +{ + int status; + + /* + * Create the tlsmgr client handle. + */ + if (tls_mgr == 0) + tls_mgr_open(); + + /* + * Request policy. + */ + if (attr_clnt_request(tls_mgr, + ATTR_FLAG_NONE, /* Request attributes */ + SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_POLICY), + SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + ATTR_TYPE_END, + ATTR_FLAG_MISSING, /* Reply attributes */ + RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status), + RECV_ATTR_INT(TLS_MGR_ATTR_CACHABLE, cachable), + RECV_ATTR_INT(TLS_MGR_ATTR_SESSTOUT, timeout), + ATTR_TYPE_END) != 3) + status = TLS_MGR_STAT_FAIL; + return (status); +} + +/* tls_mgr_lookup - request cached session */ + +int tls_mgr_lookup(const char *cache_type, const char *cache_id, + VSTRING *buf) +{ + int status; + + /* + * Create the tlsmgr client handle. + */ + if (tls_mgr == 0) + tls_mgr_open(); + + /* + * Send the request and receive the reply. + */ + if (attr_clnt_request(tls_mgr, + ATTR_FLAG_NONE, /* Request */ + SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_LOOKUP), + SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id), + ATTR_TYPE_END, + ATTR_FLAG_MISSING, /* Reply */ + RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status), + RECV_ATTR_DATA(TLS_MGR_ATTR_SESSION, buf), + ATTR_TYPE_END) != 2) + status = TLS_MGR_STAT_FAIL; + return (status); +} + +/* tls_mgr_update - save session to cache */ + +int tls_mgr_update(const char *cache_type, const char *cache_id, + const char *buf, ssize_t len) +{ + int status; + + /* + * Create the tlsmgr client handle. + */ + if (tls_mgr == 0) + tls_mgr_open(); + + /* + * Send the request and receive the reply. + */ + if (attr_clnt_request(tls_mgr, + ATTR_FLAG_NONE, /* Request */ + SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_UPDATE), + SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id), + SEND_ATTR_DATA(TLS_MGR_ATTR_SESSION, len, buf), + ATTR_TYPE_END, + ATTR_FLAG_MISSING, /* Reply */ + RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status), + ATTR_TYPE_END) != 1) + status = TLS_MGR_STAT_FAIL; + return (status); +} + +/* tls_mgr_delete - remove cached session */ + +int tls_mgr_delete(const char *cache_type, const char *cache_id) +{ + int status; + + /* + * Create the tlsmgr client handle. + */ + if (tls_mgr == 0) + tls_mgr_open(); + + /* + * Send the request and receive the reply. + */ + if (attr_clnt_request(tls_mgr, + ATTR_FLAG_NONE, /* Request */ + SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_DELETE), + SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + SEND_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id), + ATTR_TYPE_END, + ATTR_FLAG_MISSING, /* Reply */ + RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status), + ATTR_TYPE_END) != 1) + status = TLS_MGR_STAT_FAIL; + return (status); +} + +/* request_scache_key - ask tlsmgr(8) for matching key */ + +static TLS_TICKET_KEY *request_scache_key(unsigned char *keyname) +{ + TLS_TICKET_KEY tmp; + static VSTRING *keybuf; + char *name; + size_t len; + int status; + + /* + * Create the tlsmgr client handle. + */ + if (tls_mgr == 0) + tls_mgr_open(); + + if (keybuf == 0) + keybuf = vstring_alloc(sizeof(tmp)); + + /* In tlsmgr requests we encode null key names as empty strings. */ + name = keyname ? (char *) keyname : ""; + len = keyname ? TLS_TICKET_NAMELEN : 0; + + /* + * Send the request and receive the reply. + */ + if (attr_clnt_request(tls_mgr, + ATTR_FLAG_NONE, /* Request */ + SEND_ATTR_STR(TLS_MGR_ATTR_REQ, TLS_MGR_REQ_TKTKEY), + SEND_ATTR_DATA(TLS_MGR_ATTR_KEYNAME, len, name), + ATTR_TYPE_END, + ATTR_FLAG_MISSING, /* Reply */ + RECV_ATTR_INT(TLS_MGR_ATTR_STATUS, &status), + RECV_ATTR_DATA(TLS_MGR_ATTR_KEYBUF, keybuf), + ATTR_TYPE_END) != 2 + || status != TLS_MGR_STAT_OK + || LEN(keybuf) != sizeof(tmp)) + return (0); + + memcpy((void *) &tmp, STR(keybuf), sizeof(tmp)); + return (tls_scache_key_rotate(&tmp)); +} + +/* tls_mgr_key - session ticket key lookup, local cache, then tlsmgr(8) */ + +TLS_TICKET_KEY *tls_mgr_key(unsigned char *keyname, int timeout) +{ + TLS_TICKET_KEY *key = 0; + time_t now = time((time_t *) 0); + + /* A zero timeout disables session tickets. */ + if (timeout <= 0) + return (0); + + if ((key = tls_scache_key(keyname, now, timeout)) == 0) + key = request_scache_key(keyname); + return (key); +} + +#ifdef TEST + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +int main(int unused_ac, char **av) +{ + VSTRING *inbuf = vstring_alloc(10); + int status; + ARGV *argv = 0; + + msg_vstream_init(av[0], VSTREAM_ERR); + + msg_verbose = 3; + + mail_conf_read(); + msg_info("using config files in %s", var_config_dir); + + if (chdir(var_queue_dir) < 0) + msg_fatal("chdir %s: %m", var_queue_dir); + + while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) { + argv = argv_split(STR(inbuf), CHARS_SPACE); + if (argv->argc == 0) { + argv_free(argv); + continue; + } +#define COMMAND(argv, str, len) \ + (strcasecmp(argv->argv[0], str) == 0 && argv->argc == len) + + if (COMMAND(argv, "policy", 2)) { + int cachable; + int timeout; + + status = tls_mgr_policy(argv->argv[1], &cachable, &timeout); + vstream_printf("status=%d cachable=%d timeout=%d\n", + status, cachable, timeout); + } else if (COMMAND(argv, "seed", 2)) { + VSTRING *buf = vstring_alloc(10); + VSTRING *hex = vstring_alloc(10); + int len = atoi(argv->argv[1]); + + status = tls_mgr_seed(buf, len); + hex_encode(hex, STR(buf), LEN(buf)); + vstream_printf("status=%d seed=%s\n", status, STR(hex)); + vstring_free(hex); + vstring_free(buf); + } else if (COMMAND(argv, "lookup", 3)) { + VSTRING *buf = vstring_alloc(10); + + status = tls_mgr_lookup(argv->argv[1], argv->argv[2], buf); + vstream_printf("status=%d session=%.*s\n", + status, (int) LEN(buf), STR(buf)); + vstring_free(buf); + } else if (COMMAND(argv, "update", 4)) { + status = tls_mgr_update(argv->argv[1], argv->argv[2], + argv->argv[3], strlen(argv->argv[3])); + vstream_printf("status=%d\n", status); + } else if (COMMAND(argv, "delete", 3)) { + status = tls_mgr_delete(argv->argv[1], argv->argv[2]); + vstream_printf("status=%d\n", status); + } else { + vstream_printf("usage:\n" + "seed byte_count\n" + "policy smtpd|smtp|lmtp\n" + "lookup smtpd|smtp|lmtp cache_id\n" + "update smtpd|smtp|lmtp cache_id session\n" + "delete smtpd|smtp|lmtp cache_id\n"); + } + vstream_fflush(VSTREAM_OUT); + argv_free(argv); + } + + vstring_free(inbuf); + return (0); +} + +#endif /* TEST */ + +#endif /* USE_TLS */ diff --git a/src/tls/tls_mgr.h b/src/tls/tls_mgr.h new file mode 100644 index 0000000..c584175 --- /dev/null +++ b/src/tls/tls_mgr.h @@ -0,0 +1,71 @@ +#ifndef _TLS_MGR_CLNT_H_INCLUDED_ +#define _TLS_MGR_CLNT_H_INCLUDED_ + +/*++ +/* NAME +/* tls_mgr 3h +/* SUMMARY +/* tlsmgr client interface +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * TLS library + */ +#include /* Session ticket keys */ + + /* + * TLS manager protocol. + */ +#define TLS_MGR_SERVICE "tlsmgr" +#define TLS_MGR_CLASS "private" + +#define TLS_MGR_ATTR_REQ "request" +#define TLS_MGR_REQ_SEED "seed" +#define TLS_MGR_REQ_POLICY "policy" +#define TLS_MGR_REQ_LOOKUP "lookup" +#define TLS_MGR_REQ_UPDATE "update" +#define TLS_MGR_REQ_DELETE "delete" +#define TLS_MGR_REQ_TKTKEY "tktkey" +#define TLS_MGR_ATTR_CACHABLE "cachable" +#define TLS_MGR_ATTR_CACHE_TYPE "cache_type" +#define TLS_MGR_ATTR_SEED "seed" +#define TLS_MGR_ATTR_CACHE_ID "cache_id" +#define TLS_MGR_ATTR_SESSION "session" +#define TLS_MGR_ATTR_SIZE "size" +#define TLS_MGR_ATTR_STATUS "status" +#define TLS_MGR_ATTR_KEYNAME "keyname" +#define TLS_MGR_ATTR_KEYBUF "keybuf" +#define TLS_MGR_ATTR_SESSTOUT "timeout" + + /* + * TLS manager request status codes. + */ +#define TLS_MGR_STAT_OK 0 /* success */ +#define TLS_MGR_STAT_ERR (-1) /* object not found */ +#define TLS_MGR_STAT_FAIL (-2) /* protocol error */ + + /* + * Functional interface. + */ +extern int tls_mgr_seed(VSTRING *, int); +extern int tls_mgr_policy(const char *, int *, int *); +extern int tls_mgr_lookup(const char *, const char *, VSTRING *); +extern int tls_mgr_update(const char *, const char *, const char *, ssize_t); +extern int tls_mgr_delete(const char *, const char *); +extern TLS_TICKET_KEY *tls_mgr_key(unsigned char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/tls/tls_misc.c b/src/tls/tls_misc.c new file mode 100644 index 0000000..00ce10f --- /dev/null +++ b/src/tls/tls_misc.c @@ -0,0 +1,1712 @@ +/*++ +/* NAME +/* tls_misc 3 +/* SUMMARY +/* miscellaneous TLS support routines +/* SYNOPSIS +/* .SH Public functions +/* .nf +/* .na +/* #include +/* +/* void tls_log_summary(role, usage, TLScontext) +/* TLS_ROLE role; +/* TLS_USAGE usage; +/* TLS_SESS_STATE *TLScontext; +/* +/* const char *tls_compile_version(void) +/* +/* const char *tls_run_version(void) +/* +/* const char **tls_pkey_algorithms(void) +/* +/* void tls_pre_jail_init(TLS_ROLE) +/* TLS_ROLE role; +/* +/* .SH Internal functions +/* .nf +/* .na +/* #define TLS_INTERNAL +/* #include +/* +/* char *var_tls_cnf_file; +/* char *var_tls_cnf_name; +/* char *var_tls_high_clist; +/* char *var_tls_medium_clist; +/* char *var_tls_low_clist; +/* char *var_tls_export_clist; +/* char *var_tls_null_clist; +/* char *var_tls_eecdh_auto; +/* char *var_tls_eecdh_strong; +/* char *var_tls_eecdh_ultra; +/* char *var_tls_dane_digests; +/* int var_tls_daemon_rand_bytes; +/* bool var_tls_append_def_CA; +/* bool var_tls_preempt_clist; +/* bool var_tls_bc_pkey_fprint; +/* bool var_tls_multi_wildcard; +/* char *var_tls_mgr_service; +/* char *var_tls_tkt_cipher; +/* char *var_openssl_path; +/* char *var_tls_server_sni_maps; +/* bool var_tls_fast_shutdown; +/* +/* TLS_APPL_STATE *tls_alloc_app_context(ssl_ctx, log_mask) +/* SSL_CTX *ssl_ctx; +/* int log_mask; +/* +/* void tls_free_app_context(app_ctx) +/* void *app_ctx; +/* +/* TLS_SESS_STATE *tls_alloc_sess_context(log_mask, namaddr) +/* int log_mask; +/* const char *namaddr; +/* +/* void tls_free_context(TLScontext) +/* TLS_SESS_STATE *TLScontext; +/* +/* void tls_check_version() +/* +/* long tls_bug_bits() +/* +/* void tls_param_init() +/* +/* int tls_library_init(void) +/* +/* int tls_proto_mask_lims(plist, floor, ceiling) +/* const char *plist; +/* int *floor; +/* int *ceiling; +/* +/* int tls_cipher_grade(name) +/* const char *name; +/* +/* const char *str_tls_cipher_grade(grade) +/* int grade; +/* +/* const char *tls_set_ciphers(TLScontext, grade, exclusions) +/* TLS_SESS_STATE *TLScontext; +/* int grade; +/* const char *exclusions; +/* +/* void tls_get_signature_params(TLScontext) +/* TLS_SESS_STATE *TLScontext; +/* +/* void tls_print_errors() +/* +/* void tls_info_callback(ssl, where, ret) +/* const SSL *ssl; /* unused */ +/* int where; +/* int ret; +/* +/* long tls_bio_dump_cb(bio, cmd, argp, len, argi, argl, ret, processed) +/* BIO *bio; +/* int cmd; +/* const char *argp; +/* size_t len; +/* int argi; +/* long argl; /* unused */ +/* int ret; +/* size_t *processed; +/* +/* int tls_log_mask(log_param, log_level) +/* const char *log_param; +/* const char *log_level; +/* +/* void tls_update_app_logmask(app_ctx, log_mask) +/* TLS_APPL_STATE *app_ctx; +/* int log_mask; +/* +/* const EVP_MD *tls_validate_digest(dgst) +/* const char *dgst; +/* DESCRIPTION +/* This module implements public and internal routines that +/* support the TLS client and server. +/* +/* tls_log_summary() logs a summary of a completed TLS connection. +/* The "role" argument must be TLS_ROLE_CLIENT for outgoing client +/* connections, or TLS_ROLE_SERVER for incoming server connections, +/* and the "usage" must be TLS_USAGE_NEW or TLS_USAGE_USED. +/* +/* tls_compile_version() returns a text string description of +/* the compile-time TLS library. +/* +/* tls_run_version() is just tls_compile_version() but with the runtime +/* version instead of the compile-time version. +/* +/* tls_pkey_algorithms() returns a pointer to null-terminated +/* array of string constants with the names of the supported +/* public-key algorithms. +/* +/* tls_alloc_app_context() creates an application context that +/* holds the SSL context for the application and related cached state. +/* +/* tls_free_app_context() deallocates the application context and its +/* contents (the application context is stored outside the TLS library). +/* +/* tls_alloc_sess_context() creates an initialized TLS session context +/* structure with the specified log mask and peer name[addr]. +/* +/* tls_free_context() destroys a TLScontext structure +/* together with OpenSSL structures that are attached to it. +/* +/* tls_check_version() logs a warning when the run-time OpenSSL +/* library differs in its major, minor or micro number from +/* the compile-time OpenSSL headers. +/* +/* tls_bug_bits() returns the bug compatibility mask appropriate +/* for the run-time library. Some of the bug work-arounds are +/* not appropriate for some library versions. +/* +/* tls_param_init() loads main.cf parameters used internally in +/* TLS library. Any errors are fatal. +/* +/* tls_library_init() initializes the OpenSSL library, optionally +/* loading an OpenSSL configuration file. +/* +/* tls_pre_jail_init() opens any tables that need to be opened before +/* entering a chroot jail. The "role" parameter must be TLS_ROLE_CLIENT +/* for clients and TLS_ROLE_SERVER for servers. Any errors are fatal. +/* +/* tls_proto_mask_lims() returns a bitmask of excluded protocols, and +/* and the protocol version floor/ceiling, given a list (plist) of +/* protocols to include or (preceded by a '!') exclude, or constraints +/* of the form '>=name', '<=name', '>=hexvalue', '<=hexvalue'. If "plist" +/* contains invalid protocol names, TLS_PROTOCOL_INVALID is returned and +/* no warning is logged. +/* +/* tls_cipher_grade() converts a case-insensitive cipher grade +/* name (high, medium, low, export, null) to the corresponding +/* TLS_CIPHER_ constant. When the input specifies an unrecognized +/* grade, tls_cipher_grade() logs no warning, and returns +/* TLS_CIPHER_NONE. +/* +/* str_tls_cipher_grade() converts a cipher grade to a name. +/* When the input specifies an undefined grade, str_tls_cipher_grade() +/* logs no warning, returns a null pointer. +/* +/* tls_set_ciphers() applies the requested cipher grade and exclusions +/* to the provided TLS session context, returning the resulting cipher +/* list string. The return value is the cipherlist used and is +/* overwritten upon each call. When the input is invalid, +/* tls_set_ciphers() logs a warning, and returns a null result. +/* +/* tls_get_signature_params() updates the "TLScontext" with handshake +/* signature parameters pertaining to TLS 1.3, where the ciphersuite +/* no longer describes the asymmetric algorithms employed in the +/* handshake, which are negotiated separately. This function +/* has no effect for TLS 1.2 and earlier. +/* +/* tls_print_errors() queries the OpenSSL error stack, +/* logs the error messages, and clears the error stack. +/* +/* tls_info_callback() is a call-back routine for the +/* SSL_CTX_set_info_callback() routine. It logs SSL events +/* to the Postfix logfile. +/* +/* tls_bio_dump_cb() is a call-back routine for the +/* BIO_set_callback() routine. It logs SSL content to the +/* Postfix logfile. +/* +/* tls_log_mask() converts a TLS log_level value from string +/* to mask. The main.cf parameter name is passed along for +/* diagnostics. +/* +/* tls_update_app_logmask() changes the log mask of the +/* application TLS context to the new setting. +/* +/* tls_validate_digest() returns a static handle for the named +/* digest algorithm, or NULL on error. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Originally written by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Victor Duchovni +/* Morgan Stanley +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include + + /* + * TLS library. + */ +#define TLS_INTERNAL +#include + + /* Application-specific. */ + + /* + * Tunable parameters. + */ +char *var_tls_cnf_file; +char *var_tls_cnf_name; +char *var_tls_high_clist; +char *var_tls_medium_clist; +char *var_tls_low_clist; +char *var_tls_export_clist; +char *var_tls_null_clist; +int var_tls_daemon_rand_bytes; +char *var_tls_eecdh_auto; +char *var_tls_eecdh_strong; +char *var_tls_eecdh_ultra; +char *var_tls_dane_digests; +bool var_tls_append_def_CA; +char *var_tls_bug_tweaks; +char *var_tls_ssl_options; +bool var_tls_bc_pkey_fprint; +bool var_tls_multi_wildcard; +char *var_tls_mgr_service; +char *var_tls_tkt_cipher; +char *var_openssl_path; +char *var_tls_server_sni_maps; +bool var_tls_fast_shutdown; +bool var_tls_preempt_clist; + +#ifdef USE_TLS + +static MAPS *tls_server_sni_maps; + + /* + * Index to attach TLScontext pointers to SSL objects, so that they can be + * accessed by call-back routines. + */ +int TLScontext_index = -1; + + /* + * Protocol name <=> mask conversion. + */ +static const NAME_CODE protocol_table[] = { + SSL_TXT_SSLV2, TLS_PROTOCOL_SSLv2, + SSL_TXT_SSLV3, TLS_PROTOCOL_SSLv3, + SSL_TXT_TLSV1, TLS_PROTOCOL_TLSv1, + SSL_TXT_TLSV1_1, TLS_PROTOCOL_TLSv1_1, + SSL_TXT_TLSV1_2, TLS_PROTOCOL_TLSv1_2, + TLS_PROTOCOL_TXT_TLSV1_3, TLS_PROTOCOL_TLSv1_3, + 0, TLS_PROTOCOL_INVALID, +}; + +/* + * Protocol name => numeric version, for MinProtocol and MaxProtocol + */ +static const NAME_CODE tls_version_table[] = { + "None", 0, + SSL_TXT_SSLV3, SSL3_VERSION, + SSL_TXT_TLSV1, TLS1_VERSION, + SSL_TXT_TLSV1_1, TLS1_1_VERSION, + SSL_TXT_TLSV1_2, TLS1_2_VERSION, + TLS_PROTOCOL_TXT_TLSV1_3, TLS1_3_VERSION, + 0, -1, +}; + + /* + * SSL_OP_MUMBLE bug work-around name <=> mask conversion. + */ +#define NAMEBUG(x) #x, SSL_OP_##x +static const LONG_NAME_MASK ssl_bug_tweaks[] = { + +#ifndef SSL_OP_MICROSOFT_SESS_ID_BUG +#define SSL_OP_MICROSOFT_SESS_ID_BUG 0 +#endif + NAMEBUG(MICROSOFT_SESS_ID_BUG), + +#ifndef SSL_OP_NETSCAPE_CHALLENGE_BUG +#define SSL_OP_NETSCAPE_CHALLENGE_BUG 0 +#endif + NAMEBUG(NETSCAPE_CHALLENGE_BUG), + +#ifndef SSL_OP_LEGACY_SERVER_CONNECT +#define SSL_OP_LEGACY_SERVER_CONNECT 0 +#endif + NAMEBUG(LEGACY_SERVER_CONNECT), + +#ifndef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG +#define SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG 0 +#endif + NAMEBUG(NETSCAPE_REUSE_CIPHER_CHANGE_BUG), + "CVE-2010-4180", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG, + +#ifndef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG +#define SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG 0 +#endif + NAMEBUG(SSLREF2_REUSE_CERT_TYPE_BUG), + +#ifndef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER +#define SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER 0 +#endif + NAMEBUG(MICROSOFT_BIG_SSLV3_BUFFER), + +#ifndef SSL_OP_MSIE_SSLV2_RSA_PADDING +#define SSL_OP_MSIE_SSLV2_RSA_PADDING 0 +#endif + NAMEBUG(MSIE_SSLV2_RSA_PADDING), + "CVE-2005-2969", SSL_OP_MSIE_SSLV2_RSA_PADDING, + +#ifndef SSL_OP_SSLEAY_080_CLIENT_DH_BUG +#define SSL_OP_SSLEAY_080_CLIENT_DH_BUG 0 +#endif + NAMEBUG(SSLEAY_080_CLIENT_DH_BUG), + +#ifndef SSL_OP_TLS_D5_BUG +#define SSL_OP_TLS_D5_BUG 0 +#endif + NAMEBUG(TLS_D5_BUG), + +#ifndef SSL_OP_TLS_BLOCK_PADDING_BUG +#define SSL_OP_TLS_BLOCK_PADDING_BUG 0 +#endif + NAMEBUG(TLS_BLOCK_PADDING_BUG), + +#ifndef SSL_OP_TLS_ROLLBACK_BUG +#define SSL_OP_TLS_ROLLBACK_BUG 0 +#endif + NAMEBUG(TLS_ROLLBACK_BUG), + +#ifndef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS +#define SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS 0 +#endif + NAMEBUG(DONT_INSERT_EMPTY_FRAGMENTS), + +#ifndef SSL_OP_CRYPTOPRO_TLSEXT_BUG +#define SSL_OP_CRYPTOPRO_TLSEXT_BUG 0 +#endif + NAMEBUG(CRYPTOPRO_TLSEXT_BUG), + +#ifndef SSL_OP_TLSEXT_PADDING +#define SSL_OP_TLSEXT_PADDING 0 +#endif + NAMEBUG(TLSEXT_PADDING), + +#if 0 + + /* + * XXX: New with OpenSSL 1.1.1, this is turned on implicitly in + * SSL_CTX_new() and is not included in SSL_OP_ALL. Allowing users to + * disable this would thus be a code change that would require clearing + * bug work-around bits in SSL_CTX, after setting SSL_OP_ALL. Since this + * is presumably required for TLS 1.3 on today's Internet, the code + * change will be done separately later. For now this implicit bug + * work-around cannot be disabled via supported Postfix mechanisms. + */ +#ifndef SSL_OP_ENABLE_MIDDLEBOX_COMPAT +#define SSL_OP_ENABLE_MIDDLEBOX_COMPAT 0 +#endif + NAMEBUG(ENABLE_MIDDLEBOX_COMPAT), +#endif + + 0, 0, +}; + + /* + * SSL_OP_MUMBLE option name <=> mask conversion for options that are not + * (or may in the future not be) in SSL_OP_ALL. These enable optional + * behavior, rather than bug interoperability work-arounds. + */ +#define NAME_SSL_OP(x) #x, SSL_OP_##x +static const LONG_NAME_MASK ssl_op_tweaks[] = { + +#ifndef SSL_OP_LEGACY_SERVER_CONNECT +#define SSL_OP_LEGACY_SERVER_CONNECT 0 +#endif + NAME_SSL_OP(LEGACY_SERVER_CONNECT), + +#ifndef SSL_OP_NO_TICKET +#define SSL_OP_NO_TICKET 0 +#endif + NAME_SSL_OP(NO_TICKET), + +#ifndef SSL_OP_NO_COMPRESSION +#define SSL_OP_NO_COMPRESSION 0 +#endif + NAME_SSL_OP(NO_COMPRESSION), + +#ifndef SSL_OP_NO_RENEGOTIATION +#define SSL_OP_NO_RENEGOTIATION 0 +#endif + NAME_SSL_OP(NO_RENEGOTIATION), + +#ifndef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION +#define SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION 0 +#endif + NAME_SSL_OP(NO_SESSION_RESUMPTION_ON_RENEGOTIATION), + +#ifndef SSL_OP_PRIORITIZE_CHACHA +#define SSL_OP_PRIORITIZE_CHACHA 0 +#endif + NAME_SSL_OP(PRIORITIZE_CHACHA), + +#ifndef SSL_OP_ENABLE_MIDDLEBOX_COMPAT +#define SSL_OP_ENABLE_MIDDLEBOX_COMPAT 0 +#endif + NAME_SSL_OP(ENABLE_MIDDLEBOX_COMPAT), + + 0, 0, +}; + + /* + * Once these have been a NOOP long enough, they might some day be removed + * from OpenSSL. The defines below will avoid bitrot issues if/when that + * happens. + */ +#ifndef SSL_OP_SINGLE_DH_USE +#define SSL_OP_SINGLE_DH_USE 0 +#endif +#ifndef SSL_OP_SINGLE_ECDH_USE +#define SSL_OP_SINGLE_ECDH_USE 0 +#endif + + /* + * Ciphersuite name <=> code conversion. + */ +const NAME_CODE tls_cipher_grade_table[] = { + "high", TLS_CIPHER_HIGH, + "medium", TLS_CIPHER_MEDIUM, + "low", TLS_CIPHER_LOW, + "export", TLS_CIPHER_EXPORT, + "null", TLS_CIPHER_NULL, + "invalid", TLS_CIPHER_NONE, + 0, TLS_CIPHER_NONE, +}; + + /* + * Log keyword <=> mask conversion. + */ +#define TLS_LOG_0 TLS_LOG_NONE +#define TLS_LOG_1 TLS_LOG_SUMMARY +#define TLS_LOG_2 (TLS_LOG_1 | TLS_LOG_VERBOSE | TLS_LOG_CACHE | TLS_LOG_DEBUG) +#define TLS_LOG_3 (TLS_LOG_2 | TLS_LOG_TLSPKTS) +#define TLS_LOG_4 (TLS_LOG_3 | TLS_LOG_ALLPKTS) + +static const NAME_MASK tls_log_table[] = { + "0", TLS_LOG_0, + "none", TLS_LOG_NONE, + "1", TLS_LOG_1, + "routine", TLS_LOG_1, + "2", TLS_LOG_2, + "debug", TLS_LOG_2, + "3", TLS_LOG_3, + "ssl-expert", TLS_LOG_3, + "4", TLS_LOG_4, + "ssl-developer", TLS_LOG_4, + "5", TLS_LOG_4, /* for good measure */ + "6", TLS_LOG_4, /* for good measure */ + "7", TLS_LOG_4, /* for good measure */ + "8", TLS_LOG_4, /* for good measure */ + "9", TLS_LOG_4, /* for good measure */ + "summary", TLS_LOG_SUMMARY, + "untrusted", TLS_LOG_UNTRUSTED, + "peercert", TLS_LOG_PEERCERT, + "certmatch", TLS_LOG_CERTMATCH, + "verbose", TLS_LOG_VERBOSE, /* Postfix TLS library verbose */ + "cache", TLS_LOG_CACHE, + "dane", TLS_LOG_DANE, /* DANE policy construction */ + "ssl-debug", TLS_LOG_DEBUG, /* SSL library debug/verbose */ + "ssl-handshake-packet-dump", TLS_LOG_TLSPKTS, + "ssl-session-packet-dump", TLS_LOG_TLSPKTS | TLS_LOG_ALLPKTS, + 0, 0, +}; + + /* + * Parsed OpenSSL version number. + */ +typedef struct { + int major; + int minor; + int micro; + int patch; + int status; +} TLS_VINFO; + +/* tls_log_mask - Convert user TLS loglevel to internal log feature mask */ + +int tls_log_mask(const char *log_param, const char *log_level) +{ + int mask; + + mask = name_mask_opt(log_param, tls_log_table, log_level, + NAME_MASK_ANY_CASE | NAME_MASK_RETURN); + return (mask); +} + +/* tls_update_app_logmask - update log level after init */ + +void tls_update_app_logmask(TLS_APPL_STATE *app_ctx, int log_mask) +{ + app_ctx->log_mask = log_mask; +} + +/* parse_version - parse TLS protocol version name or hex number */ + +static int parse_tls_version(const char *tok, int *version) +{ + int code = name_code(tls_version_table, NAME_CODE_FLAG_NONE, tok); + char *_end; + unsigned long ulval; + + if (code != -1) { + *version = code; + return (0); + } + errno = 0; + ulval = strtoul(tok, &_end, 16); + if (*_end != 0 + || (ulval == ULONG_MAX && errno == ERANGE) + || ulval > INT_MAX) + return TLS_PROTOCOL_INVALID; + + *version = (int) ulval; + return (0); +} + +/* tls_proto_mask_lims - protocols to exclude and floor/ceiling */ + +int tls_proto_mask_lims(const char *plist, int *floor, int *ceiling) +{ + char *save; + char *tok; + char *cp; + int code; + int exclude = 0; + int include = 0; + +#define FREE_AND_RETURN(ptr, res) do { \ + myfree(ptr); \ + return (res); \ + } while (0) + + *floor = *ceiling = 0; + + save = cp = mystrdup(plist); + while ((tok = mystrtok(&cp, CHARS_COMMA_SP ":")) != 0) { + if (strncmp(tok, ">=", 2) == 0) + code = parse_tls_version(tok + 2, floor); + else if (strncmp(tok, "<=", 2) == 0) + code = parse_tls_version(tok + 2, ceiling); + else if (*tok == '!') + exclude |= code = + name_code(protocol_table, NAME_CODE_FLAG_NONE, ++tok); + else + include |= code = + name_code(protocol_table, NAME_CODE_FLAG_NONE, tok); + if (code == TLS_PROTOCOL_INVALID) + FREE_AND_RETURN(save, TLS_PROTOCOL_INVALID); + } + + /* + * When the include list is empty, use only the explicit exclusions. + * Otherwise, also exclude the complement of the include list from the + * built-in list of known protocols. There is no way to exclude protocols + * we don't know about at compile time, and this is unavoidable because + * the OpenSSL API works with compile-time *exclusion* bit-masks. + */ + FREE_AND_RETURN(save, + (include ? (exclude | (TLS_KNOWN_PROTOCOLS & ~include)) : exclude)); +} + +/* tls_param_init - Load TLS related config parameters */ + +void tls_param_init(void) +{ + /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */ + static const CONFIG_STR_TABLE str_table[] = { + VAR_TLS_CNF_FILE, DEF_TLS_CNF_FILE, &var_tls_cnf_file, 0, 0, + VAR_TLS_CNF_NAME, DEF_TLS_CNF_NAME, &var_tls_cnf_name, 0, 0, + VAR_TLS_HIGH_CLIST, DEF_TLS_HIGH_CLIST, &var_tls_high_clist, 1, 0, + VAR_TLS_MEDIUM_CLIST, DEF_TLS_MEDIUM_CLIST, &var_tls_medium_clist, 1, 0, + VAR_TLS_LOW_CLIST, DEF_TLS_LOW_CLIST, &var_tls_low_clist, 1, 0, + VAR_TLS_EXPORT_CLIST, DEF_TLS_EXPORT_CLIST, &var_tls_export_clist, 1, 0, + VAR_TLS_NULL_CLIST, DEF_TLS_NULL_CLIST, &var_tls_null_clist, 1, 0, + VAR_TLS_EECDH_AUTO, DEF_TLS_EECDH_AUTO, &var_tls_eecdh_auto, 1, 0, + VAR_TLS_EECDH_STRONG, DEF_TLS_EECDH_STRONG, &var_tls_eecdh_strong, 1, 0, + VAR_TLS_EECDH_ULTRA, DEF_TLS_EECDH_ULTRA, &var_tls_eecdh_ultra, 1, 0, + VAR_TLS_BUG_TWEAKS, DEF_TLS_BUG_TWEAKS, &var_tls_bug_tweaks, 0, 0, + VAR_TLS_SSL_OPTIONS, DEF_TLS_SSL_OPTIONS, &var_tls_ssl_options, 0, 0, + VAR_TLS_DANE_DIGESTS, DEF_TLS_DANE_DIGESTS, &var_tls_dane_digests, 1, 0, + VAR_TLS_MGR_SERVICE, DEF_TLS_MGR_SERVICE, &var_tls_mgr_service, 1, 0, + VAR_TLS_TKT_CIPHER, DEF_TLS_TKT_CIPHER, &var_tls_tkt_cipher, 0, 0, + VAR_OPENSSL_PATH, DEF_OPENSSL_PATH, &var_openssl_path, 1, 0, + 0, + }; + + /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */ + static const CONFIG_INT_TABLE int_table[] = { + VAR_TLS_DAEMON_RAND_BYTES, DEF_TLS_DAEMON_RAND_BYTES, &var_tls_daemon_rand_bytes, 1, 0, + 0, + }; + + /* If this changes, update TLS_CLIENT_PARAMS in tls_proxy.h. */ + static const CONFIG_BOOL_TABLE bool_table[] = { + VAR_TLS_APPEND_DEF_CA, DEF_TLS_APPEND_DEF_CA, &var_tls_append_def_CA, + VAR_TLS_BC_PKEY_FPRINT, DEF_TLS_BC_PKEY_FPRINT, &var_tls_bc_pkey_fprint, + VAR_TLS_PREEMPT_CLIST, DEF_TLS_PREEMPT_CLIST, &var_tls_preempt_clist, + VAR_TLS_MULTI_WILDCARD, DEF_TLS_MULTI_WILDCARD, &var_tls_multi_wildcard, + VAR_TLS_FAST_SHUTDOWN, DEF_TLS_FAST_SHUTDOWN, &var_tls_fast_shutdown, + 0, + }; + static int init_done; + + if (init_done) + return; + init_done = 1; + + get_mail_conf_str_table(str_table); + get_mail_conf_int_table(int_table); + get_mail_conf_bool_table(bool_table); +} + +/* tls_library_init - perform OpenSSL library initialization */ + +int tls_library_init(void) +{ + OPENSSL_INIT_SETTINGS *init_settings; + char *conf_name = *var_tls_cnf_name ? var_tls_cnf_name : 0; + char *conf_file = 0; + unsigned long init_opts = 0; + +#define TLS_LIB_INIT_TODO (-1) +#define TLS_LIB_INIT_ERR (0) +#define TLS_LIB_INIT_OK (1) + + static int init_res = TLS_LIB_INIT_TODO; + + if (init_res != TLS_LIB_INIT_TODO) + return (init_res); + + /* + * Backwards compatibility: skip this function unless the Postfix + * configuration actually has non-default tls_config_xxx settings. + */ + if (strcmp(var_tls_cnf_file, DEF_TLS_CNF_FILE) == 0 + && strcmp(var_tls_cnf_name, DEF_TLS_CNF_NAME) == 0) { + if (msg_verbose) + msg_info("tls_library_init: using backwards-compatible defaults"); + return (init_res = TLS_LIB_INIT_OK); + } + if ((init_settings = OPENSSL_INIT_new()) == 0) { + msg_warn("error allocating OpenSSL init settings, " + "disabling TLS support"); + return (init_res = TLS_LIB_INIT_ERR); + } +#define TLS_LIB_INIT_RETURN(x) \ + do { OPENSSL_INIT_free(init_settings); return (init_res = (x)); } while(0) + +#if OPENSSL_VERSION_NUMBER < 0x1010102fL + + /* + * OpenSSL 1.1.0 through 1.1.1a, no support for custom configuration + * files, disabling loading of the file, or getting strict error + * handling. Thus, the only supported configuration file is "default". + */ + if (strcmp(var_tls_cnf_file, "default") != 0) { + msg_warn("non-default %s = %s requires OpenSSL 1.1.1b or later, " + "disabling TLS support", VAR_TLS_CNF_FILE, var_tls_cnf_file); + TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR); + } +#else + { + unsigned long file_flags = 0; + + /*- + * OpenSSL 1.1.1b or later: + * We can now use a non-default configuration file, or + * use none at all. We can also request strict error + * reporting. + */ + if (strcmp(var_tls_cnf_file, "none") == 0) { + init_opts |= OPENSSL_INIT_NO_LOAD_CONFIG; + } else if (strcmp(var_tls_cnf_file, "default") == 0) { + + /* + * The default global config file is optional. With "default" + * initialisation we don't insist on a match for the requested + * application name, allowing fallback to the default application + * name, even when a non-default application name is specified. + * Errors in loading the default configuration are ignored. + */ + conf_file = 0; + file_flags |= CONF_MFLAGS_IGNORE_MISSING_FILE; + file_flags |= CONF_MFLAGS_DEFAULT_SECTION; + file_flags |= CONF_MFLAGS_IGNORE_RETURN_CODES | CONF_MFLAGS_SILENT; + } else if (*var_tls_cnf_file == '/') { + + /* + * A custom config file must be present, error reporting is + * strict and the configuration section for the requested + * application name does not fall back to "openssl_conf" when + * missing. + */ + conf_file = var_tls_cnf_file; + } else { + msg_warn("non-default %s = %s is not an absolute pathname, " + "disabling TLS support", VAR_TLS_CNF_FILE, var_tls_cnf_file); + TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR); + } + + OPENSSL_INIT_set_config_file_flags(init_settings, file_flags); + } +#endif + + if (conf_file) + OPENSSL_INIT_set_config_filename(init_settings, conf_file); + if (conf_name) + OPENSSL_INIT_set_config_appname(init_settings, conf_name); + + if (OPENSSL_init_ssl(init_opts, init_settings) <= 0) { + if ((init_opts & OPENSSL_INIT_NO_LOAD_CONFIG) == 0) + msg_warn("error loading the '%s' settings from the %s OpenSSL " + "configuration file, disabling TLS support", + conf_name ? conf_name : "global", + conf_file ? conf_file : "default"); + else + msg_warn("error initializing the OpenSSL library, " + "disabling TLS support"); + tls_print_errors(); + TLS_LIB_INIT_RETURN(TLS_LIB_INIT_ERR); + } + TLS_LIB_INIT_RETURN(TLS_LIB_INIT_OK); +} + +/* tls_pre_jail_init - Load TLS related pre-jail tables */ + +void tls_pre_jail_init(TLS_ROLE role) +{ + static const CONFIG_STR_TABLE str_table[] = { + VAR_TLS_SERVER_SNI_MAPS, DEF_TLS_SERVER_SNI_MAPS, &var_tls_server_sni_maps, 0, 0, + 0, + }; + int flags; + + tls_param_init(); + + /* Nothing for clients at this time */ + if (role != TLS_ROLE_SERVER) + return; + + get_mail_conf_str_table(str_table); + if (*var_tls_server_sni_maps == 0) + return; + + flags = DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX | DICT_FLAG_SRC_RHS_IS_FILE; + tls_server_sni_maps = + maps_create(VAR_TLS_SERVER_SNI_MAPS, var_tls_server_sni_maps, flags); +} + +/* server_sni_callback - process client's SNI extension */ + +static int server_sni_callback(SSL *ssl, int *alert, void *arg) +{ + SSL_CTX *sni_ctx = (SSL_CTX *) arg; + TLS_SESS_STATE *TLScontext = SSL_get_ex_data(ssl, TLScontext_index); + const char *sni = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + const char *cp = sni; + const char *pem; + + /* SNI is silently ignored when we don't care or is NULL or empty */ + if (!sni_ctx || !tls_server_sni_maps || !sni || !*sni) + return SSL_TLSEXT_ERR_NOACK; + + if (!valid_hostname(sni, DONT_GRIPE)) { + msg_warn("TLS SNI from %s is invalid: %s", + TLScontext->namaddr, sni); + return SSL_TLSEXT_ERR_NOACK; + } + + /* + * With TLS 1.3, when the client's proposed key share is not supported by + * the server, the server may issue a HelloRetryRequest (HRR), and the + * client will then retry with a new key share on a curve supported by + * the server. This results in the SNI callback running twice for the + * same connection. + * + * When that happens, The client MUST send the essentially the same hello + * message, including the SNI name, and since we've already loaded our + * certificate chain, we don't need to do it again! Therefore, if we've + * already recorded the peer SNI name, just check that it has not + * changed, and return success. + */ + if (TLScontext->peer_sni) { + if (strcmp(sni, TLScontext->peer_sni) == 0) + return SSL_TLSEXT_ERR_OK; + msg_warn("TLS SNI changed from %s initially %s, %s after hello retry", + TLScontext->namaddr, TLScontext->peer_sni, sni); + return SSL_TLSEXT_ERR_NOACK; + } + do { + /* Don't silently skip maps opened with the wrong flags. */ + pem = maps_file_find(tls_server_sni_maps, cp, 0); + } while (!pem + && !tls_server_sni_maps->error + && (cp = strchr(cp + 1, '.')) != 0); + + if (!pem) { + if (tls_server_sni_maps->error) { + msg_warn("%s: %s map lookup problem", + tls_server_sni_maps->title, sni); + *alert = SSL_AD_INTERNAL_ERROR; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + msg_info("TLS SNI %s from %s not matched, using default chain", + sni, TLScontext->namaddr); + + /* + * XXX: We could lie and pretend to accept the name, but since we've + * previously not implemented the callback (with OpenSSL then + * declining the extension), and nothing bad happened, declining it + * explicitly should be safe. + */ + return SSL_TLSEXT_ERR_NOACK; + } + SSL_set_SSL_CTX(ssl, sni_ctx); + if (tls_load_pem_chain(ssl, pem, sni) != 0) { + /* errors already logged */ + *alert = SSL_AD_INTERNAL_ERROR; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + TLScontext->peer_sni = mystrdup(sni); + return SSL_TLSEXT_ERR_OK; +} + +/* tls_set_ciphers - Set SSL context cipher list */ + +const char *tls_set_ciphers(TLS_SESS_STATE *TLScontext, const char *grade, + const char *exclusions) +{ + const char *myname = "tls_set_ciphers"; + static VSTRING *buf; + char *save; + char *cp; + char *tok; + + if (buf == 0) + buf = vstring_alloc(10); + VSTRING_RESET(buf); + + switch (tls_cipher_grade(grade)) { + case TLS_CIPHER_NONE: + msg_warn("%s: invalid cipher grade: \"%s\"", + TLScontext->namaddr, grade); + return (0); + case TLS_CIPHER_HIGH: + vstring_strcpy(buf, var_tls_high_clist); + break; + case TLS_CIPHER_MEDIUM: + vstring_strcpy(buf, var_tls_medium_clist); + break; + case TLS_CIPHER_LOW: + vstring_strcpy(buf, var_tls_low_clist); + break; + case TLS_CIPHER_EXPORT: + vstring_strcpy(buf, var_tls_export_clist); + break; + case TLS_CIPHER_NULL: + vstring_strcpy(buf, var_tls_null_clist); + break; + default: + /* Internal error, valid grade, but missing case label. */ + msg_panic("%s: unexpected cipher grade: %s", myname, grade); + } + + /* + * The base lists for each grade can't be empty. + */ + if (VSTRING_LEN(buf) == 0) + msg_panic("%s: empty \"%s\" cipherlist", myname, grade); + + /* + * Apply locally-specified exclusions. + */ +#define CIPHER_SEP CHARS_COMMA_SP ":" + if (exclusions != 0) { + cp = save = mystrdup(exclusions); + while ((tok = mystrtok(&cp, CIPHER_SEP)) != 0) { + + /* + * Can't exclude ciphers that start with modifiers. + */ + if (strchr("!+-@", *tok)) { + msg_warn("%s: invalid unary '!+-@' in cipher exclusion: %s", + TLScontext->namaddr, tok); + return (0); + } + vstring_sprintf_append(buf, ":!%s", tok); + } + myfree(save); + } + ERR_clear_error(); + if (SSL_set_cipher_list(TLScontext->con, vstring_str(buf)) == 0) { + msg_warn("%s: error setting cipher grade: \"%s\"", + TLScontext->namaddr, grade); + tls_print_errors(); + return (0); + } + return (vstring_str(buf)); +} + +/* ec_curve_name - copy EC key curve group name */ + +#ifndef OPENSSL_NO_EC +static char *ec_curve_name(EVP_PKEY *pkey) +{ + char *curve = 0; + +#if OPENSSL_VERSION_PREREQ(3,0) + size_t namelen; + + if (EVP_PKEY_get_group_name(pkey, 0, 0, &namelen)) { + curve = mymalloc(++namelen); + if (!EVP_PKEY_get_group_name(pkey, curve, namelen, 0)) { + myfree(curve); + curve = 0; + } + } +#else + EC_KEY *eckey = EVP_PKEY_get0_EC_KEY(pkey); + int nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(eckey)); + const char *tmp = EC_curve_nid2nist(nid); + + if (!tmp) + tmp = OBJ_nid2sn(nid); + if (tmp) + curve = mystrdup(tmp); +#endif + return (curve); +} + +#endif + +/* tls_get_signature_params - TLS 1.3 signature details */ + +void tls_get_signature_params(TLS_SESS_STATE *TLScontext) +{ + const char *kex_name = 0; + const char *locl_sig_name = 0; + const char *locl_sig_dgst = 0; + const char *peer_sig_name = 0; + const char *peer_sig_dgst = 0; + char *kex_curve = 0; + char *locl_sig_curve = 0; + char *peer_sig_curve = 0; + int nid; + SSL *ssl = TLScontext->con; + int srvr = SSL_is_server(ssl); + EVP_PKEY *dh_pkey = 0; + X509 *local_cert; + EVP_PKEY *local_pkey = 0; + X509 *peer_cert; + EVP_PKEY *peer_pkey = 0; + +#define SIG_PROP(c, s, p) (*((s) ? &c->srvr_sig_##p : &c->clnt_sig_##p)) + + if (SSL_version(ssl) < TLS1_3_VERSION) + return; + + if (tls_get_peer_dh_pubkey(ssl, &dh_pkey)) { + switch (nid = EVP_PKEY_id(dh_pkey)) { + default: + kex_name = OBJ_nid2sn(EVP_PKEY_type(nid)); + break; + + case EVP_PKEY_DH: + kex_name = "DHE"; + TLScontext->kex_bits = EVP_PKEY_bits(dh_pkey); + break; + +#ifndef OPENSSL_NO_EC + case EVP_PKEY_EC: + kex_name = "ECDHE"; + kex_curve = ec_curve_name(dh_pkey); + break; +#endif + } + EVP_PKEY_free(dh_pkey); + } + + /* + * On the client end, the certificate may be preset, but not used, so we + * check via SSL_get_signature_nid(). This means that local signature + * data on clients requires at least 1.1.1a. + */ + if (srvr || SSL_get_signature_nid(ssl, &nid)) + local_cert = SSL_get_certificate(ssl); + else + local_cert = 0; + + /* Signature algorithms for the local end of the connection */ + if (local_cert) { + local_pkey = X509_get0_pubkey(local_cert); + + /* + * Override the built-in name for the "ECDSA" algorithms OID, with + * the more familiar name. For "RSA" keys report "RSA-PSS", which + * must be used with TLS 1.3. + */ + if ((nid = EVP_PKEY_type(EVP_PKEY_id(local_pkey))) != NID_undef) { + switch (nid) { + default: + locl_sig_name = OBJ_nid2sn(nid); + break; + + case EVP_PKEY_RSA: + /* For RSA, TLS 1.3 mandates PSS signatures */ + locl_sig_name = "RSA-PSS"; + SIG_PROP(TLScontext, srvr, bits) = EVP_PKEY_bits(local_pkey); + break; + +#ifndef OPENSSL_NO_EC + case EVP_PKEY_EC: + locl_sig_name = "ECDSA"; + locl_sig_curve = ec_curve_name(local_pkey); + break; +#endif + } + /* No X509_free(local_cert) */ + } + + /* + * With Ed25519 and Ed448 there is no pre-signature digest, but the + * accessor does not fail, rather we get NID_undef. + */ + if (SSL_get_signature_nid(ssl, &nid) && nid != NID_undef) + locl_sig_dgst = OBJ_nid2sn(nid); + } + /* Signature algorithms for the peer end of the connection */ + if ((peer_cert = TLS_PEEK_PEER_CERT(ssl)) != 0) { + peer_pkey = X509_get0_pubkey(peer_cert); + + /* + * Override the built-in name for the "ECDSA" algorithms OID, with + * the more familiar name. For "RSA" keys report "RSA-PSS", which + * must be used with TLS 1.3. + */ + if ((nid = EVP_PKEY_type(EVP_PKEY_id(peer_pkey))) != NID_undef) { + switch (nid) { + default: + peer_sig_name = OBJ_nid2sn(nid); + break; + + case EVP_PKEY_RSA: + /* For RSA, TLS 1.3 mandates PSS signatures */ + peer_sig_name = "RSA-PSS"; + SIG_PROP(TLScontext, !srvr, bits) = EVP_PKEY_bits(peer_pkey); + break; + +#ifndef OPENSSL_NO_EC + case EVP_PKEY_EC: + peer_sig_name = "ECDSA"; + peer_sig_curve = ec_curve_name(peer_pkey); + break; +#endif + } + } + + /* + * With Ed25519 and Ed448 there is no pre-signature digest, but the + * accessor does not fail, rather we get NID_undef. + */ + if (SSL_get_peer_signature_nid(ssl, &nid) && nid != NID_undef) + peer_sig_dgst = OBJ_nid2sn(nid); + + TLS_FREE_PEER_CERT(peer_cert); + } + if (kex_name) { + TLScontext->kex_name = mystrdup(kex_name); + TLScontext->kex_curve = kex_curve; + } + if (locl_sig_name) { + SIG_PROP(TLScontext, srvr, name) = mystrdup(locl_sig_name); + SIG_PROP(TLScontext, srvr, curve) = locl_sig_curve; + if (locl_sig_dgst) + SIG_PROP(TLScontext, srvr, dgst) = mystrdup(locl_sig_dgst); + } + if (peer_sig_name) { + SIG_PROP(TLScontext, !srvr, name) = mystrdup(peer_sig_name); + SIG_PROP(TLScontext, !srvr, curve) = peer_sig_curve; + if (peer_sig_dgst) + SIG_PROP(TLScontext, !srvr, dgst) = mystrdup(peer_sig_dgst); + } +} + +/* tls_log_summary - TLS loglevel 1 one-liner, embellished with TLS 1.3 details */ + +void tls_log_summary(TLS_ROLE role, TLS_USAGE usage, TLS_SESS_STATE *ctx) +{ + VSTRING *msg = vstring_alloc(100); + const char *direction = (role == TLS_ROLE_CLIENT) ? "to" : "from"; + const char *sni = (role == TLS_ROLE_CLIENT) ? 0 : ctx->peer_sni; + + /* + * When SNI was sent and accepted, the server-side log message now + * includes a "to " detail after the "from " detail + * identifying the remote client. We don't presently log (purportedly) + * accepted SNI on the client side. + */ + vstring_sprintf(msg, "%s TLS connection %s %s %s%s%s: %s" + " with cipher %s (%d/%d bits)", + !TLS_CERT_IS_PRESENT(ctx) ? "Anonymous" : + TLS_CERT_IS_SECURED(ctx) ? "Verified" : + TLS_CERT_IS_TRUSTED(ctx) ? "Trusted" : "Untrusted", + usage == TLS_USAGE_NEW ? "established" : "reused", + direction, ctx->namaddr, sni ? " to " : "", sni ? sni : "", + ctx->protocol, ctx->cipher_name, ctx->cipher_usebits, + ctx->cipher_algbits); + + if (ctx->kex_name && *ctx->kex_name) { + vstring_sprintf_append(msg, " key-exchange %s", ctx->kex_name); + if (ctx->kex_curve && *ctx->kex_curve) + vstring_sprintf_append(msg, " (%s)", ctx->kex_curve); + else if (ctx->kex_bits > 0) + vstring_sprintf_append(msg, " (%d bits)", ctx->kex_bits); + } + if (ctx->srvr_sig_name && *ctx->srvr_sig_name) { + vstring_sprintf_append(msg, " server-signature %s", + ctx->srvr_sig_name); + if (ctx->srvr_sig_curve && *ctx->srvr_sig_curve) + vstring_sprintf_append(msg, " (%s)", ctx->srvr_sig_curve); + else if (ctx->srvr_sig_bits > 0) + vstring_sprintf_append(msg, " (%d bits)", ctx->srvr_sig_bits); + if (ctx->srvr_sig_dgst && *ctx->srvr_sig_dgst) + vstring_sprintf_append(msg, " server-digest %s", + ctx->srvr_sig_dgst); + } + if (ctx->clnt_sig_name && *ctx->clnt_sig_name) { + vstring_sprintf_append(msg, " client-signature %s", + ctx->clnt_sig_name); + if (ctx->clnt_sig_curve && *ctx->clnt_sig_curve) + vstring_sprintf_append(msg, " (%s)", ctx->clnt_sig_curve); + else if (ctx->clnt_sig_bits > 0) + vstring_sprintf_append(msg, " (%d bits)", ctx->clnt_sig_bits); + if (ctx->clnt_sig_dgst && *ctx->clnt_sig_dgst) + vstring_sprintf_append(msg, " client-digest %s", + ctx->clnt_sig_dgst); + } + msg_info("%s", vstring_str(msg)); + vstring_free(msg); +} + +/* tls_alloc_app_context - allocate TLS application context */ + +TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *ssl_ctx, SSL_CTX *sni_ctx, + int log_mask) +{ + TLS_APPL_STATE *app_ctx; + + app_ctx = (TLS_APPL_STATE *) mymalloc(sizeof(*app_ctx)); + + /* See portability note below with other memset() call. */ + memset((void *) app_ctx, 0, sizeof(*app_ctx)); + app_ctx->ssl_ctx = ssl_ctx; + app_ctx->sni_ctx = sni_ctx; + app_ctx->log_mask = log_mask; + + /* See also: cache purging code in tls_set_ciphers(). */ + app_ctx->cache_type = 0; + + if (tls_server_sni_maps) { + SSL_CTX_set_tlsext_servername_callback(ssl_ctx, server_sni_callback); + SSL_CTX_set_tlsext_servername_arg(ssl_ctx, (void *) sni_ctx); + } + return (app_ctx); +} + +/* tls_free_app_context - Free TLS application context */ + +void tls_free_app_context(TLS_APPL_STATE *app_ctx) +{ + if (app_ctx->ssl_ctx) + SSL_CTX_free(app_ctx->ssl_ctx); + if (app_ctx->sni_ctx) + SSL_CTX_free(app_ctx->sni_ctx); + if (app_ctx->cache_type) + myfree(app_ctx->cache_type); + myfree((void *) app_ctx); +} + +/* tls_alloc_sess_context - allocate TLS session context */ + +TLS_SESS_STATE *tls_alloc_sess_context(int log_mask, const char *namaddr) +{ + TLS_SESS_STATE *TLScontext; + + /* + * PORTABILITY: Do not assume that null pointers are all-zero bits. Use + * explicit assignments to initialize pointers. + * + * See the C language FAQ item 5.17, or if you have time to burn, + * http://www.google.com/search?q=zero+bit+null+pointer + * + * However, it's OK to use memset() to zero integer values. + */ + TLScontext = (TLS_SESS_STATE *) mymalloc(sizeof(TLS_SESS_STATE)); + memset((void *) TLScontext, 0, sizeof(*TLScontext)); + TLScontext->con = 0; + TLScontext->cache_type = 0; + TLScontext->serverid = 0; + TLScontext->peer_CN = 0; + TLScontext->issuer_CN = 0; + TLScontext->peer_sni = 0; + TLScontext->peer_cert_fprint = 0; + TLScontext->peer_pkey_fprint = 0; + TLScontext->protocol = 0; + TLScontext->cipher_name = 0; + TLScontext->kex_name = 0; + TLScontext->kex_curve = 0; + TLScontext->clnt_sig_name = 0; + TLScontext->clnt_sig_curve = 0; + TLScontext->clnt_sig_dgst = 0; + TLScontext->srvr_sig_name = 0; + TLScontext->srvr_sig_curve = 0; + TLScontext->srvr_sig_dgst = 0; + TLScontext->log_mask = log_mask; + TLScontext->namaddr = lowercase(mystrdup(namaddr)); + TLScontext->mdalg = 0; /* Alias for props->mdalg */ + TLScontext->dane = 0; /* Alias for props->dane */ + TLScontext->errordepth = -1; + TLScontext->errorcode = X509_V_OK; + TLScontext->errorcert = 0; + + return (TLScontext); +} + +/* tls_free_context - deallocate TLScontext and members */ + +void tls_free_context(TLS_SESS_STATE *TLScontext) +{ + + /* + * Free the SSL structure and the BIOs. Warning: the internal_bio is + * connected to the SSL structure and is automatically freed with it. Do + * not free it again (core dump)!! Only free the network_bio. + */ + if (TLScontext->con != 0) + SSL_free(TLScontext->con); + + if (TLScontext->namaddr) + myfree(TLScontext->namaddr); + if (TLScontext->serverid) + myfree(TLScontext->serverid); + + if (TLScontext->peer_CN) + myfree(TLScontext->peer_CN); + if (TLScontext->issuer_CN) + myfree(TLScontext->issuer_CN); + if (TLScontext->peer_sni) + myfree(TLScontext->peer_sni); + if (TLScontext->peer_cert_fprint) + myfree(TLScontext->peer_cert_fprint); + if (TLScontext->peer_pkey_fprint) + myfree(TLScontext->peer_pkey_fprint); + if (TLScontext->kex_name) + myfree((void *) TLScontext->kex_name); + if (TLScontext->kex_curve) + myfree((void *) TLScontext->kex_curve); + if (TLScontext->clnt_sig_name) + myfree((void *) TLScontext->clnt_sig_name); + if (TLScontext->clnt_sig_curve) + myfree((void *) TLScontext->clnt_sig_curve); + if (TLScontext->clnt_sig_dgst) + myfree((void *) TLScontext->clnt_sig_dgst); + if (TLScontext->srvr_sig_name) + myfree((void *) TLScontext->srvr_sig_name); + if (TLScontext->srvr_sig_curve) + myfree((void *) TLScontext->srvr_sig_curve); + if (TLScontext->srvr_sig_dgst) + myfree((void *) TLScontext->srvr_sig_dgst); + if (TLScontext->errorcert) + X509_free(TLScontext->errorcert); + + myfree((void *) TLScontext); +} + +/* tls_version_split - Split OpenSSL version number into major, minor, ... */ + +static void tls_version_split(unsigned long version, TLS_VINFO *info) +{ + + /* + * OPENSSL_VERSION_NUMBER(3): + * + * OPENSSL_VERSION_NUMBER is a numeric release version identifier: + * + * MMNNFFPPS: major minor fix patch status + * + * The status nibble has one of the values 0 for development, 1 to e for + * betas 1 to 14, and f for release. Parsed OpenSSL version number. for + * example: 0x1010103f == 1.1.1c. + */ + info->status = version & 0xf; + version >>= 4; + info->patch = version & 0xff; + version >>= 8; + info->micro = version & 0xff; + version >>= 8; + info->minor = version & 0xff; + version >>= 8; + info->major = version & 0xff; +} + +/* tls_check_version - Detect mismatch between headers and library. */ + +void tls_check_version(void) +{ + TLS_VINFO hdr_info; + TLS_VINFO lib_info; + + tls_version_split(OPENSSL_VERSION_NUMBER, &hdr_info); + tls_version_split(OpenSSL_version_num(), &lib_info); + + /* + * Warn if run-time library is different from compile-time library, + * allowing later run-time "micro" versions starting with 1.1.0. + */ + if (lib_info.major != hdr_info.major + || lib_info.minor != hdr_info.minor + || (lib_info.micro != hdr_info.micro + && (lib_info.micro < hdr_info.micro + || hdr_info.major == 0 + || (hdr_info.major == 1 && hdr_info.minor == 0)))) + msg_warn("run-time library vs. compile-time header version mismatch: " + "OpenSSL %d.%d.%d may not be compatible with OpenSSL %d.%d.%d", + lib_info.major, lib_info.minor, lib_info.micro, + hdr_info.major, hdr_info.minor, hdr_info.micro); +} + +/* tls_compile_version - compile-time OpenSSL version */ + +const char *tls_compile_version(void) +{ + return (OPENSSL_VERSION_TEXT); +} + +/* tls_run_version - run-time version "major.minor.micro" */ + +const char *tls_run_version(void) +{ + return (OpenSSL_version(OPENSSL_VERSION)); +} + +const char **tls_pkey_algorithms(void) +{ + + /* + * Return an array, not string, so that the result can be inspected + * without parsing. Sort the result alphabetically, not chronologically. + */ + static const char *algs[] = { +#ifndef OPENSSL_NO_DSA + "dsa", +#endif +#ifndef OPENSSL_NO_ECDSA + "ecdsa", +#endif +#ifndef OPENSSL_NO_RSA + "rsa", +#endif + 0, + }; + + return (algs); +} + +/* tls_bug_bits - SSL bug compatibility bits for this OpenSSL version */ + +long tls_bug_bits(void) +{ + long bits = SSL_OP_ALL; /* Work around all known bugs */ + + /* + * Silently ignore any strings that don't appear in the tweaks table, or + * hex bits that are not in SSL_OP_ALL. + */ + if (*var_tls_bug_tweaks) { + bits &= ~long_name_mask_opt(VAR_TLS_BUG_TWEAKS, ssl_bug_tweaks, + var_tls_bug_tweaks, NAME_MASK_ANY_CASE | + NAME_MASK_NUMBER | NAME_MASK_WARN); +#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG + /* Not relevant to SMTP */ + bits &= ~SSL_OP_SAFARI_ECDHE_ECDSA_BUG; +#endif + } + + /* + * Allow users to set options not in SSL_OP_ALL, and not already managed + * via other Postfix parameters. + */ + if (*var_tls_ssl_options) { + long enable; + + enable = long_name_mask_opt(VAR_TLS_SSL_OPTIONS, ssl_op_tweaks, + var_tls_ssl_options, NAME_MASK_ANY_CASE | + NAME_MASK_NUMBER | NAME_MASK_WARN); + enable &= ~(SSL_OP_ALL | TLS_SSL_OP_MANAGED_BITS); + bits |= enable; + } + + /* + * We unconditionally avoid re-use of ephemeral keys, note that we set DH + * keys via a callback, so reuse was never possible, but the ECDH key is + * set statically, so that is potentially subject to reuse. Set both + * options just in case. + */ + bits |= SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_DH_USE; + return (bits); +} + +/* tls_print_errors - print and clear the error stack */ + +void tls_print_errors(void) +{ + unsigned long err; + char buffer[1024]; /* XXX */ + const char *file; + const char *data; + int line; + int flags; + +#if OPENSSL_VERSION_PREREQ(3,0) +/* XXX: We're ignoring the function name, do we want to log it? */ +#define ERRGET(fi, l, d, fl) ERR_get_error_all(fi, l, 0, d, fl) +#else +#define ERRGET(fi, l, d, fl) ERR_get_error_line_data(fi, l, d, fl) +#endif + + while ((err = ERRGET(&file, &line, &data, &flags)) != 0) { + ERR_error_string_n(err, buffer, sizeof(buffer)); + if (flags & ERR_TXT_STRING) + msg_warn("TLS library problem: %s:%s:%d:%s:", + buffer, file, line, data); + else + msg_warn("TLS library problem: %s:%s:%d:", buffer, file, line); + } +} + +/* tls_info_callback - callback for logging SSL events via Postfix */ + +void tls_info_callback(const SSL *s, int where, int ret) +{ + char *str; + int w; + + /* Adapted from OpenSSL apps/s_cb.c. */ + + w = where & ~SSL_ST_MASK; + + if (w & SSL_ST_CONNECT) + str = "SSL_connect"; + else if (w & SSL_ST_ACCEPT) + str = "SSL_accept"; + else + str = "unknown"; + + if (where & SSL_CB_LOOP) { + msg_info("%s:%s", str, SSL_state_string_long((SSL *) s)); + } else if (where & SSL_CB_ALERT) { + str = (where & SSL_CB_READ) ? "read" : "write"; + if ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY) + msg_info("SSL3 alert %s:%s:%s", str, + SSL_alert_type_string_long(ret), + SSL_alert_desc_string_long(ret)); + } else if (where & SSL_CB_EXIT) { + if (ret == 0) + msg_info("%s:failed in %s", + str, SSL_state_string_long((SSL *) s)); + else if (ret < 0) { +#ifndef LOG_NON_ERROR_STATES + switch (SSL_get_error((SSL *) s, ret)) { + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + /* Don't log non-error states. */ + break; + default: +#endif + msg_info("%s:error in %s", + str, SSL_state_string_long((SSL *) s)); +#ifndef LOG_NON_ERROR_STATES + } +#endif + } + } +} + + /* + * taken from OpenSSL crypto/bio/b_dump.c. + * + * Modified to save a lot of strcpy and strcat by Matti Aarnio. + * + * Rewritten by Wietse to eliminate fixed-size stack buffer, array index + * multiplication and division, sprintf() and strcpy(), and lots of strlen() + * calls. We could make it a little faster by using a fixed-size stack-based + * buffer. + * + * 200412 - use %lx to print pointers, after casting them to unsigned long. + */ + +#define TRUNCATE_SPACE_NULL +#define DUMP_WIDTH 16 +#define VERT_SPLIT 7 + +static void tls_dump_buffer(const unsigned char *start, int len) +{ + VSTRING *buf = vstring_alloc(100); + const unsigned char *last = start + len - 1; + const unsigned char *row; + const unsigned char *col; + int ch; + +#ifdef TRUNCATE_SPACE_NULL + while (last >= start && (*last == ' ' || *last == 0)) + last--; +#endif + + for (row = start; row <= last; row += DUMP_WIDTH) { + VSTRING_RESET(buf); + vstring_sprintf(buf, "%04lx ", (unsigned long) (row - start)); + for (col = row; col < row + DUMP_WIDTH; col++) { + if (col > last) { + vstring_strcat(buf, " "); + } else { + ch = *col; + vstring_sprintf_append(buf, "%02x%c", + ch, col - row == VERT_SPLIT ? '|' : ' '); + } + } + VSTRING_ADDCH(buf, ' '); + for (col = row; col < row + DUMP_WIDTH; col++) { + if (col > last) + break; + ch = *col; + if (!ISPRINT(ch)) + ch = '.'; + VSTRING_ADDCH(buf, ch); + if (col - row == VERT_SPLIT) + VSTRING_ADDCH(buf, ' '); + } + VSTRING_TERMINATE(buf); + msg_info("%s", vstring_str(buf)); + } +#ifdef TRUNCATE_SPACE_NULL + if ((last + 1) - start < len) + msg_info("%04lx - ", + (unsigned long) ((last + 1) - start)); +#endif + vstring_free(buf); +} + +/* taken from OpenSSL apps/s_cb.c */ + +#if !OPENSSL_VERSION_PREREQ(3,0) +long tls_bio_dump_cb(BIO *bio, int cmd, const char *argp, int argi, + long unused_argl, long ret) +{ + if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) { + msg_info("read from %08lX [%08lX] (%d bytes => %ld (0x%lX))", + (unsigned long) bio, (unsigned long) argp, argi, + ret, (unsigned long) ret); + tls_dump_buffer((unsigned char *) argp, (int) ret); + } else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) { + msg_info("write to %08lX [%08lX] (%d bytes => %ld (0x%lX))", + (unsigned long) bio, (unsigned long) argp, argi, + ret, (unsigned long) ret); + tls_dump_buffer((unsigned char *) argp, (int) ret); + } + return (ret); +} + +#else +long tls_bio_dump_cb(BIO *bio, int cmd, const char *argp, size_t len, + int argi, long unused_argl, int ret, size_t *processed) +{ + size_t bytes = (ret > 0 && processed != NULL) ? *processed : len; + + if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) { + if (ret > 0) { + msg_info("read from %08lX [%08lX] (%ld bytes => %ld (0x%lX))", + (unsigned long) bio, (unsigned long) argp, (long) len, + (long) bytes, (long) bytes); + tls_dump_buffer((unsigned char *) argp, (int) bytes); + } else { + msg_info("read from %08lX [%08lX] (%ld bytes => %d)", + (unsigned long) bio, (unsigned long) argp, + (long) len, ret); + } + } else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) { + if (ret > 0) { + msg_info("write to %08lX [%08lX] (%ld bytes => %ld (0x%lX))", + (unsigned long) bio, (unsigned long) argp, (long) len, + (long) bytes, (long) bytes); + tls_dump_buffer((unsigned char *) argp, (int) bytes); + } else { + msg_info("write to %08lX [%08lX] (%ld bytes => %d)", + (unsigned long) bio, (unsigned long) argp, + (long) len, ret); + } + } + return ret; +} + +#endif + +const EVP_MD *tls_validate_digest(const char *dgst) +{ + const EVP_MD *md_alg; + + /* + * If the administrator specifies an unsupported digest algorithm, fail + * now, rather than in the middle of a TLS handshake. + */ + if ((md_alg = tls_digest_byname(dgst, NULL)) == 0) + msg_warn("Digest algorithm \"%s\" not found", dgst); + return md_alg; +} + +#else + + /* + * Broken linker workaround. + */ +int tls_dummy_for_broken_linkers; + +#endif diff --git a/src/tls/tls_prng.h b/src/tls/tls_prng.h new file mode 100644 index 0000000..df7fad9 --- /dev/null +++ b/src/tls/tls_prng.h @@ -0,0 +1,50 @@ +#ifndef _TLS_PRNG_SRC_H_INCLUDED_ +#define _TLS_PRNG_SRC_H_INCLUDED_ + +/*++ +/* NAME +/* tls_prng_src 3h +/* SUMMARY +/* OpenSSL PRNG maintenance routines +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +typedef struct TLS_PRNG_SRC { + int fd; /* file handle */ + char *name; /* resource name */ + int timeout; /* time limit of applicable */ +} TLS_PRNG_SRC; + +extern TLS_PRNG_SRC *tls_prng_egd_open(const char *, int); +extern ssize_t tls_prng_egd_read(TLS_PRNG_SRC *, size_t); +extern int tls_prng_egd_close(TLS_PRNG_SRC *); + +extern TLS_PRNG_SRC *tls_prng_dev_open(const char *, int); +extern ssize_t tls_prng_dev_read(TLS_PRNG_SRC *, size_t); +extern int tls_prng_dev_close(TLS_PRNG_SRC *); + +extern TLS_PRNG_SRC *tls_prng_file_open(const char *, int); +extern ssize_t tls_prng_file_read(TLS_PRNG_SRC *, size_t); +extern int tls_prng_file_close(TLS_PRNG_SRC *); + +extern TLS_PRNG_SRC *tls_prng_exch_open(const char *); +extern void tls_prng_exch_update(TLS_PRNG_SRC *); +extern void tls_prng_exch_close(TLS_PRNG_SRC *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/tls/tls_prng_dev.c b/src/tls/tls_prng_dev.c new file mode 100644 index 0000000..47abf47 --- /dev/null +++ b/src/tls/tls_prng_dev.c @@ -0,0 +1,155 @@ +/*++ +/* NAME +/* tls_prng_dev 3 +/* SUMMARY +/* seed OpenSSL PRNG from entropy device +/* SYNOPSIS +/* #include +/* +/* TLS_PRNG_SRC *tls_prng_dev_open(name, timeout) +/* const char *name; +/* int timeout; +/* +/* ssize_t tls_prng_dev_read(dev, length) +/* TLS_PRNG_SRC *dev; +/* size_t length; +/* +/* int tls_prng_dev_close(dev) +/* TLS_PRNG_SRC *dev; +/* DESCRIPTION +/* tls_prng_dev_open() opens the specified entropy device +/* and returns a handle that should be used with all subsequent +/* access. +/* +/* tls_prng_dev_read() reads the requested number of bytes from +/* the entropy device and updates the OpenSSL PRNG. +/* +/* tls_prng_dev_close() closes the specified entropy device +/* and releases memory that was allocated for the handle. +/* +/* Arguments: +/* .IP name +/* The pathname of the entropy device. +/* .IP length +/* The number of bytes to read from the entropy device. +/* Request lengths will be truncated at 255 bytes. +/* .IP timeout +/* Time limit on individual I/O operations. +/* DIAGNOSTICS +/* tls_prng_dev_open() returns a null pointer on error. +/* +/* tls_prng_dev_read() returns -1 on error, the number +/* of bytes received on success. +/* +/* tls_prng_dev_close() returns -1 on error, 0 on success. +/* +/* In all cases the errno variable indicates the type of error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +#ifndef UCHAR_MAX +#define UCHAR_MAX 0xff +#endif + +/* OpenSSL library. */ + +#ifdef USE_TLS +#include /* For the PRNG */ + +/* Utility library. */ + +#include +#include +#include +#include + +/* TLS library. */ + +#include + +/* tls_prng_dev_open - open entropy device */ + +TLS_PRNG_SRC *tls_prng_dev_open(const char *name, int timeout) +{ + const char *myname = "tls_prng_dev_open"; + TLS_PRNG_SRC *dev; + int fd; + + if ((fd = open(name, O_RDONLY, 0)) < 0) { + if (msg_verbose) + msg_info("%s: cannot open entropy device %s: %m", myname, name); + return (0); + } else { + dev = (TLS_PRNG_SRC *) mymalloc(sizeof(*dev)); + dev->fd = fd; + dev->name = mystrdup(name); + dev->timeout = timeout; + if (msg_verbose) + msg_info("%s: opened entropy device %s", myname, name); + return (dev); + } +} + +/* tls_prng_dev_read - update internal PRNG from device */ + +ssize_t tls_prng_dev_read(TLS_PRNG_SRC *dev, size_t len) +{ + const char *myname = "tls_prng_dev_read"; + unsigned char buffer[UCHAR_MAX]; + ssize_t count; + size_t rand_bytes; + + if (len <= 0) + msg_panic("%s: bad read length: %ld", myname, (long) len); + + if (len > sizeof(buffer)) + rand_bytes = sizeof(buffer); + else + rand_bytes = len; + errno = 0; + count = timed_read(dev->fd, buffer, rand_bytes, dev->timeout, (void *) 0); + if (count > 0) { + if (msg_verbose) + msg_info("%s: read %ld bytes from entropy device %s", + myname, (long) count, dev->name); + RAND_seed(buffer, count); + } else { + if (msg_verbose) + msg_info("%s: cannot read %ld bytes from entropy device %s: %m", + myname, (long) rand_bytes, dev->name); + } + return (count); +} + +/* tls_prng_dev_close - disconnect from EGD server */ + +int tls_prng_dev_close(TLS_PRNG_SRC *dev) +{ + const char *myname = "tls_prng_dev_close"; + int err; + + if (msg_verbose) + msg_info("%s: close entropy device %s", myname, dev->name); + err = close(dev->fd); + myfree(dev->name); + myfree((void *) dev); + return (err); +} + +#endif diff --git a/src/tls/tls_prng_egd.c b/src/tls/tls_prng_egd.c new file mode 100644 index 0000000..e4a4cd5 --- /dev/null +++ b/src/tls/tls_prng_egd.c @@ -0,0 +1,166 @@ +/*++ +/* NAME +/* tls_prng_egd 3 +/* SUMMARY +/* seed OpenSSL PRNG from EGD server +/* SYNOPSIS +/* #include +/* +/* TLS_PRNG_SRC *tls_prng_egd_open(name, timeout) +/* const char *name; +/* int timeout; +/* +/* ssize_t tls_prng_egd_read(egd, length) +/* TLS_PRNG_SRC *egd; +/* size_t length; +/* +/* int tls_prng_egd_close(egd) +/* TLS_PRNG_SRC *egd; +/* DESCRIPTION +/* tls_prng_egd_open() connect to the specified UNIX-domain service +/* and returns a handle that should be used with all subsequent +/* access. +/* +/* tls_prng_egd_read() reads the requested number of bytes from +/* the EGD server and updates the OpenSSL PRNG. +/* +/* tls_prng_egd_close() disconnects from the specified EGD server +/* and releases memory that was allocated for the handle. +/* +/* Arguments: +/* .IP name +/* The UNIX-domain pathname of the EGD service. +/* .IP length +/* The number of bytes to read from the EGD server. +/* Request lengths will be truncated at 255 bytes. +/* .IP timeout +/* Time limit on individual I/O operations. +/* DIAGNOSTICS +/* tls_prng_egd_open() returns a null pointer on error. +/* +/* tls_prng_egd_read() returns -1 on error, the number +/* of bytes received on success. +/* +/* tls_prng_egd_close() returns -1 on error, 0 on success. +/* +/* In all cases the errno variable indicates the type of error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +#ifndef UCHAR_MAX +#define UCHAR_MAX 0xff +#endif + +/* OpenSSL library. */ + +#ifdef USE_TLS +#include /* For the PRNG */ + +/* Utility library. */ + +#include +#include +#include +#include + +/* TLS library. */ + +#include + +/* tls_prng_egd_open - connect to EGD server */ + +TLS_PRNG_SRC *tls_prng_egd_open(const char *name, int timeout) +{ + const char *myname = "tls_prng_egd_open"; + TLS_PRNG_SRC *egd; + int fd; + + if (msg_verbose) + msg_info("%s: connect to EGD server %s", myname, name); + + if ((fd = unix_connect(name, BLOCKING, timeout)) < 0) { + if (msg_verbose) + msg_info("%s: cannot connect to EGD server %s: %m", myname, name); + return (0); + } else { + egd = (TLS_PRNG_SRC *) mymalloc(sizeof(*egd)); + egd->fd = fd; + egd->name = mystrdup(name); + egd->timeout = timeout; + if (msg_verbose) + msg_info("%s: connected to EGD server %s", myname, name); + return (egd); + } +} + +/* tls_prng_egd_read - update internal PRNG from EGD server */ + +ssize_t tls_prng_egd_read(TLS_PRNG_SRC *egd, size_t len) +{ + const char *myname = "tls_prng_egd_read"; + unsigned char buffer[UCHAR_MAX]; + ssize_t count; + + if (len <= 0) + msg_panic("%s: bad length %ld", myname, (long) len); + + buffer[0] = 1; + buffer[1] = (len > UCHAR_MAX ? UCHAR_MAX : len); + + if (timed_write(egd->fd, buffer, 2, egd->timeout, (void *) 0) != 2) { + msg_info("cannot write to EGD server %s: %m", egd->name); + return (-1); + } + if (timed_read(egd->fd, buffer, 1, egd->timeout, (void *) 0) != 1) { + msg_info("cannot read from EGD server %s: %m", egd->name); + return (-1); + } + count = buffer[0]; + if (count > sizeof(buffer)) + count = sizeof(buffer); + if (count == 0) { + msg_info("EGD server %s reports zero bytes available", egd->name); + return (-1); + } + if (timed_read(egd->fd, buffer, count, egd->timeout, (void *) 0) != count) { + msg_info("cannot read %ld bytes from EGD server %s: %m", + (long) count, egd->name); + return (-1); + } + if (msg_verbose) + msg_info("%s: got %ld bytes from EGD server %s", myname, + (long) count, egd->name); + RAND_seed(buffer, count); + return (count); +} + +/* tls_prng_egd_close - disconnect from EGD server */ + +int tls_prng_egd_close(TLS_PRNG_SRC *egd) +{ + const char *myname = "tls_prng_egd_close"; + int err; + + if (msg_verbose) + msg_info("%s: close EGD server %s", myname, egd->name); + err = close(egd->fd); + myfree(egd->name); + myfree((void *) egd); + return (err); +} + +#endif diff --git a/src/tls/tls_prng_exch.c b/src/tls/tls_prng_exch.c new file mode 100644 index 0000000..31523a3 --- /dev/null +++ b/src/tls/tls_prng_exch.c @@ -0,0 +1,142 @@ +/*++ +/* NAME +/* tls_prng_exch 3 +/* SUMMARY +/* maintain PRNG exchange file +/* SYNOPSIS +/* #include +/* +/* TLS_PRNG_SRC *tls_prng_exch_open(name, timeout) +/* const char *name; +/* int timeout; +/* +/* void tls_prng_exch_update(fh, length) +/* TLS_PRNG_SRC *fh; +/* size_t length; +/* +/* void tls_prng_exch_close(fh) +/* TLS_PRNG_SRC *fh; +/* DESCRIPTION +/* tls_prng_exch_open() opens the specified PRNG exchange file +/* and returns a handle that should be used with all subsequent +/* access. +/* +/* tls_prng_exch_update() reads the requested number of bytes +/* from the PRNG exchange file, updates the OpenSSL PRNG, and +/* writes the requested number of bytes to the exchange file. +/* The file is locked for exclusive access. +/* +/* tls_prng_exch_close() closes the specified PRNG exchange +/* file and releases memory that was allocated for the handle. +/* +/* Arguments: +/* .IP name +/* The name of the PRNG exchange file. +/* .IP length +/* The number of bytes to read from/write to the entropy file. +/* .IP timeout +/* Time limit on individual I/O operations. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* OpenSSL library. */ + +#ifdef USE_TLS +#include /* For the PRNG */ + +/* Utility library. */ + +#include +#include +#include +#include + +/* TLS library. */ + +#include + +/* Application specific. */ + +#define TLS_PRNG_EXCH_SIZE 1024 /* XXX Why not configurable? */ + +/* tls_prng_exch_open - open PRNG exchange file */ + +TLS_PRNG_SRC *tls_prng_exch_open(const char *name) +{ + const char *myname = "tls_prng_exch_open"; + TLS_PRNG_SRC *eh; + int fd; + + if ((fd = open(name, O_RDWR | O_CREAT, 0600)) < 0) + msg_fatal("%s: cannot open PRNG exchange file %s: %m", myname, name); + eh = (TLS_PRNG_SRC *) mymalloc(sizeof(*eh)); + eh->fd = fd; + eh->name = mystrdup(name); + eh->timeout = 0; + if (msg_verbose) + msg_info("%s: opened PRNG exchange file %s", myname, name); + return (eh); +} + +/* tls_prng_exch_update - update PRNG exchange file */ + +void tls_prng_exch_update(TLS_PRNG_SRC *eh) +{ + unsigned char buffer[TLS_PRNG_EXCH_SIZE]; + ssize_t count; + + /* + * Update the PRNG exchange file. Since other processes may have added + * entropy, we use a read-stir-write cycle. + */ + if (myflock(eh->fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) != 0) + msg_fatal("cannot lock PRNG exchange file %s: %m", eh->name); + if (lseek(eh->fd, 0, SEEK_SET) < 0) + msg_fatal("cannot seek PRNG exchange file %s: %m", eh->name); + if ((count = read(eh->fd, buffer, sizeof(buffer))) < 0) + msg_fatal("cannot read PRNG exchange file %s: %m", eh->name); + + if (count > 0) + RAND_seed(buffer, count); + RAND_bytes(buffer, sizeof(buffer)); + + if (lseek(eh->fd, 0, SEEK_SET) < 0) + msg_fatal("cannot seek PRNG exchange file %s: %m", eh->name); + if (write(eh->fd, buffer, sizeof(buffer)) != sizeof(buffer)) + msg_fatal("cannot write PRNG exchange file %s: %m", eh->name); + if (myflock(eh->fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) != 0) + msg_fatal("cannot unlock PRNG exchange file %s: %m", eh->name); +} + +/* tls_prng_exch_close - close PRNG exchange file */ + +void tls_prng_exch_close(TLS_PRNG_SRC *eh) +{ + const char *myname = "tls_prng_exch_close"; + + if (close(eh->fd) < 0) + msg_fatal("close PRNG exchange file %s: %m", eh->name); + if (msg_verbose) + msg_info("%s: closed PRNG exchange file %s", myname, eh->name); + myfree(eh->name); + myfree((void *) eh); +} + +#endif diff --git a/src/tls/tls_prng_file.c b/src/tls/tls_prng_file.c new file mode 100644 index 0000000..23865be --- /dev/null +++ b/src/tls/tls_prng_file.c @@ -0,0 +1,155 @@ +/*++ +/* NAME +/* tls_prng_file 3 +/* SUMMARY +/* seed OpenSSL PRNG from entropy file +/* SYNOPSIS +/* #include +/* +/* TLS_PRNG_SRC *tls_prng_file_open(name, timeout) +/* const char *name; +/* int timeout; +/* +/* ssize_t tls_prng_file_read(fh, length) +/* TLS_PRNG_SRC *fh; +/* size_t length; +/* +/* int tls_prng_file_close(fh) +/* TLS_PRNG_SRC *fh; +/* DESCRIPTION +/* tls_prng_file_open() open the specified file and returns +/* a handle that should be used with all subsequent access. +/* +/* tls_prng_file_read() reads the requested number of bytes from +/* the entropy file and updates the OpenSSL PRNG. The file is not +/* locked for shared or exclusive access. +/* +/* tls_prng_file_close() closes the specified entropy file +/* and releases memory that was allocated for the handle. +/* +/* Arguments: +/* .IP name +/* The pathname of the entropy file. +/* .IP length +/* The number of bytes to read from the entropy file. +/* .IP timeout +/* Time limit on individual I/O operations. +/* DIAGNOSTICS +/* tls_prng_file_open() returns a null pointer on error. +/* +/* tls_prng_file_read() returns -1 on error, the number +/* of bytes received on success. +/* +/* tls_prng_file_close() returns -1 on error, 0 on success. +/* +/* In all cases the errno variable indicates the type of error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* OpenSSL library. */ + +#ifdef USE_TLS +#include /* For the PRNG */ + +/* Utility library. */ + +#include +#include +#include +#include + +/* TLS library. */ + +#include + +/* tls_prng_file_open - open entropy file */ + +TLS_PRNG_SRC *tls_prng_file_open(const char *name, int timeout) +{ + const char *myname = "tls_prng_file_open"; + TLS_PRNG_SRC *fh; + int fd; + + if ((fd = open(name, O_RDONLY, 0)) < 0) { + if (msg_verbose) + msg_info("%s: cannot open entropy file %s: %m", myname, name); + return (0); + } else { + fh = (TLS_PRNG_SRC *) mymalloc(sizeof(*fh)); + fh->fd = fd; + fh->name = mystrdup(name); + fh->timeout = timeout; + if (msg_verbose) + msg_info("%s: opened entropy file %s", myname, name); + return (fh); + } +} + +/* tls_prng_file_read - update internal PRNG from entropy file */ + +ssize_t tls_prng_file_read(TLS_PRNG_SRC *fh, size_t len) +{ + const char *myname = "tls_prng_file_read"; + char buffer[8192]; + ssize_t to_read; + ssize_t count; + + if (msg_verbose) + msg_info("%s: seed internal pool from file %s", myname, fh->name); + + if (lseek(fh->fd, 0, SEEK_SET) < 0) { + if (msg_verbose) + msg_info("cannot seek entropy file %s: %m", fh->name); + return (-1); + } + errno = 0; + for (to_read = len; to_read > 0; to_read -= count) { + if ((count = timed_read(fh->fd, buffer, to_read > sizeof(buffer) ? + sizeof(buffer) : to_read, + fh->timeout, (void *) 0)) < 0) { + if (msg_verbose) + msg_info("cannot read entropy file %s: %m", fh->name); + return (-1); + } + if (count == 0) + break; + RAND_seed(buffer, count); + } + if (msg_verbose) + msg_info("read %ld bytes from entropy file %s: %m", + (long) (len - to_read), fh->name); + return (len - to_read); +} + +/* tls_prng_file_close - close entropy file */ + +int tls_prng_file_close(TLS_PRNG_SRC *fh) +{ + const char *myname = "tls_prng_file_close"; + int err; + + if (msg_verbose) + msg_info("%s: close entropy file %s", myname, fh->name); + err = close(fh->fd); + myfree(fh->name); + myfree((void *) fh); + return (err); +} + +#endif diff --git a/src/tls/tls_proxy.h b/src/tls/tls_proxy.h new file mode 100644 index 0000000..1d96a8b --- /dev/null +++ b/src/tls/tls_proxy.h @@ -0,0 +1,287 @@ +#ifndef _TLS_PROXY_H_INCLUDED_ +#define _TLS_PROXY_H_INCLUDED_ + +/*++ +/* NAME +/* tls_proxy_clnt 3h +/* SUMMARY +/* postscreen TLS proxy support +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * TLS library. + */ +#include + + /* + * External interface. + */ +#define TLS_PROXY_FLAG_ROLE_SERVER (1<<0) /* request server role */ +#define TLS_PROXY_FLAG_ROLE_CLIENT (1<<1) /* request client role */ +#define TLS_PROXY_FLAG_SEND_CONTEXT (1<<2) /* send TLS context */ + +#ifdef USE_TLS + + /* + * TLS_CLIENT_PARAMS structure. If this changes, update all + * TLS_CLIENT_PARAMS related functions in tls_proxy_client_*.c. + * + * In the serialization these attributes are identified by their configuration + * parameter names. + * + * NOTE: this does not include openssl_path. + * + * TODO: TLS_SERVER_PARAM structure, like TLS_CLIENT_PARAMS plus + * VAR_TLS_SERVER_SNI_MAPS. + */ +typedef struct TLS_CLIENT_PARAMS { + char *tls_cnf_file; + char *tls_cnf_name; + char *tls_high_clist; + char *tls_medium_clist; + char *tls_low_clist; + char *tls_export_clist; + char *tls_null_clist; + char *tls_eecdh_auto; + char *tls_eecdh_strong; + char *tls_eecdh_ultra; + char *tls_bug_tweaks; + char *tls_ssl_options; + char *tls_dane_digests; + char *tls_mgr_service; + char *tls_tkt_cipher; + int tls_daemon_rand_bytes; + int tls_append_def_CA; + int tls_bc_pkey_fprint; + int tls_preempt_clist; + int tls_multi_wildcard; +} TLS_CLIENT_PARAMS; + +#define TLS_PROXY_PARAMS(params, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) \ + (((params)->a1), ((params)->a2), ((params)->a3), \ + ((params)->a4), ((params)->a5), ((params)->a6), ((params)->a7), \ + ((params)->a8), ((params)->a9), ((params)->a10), ((params)->a11), \ + ((params)->a12), ((params)->a13), ((params)->a14), ((params)->a15), \ + ((params)->a16), ((params)->a17), ((params)->a18), ((params)->a19), \ + ((params)->a20)) + + /* + * tls_proxy_client_param_misc.c, tls_proxy_client_param_print.c, and + * tls_proxy_client_param_scan.c. + */ +extern TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(TLS_CLIENT_PARAMS *); +extern char *tls_proxy_client_param_serialize(ATTR_PRINT_COMMON_FN, VSTRING *, const TLS_CLIENT_PARAMS *); +extern int tls_proxy_client_param_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); +extern void tls_proxy_client_param_free(TLS_CLIENT_PARAMS *); +extern int tls_proxy_client_param_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *); + + /* + * Functions that handle TLS_XXX_INIT_PROPS and TLS_XXX_START_PROPS. These + * data structures are defined elsewhere, because they are also used in + * non-proxied requests. + */ +#define tls_proxy_legacy_open(service, flags, peer_stream, peer_addr, \ + peer_port, timeout, serverid) \ + tls_proxy_open((service), (flags), (peer_stream), (peer_addr), \ + (peer_port), (timeout), (timeout), (serverid), \ + (void *) 0, (void *) 0, (void *) 0) + +extern VSTREAM *tls_proxy_open(const char *, int, VSTREAM *, const char *, + const char *, int, int, const char *, + void *, void *, void *); + +#define TLS_PROXY_CLIENT_INIT_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14) \ + (((props)->a1), ((props)->a2), ((props)->a3), \ + ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ + ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ + ((props)->a12), ((props)->a13), ((props)->a14)) + +#define TLS_PROXY_CLIENT_START_PROPS(props, a1, a2, a3, a4, a5, a6, a7, a8, \ + a9, a10, a11, a12, a13, a14) \ + (((props)->a1), ((props)->a2), ((props)->a3), \ + ((props)->a4), ((props)->a5), ((props)->a6), ((props)->a7), \ + ((props)->a8), ((props)->a9), ((props)->a10), ((props)->a11), \ + ((props)->a12), ((props)->a13), ((props)->a14)) + +extern TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *); +extern void tls_proxy_context_free(TLS_SESS_STATE *); +extern int tls_proxy_context_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); +extern int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *); + +extern int tls_proxy_client_init_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); +extern int tls_proxy_client_init_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *); +extern void tls_proxy_client_init_free(TLS_CLIENT_INIT_PROPS *); +extern char *tls_proxy_client_init_serialize(ATTR_PRINT_COMMON_FN, VSTRING *, const TLS_CLIENT_INIT_PROPS *); + +extern int tls_proxy_client_start_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); +extern int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *); +extern void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *); + +extern int tls_proxy_server_init_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); +extern int tls_proxy_server_init_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *); +extern void tls_proxy_server_init_free(TLS_SERVER_INIT_PROPS *); + +extern int tls_proxy_server_start_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); +extern int tls_proxy_server_start_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *); + +extern void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *); + +#endif /* USE_TLS */ + + /* + * TLSPROXY attributes, unconditionally exposed. + */ +#define TLS_ATTR_REMOTE_ENDPT "remote_endpoint" /* name[addr]:port */ +#define TLS_ATTR_FLAGS "flags" +#define TLS_ATTR_TIMEOUT "timeout" +#define TLS_ATTR_SERVERID "serverid" + +#ifdef USE_TLS + + /* + * Misc attributes. + */ +#define TLS_ATTR_COUNT "count" + + /* + * TLS_SESS_STATE attributes. + */ +#define TLS_ATTR_PEER_CN "peer_CN" +#define TLS_ATTR_ISSUER_CN "issuer_CN" +#define TLS_ATTR_PEER_CERT_FPT "peer_fingerprint" +#define TLS_ATTR_PEER_PKEY_FPT "peer_pubkey_fingerprint" +#define TLS_ATTR_SEC_LEVEL "level" +#define TLS_ATTR_PEER_STATUS "peer_status" +#define TLS_ATTR_CIPHER_PROTOCOL "cipher_protocol" +#define TLS_ATTR_CIPHER_NAME "cipher_name" +#define TLS_ATTR_CIPHER_USEBITS "cipher_usebits" +#define TLS_ATTR_CIPHER_ALGBITS "cipher_algbits" +#define TLS_ATTR_KEX_NAME "key_exchange" +#define TLS_ATTR_KEX_CURVE "key_exchange_curve" +#define TLS_ATTR_KEX_BITS "key_exchange_bits" +#define TLS_ATTR_CLNT_SIG_NAME "clnt_signature" +#define TLS_ATTR_CLNT_SIG_CURVE "clnt_signature_curve" +#define TLS_ATTR_CLNT_SIG_BITS "clnt_signature_bits" +#define TLS_ATTR_CLNT_SIG_DGST "clnt_signature_digest" +#define TLS_ATTR_SRVR_SIG_NAME "srvr_signature" +#define TLS_ATTR_SRVR_SIG_CURVE "srvr_signature_curve" +#define TLS_ATTR_SRVR_SIG_BITS "srvr_signature_bits" +#define TLS_ATTR_SRVR_SIG_DGST "srvr_signature_digest" +#define TLS_ATTR_NAMADDR "namaddr" + + /* + * TLS_SERVER_INIT_PROPS attributes. + */ +#define TLS_ATTR_LOG_PARAM "log_param" +#define TLS_ATTR_LOG_LEVEL "log_level" +#define TLS_ATTR_VERIFYDEPTH "verifydepth" +#define TLS_ATTR_CACHE_TYPE "cache_type" +#define TLS_ATTR_SET_SESSID "set_sessid" +#define TLS_ATTR_CHAIN_FILES "chain_files" +#define TLS_ATTR_CERT_FILE "cert_file" +#define TLS_ATTR_KEY_FILE "key_file" +#define TLS_ATTR_DCERT_FILE "dcert_file" +#define TLS_ATTR_DKEY_FILE "dkey_file" +#define TLS_ATTR_ECCERT_FILE "eccert_file" +#define TLS_ATTR_ECKEY_FILE "eckey_file" +#define TLS_ATTR_CAFILE "CAfile" +#define TLS_ATTR_CAPATH "CApath" +#define TLS_ATTR_PROTOCOLS "protocols" +#define TLS_ATTR_EECDH_GRADE "eecdh_grade" +#define TLS_ATTR_DH1K_PARAM_FILE "dh1024_param_file" +#define TLS_ATTR_DH512_PARAM_FILE "dh512_param_file" +#define TLS_ATTR_ASK_CCERT "ask_ccert" +#define TLS_ATTR_MDALG "mdalg" + + /* + * TLS_SERVER_START_PROPS attributes. + */ +#define TLS_ATTR_TIMEOUT "timeout" +#define TLS_ATTR_REQUIRECERT "requirecert" +#define TLS_ATTR_SERVERID "serverid" +#define TLS_ATTR_NAMADDR "namaddr" +#define TLS_ATTR_CIPHER_GRADE "cipher_grade" +#define TLS_ATTR_CIPHER_EXCLUSIONS "cipher_exclusions" +#define TLS_ATTR_MDALG "mdalg" + + /* + * TLS_CLIENT_INIT_PROPS attributes. + */ +#define TLS_ATTR_CNF_FILE "config_file" +#define TLS_ATTR_CNF_NAME "config_name" +#define TLS_ATTR_LOG_PARAM "log_param" +#define TLS_ATTR_LOG_LEVEL "log_level" +#define TLS_ATTR_VERIFYDEPTH "verifydepth" +#define TLS_ATTR_CACHE_TYPE "cache_type" +#define TLS_ATTR_CHAIN_FILES "chain_files" +#define TLS_ATTR_CERT_FILE "cert_file" +#define TLS_ATTR_KEY_FILE "key_file" +#define TLS_ATTR_DCERT_FILE "dcert_file" +#define TLS_ATTR_DKEY_FILE "dkey_file" +#define TLS_ATTR_ECCERT_FILE "eccert_file" +#define TLS_ATTR_ECKEY_FILE "eckey_file" +#define TLS_ATTR_CAFILE "CAfile" +#define TLS_ATTR_CAPATH "CApath" +#define TLS_ATTR_MDALG "mdalg" + + /* + * TLS_CLIENT_START_PROPS attributes. + */ +#define TLS_ATTR_TIMEOUT "timeout" +#define TLS_ATTR_TLS_LEVEL "tls_level" +#define TLS_ATTR_NEXTHOP "nexthop" +#define TLS_ATTR_HOST "host" +#define TLS_ATTR_NAMADDR "namaddr" +#define TLS_ATTR_SNI "sni" +#define TLS_ATTR_SERVERID "serverid" +#define TLS_ATTR_HELO "helo" +#define TLS_ATTR_PROTOCOLS "protocols" +#define TLS_ATTR_CIPHER_GRADE "cipher_grade" +#define TLS_ATTR_CIPHER_EXCLUSIONS "cipher_exclusions" +#define TLS_ATTR_MATCHARGV "matchargv" +#define TLS_ATTR_MDALG "mdalg" +#define TLS_ATTR_DANE "dane" + + /* + * TLS_TLSA attributes. + */ +#define TLS_ATTR_USAGE "usage" +#define TLS_ATTR_SELECTOR "selector" +#define TLS_ATTR_MTYPE "mtype" +#define TLS_ATTR_DATA "data" + + /* + * TLS_DANE attributes. + */ +#define TLS_ATTR_DOMAIN "domain" + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/tls/tls_proxy_client_misc.c b/src/tls/tls_proxy_client_misc.c new file mode 100644 index 0000000..2191dce --- /dev/null +++ b/src/tls/tls_proxy_client_misc.c @@ -0,0 +1,130 @@ +/*++ +/* NAME +/* tls_proxy_client_misc 3 +/* SUMMARY +/* TLS_CLIENT_XXX structure support +/* SYNOPSIS +/* #include +/* +/* TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(params) +/* TLS_CLIENT_PARAMS *params; +/* +/* char *tls_proxy_client_param_serialize(print_fn, buf, params) +/* ATTR_PRINT_COMMON_FN print_fn; +/* VSTRING *buf; +/* const TLS_CLIENT_PARAMS *params; +/* +/* char *tls_proxy_client_init_serialize(print_fn, buf, init_props) +/* ATTR_PRINT_COMMON_FN print_fn; +/* VSTRING *buf; +/* const TLS_CLIENT_INIT_PROPS *init_props; +/* DESCRIPTION +/* tls_proxy_client_param_from_config() initializes a TLS_CLIENT_PARAMS +/* structure from configuration parameters and returns its +/* argument. Strings are not copied. The result must therefore +/* not be passed to tls_proxy_client_param_free(). +/* +/* tls_proxy_client_param_serialize() and +/* tls_proxy_client_init_serialize() serialize the specified +/* object to a memory buffer, using the specified print function +/* (typically, attr_print_plain). The result can be used +/* determine whether there are any differences between instances +/* of the same object type. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include +#include + +/* Global library. */ + +#include + +/* TLS library. */ + +#include +#include + +/* tls_proxy_client_param_from_config - initialize TLS_CLIENT_PARAMS from configuration */ + +TLS_CLIENT_PARAMS *tls_proxy_client_param_from_config(TLS_CLIENT_PARAMS *params) +{ + TLS_PROXY_PARAMS(params, + tls_cnf_file = var_tls_cnf_file, + tls_cnf_name = var_tls_cnf_name, + tls_high_clist = var_tls_high_clist, + tls_medium_clist = var_tls_medium_clist, + tls_low_clist = var_tls_low_clist, + tls_export_clist = var_tls_export_clist, + tls_null_clist = var_tls_null_clist, + tls_eecdh_auto = var_tls_eecdh_auto, + tls_eecdh_strong = var_tls_eecdh_strong, + tls_eecdh_ultra = var_tls_eecdh_ultra, + tls_bug_tweaks = var_tls_bug_tweaks, + tls_ssl_options = var_tls_ssl_options, + tls_dane_digests = var_tls_dane_digests, + tls_mgr_service = var_tls_mgr_service, + tls_tkt_cipher = var_tls_tkt_cipher, + tls_daemon_rand_bytes = var_tls_daemon_rand_bytes, + tls_append_def_CA = var_tls_append_def_CA, + tls_bc_pkey_fprint = var_tls_bc_pkey_fprint, + tls_preempt_clist = var_tls_preempt_clist, + tls_multi_wildcard = var_tls_multi_wildcard); + return (params); +} + +/* tls_proxy_client_param_serialize - serialize TLS_CLIENT_PARAMS to string */ + +char *tls_proxy_client_param_serialize(ATTR_PRINT_COMMON_FN print_fn, + VSTRING *buf, + const TLS_CLIENT_PARAMS *params) +{ + const char myname[] = "tls_proxy_client_param_serialize"; + VSTREAM *mp; + + if ((mp = vstream_memopen(buf, O_WRONLY)) == 0 + || print_fn(mp, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_client_param_print, + (const void *) params), + ATTR_TYPE_END) != 0 + || vstream_fclose(mp) != 0) + msg_fatal("%s: can't serialize properties: %m", myname); + return (vstring_str(buf)); +} + +/* tls_proxy_client_init_serialize - serialize to string */ + +char *tls_proxy_client_init_serialize(ATTR_PRINT_COMMON_FN print_fn, + VSTRING *buf, + const TLS_CLIENT_INIT_PROPS *props) +{ + const char myname[] = "tls_proxy_client_init_serialize"; + VSTREAM *mp; + + if ((mp = vstream_memopen(buf, O_WRONLY)) == 0 + || print_fn(mp, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_client_init_print, + (const void *) props), + ATTR_TYPE_END) != 0 + || vstream_fclose(mp) != 0) + msg_fatal("%s: can't serialize properties: %m", myname); + return (vstring_str(buf)); +} + +#endif diff --git a/src/tls/tls_proxy_client_print.c b/src/tls/tls_proxy_client_print.c new file mode 100644 index 0000000..fe0b397 --- /dev/null +++ b/src/tls/tls_proxy_client_print.c @@ -0,0 +1,294 @@ +/*++ +/* NAME +/* tls_proxy_client_print 3 +/* SUMMARY +/* write TLS_CLIENT_XXX structures to stream +/* SYNOPSIS +/* #include +/* +/* int tls_proxy_client_param_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_COMMON_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* const void *ptr; +/* +/* int tls_proxy_client_init_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_COMMON_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* const void *ptr; +/* +/* int tls_proxy_client_start_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_COMMON_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* const void *ptr; +/* DESCRIPTION +/* tls_proxy_client_param_print() writes a TLS_CLIENT_PARAMS structure to +/* the named stream using the specified attribute print routine. +/* tls_proxy_client_param_print() is meant to be passed as a call-back to +/* attr_print(), thusly: +/* +/* SEND_ATTR_FUNC(tls_proxy_client_param_print, (const void *) param), ... +/* +/* tls_proxy_client_init_print() writes a full TLS_CLIENT_INIT_PROPS +/* structure to the named stream using the specified attribute +/* print routine. tls_proxy_client_init_print() is meant to +/* be passed as a call-back to attr_print(), thusly: +/* +/* SEND_ATTR_FUNC(tls_proxy_client_init_print, (const void *) init_props), ... +/* +/* tls_proxy_client_start_print() writes a TLS_CLIENT_START_PROPS +/* structure, without stream or file descriptor members, to +/* the named stream using the specified attribute print routine. +/* tls_proxy_client_start_print() is meant to be passed as a +/* call-back to attr_print(), thusly: +/* +/* SEND_ATTR_FUNC(tls_proxy_client_start_print, (const void *) start_props), ... +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* TLS library. */ + +#include +#include + + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* tls_proxy_client_param_print - send TLS_CLIENT_PARAMS over stream */ + +int tls_proxy_client_param_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp, + int flags, const void *ptr) +{ + const TLS_CLIENT_PARAMS *params = (const TLS_CLIENT_PARAMS *) ptr; + int ret; + + if (msg_verbose) + msg_info("begin tls_proxy_client_param_print"); + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(TLS_ATTR_CNF_FILE, params->tls_cnf_file), + SEND_ATTR_STR(TLS_ATTR_CNF_NAME, params->tls_cnf_name), + SEND_ATTR_STR(VAR_TLS_HIGH_CLIST, params->tls_high_clist), + SEND_ATTR_STR(VAR_TLS_MEDIUM_CLIST, + params->tls_medium_clist), + SEND_ATTR_STR(VAR_TLS_LOW_CLIST, params->tls_low_clist), + SEND_ATTR_STR(VAR_TLS_EXPORT_CLIST, + params->tls_export_clist), + SEND_ATTR_STR(VAR_TLS_NULL_CLIST, params->tls_null_clist), + SEND_ATTR_STR(VAR_TLS_EECDH_AUTO, params->tls_eecdh_auto), + SEND_ATTR_STR(VAR_TLS_EECDH_STRONG, + params->tls_eecdh_strong), + SEND_ATTR_STR(VAR_TLS_EECDH_ULTRA, + params->tls_eecdh_ultra), + SEND_ATTR_STR(VAR_TLS_BUG_TWEAKS, params->tls_bug_tweaks), + SEND_ATTR_STR(VAR_TLS_SSL_OPTIONS, + params->tls_ssl_options), + SEND_ATTR_STR(VAR_TLS_DANE_DIGESTS, + params->tls_dane_digests), + SEND_ATTR_STR(VAR_TLS_MGR_SERVICE, + params->tls_mgr_service), + SEND_ATTR_STR(VAR_TLS_TKT_CIPHER, params->tls_tkt_cipher), + SEND_ATTR_INT(VAR_TLS_DAEMON_RAND_BYTES, + params->tls_daemon_rand_bytes), + SEND_ATTR_INT(VAR_TLS_APPEND_DEF_CA, + params->tls_append_def_CA), + SEND_ATTR_INT(VAR_TLS_BC_PKEY_FPRINT, + params->tls_bc_pkey_fprint), + SEND_ATTR_INT(VAR_TLS_PREEMPT_CLIST, + params->tls_preempt_clist), + SEND_ATTR_INT(VAR_TLS_MULTI_WILDCARD, + params->tls_multi_wildcard), + ATTR_TYPE_END); + /* Do not flush the stream. */ + if (msg_verbose) + msg_info("tls_proxy_client_param_print ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_init_print - send TLS_CLIENT_INIT_PROPS over stream */ + +int tls_proxy_client_init_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp, + int flags, const void *ptr) +{ + const TLS_CLIENT_INIT_PROPS *props = (const TLS_CLIENT_INIT_PROPS *) ptr; + int ret; + + if (msg_verbose) + msg_info("begin tls_proxy_client_init_print"); + +#define STRING_OR_EMPTY(s) ((s) ? (s) : "") + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(TLS_ATTR_LOG_PARAM, + STRING_OR_EMPTY(props->log_param)), + SEND_ATTR_STR(TLS_ATTR_LOG_LEVEL, + STRING_OR_EMPTY(props->log_level)), + SEND_ATTR_INT(TLS_ATTR_VERIFYDEPTH, props->verifydepth), + SEND_ATTR_STR(TLS_ATTR_CACHE_TYPE, + STRING_OR_EMPTY(props->cache_type)), + SEND_ATTR_STR(TLS_ATTR_CHAIN_FILES, + STRING_OR_EMPTY(props->chain_files)), + SEND_ATTR_STR(TLS_ATTR_CERT_FILE, + STRING_OR_EMPTY(props->cert_file)), + SEND_ATTR_STR(TLS_ATTR_KEY_FILE, + STRING_OR_EMPTY(props->key_file)), + SEND_ATTR_STR(TLS_ATTR_DCERT_FILE, + STRING_OR_EMPTY(props->dcert_file)), + SEND_ATTR_STR(TLS_ATTR_DKEY_FILE, + STRING_OR_EMPTY(props->dkey_file)), + SEND_ATTR_STR(TLS_ATTR_ECCERT_FILE, + STRING_OR_EMPTY(props->eccert_file)), + SEND_ATTR_STR(TLS_ATTR_ECKEY_FILE, + STRING_OR_EMPTY(props->eckey_file)), + SEND_ATTR_STR(TLS_ATTR_CAFILE, + STRING_OR_EMPTY(props->CAfile)), + SEND_ATTR_STR(TLS_ATTR_CAPATH, + STRING_OR_EMPTY(props->CApath)), + SEND_ATTR_STR(TLS_ATTR_MDALG, + STRING_OR_EMPTY(props->mdalg)), + ATTR_TYPE_END); + /* Do not flush the stream. */ + if (msg_verbose) + msg_info("tls_proxy_client_init_print ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_tlsa_print - send TLS_TLSA over stream */ + +static int tls_proxy_client_tlsa_print(ATTR_PRINT_COMMON_FN print_fn, + VSTREAM *fp, int flags, const void *ptr) +{ + const TLS_TLSA *head = (const TLS_TLSA *) ptr; + const TLS_TLSA *tp; + int count; + int ret; + + for (tp = head, count = 0; tp != 0; tp = tp->next) + ++count; + if (msg_verbose) + msg_info("tls_proxy_client_tlsa_print count=%d", count); + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(TLS_ATTR_COUNT, count), + ATTR_TYPE_END); + + for (tp = head; ret == 0 && tp != 0; tp = tp->next) + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(TLS_ATTR_USAGE, tp->usage), + SEND_ATTR_INT(TLS_ATTR_SELECTOR, tp->selector), + SEND_ATTR_INT(TLS_ATTR_MTYPE, tp->mtype), + SEND_ATTR_DATA(TLS_ATTR_DATA, tp->length, tp->data), + ATTR_TYPE_END); + + /* Do not flush the stream. */ + if (msg_verbose) + msg_info("tls_proxy_client_tlsa_print ret=%d", count); + return (ret); +} + +/* tls_proxy_client_dane_print - send TLS_DANE over stream */ + +static int tls_proxy_client_dane_print(ATTR_PRINT_COMMON_FN print_fn, + VSTREAM *fp, int flags, const void *ptr) +{ + const TLS_DANE *dane = (const TLS_DANE *) ptr; + int ret; + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(TLS_ATTR_DANE, dane != 0), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("tls_proxy_client_dane_print dane=%d", dane != 0); + + if (ret == 0 && dane != 0) { + /* Send the base_domain and RRs, we don't need the other fields */ + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(TLS_ATTR_DOMAIN, + STRING_OR_EMPTY(dane->base_domain)), + SEND_ATTR_FUNC(tls_proxy_client_tlsa_print, + (const void *) dane->tlsa), + ATTR_TYPE_END); + } + /* Do not flush the stream. */ + if (msg_verbose) + msg_info("tls_proxy_client_dane_print ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_start_print - send TLS_CLIENT_START_PROPS over stream */ + +int tls_proxy_client_start_print(ATTR_PRINT_COMMON_FN print_fn, + VSTREAM *fp, int flags, const void *ptr) +{ + const TLS_CLIENT_START_PROPS *props = (const TLS_CLIENT_START_PROPS *) ptr; + int ret; + + if (msg_verbose) + msg_info("begin tls_proxy_client_start_print"); + +#define STRING_OR_EMPTY(s) ((s) ? (s) : "") + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout), + SEND_ATTR_INT(TLS_ATTR_TLS_LEVEL, props->tls_level), + SEND_ATTR_STR(TLS_ATTR_NEXTHOP, + STRING_OR_EMPTY(props->nexthop)), + SEND_ATTR_STR(TLS_ATTR_HOST, + STRING_OR_EMPTY(props->host)), + SEND_ATTR_STR(TLS_ATTR_NAMADDR, + STRING_OR_EMPTY(props->namaddr)), + SEND_ATTR_STR(TLS_ATTR_SNI, + STRING_OR_EMPTY(props->sni)), + SEND_ATTR_STR(TLS_ATTR_SERVERID, + STRING_OR_EMPTY(props->serverid)), + SEND_ATTR_STR(TLS_ATTR_HELO, + STRING_OR_EMPTY(props->helo)), + SEND_ATTR_STR(TLS_ATTR_PROTOCOLS, + STRING_OR_EMPTY(props->protocols)), + SEND_ATTR_STR(TLS_ATTR_CIPHER_GRADE, + STRING_OR_EMPTY(props->cipher_grade)), + SEND_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS, + STRING_OR_EMPTY(props->cipher_exclusions)), + SEND_ATTR_FUNC(argv_attr_print, + (const void *) props->matchargv), + SEND_ATTR_STR(TLS_ATTR_MDALG, + STRING_OR_EMPTY(props->mdalg)), + SEND_ATTR_FUNC(tls_proxy_client_dane_print, + (const void *) props->dane), + ATTR_TYPE_END); + /* Do not flush the stream. */ + if (msg_verbose) + msg_info("tls_proxy_client_start_print ret=%d", ret); + return (ret); +} + +#endif diff --git a/src/tls/tls_proxy_client_scan.c b/src/tls/tls_proxy_client_scan.c new file mode 100644 index 0000000..7083353 --- /dev/null +++ b/src/tls/tls_proxy_client_scan.c @@ -0,0 +1,496 @@ +/*++ +/* NAME +/* tls_proxy_client_scan 3 +/* SUMMARY +/* read TLS_CLIENT_XXX structures from stream +/* SYNOPSIS +/* #include +/* +/* int tls_proxy_client_param_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_COMMON_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* void tls_proxy_client_param_free(params) +/* TLS_CLIENT_PARAMS *params; +/* +/* int tls_proxy_client_init_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_COMMON_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* void tls_proxy_client_init_free(init_props) +/* TLS_CLIENT_INIT_PROPS *init_props; +/* +/* int tls_proxy_client_start_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_COMMON_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* void tls_proxy_client_start_free(start_props) +/* TLS_CLIENT_START_PROPS *start_props; +/* DESCRIPTION +/* tls_proxy_client_param_scan() reads a TLS_CLIENT_PARAMS structure from +/* the named stream using the specified attribute scan routine. +/* tls_proxy_client_param_scan() is meant to be passed as a call-back +/* function to attr_scan(), as shown below. +/* +/* tls_proxy_client_param_free() destroys a TLS_CLIENT_PARAMS structure +/* that was created by tls_proxy_client_param_scan(). +/* +/* TLS_CLIENT_PARAMS *param = 0; +/* ... +/* ... RECV_ATTR_FUNC(tls_proxy_client_param_scan, (void *) ¶m) +/* ... +/* if (param != 0) +/* tls_proxy_client_param_free(param); +/* +/* tls_proxy_client_init_scan() reads a full TLS_CLIENT_INIT_PROPS +/* structure from the named stream using the specified attribute +/* scan routine. tls_proxy_client_init_scan() is meant to be passed +/* as a call-back function to attr_scan(), as shown below. +/* +/* tls_proxy_client_init_free() destroys a TLS_CLIENT_INIT_PROPS +/* structure that was created by tls_proxy_client_init_scan(). +/* +/* TLS_CLIENT_INIT_PROPS *init_props = 0; +/* ... +/* ... RECV_ATTR_FUNC(tls_proxy_client_init_scan, (void *) &init_props) +/* ... +/* if (init_props != 0) +/* tls_proxy_client_init_free(init_props); +/* +/* tls_proxy_client_start_scan() reads a TLS_CLIENT_START_PROPS +/* structure, without the stream of file descriptor members, +/* from the named stream using the specified attribute scan +/* routine. tls_proxy_client_start_scan() is meant to be passed +/* as a call-back function to attr_scan(), as shown below. +/* +/* tls_proxy_client_start_free() destroys a TLS_CLIENT_START_PROPS +/* structure that was created by tls_proxy_client_start_scan(). +/* +/* TLS_CLIENT_START_PROPS *start_props = 0; +/* ... +/* ... RECV_ATTR_FUNC(tls_proxy_client_start_scan, (void *) &start_props) +/* ... +/* if (start_props != 0) +/* tls_proxy_client_start_free(start_props); +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* TLS library. */ + +#define TLS_INTERNAL +#include +#include + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* tls_proxy_client_param_free - destroy TLS_CLIENT_PARAMS structure */ + +void tls_proxy_client_param_free(TLS_CLIENT_PARAMS *params) +{ + myfree(params->tls_cnf_file); + myfree(params->tls_cnf_name); + myfree(params->tls_high_clist); + myfree(params->tls_medium_clist); + myfree(params->tls_low_clist); + myfree(params->tls_export_clist); + myfree(params->tls_null_clist); + myfree(params->tls_eecdh_auto); + myfree(params->tls_eecdh_strong); + myfree(params->tls_eecdh_ultra); + myfree(params->tls_bug_tweaks); + myfree(params->tls_ssl_options); + myfree(params->tls_dane_digests); + myfree(params->tls_mgr_service); + myfree(params->tls_tkt_cipher); + myfree((void *) params); +} + +/* tls_proxy_client_param_scan - receive TLS_CLIENT_PARAMS from stream */ + +int tls_proxy_client_param_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_CLIENT_PARAMS *params + = (TLS_CLIENT_PARAMS *) mymalloc(sizeof(*params)); + int ret; + VSTRING *cnf_file = vstring_alloc(25); + VSTRING *cnf_name = vstring_alloc(25); + VSTRING *tls_high_clist = vstring_alloc(25); + VSTRING *tls_medium_clist = vstring_alloc(25); + VSTRING *tls_low_clist = vstring_alloc(25); + VSTRING *tls_export_clist = vstring_alloc(25); + VSTRING *tls_null_clist = vstring_alloc(25); + VSTRING *tls_eecdh_auto = vstring_alloc(25); + VSTRING *tls_eecdh_strong = vstring_alloc(25); + VSTRING *tls_eecdh_ultra = vstring_alloc(25); + VSTRING *tls_bug_tweaks = vstring_alloc(25); + VSTRING *tls_ssl_options = vstring_alloc(25); + VSTRING *tls_dane_digests = vstring_alloc(25); + VSTRING *tls_mgr_service = vstring_alloc(25); + VSTRING *tls_tkt_cipher = vstring_alloc(25); + + if (msg_verbose) + msg_info("begin tls_proxy_client_param_scan"); + + /* + * Note: memset() is not a portable way to initialize non-integer types. + */ + memset(params, 0, sizeof(*params)); + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(TLS_ATTR_CNF_FILE, cnf_file), + RECV_ATTR_STR(TLS_ATTR_CNF_NAME, cnf_name), + RECV_ATTR_STR(VAR_TLS_HIGH_CLIST, tls_high_clist), + RECV_ATTR_STR(VAR_TLS_MEDIUM_CLIST, tls_medium_clist), + RECV_ATTR_STR(VAR_TLS_LOW_CLIST, tls_low_clist), + RECV_ATTR_STR(VAR_TLS_EXPORT_CLIST, tls_export_clist), + RECV_ATTR_STR(VAR_TLS_NULL_CLIST, tls_null_clist), + RECV_ATTR_STR(VAR_TLS_EECDH_AUTO, tls_eecdh_auto), + RECV_ATTR_STR(VAR_TLS_EECDH_STRONG, tls_eecdh_strong), + RECV_ATTR_STR(VAR_TLS_EECDH_ULTRA, tls_eecdh_ultra), + RECV_ATTR_STR(VAR_TLS_BUG_TWEAKS, tls_bug_tweaks), + RECV_ATTR_STR(VAR_TLS_SSL_OPTIONS, tls_ssl_options), + RECV_ATTR_STR(VAR_TLS_DANE_DIGESTS, tls_dane_digests), + RECV_ATTR_STR(VAR_TLS_MGR_SERVICE, tls_mgr_service), + RECV_ATTR_STR(VAR_TLS_TKT_CIPHER, tls_tkt_cipher), + RECV_ATTR_INT(VAR_TLS_DAEMON_RAND_BYTES, + ¶ms->tls_daemon_rand_bytes), + RECV_ATTR_INT(VAR_TLS_APPEND_DEF_CA, + ¶ms->tls_append_def_CA), + RECV_ATTR_INT(VAR_TLS_BC_PKEY_FPRINT, + ¶ms->tls_bc_pkey_fprint), + RECV_ATTR_INT(VAR_TLS_PREEMPT_CLIST, + ¶ms->tls_preempt_clist), + RECV_ATTR_INT(VAR_TLS_MULTI_WILDCARD, + ¶ms->tls_multi_wildcard), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + params->tls_cnf_file = vstring_export(cnf_file); + params->tls_cnf_name = vstring_export(cnf_name); + params->tls_high_clist = vstring_export(tls_high_clist); + params->tls_medium_clist = vstring_export(tls_medium_clist); + params->tls_low_clist = vstring_export(tls_low_clist); + params->tls_export_clist = vstring_export(tls_export_clist); + params->tls_null_clist = vstring_export(tls_null_clist); + params->tls_eecdh_auto = vstring_export(tls_eecdh_auto); + params->tls_eecdh_strong = vstring_export(tls_eecdh_strong); + params->tls_eecdh_ultra = vstring_export(tls_eecdh_ultra); + params->tls_bug_tweaks = vstring_export(tls_bug_tweaks); + params->tls_ssl_options = vstring_export(tls_ssl_options); + params->tls_dane_digests = vstring_export(tls_dane_digests); + params->tls_mgr_service = vstring_export(tls_mgr_service); + params->tls_tkt_cipher = vstring_export(tls_tkt_cipher); + + ret = (ret == 20 ? 1 : -1); + if (ret != 1) { + tls_proxy_client_param_free(params); + params = 0; + } + *(TLS_CLIENT_PARAMS **) ptr = params; + if (msg_verbose) + msg_info("tls_proxy_client_param_scan ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_init_free - destroy TLS_CLIENT_INIT_PROPS structure */ + +void tls_proxy_client_init_free(TLS_CLIENT_INIT_PROPS *props) +{ + myfree((void *) props->log_param); + myfree((void *) props->log_level); + myfree((void *) props->cache_type); + myfree((void *) props->chain_files); + myfree((void *) props->cert_file); + myfree((void *) props->key_file); + myfree((void *) props->dcert_file); + myfree((void *) props->dkey_file); + myfree((void *) props->eccert_file); + myfree((void *) props->eckey_file); + myfree((void *) props->CAfile); + myfree((void *) props->CApath); + myfree((void *) props->mdalg); + myfree((void *) props); +} + +/* tls_proxy_client_init_scan - receive TLS_CLIENT_INIT_PROPS from stream */ + +int tls_proxy_client_init_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_CLIENT_INIT_PROPS *props + = (TLS_CLIENT_INIT_PROPS *) mymalloc(sizeof(*props)); + int ret; + VSTRING *log_param = vstring_alloc(25); + VSTRING *log_level = vstring_alloc(25); + VSTRING *cache_type = vstring_alloc(25); + VSTRING *chain_files = vstring_alloc(25); + VSTRING *cert_file = vstring_alloc(25); + VSTRING *key_file = vstring_alloc(25); + VSTRING *dcert_file = vstring_alloc(25); + VSTRING *dkey_file = vstring_alloc(25); + VSTRING *eccert_file = vstring_alloc(25); + VSTRING *eckey_file = vstring_alloc(25); + VSTRING *CAfile = vstring_alloc(25); + VSTRING *CApath = vstring_alloc(25); + VSTRING *mdalg = vstring_alloc(25); + + if (msg_verbose) + msg_info("begin tls_proxy_client_init_scan"); + + /* + * Note: memset() is not a portable way to initialize non-integer types. + */ + memset(props, 0, sizeof(*props)); + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(TLS_ATTR_LOG_PARAM, log_param), + RECV_ATTR_STR(TLS_ATTR_LOG_LEVEL, log_level), + RECV_ATTR_INT(TLS_ATTR_VERIFYDEPTH, &props->verifydepth), + RECV_ATTR_STR(TLS_ATTR_CACHE_TYPE, cache_type), + RECV_ATTR_STR(TLS_ATTR_CHAIN_FILES, chain_files), + RECV_ATTR_STR(TLS_ATTR_CERT_FILE, cert_file), + RECV_ATTR_STR(TLS_ATTR_KEY_FILE, key_file), + RECV_ATTR_STR(TLS_ATTR_DCERT_FILE, dcert_file), + RECV_ATTR_STR(TLS_ATTR_DKEY_FILE, dkey_file), + RECV_ATTR_STR(TLS_ATTR_ECCERT_FILE, eccert_file), + RECV_ATTR_STR(TLS_ATTR_ECKEY_FILE, eckey_file), + RECV_ATTR_STR(TLS_ATTR_CAFILE, CAfile), + RECV_ATTR_STR(TLS_ATTR_CAPATH, CApath), + RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + props->log_param = vstring_export(log_param); + props->log_level = vstring_export(log_level); + props->cache_type = vstring_export(cache_type); + props->chain_files = vstring_export(chain_files); + props->cert_file = vstring_export(cert_file); + props->key_file = vstring_export(key_file); + props->dcert_file = vstring_export(dcert_file); + props->dkey_file = vstring_export(dkey_file); + props->eccert_file = vstring_export(eccert_file); + props->eckey_file = vstring_export(eckey_file); + props->CAfile = vstring_export(CAfile); + props->CApath = vstring_export(CApath); + props->mdalg = vstring_export(mdalg); + ret = (ret == 14 ? 1 : -1); + if (ret != 1) { + tls_proxy_client_init_free(props); + props = 0; + } + *(TLS_CLIENT_INIT_PROPS **) ptr = props; + if (msg_verbose) + msg_info("tls_proxy_client_init_scan ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_start_free - destroy TLS_CLIENT_START_PROPS structure */ + +void tls_proxy_client_start_free(TLS_CLIENT_START_PROPS *props) +{ + myfree((void *) props->nexthop); + myfree((void *) props->host); + myfree((void *) props->namaddr); + myfree((void *) props->sni); + myfree((void *) props->serverid); + myfree((void *) props->helo); + myfree((void *) props->protocols); + myfree((void *) props->cipher_grade); + myfree((void *) props->cipher_exclusions); + if (props->matchargv) + argv_free((ARGV *) props->matchargv); + myfree((void *) props->mdalg); + if (props->dane) + tls_dane_free((TLS_DANE *) props->dane); + myfree((void *) props); +} + +/* tls_proxy_client_tlsa_scan - receive TLS_TLSA from stream */ + +static int tls_proxy_client_tlsa_scan(ATTR_SCAN_COMMON_FN scan_fn, + VSTREAM *fp, int flags, void *ptr) +{ + static VSTRING *data; + TLS_TLSA *head; + int count; + int ret; + + if (data == 0) + data = vstring_alloc(64); + + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(TLS_ATTR_COUNT, &count), + ATTR_TYPE_END); + if (ret == 1 && msg_verbose) + msg_info("tls_proxy_client_tlsa_scan count=%d", count); + + for (head = 0; ret == 1 && count > 0; --count) { + int u, s, m; + + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(TLS_ATTR_USAGE, &u), + RECV_ATTR_INT(TLS_ATTR_SELECTOR, &s), + RECV_ATTR_INT(TLS_ATTR_MTYPE, &m), + RECV_ATTR_DATA(TLS_ATTR_DATA, data), + ATTR_TYPE_END); + if (ret == 4) { + ret = 1; + /* This makes a copy of the static vstring content */ + head = tlsa_prepend(head, u, s, m, (unsigned char *) STR(data), + LEN(data)); + } else + ret = -1; + } + + if (ret != 1) { + tls_tlsa_free(head); + head = 0; + } + *(TLS_TLSA **) ptr = head; + if (msg_verbose) + msg_info("tls_proxy_client_tlsa_scan ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_dane_scan - receive TLS_DANE from stream */ + +static int tls_proxy_client_dane_scan(ATTR_SCAN_COMMON_FN scan_fn, + VSTREAM *fp, int flags, void *ptr) +{ + TLS_DANE *dane = 0; + int ret; + int have_dane = 0; + + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(TLS_ATTR_DANE, &have_dane), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("tls_proxy_client_dane_scan have_dane=%d", have_dane); + + if (ret == 1 && have_dane) { + VSTRING *base_domain = vstring_alloc(25); + + dane = tls_dane_alloc(); + /* We only need the base domain and TLSA RRs */ + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(TLS_ATTR_DOMAIN, base_domain), + RECV_ATTR_FUNC(tls_proxy_client_tlsa_scan, + &dane->tlsa), + ATTR_TYPE_END); + + /* Always construct a well-formed structure. */ + dane->base_domain = vstring_export(base_domain); + ret = (ret == 2 ? 1 : -1); + if (ret != 1) { + tls_dane_free(dane); + dane = 0; + } + } + *(TLS_DANE **) ptr = dane; + if (msg_verbose) + msg_info("tls_proxy_client_dane_scan ret=%d", ret); + return (ret); +} + +/* tls_proxy_client_start_scan - receive TLS_CLIENT_START_PROPS from stream */ + +int tls_proxy_client_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_CLIENT_START_PROPS *props + = (TLS_CLIENT_START_PROPS *) mymalloc(sizeof(*props)); + int ret; + VSTRING *nexthop = vstring_alloc(25); + VSTRING *host = vstring_alloc(25); + VSTRING *namaddr = vstring_alloc(25); + VSTRING *sni = vstring_alloc(25); + VSTRING *serverid = vstring_alloc(25); + VSTRING *helo = vstring_alloc(25); + VSTRING *protocols = vstring_alloc(25); + VSTRING *cipher_grade = vstring_alloc(25); + VSTRING *cipher_exclusions = vstring_alloc(25); + VSTRING *mdalg = vstring_alloc(25); + + if (msg_verbose) + msg_info("begin tls_proxy_client_start_scan"); + + /* + * Note: memset() is not a portable way to initialize non-integer types. + */ + memset(props, 0, sizeof(*props)); + props->ctx = 0; + props->stream = 0; + props->fd = -1; + props->dane = 0; /* scan_fn may return early */ + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout), + RECV_ATTR_INT(TLS_ATTR_TLS_LEVEL, &props->tls_level), + RECV_ATTR_STR(TLS_ATTR_NEXTHOP, nexthop), + RECV_ATTR_STR(TLS_ATTR_HOST, host), + RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr), + RECV_ATTR_STR(TLS_ATTR_SNI, sni), + RECV_ATTR_STR(TLS_ATTR_SERVERID, serverid), + RECV_ATTR_STR(TLS_ATTR_HELO, helo), + RECV_ATTR_STR(TLS_ATTR_PROTOCOLS, protocols), + RECV_ATTR_STR(TLS_ATTR_CIPHER_GRADE, cipher_grade), + RECV_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS, + cipher_exclusions), + RECV_ATTR_FUNC(argv_attr_scan, &props->matchargv), + RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg), + RECV_ATTR_FUNC(tls_proxy_client_dane_scan, + &props->dane), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + props->nexthop = vstring_export(nexthop); + props->host = vstring_export(host); + props->namaddr = vstring_export(namaddr); + props->sni = vstring_export(sni); + props->serverid = vstring_export(serverid); + props->helo = vstring_export(helo); + props->protocols = vstring_export(protocols); + props->cipher_grade = vstring_export(cipher_grade); + props->cipher_exclusions = vstring_export(cipher_exclusions); + props->mdalg = vstring_export(mdalg); + ret = (ret == 14 ? 1 : -1); + if (ret != 1) { + tls_proxy_client_start_free(props); + props = 0; + } + *(TLS_CLIENT_START_PROPS **) ptr = props; + if (msg_verbose) + msg_info("tls_proxy_client_start_scan ret=%d", ret); + return (ret); +} + +#endif diff --git a/src/tls/tls_proxy_clnt.c b/src/tls/tls_proxy_clnt.c new file mode 100644 index 0000000..ca6a2e4 --- /dev/null +++ b/src/tls/tls_proxy_clnt.c @@ -0,0 +1,300 @@ +/*++ +/* NAME +/* tlsproxy_clnt 3 +/* SUMMARY +/* tlsproxy(8) client support +/* SYNOPSIS +/* #include +/* +/* VSTREAM *tls_proxy_open(service, flags, peer_stream, peer_addr, +/* peer_port, handshake_timeout, session_timeout, +/* serverid, tls_params, init_props, start_props) +/* const char *service; +/* int flags; +/* VSTREAM *peer_stream; +/* const char *peer_addr; +/* const char *peer_port; +/* int handshake_timeout; +/* int session_timeout; +/* const char *serverid; +/* void *tls_params; +/* void *init_props; +/* void *start_props; +/* +/* TLS_SESS_STATE *tls_proxy_context_receive(proxy_stream) +/* VSTREAM *proxy_stream; +/* AUXILIARY FUNCTIONS +/* VSTREAM *tls_proxy_legacy_open(service, flags, peer_stream, +/* peer_addr, peer_port, +/* timeout, serverid) +/* const char *service; +/* int flags; +/* VSTREAM *peer_stream; +/* const char *peer_addr; +/* const char *peer_port; +/* int timeout; +/* const char *serverid; +/* DESCRIPTION +/* tls_proxy_open() prepares for inserting the tlsproxy(8) +/* daemon between the current process and a remote peer (the +/* actual insert operation is described in the next paragraph). +/* The result value is a null pointer on failure. The peer_stream +/* is not closed. The resulting proxy stream is single-buffered. +/* +/* After this, it is a good idea to use the CA_VSTREAM_CTL_SWAP_FD +/* request to swap the file descriptors between the plaintext +/* peer_stream and the proxy stream from tls_proxy_open(). +/* This avoids the loss of application-configurable VSTREAM +/* attributes on the plaintext peer_stream (such as longjmp +/* buffer, timeout, etc.). Once the file descriptors are +/* swapped, the proxy stream should be closed. +/* +/* tls_proxy_context_receive() receives the TLS context object +/* for the named proxy stream. This function must be called +/* only if the TLS_PROXY_SEND_CONTEXT flag was specified in +/* the tls_proxy_open() call. Note that this TLS context object +/* is not compatible with tls_session_free(). It must be given +/* to tls_proxy_context_free() instead. +/* +/* After this, the proxy_stream is ready for plain-text I/O. +/* +/* tls_proxy_legacy_open() is a backwards-compatibility feature +/* that provides a historical interface. +/* +/* Arguments: +/* .IP service +/* The (base) name of the tlsproxy service. +/* .IP flags +/* Bit-wise OR of: +/* .RS +/* .IP TLS_PROXY_FLAG_ROLE_SERVER +/* Request the TLS server proxy role. +/* .IP TLS_PROXY_FLAG_ROLE_CLIENT +/* Request the TLS client proxy role. +/* .IP TLS_PROXY_FLAG_SEND_CONTEXT +/* Send the TLS context object. +/* .RE +/* .IP peer_stream +/* Stream that connects the current process to a remote peer. +/* .IP peer_addr +/* Printable IP address of the remote peer_stream endpoint. +/* .IP peer_port +/* Printable TCP port of the remote peer_stream endpoint. +/* .IP handshake_timeout +/* Time limit that the tlsproxy(8) daemon should use during +/* the TLS handshake. +/* .IP session_timeout +/* Time limit that the tlsproxy(8) daemon should use after the +/* TLS handshake. +/* .IP serverid +/* Unique service identifier. +/* .IP tls_params +/* Pointer to TLS_CLIENT_PARAMS or TLS_SERVER_PARAMS. +/* .IP init_props +/* Pointer to TLS_CLIENT_INIT_PROPS or TLS_SERVER_INIT_PROPS. +/* .IP start_props +/* Pointer to TLS_CLIENT_START_PROPS or TLS_SERVER_START_PROPS. +/* .IP proxy_stream +/* Stream from tls_proxy_open(). +/* .IP tls_context +/* TLS session object from tls_proxy_context_receive(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include + +/* TLS library-specific. */ + +#include +#include + +#define TLSPROXY_INIT_TIMEOUT 10 + +/* SLMs. */ + +#define STR vstring_str + +/* tls_proxy_open - open negotiations with TLS proxy */ + +VSTREAM *tls_proxy_open(const char *service, int flags, + VSTREAM *peer_stream, + const char *peer_addr, + const char *peer_port, + int handshake_timeout, + int session_timeout, + const char *serverid, + void *tls_params, + void *init_props, + void *start_props) +{ + const char myname[] = "tls_proxy_open"; + VSTREAM *tlsproxy_stream; + int status; + int fd; + static VSTRING *tlsproxy_service = 0; + static VSTRING *remote_endpt = 0; + + /* + * Initialize. + */ + if (tlsproxy_service == 0) { + tlsproxy_service = vstring_alloc(20); + remote_endpt = vstring_alloc(20); + } + + /* + * Connect to the tlsproxy(8) daemon. + */ + vstring_sprintf(tlsproxy_service, "%s/%s", MAIL_CLASS_PRIVATE, service); + if ((fd = LOCAL_CONNECT(STR(tlsproxy_service), BLOCKING, + TLSPROXY_INIT_TIMEOUT)) < 0) { + msg_warn("connect to %s service: %m", STR(tlsproxy_service)); + return (0); + } + + /* + * Initial handshake. Send common data attributes now, and send the + * remote peer file descriptor in a later transaction. + */ + tlsproxy_stream = vstream_fdopen(fd, O_RDWR); + if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSPROXY), + ATTR_TYPE_END) != 0) { + msg_warn("error receiving %s service initial response", + STR(tlsproxy_service)); + vstream_fclose(tlsproxy_stream); + return (0); + } + vstring_sprintf(remote_endpt, "[%s]:%s", peer_addr, peer_port); + attr_print(tlsproxy_stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, STR(remote_endpt)), + SEND_ATTR_INT(TLS_ATTR_FLAGS, flags), + SEND_ATTR_INT(TLS_ATTR_TIMEOUT, handshake_timeout), + SEND_ATTR_INT(TLS_ATTR_TIMEOUT, session_timeout), + SEND_ATTR_STR(TLS_ATTR_SERVERID, serverid), + ATTR_TYPE_END); + /* Do not flush the stream yet. */ + if (vstream_ferror(tlsproxy_stream) != 0) { + msg_warn("error sending request to %s service: %m", + STR(tlsproxy_service)); + vstream_fclose(tlsproxy_stream); + return (0); + } + switch (flags & (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_ROLE_SERVER)) { + case TLS_PROXY_FLAG_ROLE_CLIENT: + attr_print(tlsproxy_stream, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_client_param_print, tls_params), + SEND_ATTR_FUNC(tls_proxy_client_init_print, init_props), + SEND_ATTR_FUNC(tls_proxy_client_start_print, start_props), + ATTR_TYPE_END); + break; + case TLS_PROXY_FLAG_ROLE_SERVER: +#if 0 + attr_print(tlsproxy_stream, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_server_param_print, tls_params), + SEND_ATTR_FUNC(tls_proxy_server_init_print, init_props), + SEND_ATTR_FUNC(tls_proxy_server_start_print, start_props), + ATTR_TYPE_END); +#endif + break; + default: + msg_panic("%s: bad flags: 0x%x", myname, flags); + } + if (vstream_fflush(tlsproxy_stream) != 0) { + msg_warn("error sending request to %s service: %m", + STR(tlsproxy_service)); + vstream_fclose(tlsproxy_stream); + return (0); + } + + /* + * Receive the "TLS is available" indication. + * + * This may seem out of order, but we must have a read transaction between + * sending the request attributes and sending the plaintext file + * descriptor. We can't assume UNIX-domain socket semantics here. + */ + if (attr_scan(tlsproxy_stream, ATTR_FLAG_STRICT, + RECV_ATTR_INT(MAIL_ATTR_STATUS, &status), + /* TODO: informative message. */ + ATTR_TYPE_END) != 1 || status == 0) { + + /* + * The TLS proxy reports that the TLS engine is not available (due to + * configuration error, or other causes). + */ + msg_warn("%s service role \"%s\" is not available", + STR(tlsproxy_service), + (flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "server" : + (flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "client" : + "bogus role"); + vstream_fclose(tlsproxy_stream); + return (0); + } + + /* + * Send the remote peer file descriptor. + */ + if (LOCAL_SEND_FD(vstream_fileno(tlsproxy_stream), + vstream_fileno(peer_stream)) < 0) { + + /* + * Some error: drop the TLS proxy stream. + */ + msg_warn("sending file handle to %s service: %m", + STR(tlsproxy_service)); + vstream_fclose(tlsproxy_stream); + return (0); + } + return (tlsproxy_stream); +} + + +/* tls_proxy_context_receive - receive TLS session object from tlsproxy(8) */ + +TLS_SESS_STATE *tls_proxy_context_receive(VSTREAM *proxy_stream) +{ + TLS_SESS_STATE *tls_context = 0; + + if (attr_scan(proxy_stream, ATTR_FLAG_STRICT, + RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) &tls_context), + ATTR_TYPE_END) != 1) { + if (tls_context) + tls_proxy_context_free(tls_context); + return (0); + } else { + return (tls_context); + } +} + +#endif diff --git a/src/tls/tls_proxy_context_print.c b/src/tls/tls_proxy_context_print.c new file mode 100644 index 0000000..04123cb --- /dev/null +++ b/src/tls/tls_proxy_context_print.c @@ -0,0 +1,114 @@ +/*++ +/* NAME +/* tls_proxy_context_print +/* SUMMARY +/* write TLS_ATTR_STATE structure to stream +/* SYNOPSIS +/* #include +/* +/* int tls_proxy_context_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_COMMON_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* const void *ptr; +/* DESCRIPTION +/* tls_proxy_context_print() writes the public members of a +/* TLS_ATTR_STATE structure to the named stream using the +/* specified attribute print routine. tls_proxy_context_print() +/* is meant to be passed as a call-back to attr_print(), thusly: +/* +/* ... SEND_ATTR_FUNC(tls_proxy_context_print, (const void *) tls_context), ... +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include + +/* TLS library. */ + +#include +#include + +/* tls_proxy_context_print - send TLS session state over stream */ + +int tls_proxy_context_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp, + int flags, const void *ptr) +{ + const TLS_SESS_STATE *tp = (const TLS_SESS_STATE *) ptr; + int ret; + +#define STRING_OR_EMPTY(s) ((s) ? (s) : "") + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(TLS_ATTR_PEER_CN, + STRING_OR_EMPTY(tp->peer_CN)), + SEND_ATTR_STR(TLS_ATTR_ISSUER_CN, + STRING_OR_EMPTY(tp->issuer_CN)), + SEND_ATTR_STR(TLS_ATTR_PEER_CERT_FPT, + STRING_OR_EMPTY(tp->peer_cert_fprint)), + SEND_ATTR_STR(TLS_ATTR_PEER_PKEY_FPT, + STRING_OR_EMPTY(tp->peer_pkey_fprint)), + SEND_ATTR_INT(TLS_ATTR_SEC_LEVEL, + tp->level), + SEND_ATTR_INT(TLS_ATTR_PEER_STATUS, + tp->peer_status), + SEND_ATTR_STR(TLS_ATTR_CIPHER_PROTOCOL, + STRING_OR_EMPTY(tp->protocol)), + SEND_ATTR_STR(TLS_ATTR_CIPHER_NAME, + STRING_OR_EMPTY(tp->cipher_name)), + SEND_ATTR_INT(TLS_ATTR_CIPHER_USEBITS, + tp->cipher_usebits), + SEND_ATTR_INT(TLS_ATTR_CIPHER_ALGBITS, + tp->cipher_algbits), + SEND_ATTR_STR(TLS_ATTR_KEX_NAME, + STRING_OR_EMPTY(tp->kex_name)), + SEND_ATTR_STR(TLS_ATTR_KEX_CURVE, + STRING_OR_EMPTY(tp->kex_curve)), + SEND_ATTR_INT(TLS_ATTR_KEX_BITS, + tp->kex_bits), + SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME, + STRING_OR_EMPTY(tp->clnt_sig_name)), + SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE, + STRING_OR_EMPTY(tp->clnt_sig_curve)), + SEND_ATTR_INT(TLS_ATTR_CLNT_SIG_BITS, + tp->clnt_sig_bits), + SEND_ATTR_STR(TLS_ATTR_CLNT_SIG_DGST, + STRING_OR_EMPTY(tp->clnt_sig_dgst)), + SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_NAME, + STRING_OR_EMPTY(tp->srvr_sig_name)), + SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_CURVE, + STRING_OR_EMPTY(tp->srvr_sig_curve)), + SEND_ATTR_INT(TLS_ATTR_SRVR_SIG_BITS, + tp->srvr_sig_bits), + SEND_ATTR_STR(TLS_ATTR_SRVR_SIG_DGST, + STRING_OR_EMPTY(tp->srvr_sig_dgst)), + SEND_ATTR_STR(TLS_ATTR_NAMADDR, + STRING_OR_EMPTY(tp->namaddr)), + ATTR_TYPE_END); + /* Do not flush the stream. */ + return (ret); +} + +#endif diff --git a/src/tls/tls_proxy_context_scan.c b/src/tls/tls_proxy_context_scan.c new file mode 100644 index 0000000..1d463ad --- /dev/null +++ b/src/tls/tls_proxy_context_scan.c @@ -0,0 +1,190 @@ +/*++ +/* NAME +/* tls_proxy_context_scan +/* SUMMARY +/* read TLS session state from stream +/* SYNOPSIS +/* #include +/* +/* int tls_proxy_context_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_COMMON_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* void tls_proxy_context_free(tls_context) +/* TLS_SESS_STATE *tls_context; +/* DESCRIPTION +/* tls_proxy_context_scan() reads the public members of a +/* TLS_ATTR_STATE structure from the named stream using the +/* specified attribute scan routine. tls_proxy_context_scan() +/* is meant to be passed as a call-back to attr_scan() as shown +/* below. +/* +/* tls_proxy_context_free() destroys a TLS context object that +/* was received with tls_proxy_context_scan(). +/* +/* TLS_ATTR_STATE *tls_context = 0; +/* ... +/* ... RECV_ATTR_FUNC(tls_proxy_context_scan, (void *) &tls_context), ... +/* ... +/* if (tls_context) +/* tls_proxy_context_free(tls_context); +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include +#include + +/* TLS library. */ + +#include +#include + +/* tls_proxy_context_scan - receive TLS session state from stream */ + +int tls_proxy_context_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_SESS_STATE *tls_context + = (TLS_SESS_STATE *) mymalloc(sizeof(*tls_context));; + int ret; + VSTRING *peer_CN = vstring_alloc(25); + VSTRING *issuer_CN = vstring_alloc(25); + VSTRING *peer_cert_fprint = vstring_alloc(60); /* 60 for SHA-1 */ + VSTRING *peer_pkey_fprint = vstring_alloc(60); /* 60 for SHA-1 */ + VSTRING *protocol = vstring_alloc(25); + VSTRING *cipher_name = vstring_alloc(25); + VSTRING *kex_name = vstring_alloc(25); + VSTRING *kex_curve = vstring_alloc(25); + VSTRING *clnt_sig_name = vstring_alloc(25); + VSTRING *clnt_sig_curve = vstring_alloc(25); + VSTRING *clnt_sig_dgst = vstring_alloc(25); + VSTRING *srvr_sig_name = vstring_alloc(25); + VSTRING *srvr_sig_curve = vstring_alloc(25); + VSTRING *srvr_sig_dgst = vstring_alloc(25); + VSTRING *namaddr = vstring_alloc(100); + + if (msg_verbose) + msg_info("begin tls_proxy_context_scan"); + + /* + * Note: memset() is not a portable way to initialize non-integer types. + */ + memset(tls_context, 0, sizeof(*tls_context)); + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(TLS_ATTR_PEER_CN, peer_CN), + RECV_ATTR_STR(TLS_ATTR_ISSUER_CN, issuer_CN), + RECV_ATTR_STR(TLS_ATTR_PEER_CERT_FPT, peer_cert_fprint), + RECV_ATTR_STR(TLS_ATTR_PEER_PKEY_FPT, peer_pkey_fprint), + RECV_ATTR_INT(TLS_ATTR_SEC_LEVEL, + &tls_context->level), + RECV_ATTR_INT(TLS_ATTR_PEER_STATUS, + &tls_context->peer_status), + RECV_ATTR_STR(TLS_ATTR_CIPHER_PROTOCOL, protocol), + RECV_ATTR_STR(TLS_ATTR_CIPHER_NAME, cipher_name), + RECV_ATTR_INT(TLS_ATTR_CIPHER_USEBITS, + &tls_context->cipher_usebits), + RECV_ATTR_INT(TLS_ATTR_CIPHER_ALGBITS, + &tls_context->cipher_algbits), + RECV_ATTR_STR(TLS_ATTR_KEX_NAME, kex_name), + RECV_ATTR_STR(TLS_ATTR_KEX_CURVE, kex_curve), + RECV_ATTR_INT(TLS_ATTR_KEX_BITS, &tls_context->kex_bits), + RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_NAME, clnt_sig_name), + RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_CURVE, clnt_sig_curve), + RECV_ATTR_INT(TLS_ATTR_CLNT_SIG_BITS, &tls_context->clnt_sig_bits), + RECV_ATTR_STR(TLS_ATTR_CLNT_SIG_DGST, clnt_sig_dgst), + RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_NAME, srvr_sig_name), + RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_CURVE, srvr_sig_curve), + RECV_ATTR_INT(TLS_ATTR_SRVR_SIG_BITS, &tls_context->srvr_sig_bits), + RECV_ATTR_STR(TLS_ATTR_SRVR_SIG_DGST, srvr_sig_dgst), + RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + tls_context->peer_CN = vstring_export(peer_CN); + tls_context->issuer_CN = vstring_export(issuer_CN); + tls_context->peer_cert_fprint = vstring_export(peer_cert_fprint); + tls_context->peer_pkey_fprint = vstring_export(peer_pkey_fprint); + tls_context->protocol = vstring_export(protocol); + tls_context->cipher_name = vstring_export(cipher_name); + tls_context->kex_name = vstring_export(kex_name); + tls_context->kex_curve = vstring_export(kex_curve); + tls_context->clnt_sig_name = vstring_export(clnt_sig_name); + tls_context->clnt_sig_curve = vstring_export(clnt_sig_curve); + tls_context->clnt_sig_dgst = vstring_export(clnt_sig_dgst); + tls_context->srvr_sig_name = vstring_export(srvr_sig_name); + tls_context->srvr_sig_curve = vstring_export(srvr_sig_curve); + tls_context->srvr_sig_dgst = vstring_export(srvr_sig_dgst); + tls_context->namaddr = vstring_export(namaddr); + ret = (ret == 22 ? 1 : -1); + if (ret != 1) { + tls_proxy_context_free(tls_context); + tls_context = 0; + } + *(TLS_SESS_STATE **) ptr = tls_context; + if (msg_verbose) + msg_info("tls_proxy_context_scan ret=%d", ret); + return (ret); +} + +/* tls_proxy_context_free - destroy object from tls_proxy_context_receive() */ + +void tls_proxy_context_free(TLS_SESS_STATE *tls_context) +{ + if (tls_context->peer_CN) + myfree(tls_context->peer_CN); + if (tls_context->issuer_CN) + myfree(tls_context->issuer_CN); + if (tls_context->peer_cert_fprint) + myfree(tls_context->peer_cert_fprint); + if (tls_context->peer_pkey_fprint) + myfree(tls_context->peer_pkey_fprint); + if (tls_context->protocol) + myfree((void *) tls_context->protocol); + if (tls_context->cipher_name) + myfree((void *) tls_context->cipher_name); + if (tls_context->kex_name) + myfree((void *) tls_context->kex_name); + if (tls_context->kex_curve) + myfree((void *) tls_context->kex_curve); + if (tls_context->clnt_sig_name) + myfree((void *) tls_context->clnt_sig_name); + if (tls_context->clnt_sig_curve) + myfree((void *) tls_context->clnt_sig_curve); + if (tls_context->clnt_sig_dgst) + myfree((void *) tls_context->clnt_sig_dgst); + if (tls_context->srvr_sig_name) + myfree((void *) tls_context->srvr_sig_name); + if (tls_context->srvr_sig_curve) + myfree((void *) tls_context->srvr_sig_curve); + if (tls_context->srvr_sig_dgst) + myfree((void *) tls_context->srvr_sig_dgst); + if (tls_context->namaddr) + myfree((void *) tls_context->namaddr); + myfree((void *) tls_context); +} + +#endif diff --git a/src/tls/tls_proxy_server_print.c b/src/tls/tls_proxy_server_print.c new file mode 100644 index 0000000..8d51422 --- /dev/null +++ b/src/tls/tls_proxy_server_print.c @@ -0,0 +1,143 @@ +/*++ +/* NAME +/* tls_proxy_server_print 3 +/* SUMMARY +/* write TLS_SERVER_XXX structures to stream +/* SYNOPSIS +/* #include +/* +/* int tls_proxy_server_init_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_COMMON_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* int tls_proxy_server_start_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_COMMON_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* DESCRIPTION +/* tls_proxy_server_init_print() writes a TLS_SERVER_INIT_PROPS +/* structure to the named stream using the specified attribute print +/* routine. tls_proxy_server_init_print() is meant to be passed as +/* a call-back to attr_print(), thusly: +/* +/* ... SEND_ATTR_FUNC(tls_proxy_server_init_print, (const void *) init_props), ... +/* +/* tls_proxy_server_start_print() writes a TLS_SERVER_START_PROPS +/* structure to the named stream using the specified attribute print +/* routine. tls_proxy_server_start_print() is meant to be passed as +/* a call-back to attr_print(), thusly: +/* +/* ... SEND_ATTR_FUNC(tls_proxy_server_start_print, (const void *) start_props), ... +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include + +/* TLS library. */ + +#include +#include + +/* tls_proxy_server_init_print - send TLS_SERVER_INIT_PROPS over stream */ + +int tls_proxy_server_init_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp, + int flags, const void *ptr) +{ + const TLS_SERVER_INIT_PROPS *props = (const TLS_SERVER_INIT_PROPS *) ptr; + int ret; + +#define STRING_OR_EMPTY(s) ((s) ? (s) : "") + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(TLS_ATTR_LOG_PARAM, + STRING_OR_EMPTY(props->log_param)), + SEND_ATTR_STR(TLS_ATTR_LOG_LEVEL, + STRING_OR_EMPTY(props->log_level)), + SEND_ATTR_INT(TLS_ATTR_VERIFYDEPTH, props->verifydepth), + SEND_ATTR_STR(TLS_ATTR_CACHE_TYPE, + STRING_OR_EMPTY(props->cache_type)), + SEND_ATTR_INT(TLS_ATTR_SET_SESSID, props->set_sessid), + SEND_ATTR_STR(TLS_ATTR_CHAIN_FILES, + STRING_OR_EMPTY(props->chain_files)), + SEND_ATTR_STR(TLS_ATTR_CERT_FILE, + STRING_OR_EMPTY(props->cert_file)), + SEND_ATTR_STR(TLS_ATTR_KEY_FILE, + STRING_OR_EMPTY(props->key_file)), + SEND_ATTR_STR(TLS_ATTR_DCERT_FILE, + STRING_OR_EMPTY(props->dcert_file)), + SEND_ATTR_STR(TLS_ATTR_DKEY_FILE, + STRING_OR_EMPTY(props->dkey_file)), + SEND_ATTR_STR(TLS_ATTR_ECCERT_FILE, + STRING_OR_EMPTY(props->eccert_file)), + SEND_ATTR_STR(TLS_ATTR_ECKEY_FILE, + STRING_OR_EMPTY(props->eckey_file)), + SEND_ATTR_STR(TLS_ATTR_CAFILE, + STRING_OR_EMPTY(props->CAfile)), + SEND_ATTR_STR(TLS_ATTR_CAPATH, + STRING_OR_EMPTY(props->CApath)), + SEND_ATTR_STR(TLS_ATTR_PROTOCOLS, + STRING_OR_EMPTY(props->protocols)), + SEND_ATTR_STR(TLS_ATTR_EECDH_GRADE, + STRING_OR_EMPTY(props->eecdh_grade)), + SEND_ATTR_STR(TLS_ATTR_DH1K_PARAM_FILE, + STRING_OR_EMPTY(props->dh1024_param_file)), + SEND_ATTR_STR(TLS_ATTR_DH512_PARAM_FILE, + STRING_OR_EMPTY(props->dh512_param_file)), + SEND_ATTR_INT(TLS_ATTR_ASK_CCERT, props->ask_ccert), + SEND_ATTR_STR(TLS_ATTR_MDALG, + STRING_OR_EMPTY(props->mdalg)), + ATTR_TYPE_END); + /* Do not flush the stream. */ + return (ret); +} + +/* tls_proxy_server_start_print - send TLS_SERVER_START_PROPS over stream */ + +int tls_proxy_server_start_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp, + int flags, const void *ptr) +{ + const TLS_SERVER_START_PROPS *props = (const TLS_SERVER_START_PROPS *) ptr; + int ret; + +#define STRING_OR_EMPTY(s) ((s) ? (s) : "") + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(TLS_ATTR_TIMEOUT, props->timeout), + SEND_ATTR_INT(TLS_ATTR_REQUIRECERT, props->requirecert), + SEND_ATTR_STR(TLS_ATTR_SERVERID, + STRING_OR_EMPTY(props->serverid)), + SEND_ATTR_STR(TLS_ATTR_NAMADDR, + STRING_OR_EMPTY(props->namaddr)), + SEND_ATTR_STR(TLS_ATTR_CIPHER_GRADE, + STRING_OR_EMPTY(props->cipher_grade)), + SEND_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS, + STRING_OR_EMPTY(props->cipher_exclusions)), + SEND_ATTR_STR(TLS_ATTR_MDALG, + STRING_OR_EMPTY(props->mdalg)), + ATTR_TYPE_END); + /* Do not flush the stream. */ + return (ret); +} + +#endif diff --git a/src/tls/tls_proxy_server_scan.c b/src/tls/tls_proxy_server_scan.c new file mode 100644 index 0000000..92da66c --- /dev/null +++ b/src/tls/tls_proxy_server_scan.c @@ -0,0 +1,245 @@ +/*++ +/* NAME +/* tls_proxy_server_scan 3 +/* SUMMARY +/* read TLS_SERVER_XXX structures from stream +/* SYNOPSIS +/* #include +/* +/* int tls_proxy_server_init_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_COMMON_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* tls_proxy_server_init_free(init_props) +/* TLS_SERVER_INIT_PROPS *init_props; +/* +/* int tls_proxy_server_start_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_COMMON_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* +/* void tls_proxy_server_start_free(start_props) +/* TLS_SERVER_START_PROPS *start_props; +/* DESCRIPTION +/* tls_proxy_server_init_scan() reads a TLS_SERVER_INIT_PROPS +/* structure from the named stream using the specified attribute +/* scan routine. tls_proxy_server_init_scan() is meant to be passed +/* as a call-back function to attr_scan(), as shown below. +/* +/* tls_proxy_server_init_free() destroys a TLS_SERVER_INIT_PROPS +/* structure that was created by tls_proxy_server_init_scan(). +/* +/* TLS_SERVER_INIT_PROPS *init_props = 0; +/* ... +/* ... RECV_ATTR_FUNC(tls_proxy_server_init_scan, (void *) &init_props) +/* ... +/* if (init_props) +/* tls_proxy_client_init_free(init_props); +/* +/* tls_proxy_server_start_scan() reads a TLS_SERVER_START_PROPS +/* structure from the named stream using the specified attribute +/* scan routine. tls_proxy_server_start_scan() is meant to be passed +/* as a call-back function to attr_scan(), as shown below. +/* +/* tls_proxy_server_start_free() destroys a TLS_SERVER_START_PROPS +/* structure that was created by tls_proxy_server_start_scan(). +/* +/* TLS_SERVER_START_PROPS *start_props = 0; +/* ... +/* ... RECV_ATTR_FUNC(tls_proxy_server_start_scan, (void *) &start_props) +/* ... +/* if (start_props) +/* tls_proxy_server_start_free(start_props); +/* DIAGNOSTICS +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#ifdef USE_TLS + +/* System library. */ + +#include + +/* Utility library */ + +#include + +/* TLS library. */ + +#include +#include + +/* tls_proxy_server_init_scan - receive TLS_SERVER_INIT_PROPS from stream */ + +int tls_proxy_server_init_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_SERVER_INIT_PROPS *props + = (TLS_SERVER_INIT_PROPS *) mymalloc(sizeof(*props)); + int ret; + VSTRING *log_param = vstring_alloc(25); + VSTRING *log_level = vstring_alloc(25); + VSTRING *cache_type = vstring_alloc(25); + VSTRING *chain_files = vstring_alloc(25); + VSTRING *cert_file = vstring_alloc(25); + VSTRING *key_file = vstring_alloc(25); + VSTRING *dcert_file = vstring_alloc(25); + VSTRING *dkey_file = vstring_alloc(25); + VSTRING *eccert_file = vstring_alloc(25); + VSTRING *eckey_file = vstring_alloc(25); + VSTRING *CAfile = vstring_alloc(25); + VSTRING *CApath = vstring_alloc(25); + VSTRING *protocols = vstring_alloc(25); + VSTRING *eecdh_grade = vstring_alloc(25); + VSTRING *dh1024_param_file = vstring_alloc(25); + VSTRING *dh512_param_file = vstring_alloc(25); + VSTRING *mdalg = vstring_alloc(25); + + /* + * Note: memset() is not a portable way to initialize non-integer types. + */ + memset(props, 0, sizeof(*props)); + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(TLS_ATTR_LOG_PARAM, log_param), + RECV_ATTR_STR(TLS_ATTR_LOG_LEVEL, log_level), + RECV_ATTR_INT(TLS_ATTR_VERIFYDEPTH, &props->verifydepth), + RECV_ATTR_STR(TLS_ATTR_CACHE_TYPE, cache_type), + RECV_ATTR_INT(TLS_ATTR_SET_SESSID, &props->set_sessid), + RECV_ATTR_STR(TLS_ATTR_CHAIN_FILES, chain_files), + RECV_ATTR_STR(TLS_ATTR_CERT_FILE, cert_file), + RECV_ATTR_STR(TLS_ATTR_KEY_FILE, key_file), + RECV_ATTR_STR(TLS_ATTR_DCERT_FILE, dcert_file), + RECV_ATTR_STR(TLS_ATTR_DKEY_FILE, dkey_file), + RECV_ATTR_STR(TLS_ATTR_ECCERT_FILE, eccert_file), + RECV_ATTR_STR(TLS_ATTR_ECKEY_FILE, eckey_file), + RECV_ATTR_STR(TLS_ATTR_CAFILE, CAfile), + RECV_ATTR_STR(TLS_ATTR_CAPATH, CApath), + RECV_ATTR_STR(TLS_ATTR_PROTOCOLS, protocols), + RECV_ATTR_STR(TLS_ATTR_EECDH_GRADE, eecdh_grade), + RECV_ATTR_STR(TLS_ATTR_DH1K_PARAM_FILE, dh1024_param_file), + RECV_ATTR_STR(TLS_ATTR_DH512_PARAM_FILE, dh512_param_file), + RECV_ATTR_INT(TLS_ATTR_ASK_CCERT, &props->ask_ccert), + RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg), + ATTR_TYPE_END); + /* Always construct a well-formed structure. */ + props->log_param = vstring_export(log_param); + props->log_level = vstring_export(log_level); + props->cache_type = vstring_export(cache_type); + props->chain_files = vstring_export(chain_files); + props->cert_file = vstring_export(cert_file); + props->key_file = vstring_export(key_file); + props->dcert_file = vstring_export(dcert_file); + props->dkey_file = vstring_export(dkey_file); + props->eccert_file = vstring_export(eccert_file); + props->eckey_file = vstring_export(eckey_file); + props->CAfile = vstring_export(CAfile); + props->CApath = vstring_export(CApath); + props->protocols = vstring_export(protocols); + props->eecdh_grade = vstring_export(eecdh_grade); + props->dh1024_param_file = vstring_export(dh1024_param_file); + props->dh512_param_file = vstring_export(dh512_param_file); + props->mdalg = vstring_export(mdalg); + ret = (ret == 20 ? 1 : -1); + if (ret != 1) { + tls_proxy_server_init_free(props); + props = 0; + } + *(TLS_SERVER_INIT_PROPS **) ptr = props; + return (ret); +} + +/* tls_proxy_server_init_free - destroy TLS_SERVER_INIT_PROPS structure */ + +void tls_proxy_server_init_free(TLS_SERVER_INIT_PROPS *props) +{ + myfree((void *) props->log_param); + myfree((void *) props->log_level); + myfree((void *) props->cache_type); + myfree((void *) props->chain_files); + myfree((void *) props->cert_file); + myfree((void *) props->key_file); + myfree((void *) props->dcert_file); + myfree((void *) props->dkey_file); + myfree((void *) props->eccert_file); + myfree((void *) props->eckey_file); + myfree((void *) props->CAfile); + myfree((void *) props->CApath); + myfree((void *) props->protocols); + myfree((void *) props->eecdh_grade); + myfree((void *) props->dh1024_param_file); + myfree((void *) props->dh512_param_file); + myfree((void *) props->mdalg); + myfree((void *) props); +} + +/* tls_proxy_server_start_scan - receive TLS_SERVER_START_PROPS from stream */ + +int tls_proxy_server_start_scan(ATTR_SCAN_COMMON_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + TLS_SERVER_START_PROPS *props + = (TLS_SERVER_START_PROPS *) mymalloc(sizeof(*props)); + int ret; + VSTRING *serverid = vstring_alloc(25); + VSTRING *namaddr = vstring_alloc(25); + VSTRING *cipher_grade = vstring_alloc(25); + VSTRING *cipher_exclusions = vstring_alloc(25); + VSTRING *mdalg = vstring_alloc(25); + + /* + * Note: memset() is not a portable way to initialize non-integer types. + */ + memset(props, 0, sizeof(*props)); + props->ctx = 0; + props->stream = 0; + /* XXX Caller sets fd. */ + ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &props->timeout), + RECV_ATTR_INT(TLS_ATTR_REQUIRECERT, &props->requirecert), + RECV_ATTR_STR(TLS_ATTR_SERVERID, serverid), + RECV_ATTR_STR(TLS_ATTR_NAMADDR, namaddr), + RECV_ATTR_STR(TLS_ATTR_CIPHER_GRADE, cipher_grade), + RECV_ATTR_STR(TLS_ATTR_CIPHER_EXCLUSIONS, + cipher_exclusions), + RECV_ATTR_STR(TLS_ATTR_MDALG, mdalg), + ATTR_TYPE_END); + props->serverid = vstring_export(serverid); + props->namaddr = vstring_export(namaddr); + props->cipher_grade = vstring_export(cipher_grade); + props->cipher_exclusions = vstring_export(cipher_exclusions); + props->mdalg = vstring_export(mdalg); + ret = (ret == 7 ? 1 : -1); + if (ret != 1) { + tls_proxy_server_start_free(props); + props = 0; + } + *(TLS_SERVER_START_PROPS **) ptr = props; + return (ret); +} + +/* tls_proxy_server_start_free - destroy TLS_SERVER_START_PROPS structure */ + +void tls_proxy_server_start_free(TLS_SERVER_START_PROPS *props) +{ + /* XXX Caller closes fd. */ + myfree((void *) props->serverid); + myfree((void *) props->namaddr); + myfree((void *) props->cipher_grade); + myfree((void *) props->cipher_exclusions); + myfree((void *) props->mdalg); + myfree((void *) props); +} + +#endif diff --git a/src/tls/tls_rsa.c b/src/tls/tls_rsa.c new file mode 100644 index 0000000..e69de29 diff --git a/src/tls/tls_scache.c b/src/tls/tls_scache.c new file mode 100644 index 0000000..cf722ee --- /dev/null +++ b/src/tls/tls_scache.c @@ -0,0 +1,591 @@ +/*++ +/* NAME +/* tls_scache 3 +/* SUMMARY +/* TLS session cache manager +/* SYNOPSIS +/* #include +/* +/* TLS_SCACHE *tls_scache_open(dbname, cache_label, verbose, timeout) +/* const char *dbname +/* const char *cache_label; +/* int verbose; +/* int timeout; +/* +/* void tls_scache_close(cache) +/* TLS_SCACHE *cache; +/* +/* int tls_scache_lookup(cache, cache_id, out_session) +/* TLS_SCACHE *cache; +/* const char *cache_id; +/* VSTRING *out_session; +/* +/* int tls_scache_update(cache, cache_id, session, session_len) +/* TLS_SCACHE *cache; +/* const char *cache_id; +/* const char *session; +/* ssize_t session_len; +/* +/* int tls_scache_sequence(cache, first_next, out_cache_id, +/* VSTRING *out_session) +/* TLS_SCACHE *cache; +/* int first_next; +/* char **out_cache_id; +/* VSTRING *out_session; +/* +/* int tls_scache_delete(cache, cache_id) +/* TLS_SCACHE *cache; +/* const char *cache_id; +/* +/* TLS_TICKET_KEY *tls_scache_key(keyname, now, timeout) +/* unsigned char *keyname; +/* time_t now; +/* int timeout; +/* +/* TLS_TICKET_KEY *tls_scache_key_rotate(newkey) +/* TLS_TICKET_KEY *newkey; +/* DESCRIPTION +/* This module maintains Postfix TLS session cache files. +/* each session is stored under a lookup key (hostname or +/* session ID). +/* +/* tls_scache_open() opens the specified TLS session cache +/* and returns a handle that must be used for subsequent +/* access. +/* +/* tls_scache_close() closes the specified TLS session cache +/* and releases memory that was allocated by tls_scache_open(). +/* +/* tls_scache_lookup() looks up the specified session in the +/* specified cache, and applies session timeout restrictions. +/* Entries that are too old are silently deleted. +/* +/* tls_scache_update() updates the specified TLS session cache +/* with the specified session information. +/* +/* tls_scache_sequence() iterates over the specified TLS session +/* cache and either returns the first or next entry that has not +/* timed out, or returns no data. Entries that are too old are +/* silently deleted. Specify TLS_SCACHE_SEQUENCE_NOTHING as the +/* third and last argument to disable saving of cache entry +/* content or cache entry ID information. This is useful when +/* purging expired entries. A result value of zero means that +/* the end of the cache was reached. +/* +/* tls_scache_delete() removes the specified cache entry from +/* the specified TLS session cache. +/* +/* tls_scache_key() locates a TLS session ticket key in a 2-element +/* in-memory cache. A null result is returned if no unexpired matching +/* key is found. +/* +/* tls_scache_key_rotate() saves a TLS session tickets key in the +/* in-memory cache. +/* +/* Arguments: +/* .IP dbname +/* The base name of the session cache file. +/* .IP cache_label +/* A string that is used in logging and error messages. +/* .IP verbose +/* Do verbose logging of cache operations? (zero == no) +/* .IP timeout +/* The time after which a session cache entry is considered too old. +/* .IP first_next +/* One of DICT_SEQ_FUN_FIRST (first cache element) or DICT_SEQ_FUN_NEXT +/* (next cache element). +/* .IP cache_id +/* Session cache lookup key. +/* .IP session +/* Storage for session information. +/* .IP session_len +/* The size of the session information in bytes. +/* .IP out_cache_id +/* .IP out_session +/* Storage for saving the cache_id or session information of the +/* current cache entry. +/* +/* Specify TLS_SCACHE_DONT_NEED_CACHE_ID to avoid saving +/* the session cache ID of the cache entry. +/* +/* Specify TLS_SCACHE_DONT_NEED_SESSION to avoid +/* saving the session information in the cache entry. +/* .IP keyname +/* Is null when requesting the current encryption keys. Otherwise, +/* keyname is a pointer to an array of TLS_TICKET_NAMELEN unsigned +/* chars (not NUL terminated) that is an identifier for a key +/* previously used to encrypt a session ticket. +/* .IP now +/* Current epoch time passed by caller. +/* .IP timeout +/* TLS session ticket encryption lifetime. +/* .IP newkey +/* TLS session ticket key obtained from tlsmgr(8) to be added to + * internal cache. +/* DIAGNOSTICS +/* These routines terminate with a fatal run-time error +/* for unrecoverable database errors. This allows the +/* program to restart and reset the database to an +/* empty initial state. +/* +/* tls_scache_open() never returns on failure. All other +/* functions return non-zero on success, zero when the +/* operation could not be completed. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +#ifdef USE_TLS + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +/* TLS library. */ + +#include + +/* Application-specific. */ + + /* + * Session cache entry format. + */ +typedef struct { + time_t timestamp; /* time when saved */ + char session[1]; /* actually a bunch of bytes */ +} TLS_SCACHE_ENTRY; + +static TLS_TICKET_KEY *keys[2]; + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* tls_scache_encode - encode TLS session cache entry */ + +static VSTRING *tls_scache_encode(TLS_SCACHE *cp, const char *cache_id, + const char *session, + ssize_t session_len) +{ + TLS_SCACHE_ENTRY *entry; + VSTRING *hex_data; + ssize_t binary_data_len; + + /* + * Assemble the TLS session cache entry. + * + * We could eliminate some copying by using incremental encoding, but + * sessions are so small that it really does not matter. + */ + binary_data_len = session_len + offsetof(TLS_SCACHE_ENTRY, session); + entry = (TLS_SCACHE_ENTRY *) mymalloc(binary_data_len); + entry->timestamp = time((time_t *) 0); + memcpy(entry->session, session, session_len); + + /* + * Encode the TLS session cache entry. + */ + hex_data = vstring_alloc(2 * binary_data_len + 1); + hex_encode(hex_data, (char *) entry, binary_data_len); + + /* + * Logging. + */ + if (cp->verbose) + msg_info("write %s TLS cache entry %s: time=%ld [data %ld bytes]", + cp->cache_label, cache_id, (long) entry->timestamp, + (long) session_len); + + /* + * Clean up. + */ + myfree((void *) entry); + + return (hex_data); +} + +/* tls_scache_decode - decode TLS session cache entry */ + +static int tls_scache_decode(TLS_SCACHE *cp, const char *cache_id, + const char *hex_data, ssize_t hex_data_len, + VSTRING *out_session) +{ + TLS_SCACHE_ENTRY *entry; + VSTRING *bin_data; + + /* + * Sanity check. + */ + if (hex_data_len < 2 * (offsetof(TLS_SCACHE_ENTRY, session))) { + msg_warn("%s TLS cache: truncated entry for %s: %.100s", + cp->cache_label, cache_id, hex_data); + return (0); + } + + /* + * Disassemble the TLS session cache entry. + * + * No early returns or we have a memory leak. + */ +#define FREE_AND_RETURN(ptr, x) { vstring_free(ptr); return (x); } + + bin_data = vstring_alloc(hex_data_len / 2 + 1); + if (hex_decode_opt(bin_data, hex_data, hex_data_len, + HEX_DECODE_FLAG_ALLOW_COLON) == 0) { + msg_warn("%s TLS cache: malformed entry for %s: %.100s", + cp->cache_label, cache_id, hex_data); + FREE_AND_RETURN(bin_data, 0); + } + entry = (TLS_SCACHE_ENTRY *) STR(bin_data); + + /* + * Logging. + */ + if (cp->verbose) + msg_info("read %s TLS cache entry %s: time=%ld [data %ld bytes]", + cp->cache_label, cache_id, (long) entry->timestamp, + (long) (LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session))); + + /* + * Other mandatory restrictions. + */ + if (entry->timestamp + cp->timeout < time((time_t *) 0)) + FREE_AND_RETURN(bin_data, 0); + + /* + * Optional output. + */ + if (out_session != 0) + vstring_memcpy(out_session, entry->session, + LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session)); + + /* + * Clean up. + */ + FREE_AND_RETURN(bin_data, 1); +} + +/* tls_scache_lookup - load session from cache */ + +int tls_scache_lookup(TLS_SCACHE *cp, const char *cache_id, + VSTRING *session) +{ + const char *hex_data; + + /* + * Logging. + */ + if (cp->verbose) + msg_info("lookup %s session id=%s", cp->cache_label, cache_id); + + /* + * Initialize. Don't leak data. + */ + if (session) + VSTRING_RESET(session); + + /* + * Search the cache database. + */ + if ((hex_data = dict_get(cp->db, cache_id)) == 0) + return (0); + + /* + * Decode entry and delete if expired or malformed. + */ + if (tls_scache_decode(cp, cache_id, hex_data, strlen(hex_data), + session) == 0) { + tls_scache_delete(cp, cache_id); + return (0); + } else { + return (1); + } +} + +/* tls_scache_update - save session to cache */ + +int tls_scache_update(TLS_SCACHE *cp, const char *cache_id, + const char *buf, ssize_t len) +{ + VSTRING *hex_data; + + /* + * Logging. + */ + if (cp->verbose) + msg_info("put %s session id=%s [data %ld bytes]", + cp->cache_label, cache_id, (long) len); + + /* + * Encode the cache entry. + */ + hex_data = tls_scache_encode(cp, cache_id, buf, len); + + /* + * Store the cache entry. + * + * XXX Berkeley DB supports huge database keys and values. SDBM seems to + * have a finite limit, and DBM simply can't be used at all. + */ + dict_put(cp->db, cache_id, STR(hex_data)); + + /* + * Clean up. + */ + vstring_free(hex_data); + + return (1); +} + +/* tls_scache_sequence - get first/next TLS session cache entry */ + +int tls_scache_sequence(TLS_SCACHE *cp, int first_next, + char **out_cache_id, + VSTRING *out_session) +{ + const char *member; + const char *value; + char *saved_cursor; + int found_entry; + int keep_entry; + char *saved_member; + + /* + * XXX Deleting entries while enumerating a map can he tricky. Some map + * types have a concept of cursor and support a "delete the current + * element" operation. Some map types without cursors don't behave well + * when the current first/next entry is deleted (example: with Berkeley + * DB < 2, the "next" operation produces garbage). To avoid trouble, we + * delete an expired entry after advancing the current first/next + * position beyond it, and ignore client requests to delete the current + * entry. + */ + + /* + * Find the first or next database entry. Activate the passivated entry + * and check the time stamp. Schedule the entry for deletion if it is too + * old. + * + * Save the member (cache id) so that it will not be clobbered by the + * tls_scache_lookup() call below. + */ + found_entry = (dict_seq(cp->db, first_next, &member, &value) == 0); + if (found_entry) { + keep_entry = tls_scache_decode(cp, member, value, strlen(value), + out_session); + if (keep_entry && out_cache_id) + *out_cache_id = mystrdup(member); + saved_member = mystrdup(member); + } + + /* + * Delete behind. This is a no-op if an expired cache entry was updated + * in the mean time. Use the saved lookup criteria so that the "delete + * behind" operation works as promised. + * + * The delete-behind strategy assumes that all updates are made by a single + * process. Otherwise, delete-behind may remove an entry that was updated + * after it was scheduled for deletion. + */ + if (cp->flags & TLS_SCACHE_FLAG_DEL_SAVED_CURSOR) { + cp->flags &= ~TLS_SCACHE_FLAG_DEL_SAVED_CURSOR; + saved_cursor = cp->saved_cursor; + cp->saved_cursor = 0; + tls_scache_lookup(cp, saved_cursor, (VSTRING *) 0); + myfree(saved_cursor); + } + + /* + * Otherwise, clean up if this is not the first iteration. + */ + else { + if (cp->saved_cursor) + myfree(cp->saved_cursor); + cp->saved_cursor = 0; + } + + /* + * Protect the current first/next entry against explicit or implied + * client delete requests, and schedule a bad or expired entry for + * deletion. Save the lookup criteria so that the "delete behind" + * operation will work as promised. + */ + if (found_entry) { + cp->saved_cursor = saved_member; + if (keep_entry == 0) + cp->flags |= TLS_SCACHE_FLAG_DEL_SAVED_CURSOR; + } + return (found_entry); +} + +/* tls_scache_delete - delete session from cache */ + +int tls_scache_delete(TLS_SCACHE *cp, const char *cache_id) +{ + + /* + * Logging. + */ + if (cp->verbose) + msg_info("delete %s session id=%s", cp->cache_label, cache_id); + + /* + * Do it, unless we would delete the current first/next entry. Some map + * types don't have cursors, and some of those don't behave when the + * "current" entry is deleted. + */ + return ((cp->saved_cursor != 0 && strcmp(cp->saved_cursor, cache_id) == 0) + || dict_del(cp->db, cache_id) == 0); +} + +/* tls_scache_open - open TLS session cache file */ + +TLS_SCACHE *tls_scache_open(const char *dbname, const char *cache_label, + int verbose, int timeout) +{ + TLS_SCACHE *cp; + DICT *dict; + + /* + * Logging. + */ + if (verbose) + msg_info("open %s TLS cache %s", cache_label, dbname); + + /* + * Open the dictionary with O_TRUNC, so that we never have to worry about + * opening a damaged file after some process terminated abnormally. + */ +#define DICT_FLAGS \ + (DICT_FLAG_DUP_REPLACE | DICT_FLAG_OPEN_LOCK | DICT_FLAG_SYNC_UPDATE \ + | DICT_FLAG_UTF8_REQUEST) + + dict = dict_open(dbname, O_RDWR | O_CREAT | O_TRUNC, DICT_FLAGS); + + /* + * Sanity checks. + */ + if (dict->update == 0) + msg_fatal("dictionary %s does not support update operations", dbname); + if (dict->delete == 0) + msg_fatal("dictionary %s does not support delete operations", dbname); + if (dict->sequence == 0) + msg_fatal("dictionary %s does not support sequence operations", dbname); + + /* + * Create the TLS_SCACHE object. + */ + cp = (TLS_SCACHE *) mymalloc(sizeof(*cp)); + cp->flags = 0; + cp->db = dict; + cp->cache_label = mystrdup(cache_label); + cp->verbose = verbose; + cp->timeout = timeout; + cp->saved_cursor = 0; + + return (cp); +} + +/* tls_scache_close - close TLS session cache file */ + +void tls_scache_close(TLS_SCACHE *cp) +{ + + /* + * Logging. + */ + if (cp->verbose) + msg_info("close %s TLS cache %s", cp->cache_label, cp->db->name); + + /* + * Destroy the TLS_SCACHE object. + */ + dict_close(cp->db); + myfree(cp->cache_label); + if (cp->saved_cursor) + myfree(cp->saved_cursor); + myfree((void *) cp); +} + +/* tls_scache_key - find session ticket key for given key name */ + +TLS_TICKET_KEY *tls_scache_key(unsigned char *keyname, time_t now, int timeout) +{ + int i; + + /* + * The keys array contains 2 elements, the current signing key and the + * previous key. + * + * When name == 0 we are issuing a ticket, otherwise decrypting an existing + * ticket with the given key name. For new tickets we always use the + * current key if unexpired. For existing tickets, we use either the + * current or previous key with a validation expiration that is timeout + * longer than the signing expiration. + */ + if (keyname) { + for (i = 0; i < 2 && keys[i]; ++i) { + if (memcmp(keyname, keys[i]->name, TLS_TICKET_NAMELEN) == 0) { + if (timecmp(keys[i]->tout + timeout, now) > 0) + return (keys[i]); + break; + } + } + } else if (keys[0]) { + if (timecmp(keys[0]->tout, now) > 0) + return (keys[0]); + } + return (0); +} + +/* tls_scache_key_rotate - rotate session ticket keys */ + +TLS_TICKET_KEY *tls_scache_key_rotate(TLS_TICKET_KEY *newkey) +{ + + /* + * Allocate or re-use storage of retired key, then overwrite it, since + * caller's key data is ephemeral. + */ + if (keys[1] == 0) + keys[1] = (TLS_TICKET_KEY *) mymalloc(sizeof(*newkey)); + *keys[1] = *newkey; + newkey = keys[1]; + + /* + * Rotate if required, ensuring that the keys are sorted by expiration + * time with keys[0] expiring last. + */ + if (keys[0] == 0 || keys[0]->tout < keys[1]->tout) { + keys[1] = keys[0]; + keys[0] = newkey; + } + return (newkey); +} + +#endif diff --git a/src/tls/tls_scache.h b/src/tls/tls_scache.h new file mode 100644 index 0000000..06c727a --- /dev/null +++ b/src/tls/tls_scache.h @@ -0,0 +1,73 @@ +#ifndef _TLS_SCACHE_H_INCLUDED_ +#define _TLS_SCACHE_H_INCLUDED_ + +/*++ +/* NAME +/* tls_scache 3h +/* SUMMARY +/* TLS session cache manager +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * External interface. + */ +typedef struct { + int flags; /* see below */ + DICT *db; /* database handle */ + char *cache_label; /* "smtpd", "smtp" or "lmtp" */ + int verbose; /* enable verbose logging */ + int timeout; /* smtp(d)_tls_session_cache_timeout */ + char *saved_cursor; /* cursor cache ID */ +} TLS_SCACHE; + +#define TLS_TICKET_NAMELEN 16 /* RFC 5077 ticket key name length */ +#define TLS_TICKET_IVLEN 16 /* RFC 5077 ticket IV length */ +#define TLS_TICKET_KEYLEN 32 /* AES-256-CBC key size */ +#define TLS_TICKET_MACLEN 32 /* RFC 5077 HMAC key size */ +#define TLS_SESSION_LIFEMIN 120 /* May you live to 120! */ + +typedef struct TLS_TICKET_KEY { + unsigned char name[TLS_TICKET_NAMELEN]; + unsigned char bits[TLS_TICKET_KEYLEN]; + unsigned char hmac[TLS_TICKET_MACLEN]; + time_t tout; +} TLS_TICKET_KEY; + +#define TLS_SCACHE_FLAG_DEL_SAVED_CURSOR (1<<0) + +extern TLS_SCACHE *tls_scache_open(const char *, const char *, int, int); +extern void tls_scache_close(TLS_SCACHE *); +extern int tls_scache_lookup(TLS_SCACHE *, const char *, VSTRING *); +extern int tls_scache_update(TLS_SCACHE *, const char *, const char *, ssize_t); +extern int tls_scache_delete(TLS_SCACHE *, const char *); +extern int tls_scache_sequence(TLS_SCACHE *, int, char **, VSTRING *); +extern TLS_TICKET_KEY *tls_scache_key(unsigned char *, time_t, int); +extern TLS_TICKET_KEY *tls_scache_key_rotate(TLS_TICKET_KEY *); + +#define TLS_SCACHE_DONT_NEED_CACHE_ID ((char **) 0) +#define TLS_SCACHE_DONT_NEED_SESSION ((VSTRING *) 0) + +#define TLS_SCACHE_SEQUENCE_NOTHING \ + TLS_SCACHE_DONT_NEED_CACHE_ID, TLS_SCACHE_DONT_NEED_SESSION + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/tls/tls_seed.c b/src/tls/tls_seed.c new file mode 100644 index 0000000..edb7cd9 --- /dev/null +++ b/src/tls/tls_seed.c @@ -0,0 +1,88 @@ +/*++ +/* NAME +/* tls_seed 3 +/* SUMMARY +/* TLS PRNG seeding routines +/* SYNOPSIS +/* #define TLS_INTERNAL +/* #include +/* +/* int tls_ext_seed(nbytes) +/* int nbytes; +/* +/* void tls_int_seed() +/* DESCRIPTION +/* tls_ext_seed() requests the specified number of bytes +/* from the tlsmgr(8) PRNG pool and updates the local PRNG. +/* The result is zero in case of success, -1 otherwise. +/* +/* tls_int_seed() mixes the process ID and time of day into +/* the PRNG pool. This adds a few bits of entropy with each +/* call, provided that the calls aren't made frequently. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include /* gettimeofday() */ +#include /* getpid() */ + +#ifdef USE_TLS + +/* OpenSSL library. */ + +#include /* RAND_seed() */ + +/* Utility library. */ + +#include +#include + +/* TLS library. */ + +#include +#define TLS_INTERNAL +#include + +/* Application-specific. */ + +/* tls_int_seed - add entropy to the pool by adding the time and PID */ + +void tls_int_seed(void) +{ + static struct { + pid_t pid; + struct timeval tv; + } randseed; + + if (randseed.pid == 0) + randseed.pid = getpid(); + GETTIMEOFDAY(&randseed.tv); + RAND_seed(&randseed, sizeof(randseed)); +} + +/* tls_ext_seed - request entropy from tlsmgr(8) server */ + +int tls_ext_seed(int nbytes) +{ + VSTRING *buf; + int status; + + buf = vstring_alloc(nbytes); + status = tls_mgr_seed(buf, nbytes); + RAND_seed(vstring_str(buf), VSTRING_LEN(buf)); + vstring_free(buf); + return (status == TLS_MGR_STAT_OK ? 0 : -1); +} + +#endif diff --git a/src/tls/tls_server.c b/src/tls/tls_server.c new file mode 100644 index 0000000..fb76f06 --- /dev/null +++ b/src/tls/tls_server.c @@ -0,0 +1,1051 @@ +/*++ +/* NAME +/* tls_server 3 +/* SUMMARY +/* server-side TLS engine +/* SYNOPSIS +/* #include +/* +/* TLS_APPL_STATE *tls_server_init(props) +/* const TLS_SERVER_INIT_PROPS *props; +/* +/* TLS_SESS_STATE *tls_server_start(props) +/* const TLS_SERVER_START_PROPS *props; +/* +/* TLS_SESS_STATE *tls_server_post_accept(TLScontext) +/* TLS_SESS_STATE *TLScontext; +/* +/* void tls_server_stop(app_ctx, stream, failure, TLScontext) +/* TLS_APPL_STATE *app_ctx; +/* VSTREAM *stream; +/* int failure; +/* TLS_SESS_STATE *TLScontext; +/* DESCRIPTION +/* This module is the interface between Postfix TLS servers, +/* the OpenSSL library, and the TLS entropy and cache manager. +/* +/* See "EVENT_DRIVEN APPLICATIONS" below for using this code +/* in event-driven programs. +/* +/* tls_server_init() is called once when the SMTP server +/* initializes. +/* Certificate details are also decided during this phase, +/* so that peer-specific behavior is not possible. +/* +/* tls_server_start() activates the TLS feature for the VSTREAM +/* passed as argument. We assume that network buffers are flushed +/* and the TLS handshake can begin immediately. +/* +/* tls_server_stop() sends the "close notify" alert via +/* SSL_shutdown() to the peer and resets all connection specific +/* TLS data. As RFC2487 does not specify a separate shutdown, it +/* is assumed that the underlying TCP connection is shut down +/* immediately afterwards. Any further writes to the channel will +/* be discarded, and any further reads will report end-of-file. +/* If the failure flag is set, no SSL_shutdown() handshake is performed. +/* +/* Once the TLS connection is initiated, information about the TLS +/* state is available via the TLScontext structure: +/* .IP TLScontext->protocol +/* the protocol name (SSLv2, SSLv3, TLSv1), +/* .IP TLScontext->cipher_name +/* the cipher name (e.g. RC4/MD5), +/* .IP TLScontext->cipher_usebits +/* the number of bits actually used (e.g. 40), +/* .IP TLScontext->cipher_algbits +/* the number of bits the algorithm is based on (e.g. 128). +/* .PP +/* The last two values may differ from each other when export-strength +/* encryption is used. +/* +/* If the peer offered a certificate, part of the certificate data are +/* available as: +/* .IP TLScontext->peer_status +/* A bitmask field that records the status of the peer certificate +/* verification. One or more of TLS_CERT_FLAG_PRESENT and +/* TLS_CERT_FLAG_TRUSTED. +/* .IP TLScontext->peer_CN +/* Extracted CommonName of the peer, or zero-length string +/* when information could not be extracted. +/* .IP TLScontext->issuer_CN +/* Extracted CommonName of the issuer, or zero-length string +/* when information could not be extracted. +/* .IP TLScontext->peer_cert_fprint +/* Fingerprint of the certificate, or zero-length string when no peer +/* certificate is available. +/* .PP +/* If no peer certificate is presented the peer_status is set to 0. +/* EVENT_DRIVEN APPLICATIONS +/* .ad +/* .fi +/* Event-driven programs manage multiple I/O channels. Such +/* programs cannot use the synchronous VSTREAM-over-TLS +/* implementation that the current TLS library provides, +/* including tls_server_stop() and the underlying tls_stream(3) +/* and tls_bio_ops(3) routines. +/* +/* With the current TLS library implementation, this means +/* that the application is responsible for calling and retrying +/* SSL_accept(), SSL_read(), SSL_write() and SSL_shutdown(). +/* +/* To maintain control over TLS I/O, an event-driven server +/* invokes tls_server_start() with a null VSTREAM argument and +/* with an fd argument that specifies the I/O file descriptor. +/* Then, tls_server_start() performs all the necessary +/* preparations before the TLS handshake and returns a partially +/* populated TLS context. The event-driven application is then +/* responsible for invoking SSL_accept(), and if successful, +/* for invoking tls_server_post_accept() to finish the work +/* that was started by tls_server_start(). In case of unrecoverable +/* failure, tls_server_post_accept() destroys the TLS context +/* and returns a null pointer value. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Originally written by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include + +#ifdef USE_TLS +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include /* non-blocking */ + +/* Global library. */ + +#include + +/* TLS library. */ + +#include +#define TLS_INTERNAL +#include +#if OPENSSL_VERSION_PREREQ(3,0) +#include /* EVP_MAC parameters */ +#endif + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* Application-specific. */ + + /* + * The session_id_context identifies the service that created a session. + * This information is used to distinguish between multiple TLS-based + * servers running on the same server. We use the name of the mail system. + */ +static const char server_session_id_context[] = "Postfix/TLS"; + +#ifndef OPENSSL_NO_TLSEXT + /* + * We retain the cipher handle for the lifetime of the process. + */ +static const EVP_CIPHER *tkt_cipher; +#endif + +#define GET_SID(s, v, lptr) ((v) = SSL_SESSION_get_id((s), (lptr))) + +typedef const unsigned char *session_id_t; + +/* get_server_session_cb - callback to retrieve session from server cache */ + +static SSL_SESSION *get_server_session_cb(SSL *ssl, session_id_t session_id, + int session_id_length, + int *unused_copy) +{ + const char *myname = "get_server_session_cb"; + TLS_SESS_STATE *TLScontext; + VSTRING *cache_id; + VSTRING *session_data = vstring_alloc(2048); + SSL_SESSION *session = 0; + + if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0) + msg_panic("%s: null TLScontext in session lookup callback", myname); + +#define GEN_CACHE_ID(buf, id, len, service) \ + do { \ + buf = vstring_alloc(2 * (len + strlen(service))); \ + hex_encode(buf, (char *) (id), (len)); \ + vstring_sprintf_append(buf, "&s=%s", (service)); \ + vstring_sprintf_append(buf, "&l=%ld", (long) OpenSSL_version_num()); \ + } while (0) + + + GEN_CACHE_ID(cache_id, session_id, session_id_length, TLScontext->serverid); + + if (TLScontext->log_mask & TLS_LOG_CACHE) + msg_info("%s: looking up session %s in %s cache", TLScontext->namaddr, + STR(cache_id), TLScontext->cache_type); + + /* + * Load the session from cache and decode it. + */ + if (tls_mgr_lookup(TLScontext->cache_type, STR(cache_id), + session_data) == TLS_MGR_STAT_OK) { + session = tls_session_activate(STR(session_data), LEN(session_data)); + if (session && (TLScontext->log_mask & TLS_LOG_CACHE)) + msg_info("%s: reloaded session %s from %s cache", + TLScontext->namaddr, STR(cache_id), + TLScontext->cache_type); + } + + /* + * Clean up. + */ + vstring_free(cache_id); + vstring_free(session_data); + + return (session); +} + +/* uncache_session - remove session from internal & external cache */ + +static void uncache_session(SSL_CTX *ctx, TLS_SESS_STATE *TLScontext) +{ + VSTRING *cache_id; + SSL_SESSION *session = SSL_get_session(TLScontext->con); + const unsigned char *sid; + unsigned int sid_length; + + SSL_CTX_remove_session(ctx, session); + + if (TLScontext->cache_type == 0) + return; + + GET_SID(session, sid, &sid_length); + GEN_CACHE_ID(cache_id, sid, sid_length, TLScontext->serverid); + + if (TLScontext->log_mask & TLS_LOG_CACHE) + msg_info("%s: remove session %s from %s cache", TLScontext->namaddr, + STR(cache_id), TLScontext->cache_type); + + tls_mgr_delete(TLScontext->cache_type, STR(cache_id)); + vstring_free(cache_id); +} + +/* new_server_session_cb - callback to save session to server cache */ + +static int new_server_session_cb(SSL *ssl, SSL_SESSION *session) +{ + const char *myname = "new_server_session_cb"; + VSTRING *cache_id; + TLS_SESS_STATE *TLScontext; + VSTRING *session_data; + const unsigned char *sid; + unsigned int sid_length; + + if ((TLScontext = SSL_get_ex_data(ssl, TLScontext_index)) == 0) + msg_panic("%s: null TLScontext in new session callback", myname); + + GET_SID(session, sid, &sid_length); + GEN_CACHE_ID(cache_id, sid, sid_length, TLScontext->serverid); + + if (TLScontext->log_mask & TLS_LOG_CACHE) + msg_info("%s: save session %s to %s cache", TLScontext->namaddr, + STR(cache_id), TLScontext->cache_type); + + /* + * Passivate and save the session state. + */ + session_data = tls_session_passivate(session); + if (session_data) + tls_mgr_update(TLScontext->cache_type, STR(cache_id), + STR(session_data), LEN(session_data)); + + /* + * Clean up. + */ + if (session_data) + vstring_free(session_data); + vstring_free(cache_id); + SSL_SESSION_free(session); /* 200502 */ + + return (1); +} + +#define NOENGINE ((ENGINE *) 0) +#define TLS_TKT_NOKEYS -1 /* No keys for encryption */ +#define TLS_TKT_STALE 0 /* No matching keys for decryption */ +#define TLS_TKT_ACCEPT 1 /* Ticket decryptable and re-usable */ +#define TLS_TKT_REISSUE 2 /* Ticket decryptable, not re-usable */ + +#if !defined(OPENSSL_NO_TLSEXT) + +#if OPENSSL_VERSION_PREREQ(3,0) + +/* ticket_cb - configure tls session ticket encrypt/decrypt context */ + +static int ticket_cb(SSL *con, unsigned char name[], unsigned char iv[], + EVP_CIPHER_CTX *ctx, EVP_MAC_CTX *hctx, int create) +{ + OSSL_PARAM params[3]; + TLS_TICKET_KEY *key; + TLS_SESS_STATE *TLScontext = SSL_get_ex_data(con, TLScontext_index); + int timeout = ((int) SSL_CTX_get_timeout(SSL_get_SSL_CTX(con))) / 2; + + if ((key = tls_mgr_key(create ? 0 : name, timeout)) == 0 + || (create && RAND_bytes(iv, TLS_TICKET_IVLEN) <= 0)) + return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE); + + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, + LN_sha256, 0); + params[1] = OSSL_PARAM_construct_octet_string(OSSL_MAC_PARAM_KEY, + (char *) key->hmac, + TLS_TICKET_MACLEN); + params[2] = OSSL_PARAM_construct_end(); + if (!EVP_MAC_CTX_set_params(hctx, params)) + return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE); + + if (create) { + EVP_EncryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv); + memcpy((void *) name, (void *) key->name, TLS_TICKET_NAMELEN); + if (TLScontext->log_mask & TLS_LOG_CACHE) + msg_info("%s: Issuing session ticket, key expiration: %ld", + TLScontext->namaddr, (long) key->tout); + } else { + EVP_DecryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv); + if (TLScontext->log_mask & TLS_LOG_CACHE) + msg_info("%s: Decrypting session ticket, key expiration: %ld", + TLScontext->namaddr, (long) key->tout); + } + TLScontext->ticketed = 1; + return (TLS_TKT_ACCEPT); +} + +#else /* OPENSSL_VERSION_PREREQ(3,0) */ + +/* ticket_cb - configure tls session ticket encrypt/decrypt context */ + +static int ticket_cb(SSL *con, unsigned char name[], unsigned char iv[], + EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int create) +{ + static const EVP_MD *sha256; + TLS_TICKET_KEY *key; + TLS_SESS_STATE *TLScontext = SSL_get_ex_data(con, TLScontext_index); + int timeout = ((int) SSL_CTX_get_timeout(SSL_get_SSL_CTX(con))) / 2; + + if ((!sha256 && (sha256 = EVP_sha256()) == 0) + || (key = tls_mgr_key(create ? 0 : name, timeout)) == 0 + || (create && RAND_bytes(iv, TLS_TICKET_IVLEN) <= 0)) + return (create ? TLS_TKT_NOKEYS : TLS_TKT_STALE); + + HMAC_Init_ex(hctx, key->hmac, TLS_TICKET_MACLEN, sha256, NOENGINE); + + if (create) { + EVP_EncryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv); + memcpy((void *) name, (void *) key->name, TLS_TICKET_NAMELEN); + if (TLScontext->log_mask & TLS_LOG_CACHE) + msg_info("%s: Issuing session ticket, key expiration: %ld", + TLScontext->namaddr, (long) key->tout); + } else { + EVP_DecryptInit_ex(ctx, tkt_cipher, NOENGINE, key->bits, iv); + if (TLScontext->log_mask & TLS_LOG_CACHE) + msg_info("%s: Decrypting session ticket, key expiration: %ld", + TLScontext->namaddr, (long) key->tout); + } + TLScontext->ticketed = 1; + return (TLS_TKT_ACCEPT); +} + +#endif /* OPENSSL_VERSION_PREREQ(3,0) */ + +#endif /* defined(SSL_OP_NO_TICKET) && + * !defined(OPENSSL_NO_TLSEXT) */ + +/* tls_server_init - initialize the server-side TLS engine */ + +TLS_APPL_STATE *tls_server_init(const TLS_SERVER_INIT_PROPS *props) +{ + SSL_CTX *server_ctx; + SSL_CTX *sni_ctx; + X509_STORE *cert_store; + long off = 0; + int verify_flags = SSL_VERIFY_NONE; + int cachable; + int scache_timeout; + int ticketable = 0; + int protomask; + int min_proto; + int max_proto; + TLS_APPL_STATE *app_ctx; + int log_mask; + + /* + * Convert user loglevel to internal logmask. + */ + log_mask = tls_log_mask(props->log_param, props->log_level); + + if (log_mask & TLS_LOG_VERBOSE) + msg_info("initializing the server-side TLS engine"); + + /* + * Load (mostly cipher related) TLS-library internal main.cf parameters. + */ + tls_param_init(); + + /* + * Detect mismatch between compile-time headers and run-time library. + */ + tls_check_version(); + + /* + * Initialize the OpenSSL library, possibly loading its configuration + * file. + */ + if (tls_library_init() == 0) + return (0); + + /* + * First validate the protocols. If these are invalid, we can't continue. + */ + protomask = tls_proto_mask_lims(props->protocols, &min_proto, &max_proto); + if (protomask == TLS_PROTOCOL_INVALID) { + /* tls_protocol_mask() logs no warning. */ + msg_warn("Invalid TLS protocol list \"%s\": disabling TLS support", + props->protocols); + return (0); + } + + /* + * Create an application data index for SSL objects, so that we can + * attach TLScontext information; this information is needed inside + * tls_verify_certificate_callback(). + */ + if (TLScontext_index < 0) { + if ((TLScontext_index = SSL_get_ex_new_index(0, 0, 0, 0, 0)) < 0) { + msg_warn("Cannot allocate SSL application data index: " + "disabling TLS support"); + return (0); + } + } + + /* + * If the administrator specifies an unsupported digest algorithm, fail + * now, rather than in the middle of a TLS handshake. + */ + if (!tls_validate_digest(props->mdalg)) { + msg_warn("disabling TLS support"); + return (0); + } + + /* + * Initialize the PRNG (Pseudo Random Number Generator) with some seed + * from external and internal sources. Don't enable TLS without some real + * entropy. + */ + if (tls_ext_seed(var_tls_daemon_rand_bytes) < 0) { + msg_warn("no entropy for TLS key generation: disabling TLS support"); + return (0); + } + tls_int_seed(); + + /* + * The SSL/TLS specifications require the client to send a message in the + * oldest specification it understands with the highest level it + * understands in the message. Netscape communicator can still + * communicate with SSLv2 servers, so it sends out a SSLv2 client hello. + * To deal with it, our server must be SSLv2 aware (even if we don't like + * SSLv2), so we need to have the SSLv23 server here. If we want to limit + * the protocol level, we can add an option to not use SSLv2/v3/TLSv1 + * later. + */ + ERR_clear_error(); + server_ctx = SSL_CTX_new(TLS_server_method()); + if (server_ctx == 0) { + msg_warn("cannot allocate server SSL_CTX: disabling TLS support"); + tls_print_errors(); + return (0); + } + sni_ctx = SSL_CTX_new(TLS_server_method()); + if (sni_ctx == 0) { + SSL_CTX_free(server_ctx); + msg_warn("cannot allocate server SNI SSL_CTX: disabling TLS support"); + tls_print_errors(); + return (0); + } +#ifdef SSL_SECOP_PEER + /* Backwards compatible security as a base for opportunistic TLS. */ + SSL_CTX_set_security_level(server_ctx, 0); + SSL_CTX_set_security_level(sni_ctx, 0); +#endif + + /* + * See the verify callback in tls_verify.c + */ + SSL_CTX_set_verify_depth(server_ctx, props->verifydepth + 1); + SSL_CTX_set_verify_depth(sni_ctx, props->verifydepth + 1); + + /* + * The session cache is implemented by the tlsmgr(8) server. + * + * XXX 200502 Surprise: when OpenSSL purges an entry from the in-memory + * cache, it also attempts to purge the entry from the on-disk cache. + * This is undesirable, especially when we set the in-memory cache size + * to 1. For this reason we don't allow OpenSSL to purge on-disk cache + * entries, and leave it up to the tlsmgr process instead. Found by + * Victor Duchovni. + */ + if (tls_mgr_policy(props->cache_type, &cachable, + &scache_timeout) != TLS_MGR_STAT_OK) + scache_timeout = 0; + if (scache_timeout <= 0) + cachable = 0; + + /* + * Presently we use TLS only with SMTP where truncation attacks are not + * possible as a result of application framing. If we ever use TLS in + * some other application protocol where truncation could be relevant, + * we'd need to disable truncation detection conditionally, or explicitly + * clear the option in that code path. + */ + off |= SSL_OP_IGNORE_UNEXPECTED_EOF; + + /* + * Protocol work-arounds, OpenSSL version dependent. + */ + off |= tls_bug_bits(); + + /* + * Add SSL_OP_NO_TICKET when the timeout is zero or library support is + * incomplete. + */ +#ifndef OPENSSL_NO_TLSEXT + ticketable = (*var_tls_tkt_cipher && scache_timeout > 0 + && !(off & SSL_OP_NO_TICKET)); + if (ticketable) { +#if OPENSSL_VERSION_PREREQ(3,0) + tkt_cipher = EVP_CIPHER_fetch(NULL, var_tls_tkt_cipher, NULL); +#else + tkt_cipher = EVP_get_cipherbyname(var_tls_tkt_cipher); +#endif + if (tkt_cipher == 0 + || EVP_CIPHER_mode(tkt_cipher) != EVP_CIPH_CBC_MODE + || EVP_CIPHER_iv_length(tkt_cipher) != TLS_TICKET_IVLEN + || EVP_CIPHER_key_length(tkt_cipher) < TLS_TICKET_IVLEN + || EVP_CIPHER_key_length(tkt_cipher) > TLS_TICKET_KEYLEN) { + msg_warn("%s: invalid value: %s; session tickets disabled", + VAR_TLS_TKT_CIPHER, var_tls_tkt_cipher); + ticketable = 0; + } + } + if (ticketable) { +#if OPENSSL_VERSION_PREREQ(3,0) + SSL_CTX_set_tlsext_ticket_key_evp_cb(server_ctx, ticket_cb); +#else + SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, ticket_cb); +#endif + + /* + * OpenSSL 1.1.1 introduces support for TLS 1.3, which can issue more + * than one ticket per handshake. While this may be appropriate for + * communication between browsers and webservers, it is not terribly + * useful for MTAs, many of which other than Postfix don't do TLS + * session caching at all, and Postfix has no mechanism for storing + * multiple session tickets, if more than one sent, the second + * clobbers the first. OpenSSL 1.1.1 servers default to issuing two + * tickets for non-resumption handshakes, we reduce this to one. Our + * ticket decryption callback already (since 2.11) asks OpenSSL to + * avoid issuing new tickets when the presented ticket is re-usable. + */ + SSL_CTX_set_num_tickets(server_ctx, 1); + } +#endif + if (!ticketable) + off |= SSL_OP_NO_TICKET; + + SSL_CTX_set_options(server_ctx, off); + + /* + * Global protocol selection. + */ + if (protomask != 0) + SSL_CTX_set_options(server_ctx, TLS_SSL_OP_PROTOMASK(protomask)); + SSL_CTX_set_min_proto_version(server_ctx, min_proto); + SSL_CTX_set_max_proto_version(server_ctx, max_proto); + SSL_CTX_set_min_proto_version(sni_ctx, min_proto); + SSL_CTX_set_max_proto_version(sni_ctx, max_proto); + + /* + * Some sites may want to give the client less rope. On the other hand, + * this could trigger inter-operability issues, the client should not + * offer ciphers it implements poorly, but this hasn't stopped some + * vendors from getting it wrong. + */ + if (var_tls_preempt_clist) + SSL_CTX_set_options(server_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + + /* Done with server_ctx options, clone to sni_ctx */ + SSL_CTX_clear_options(sni_ctx, ~0); + SSL_CTX_set_options(sni_ctx, SSL_CTX_get_options(server_ctx)); + + /* + * Set the call-back routine to debug handshake progress. + */ + if (log_mask & TLS_LOG_DEBUG) { + SSL_CTX_set_info_callback(server_ctx, tls_info_callback); + SSL_CTX_set_info_callback(sni_ctx, tls_info_callback); + } + + /* + * Load the CA public key certificates for both the server cert and for + * the verification of client certificates. As provided by OpenSSL we + * support two types of CA certificate handling: One possibility is to + * add all CA certificates to one large CAfile, the other possibility is + * a directory pointed to by CApath, containing separate files for each + * CA with softlinks named after the hash values of the certificate. The + * first alternative has the advantage that the file is opened and read + * at startup time, so that you don't have the hassle to maintain another + * copy of the CApath directory for chroot-jail. + */ + if (tls_set_ca_certificate_info(server_ctx, + props->CAfile, props->CApath) < 0) { + /* tls_set_ca_certificate_info() already logs a warning. */ + SSL_CTX_free(server_ctx); /* 200411 */ + SSL_CTX_free(sni_ctx); + return (0); + } + + /* + * Upref and share the cert store. Sadly we can't yet use + * SSL_CTX_set1_cert_store(3) which was added in OpenSSL 1.1.0. + */ + cert_store = SSL_CTX_get_cert_store(server_ctx); + X509_STORE_up_ref(cert_store); + SSL_CTX_set_cert_store(sni_ctx, cert_store); + + /* + * Load the server public key certificate and private key from file and + * check whether the cert matches the key. We can use RSA certificates + * ("cert") DSA certificates ("dcert") or ECDSA certificates ("eccert"). + * All three can be made available at the same time. The CA certificates + * for all three are handled in the same setup already finished. Which + * one is used depends on the cipher negotiated (that is: the first + * cipher listed by the client which does match the server). A client + * with RSA only (e.g. Netscape) will use the RSA certificate only. A + * client with openssl-library will use RSA first if not especially + * changed in the cipher setup. + */ + if (tls_set_my_certificate_key_info(server_ctx, + props->chain_files, + props->cert_file, + props->key_file, + props->dcert_file, + props->dkey_file, + props->eccert_file, + props->eckey_file) < 0) { + /* tls_set_my_certificate_key_info() already logs a warning. */ + SSL_CTX_free(server_ctx); /* 200411 */ + SSL_CTX_free(sni_ctx); + return (0); + } + + /* + * Diffie-Hellman key generation parameters can either be loaded from + * files (preferred) or taken from compiled in values. First, set the + * callback that will select the values when requested, then load the + * (possibly) available DH parameters from files. We are generous with + * the error handling, since we do have default values compiled in, so we + * will not abort but just log the error message. + */ + if (*props->dh1024_param_file != 0) + tls_set_dh_from_file(props->dh1024_param_file); + tls_tmp_dh(server_ctx, 1); + tls_tmp_dh(sni_ctx, 1); + + /* + * Enable EECDH if available, errors are not fatal, we just keep going + * with any remaining key-exchange algorithms. + */ + tls_auto_eecdh_curves(server_ctx, var_tls_eecdh_auto); + tls_auto_eecdh_curves(sni_ctx, var_tls_eecdh_auto); + + /* + * If we want to check client certificates, we have to indicate it in + * advance. By now we only allow to decide on a global basis. If we want + * to allow certificate based relaying, we must ask the client to provide + * one with SSL_VERIFY_PEER. The client now can decide, whether it + * provides one or not. We can enforce a failure of the negotiation with + * SSL_VERIFY_FAIL_IF_NO_PEER_CERT, if we do not allow a connection + * without one. In the "server hello" following the initialization by the + * "client hello" the server must provide a list of CAs it is willing to + * accept. Some clever clients will then select one from the list of + * available certificates matching these CAs. Netscape Communicator will + * present the list of certificates for selecting the one to be sent, or + * it will issue a warning, if there is no certificate matching the + * available CAs. + * + * With regard to the purpose of the certificate for relaying, we might like + * a later negotiation, maybe relaying would already be allowed for other + * reasons, but this would involve severe changes in the internal postfix + * logic, so we have to live with it the way it is. + */ + if (props->ask_ccert) + verify_flags = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE; + SSL_CTX_set_verify(server_ctx, verify_flags, + tls_verify_certificate_callback); + SSL_CTX_set_verify(sni_ctx, verify_flags, + tls_verify_certificate_callback); + if (props->ask_ccert && *props->CAfile) { + STACK_OF(X509_NAME) *calist = SSL_load_client_CA_file(props->CAfile); + + if (calist == 0) { + /* Not generally critical */ + msg_warn("error loading client CA names from: %s", + props->CAfile); + tls_print_errors(); + } + SSL_CTX_set_client_CA_list(server_ctx, calist); + + if (calist != 0 && sk_X509_NAME_num(calist) > 0) { + calist = SSL_dup_CA_list(calist); + + if (calist == 0) { + msg_warn("error duplicating client CA names for SNI"); + tls_print_errors(); + } else { + SSL_CTX_set_client_CA_list(sni_ctx, calist); + } + } + } + + /* + * Initialize our own TLS server handle, before diving into the details + * of TLS session cache management. + */ + app_ctx = tls_alloc_app_context(server_ctx, sni_ctx, log_mask); + + if (cachable || ticketable || props->set_sessid) { + + /* + * Initialize the session cache. + * + * With a large number of concurrent smtpd(8) processes, it is not a + * good idea to cache multiple large session objects in each process. + * We set the internal cache size to 1, and don't register a + * "remove_cb" so as to avoid deleting good sessions from the + * external cache prematurely (when the internal cache is full, + * OpenSSL removes sessions from the external cache also)! + * + * This makes SSL_CTX_remove_session() not useful for flushing broken + * sessions from the external cache, so we must delete them directly + * (not via a callback). + * + * Set a session id context to identify to what type of server process + * created a session. In our case, the context is simply the name of + * the mail system: "Postfix/TLS". + */ + SSL_CTX_sess_set_cache_size(server_ctx, 1); + SSL_CTX_set_session_id_context(server_ctx, + (void *) &server_session_id_context, + sizeof(server_session_id_context)); + SSL_CTX_set_session_cache_mode(server_ctx, + SSL_SESS_CACHE_SERVER | + SSL_SESS_CACHE_NO_INTERNAL | + SSL_SESS_CACHE_NO_AUTO_CLEAR); + if (cachable) { + app_ctx->cache_type = mystrdup(props->cache_type); + + SSL_CTX_sess_set_get_cb(server_ctx, get_server_session_cb); + SSL_CTX_sess_set_new_cb(server_ctx, new_server_session_cb); + } + + /* + * OpenSSL ignores timed-out sessions. We need to set the internal + * cache timeout at least as high as the external cache timeout. This + * applies even if no internal cache is used. We set the session + * lifetime to twice the cache lifetime, which is also the issuing + * and retired key validation lifetime of session tickets keys. This + * way a session always lasts longer than the server's ability to + * decrypt its session ticket. Otherwise, a bug in OpenSSL may fail + * to re-issue tickets when sessions decrypt, but are expired. + */ + SSL_CTX_set_timeout(server_ctx, 2 * scache_timeout); + } else { + + /* + * If we have no external cache, disable all caching. No use wasting + * server memory resources with sessions they are unlikely to be able + * to reuse. + */ + SSL_CTX_set_session_cache_mode(server_ctx, SSL_SESS_CACHE_OFF); + } + + return (app_ctx); +} + + /* + * This is the actual startup routine for a new connection. We expect that + * the SMTP buffers are flushed and the "220 Ready to start TLS" was sent to + * the client, so that we can immediately start the TLS handshake process. + */ +TLS_SESS_STATE *tls_server_start(const TLS_SERVER_START_PROPS *props) +{ + int sts; + TLS_SESS_STATE *TLScontext; + const char *cipher_list; + TLS_APPL_STATE *app_ctx = props->ctx; + int log_mask = app_ctx->log_mask; + + /* + * Implicitly enable logging of trust chain errors when verified certs + * are required. + */ + if (props->requirecert) + log_mask |= TLS_LOG_UNTRUSTED; + + if (log_mask & TLS_LOG_VERBOSE) + msg_info("setting up TLS connection from %s", props->namaddr); + + /* + * Allocate a new TLScontext for the new connection and get an SSL + * structure. Add the location of TLScontext to the SSL to later retrieve + * the information inside the tls_verify_certificate_callback(). + */ + TLScontext = tls_alloc_sess_context(log_mask, props->namaddr); + TLScontext->cache_type = app_ctx->cache_type; + + ERR_clear_error(); + if ((TLScontext->con = (SSL *) SSL_new(app_ctx->ssl_ctx)) == 0) { + msg_warn("Could not allocate 'TLScontext->con' with SSL_new()"); + tls_print_errors(); + tls_free_context(TLScontext); + return (0); + } + cipher_list = tls_set_ciphers(TLScontext, props->cipher_grade, + props->cipher_exclusions); + if (cipher_list == 0) { + /* already warned */ + tls_free_context(TLScontext); + return (0); + } + if (log_mask & TLS_LOG_VERBOSE) + msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list); + + TLScontext->serverid = mystrdup(props->serverid); + TLScontext->am_server = 1; + TLScontext->stream = props->stream; + TLScontext->mdalg = props->mdalg; + + if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) { + msg_warn("Could not set application data for 'TLScontext->con'"); + tls_print_errors(); + tls_free_context(TLScontext); + return (0); + } +#ifdef SSL_SECOP_PEER + /* When authenticating the peer, use 80-bit plus OpenSSL security level */ + if (props->requirecert) + SSL_set_security_level(TLScontext->con, 1); +#endif + + /* + * Before really starting anything, try to seed the PRNG a little bit + * more. + */ + tls_int_seed(); + (void) tls_ext_seed(var_tls_daemon_rand_bytes); + + /* + * Connect the SSL connection with the network socket. + */ + if (SSL_set_fd(TLScontext->con, props->stream == 0 ? props->fd : + vstream_fileno(props->stream)) != 1) { + msg_info("SSL_set_fd error to %s", props->namaddr); + tls_print_errors(); + uncache_session(app_ctx->ssl_ctx, TLScontext); + tls_free_context(TLScontext); + return (0); + } + + /* + * If the debug level selected is high enough, all of the data is dumped: + * TLS_LOG_TLSPKTS will dump the SSL negotiation, TLS_LOG_ALLPKTS will + * dump everything. + * + * We do have an SSL_set_fd() and now suddenly a BIO_ routine is called? + * Well there is a BIO below the SSL routines that is automatically + * created for us, so we can use it for debugging purposes. + */ + if (log_mask & TLS_LOG_TLSPKTS) + tls_set_bio_callback(SSL_get_rbio(TLScontext->con), tls_bio_dump_cb); + + /* + * If we don't trigger the handshake in the library, leave control over + * SSL_accept/read/write/etc with the application. + */ + if (props->stream == 0) + return (TLScontext); + + /* + * Turn on non-blocking I/O so that we can enforce timeouts on network + * I/O. + */ + non_blocking(vstream_fileno(props->stream), NON_BLOCKING); + + /* + * Start TLS negotiations. This process is a black box that invokes our + * call-backs for session caching and certificate verification. + * + * Error handling: If the SSL handshake fails, we print out an error message + * and remove all TLS state concerning this session. + */ + sts = tls_bio_accept(vstream_fileno(props->stream), props->timeout, + TLScontext); + if (sts <= 0) { + if (ERR_peek_error() != 0) { + msg_info("SSL_accept error from %s: %d", props->namaddr, sts); + tls_print_errors(); + } else if (errno != 0) { + msg_info("SSL_accept error from %s: %m", props->namaddr); + } else { + msg_info("SSL_accept error from %s: lost connection", + props->namaddr); + } + tls_free_context(TLScontext); + return (0); + } + return (tls_server_post_accept(TLScontext)); +} + +/* tls_server_post_accept - post-handshake processing */ + +TLS_SESS_STATE *tls_server_post_accept(TLS_SESS_STATE *TLScontext) +{ + const SSL_CIPHER *cipher; + X509 *peer; + char buf[CCERT_BUFSIZ]; + + /* Turn off packet dump if only dumping the handshake */ + if ((TLScontext->log_mask & TLS_LOG_ALLPKTS) == 0) + tls_set_bio_callback(SSL_get_rbio(TLScontext->con), 0); + + /* + * The caller may want to know if this session was reused or if a new + * session was negotiated. + */ + TLScontext->session_reused = SSL_session_reused(TLScontext->con); + if ((TLScontext->log_mask & TLS_LOG_CACHE) && TLScontext->session_reused) + msg_info("%s: Reusing old session%s", TLScontext->namaddr, + TLScontext->ticketed ? " (RFC 5077 session ticket)" : ""); + + /* + * Let's see whether a peer certificate is available and what is the + * actual information. We want to save it for later use. + */ + peer = TLS_PEEK_PEER_CERT(TLScontext->con); + if (peer != NULL) { + TLScontext->peer_status |= TLS_CERT_FLAG_PRESENT; + if (SSL_get_verify_result(TLScontext->con) == X509_V_OK) + TLScontext->peer_status |= TLS_CERT_FLAG_TRUSTED; + + if (TLScontext->log_mask & TLS_LOG_VERBOSE) { + X509_NAME_oneline(X509_get_subject_name(peer), + buf, sizeof(buf)); + msg_info("subject=%s", printable(buf, '?')); + X509_NAME_oneline(X509_get_issuer_name(peer), + buf, sizeof(buf)); + msg_info("issuer=%s", printable(buf, '?')); + } + TLScontext->peer_CN = tls_peer_CN(peer, TLScontext); + TLScontext->issuer_CN = tls_issuer_CN(peer, TLScontext); + TLScontext->peer_cert_fprint = tls_cert_fprint(peer, TLScontext->mdalg); + TLScontext->peer_pkey_fprint = tls_pkey_fprint(peer, TLScontext->mdalg); + + if (TLScontext->log_mask & (TLS_LOG_VERBOSE | TLS_LOG_PEERCERT)) { + msg_info("%s: subject_CN=%s, issuer=%s, fingerprint=%s" + ", pkey_fingerprint=%s", + TLScontext->namaddr, + TLScontext->peer_CN, TLScontext->issuer_CN, + TLScontext->peer_cert_fprint, + TLScontext->peer_pkey_fprint); + } + TLS_FREE_PEER_CERT(peer); + + /* + * Give them a clue. Problems with trust chain verification are + * logged when the session is first negotiated, before the session is + * stored into the cache. We don't want mystery failures, so log the + * fact the real problem is to be found in the past. + */ + if (!TLS_CERT_IS_TRUSTED(TLScontext) + && (TLScontext->log_mask & TLS_LOG_UNTRUSTED)) { + if (TLScontext->session_reused == 0) + tls_log_verify_error(TLScontext); + else + msg_info("%s: re-using session with untrusted certificate, " + "look for details earlier in the log", + TLScontext->namaddr); + } + } else { + TLScontext->peer_CN = mystrdup(""); + TLScontext->issuer_CN = mystrdup(""); + TLScontext->peer_cert_fprint = mystrdup(""); + TLScontext->peer_pkey_fprint = mystrdup(""); + } + + /* + * Finally, collect information about protocol and cipher for logging + */ + TLScontext->protocol = SSL_get_version(TLScontext->con); + cipher = SSL_get_current_cipher(TLScontext->con); + TLScontext->cipher_name = SSL_CIPHER_get_name(cipher); + TLScontext->cipher_usebits = SSL_CIPHER_get_bits(cipher, + &(TLScontext->cipher_algbits)); + + /* + * If the library triggered the SSL handshake, switch to the + * tls_timed_read/write() functions and make the TLScontext available to + * those functions. Otherwise, leave control over SSL_read/write/etc. + * with the application. + */ + if (TLScontext->stream != 0) + tls_stream_start(TLScontext->stream, TLScontext); + + /* + * With the handshake done, extract TLS 1.3 signature metadata. + */ + tls_get_signature_params(TLScontext); + + /* + * All the key facts in a single log entry. + */ + if (TLScontext->log_mask & TLS_LOG_SUMMARY) + tls_log_summary(TLS_ROLE_SERVER, TLS_USAGE_NEW, TLScontext); + + tls_int_seed(); + + return (TLScontext); +} + +#endif /* USE_TLS */ diff --git a/src/tls/tls_session.c b/src/tls/tls_session.c new file mode 100644 index 0000000..a4b7a8f --- /dev/null +++ b/src/tls/tls_session.c @@ -0,0 +1,185 @@ +/*++ +/* NAME +/* tls_session +/* SUMMARY +/* TLS client and server session routines +/* SYNOPSIS +/* #include +/* +/* void tls_session_stop(ctx, stream, timeout, failure, TLScontext) +/* TLS_APPL_STATE *ctx; +/* VSTREAM *stream; +/* int timeout; +/* int failure; +/* TLS_SESS_STATE *TLScontext; +/* +/* VSTRING *tls_session_passivate(session) +/* SSL_SESSION *session; +/* +/* SSL_SESSION *tls_session_activate(session_data, session_data_len) +/* char *session_data; +/* int session_data_len; +/* DESCRIPTION +/* tls_session_stop() implements the tls_server_shutdown() +/* and the tls_client_shutdown() routines. +/* +/* tls_session_passivate() converts an SSL_SESSION object to +/* VSTRING. The result is a null pointer in case of problems, +/* otherwise it should be disposed of with vstring_free(). +/* +/* tls_session_activate() reanimates a passivated SSL_SESSION object. +/* The result is a null pointer in case of problems, +/* otherwise it should be disposed of with SSL_SESSION_free(). +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Originally written by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include + +#ifdef USE_TLS + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* TLS library. */ + +#define TLS_INTERNAL +#include + +/* Application-specific. */ + +#define STR vstring_str + +/* tls_session_stop - shut down the TLS connection and reset state */ + +void tls_session_stop(TLS_APPL_STATE *unused_ctx, VSTREAM *stream, int timeout, + int failure, TLS_SESS_STATE *TLScontext) +{ + const char *myname = "tls_session_stop"; + int retval; + + /* + * Sanity check. + */ + if (TLScontext == 0) + msg_panic("%s: stream has no active TLS context", myname); + + /* + * According to RFC 2246 (TLS 1.0), there is no requirement to wait for + * the peer's close-notify. If the application protocol provides + * sufficient session termination signaling, then there's no need to + * duplicate that at the TLS close-notify layer. + * + * https://tools.ietf.org/html/rfc2246#section-7.2.1 + * https://tools.ietf.org/html/rfc4346#section-7.2.1 + * https://tools.ietf.org/html/rfc5246#section-7.2.1 + * + * Specify 'tls_fast_shutdown = no' to enable the historical behavior + * described below. + * + * Perform SSL_shutdown() twice, as the first attempt will send out the + * shutdown alert but it will not wait for the peer's shutdown alert. + * Therefore, when we are the first party to send the alert, we must call + * SSL_shutdown() again. On failure we don't want to resume the session, + * so we will not perform SSL_shutdown() and the session will be removed + * as being bad. + */ + if (!failure && !SSL_in_init(TLScontext->con)) { + retval = tls_bio_shutdown(vstream_fileno(stream), timeout, TLScontext); + if (!var_tls_fast_shutdown && retval == 0) + tls_bio_shutdown(vstream_fileno(stream), timeout, TLScontext); + } + tls_free_context(TLScontext); + tls_stream_stop(stream); +} + +/* tls_session_passivate - passivate SSL_SESSION object */ + +VSTRING *tls_session_passivate(SSL_SESSION *session) +{ + const char *myname = "tls_session_passivate"; + int estimate; + int actual_size; + VSTRING *session_data; + unsigned char *ptr; + + /* + * First, find out how much memory is needed for the passivated + * SSL_SESSION object. + */ + estimate = i2d_SSL_SESSION(session, (unsigned char **) 0); + if (estimate <= 0) { + msg_warn("%s: i2d_SSL_SESSION failed: unable to cache session", myname); + return (0); + } + + /* + * Passivate the SSL_SESSION object. The use of a VSTRING is slightly + * wasteful but is convenient to combine data and length. + */ + session_data = vstring_alloc(estimate); + ptr = (unsigned char *) STR(session_data); + actual_size = i2d_SSL_SESSION(session, &ptr); + if (actual_size != estimate) { + msg_warn("%s: i2d_SSL_SESSION failed: unable to cache session", myname); + vstring_free(session_data); + return (0); + } + vstring_set_payload_size(session_data, actual_size); + + return (session_data); +} + +/* tls_session_activate - activate passivated session */ + +SSL_SESSION *tls_session_activate(const char *session_data, int session_data_len) +{ + SSL_SESSION *session; + const unsigned char *ptr; + + /* + * Activate the SSL_SESSION object. + */ + ptr = (const unsigned char *) session_data; + session = d2i_SSL_SESSION((SSL_SESSION **) 0, &ptr, session_data_len); + if (!session) + tls_print_errors(); + + return (session); +} + +#endif diff --git a/src/tls/tls_stream.c b/src/tls/tls_stream.c new file mode 100644 index 0000000..4cc53a7 --- /dev/null +++ b/src/tls/tls_stream.c @@ -0,0 +1,160 @@ +/*++ +/* NAME +/* tls_stream +/* SUMMARY +/* VSTREAM over TLS +/* SYNOPSIS +/* #define TLS_INTERNAL +/* #include +/* +/* void tls_stream_start(stream, context) +/* VSTREAM *stream; +/* TLS_SESS_STATE *context; +/* +/* void tls_stream_stop(stream) +/* VSTREAM *stream; +/* DESCRIPTION +/* This module implements the VSTREAM over TLS support user interface. +/* The hard work is done elsewhere. +/* +/* tls_stream_start() enables TLS on the named stream. All read +/* and write operations are directed through the TLS library, +/* using the state information specified with the context argument. +/* +/* tls_stream_stop() replaces the VSTREAM read/write routines +/* by dummies that have no side effects, and deletes the +/* VSTREAM's reference to the TLS context. +/* DIAGNOSTICS +/* The tls_stream(3) read/write routines return the non-zero +/* number of plaintext bytes read/written if successful; -1 +/* after TLS protocol failure, system-call failure, or for any +/* reason described under "in addition" below; and zero when +/* the remote party closed the connection or sent a TLS shutdown +/* request. +/* +/* Upon return from the tls_stream(3) read/write routines the +/* global errno value is non-zero when the requested operation +/* did not complete due to system call failure. +/* +/* In addition, the result value is set to -1, and the global +/* errno value is set to ETIMEDOUT, when a network read/write +/* request did not complete within the time limit. +/* SEE ALSO +/* dummy_read(3), placebo read routine +/* dummy_write(3), placebo write routine +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Based on code that was originally written by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +#ifdef USE_TLS + +/* Utility library. */ + +#include +#include +#include + +/* TLS library. */ + +#define TLS_INTERNAL +#include + + /* + * Interface mis-match compensation. The OpenSSL read/write routines return + * unspecified negative values when an operation fails, while the vstream(3) + * plaintext timed_read/write() functions follow the convention of UNIX + * system calls, and return -1 upon error. The macro below makes OpenSSL + * read/write results consistent with the UNIX system-call convention. + */ +#define NORMALIZED_VSTREAM_RETURN(retval) ((retval) < 0 ? -1 : (retval)) + +/* tls_timed_read - read content from stream, then TLS decapsulate */ + +static ssize_t tls_timed_read(int fd, void *buf, size_t len, int timeout, + void *context) +{ + const char *myname = "tls_timed_read"; + ssize_t ret; + TLS_SESS_STATE *TLScontext; + + TLScontext = (TLS_SESS_STATE *) context; + if (!TLScontext) + msg_panic("%s: no context", myname); + + ret = tls_bio_read(fd, buf, len, timeout, TLScontext); + if (ret > 0 && (TLScontext->log_mask & TLS_LOG_ALLPKTS)) + msg_info("Read %ld chars: %.*s", + (long) ret, (int) (ret > 40 ? 40 : ret), (char *) buf); + return (NORMALIZED_VSTREAM_RETURN(ret)); +} + +/* tls_timed_write - TLS encapsulate content, then write to stream */ + +static ssize_t tls_timed_write(int fd, void *buf, size_t len, int timeout, + void *context) +{ + const char *myname = "tls_timed_write"; + ssize_t ret; + TLS_SESS_STATE *TLScontext; + + TLScontext = (TLS_SESS_STATE *) context; + if (!TLScontext) + msg_panic("%s: no context", myname); + + if (TLScontext->log_mask & TLS_LOG_ALLPKTS) + msg_info("Write %ld chars: %.*s", + (long) len, (int) (len > 40 ? 40 : len), (char *) buf); + ret = tls_bio_write(fd, buf, len, timeout, TLScontext); + return (NORMALIZED_VSTREAM_RETURN(ret)); +} + +/* tls_stream_start - start VSTREAM over TLS */ + +void tls_stream_start(VSTREAM *stream, TLS_SESS_STATE *context) +{ + vstream_control(stream, + CA_VSTREAM_CTL_READ_FN(tls_timed_read), + CA_VSTREAM_CTL_WRITE_FN(tls_timed_write), + CA_VSTREAM_CTL_CONTEXT(context), + CA_VSTREAM_CTL_END); +} + +/* tls_stream_stop - stop VSTREAM over TLS */ + +void tls_stream_stop(VSTREAM *stream) +{ + + /* + * Prevent data leakage after TLS is turned off. The Postfix/TLS patch + * provided null function pointers; we use dummy routines that make less + * noise when used. + */ + vstream_control(stream, + CA_VSTREAM_CTL_READ_FN(dummy_read), + CA_VSTREAM_CTL_WRITE_FN(dummy_write), + CA_VSTREAM_CTL_CONTEXT((void *) 0), + CA_VSTREAM_CTL_END); +} + +#endif diff --git a/src/tls/tls_verify.c b/src/tls/tls_verify.c new file mode 100644 index 0000000..f32f32b --- /dev/null +++ b/src/tls/tls_verify.c @@ -0,0 +1,425 @@ +/*++ +/* NAME +/* tls_verify 3 +/* SUMMARY +/* peer name and peer certificate verification +/* SYNOPSIS +/* #define TLS_INTERNAL +/* #include +/* +/* int tls_verify_certificate_callback(ok, ctx) +/* int ok; +/* X509_STORE_CTX *ctx; +/* +/* int tls_log_verify_error(TLScontext) +/* TLS_SESS_STATE *TLScontext; +/* +/* char *tls_peer_CN(peercert, TLScontext) +/* X509 *peercert; +/* TLS_SESS_STATE *TLScontext; +/* +/* char *tls_issuer_CN(peercert, TLScontext) +/* X509 *peercert; +/* TLS_SESS_STATE *TLScontext; +/* DESCRIPTION +/* tls_verify_certificate_callback() is called several times (directly +/* or indirectly) from crypto/x509/x509_vfy.c. It collects errors +/* and trust information at each element of the trust chain. +/* The last call at depth 0 sets the verification status based +/* on the cumulative winner (lowest depth) of errors vs. trust. +/* We always return 1 (continue the handshake) and handle trust +/* and peer-name verification problems at the application level. +/* +/* tls_log_verify_error() (called only when we care about the +/* peer certificate, that is not when opportunistic) logs the +/* reason why the certificate failed to be verified. +/* +/* tls_peer_CN() returns the text CommonName for the peer +/* certificate subject, or an empty string if no CommonName was +/* found. The result is allocated with mymalloc() and must be +/* freed by the caller; it contains UTF-8 without non-printable +/* ASCII characters. +/* +/* tls_issuer_CN() returns the text CommonName for the peer +/* certificate issuer, or an empty string if no CommonName was +/* found. The result is allocated with mymalloc() and must be +/* freed by the caller; it contains UTF-8 without non-printable +/* ASCII characters. +/* +/* Arguments: +/* .IP ok +/* Result of prior verification: non-zero means success. In +/* order to reduce the noise level, some tests or error reports +/* are disabled when verification failed because of some +/* earlier problem. +/* .IP ctx +/* SSL application context. This links to the Postfix TLScontext +/* with enforcement and logging options. +/* .IP gn +/* An OpenSSL GENERAL_NAME structure holding a DNS subjectAltName +/* to be decoded and checked for validity. +/* .IP peercert +/* Server or client X.509 certificate. +/* .IP TLScontext +/* Server or client context for warning messages. +/* DIAGNOSTICS +/* tls_peer_CN() and tls_issuer_CN() log a warning when 1) the requested +/* information is not available in the specified certificate, 2) the +/* result exceeds a fixed limit, 3) the result contains NUL characters or +/* the result contains non-printable or non-ASCII characters. +/* LICENSE +/* .ad +/* .fi +/* This software is free. You can do with it whatever you want. +/* The original author kindly requests that you acknowledge +/* the use of his software. +/* AUTHOR(S) +/* Originally written by: +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Updated by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Victor Duchovni +/* Morgan Stanley +/*--*/ + +/* System library. */ + +#include +#include + +#ifdef USE_TLS +#include + +/* Utility library. */ + +#include +#include +#include + +/* TLS library. */ + +#define TLS_INTERNAL +#include + +/* update_error_state - safely stash away error state */ + +static void update_error_state(TLS_SESS_STATE *TLScontext, int depth, + X509 *errorcert, int errorcode) +{ + /* No news is good news */ + if (TLScontext->errordepth >= 0 && TLScontext->errordepth <= depth) + return; + + /* + * The certificate pointer is stable during the verification callback, + * but may be freed after the callback returns. Since we delay error + * reporting till later, we bump the refcount so we can rely on it still + * being there until later. + */ + if (TLScontext->errorcert != 0) + X509_free(TLScontext->errorcert); + if (errorcert != 0) + X509_up_ref(errorcert); + TLScontext->errorcert = errorcert; + TLScontext->errorcode = errorcode; + TLScontext->errordepth = depth; +} + +/* tls_verify_certificate_callback - verify peer certificate info */ + +int tls_verify_certificate_callback(int ok, X509_STORE_CTX *ctx) +{ + char buf[CCERT_BUFSIZ]; + X509 *cert; + int err; + int depth; + SSL *con; + TLS_SESS_STATE *TLScontext; + + /* May be NULL as of OpenSSL 1.0, thanks for the API change! */ + cert = X509_STORE_CTX_get_current_cert(ctx); + err = X509_STORE_CTX_get_error(ctx); + con = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + TLScontext = SSL_get_ex_data(con, TLScontext_index); + depth = X509_STORE_CTX_get_error_depth(ctx); + + /* + * Transient failures to load the (DNS or synthetic TLSA) trust settings + * must poison certificate verification, since otherwise the default + * trust store may bless a certificate that would have failed + * verification with the preferred trust anchors (or fingerprints). + * + * Since we unconditionally continue, or in any case if verification is + * about to succeed, there is eventually a final depth 0 callback, at + * which point we force an "unspecified" error. The failure to load the + * trust settings was logged earlier. + */ + if (TLScontext->must_fail) { + if (depth == 0) { + X509_STORE_CTX_set_error(ctx, err = X509_V_ERR_UNSPECIFIED); + update_error_state(TLScontext, depth, cert, err); + } + return (1); + } + if (ok == 0) + update_error_state(TLScontext, depth, cert, err); + + if (TLScontext->log_mask & TLS_LOG_VERBOSE) { + if (cert) + X509_NAME_oneline(X509_get_subject_name(cert), buf, sizeof(buf)); + else + strcpy(buf, ""); + msg_info("%s: depth=%d verify=%d subject=%s", + TLScontext->namaddr, depth, ok, printable(buf, '?')); + } + return (1); +} + +/* tls_log_verify_error - Report final verification error status */ + +void tls_log_verify_error(TLS_SESS_STATE *TLScontext) +{ + char buf[CCERT_BUFSIZ]; + int err = TLScontext->errorcode; + X509 *cert = TLScontext->errorcert; + int depth = TLScontext->errordepth; + +#define PURPOSE ((depth>0) ? "CA": TLScontext->am_server ? "client": "server") + + if (err == X509_V_OK) + return; + + /* + * Specific causes for verification failure. + */ + switch (err) { + case X509_V_ERR_CERT_UNTRUSTED: + + /* + * We expect the error cert to be the leaf, but it is likely + * sufficient to omit it from the log, even less user confusion. + */ + msg_info("certificate verification failed for %s: " + "not trusted by local or TLSA policy", TLScontext->namaddr); + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + msg_info("certificate verification failed for %s: " + "self-signed certificate", TLScontext->namaddr); + break; + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + + /* + * There is no difference between issuing cert not provided and + * provided, but not found in CAfile/CApath. Either way, we don't + * trust it. + */ + if (cert) + X509_NAME_oneline(X509_get_issuer_name(cert), buf, sizeof(buf)); + else + strcpy(buf, ""); + msg_info("certificate verification failed for %s: untrusted issuer %s", + TLScontext->namaddr, printable(buf, '?')); + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + msg_info("%s certificate verification failed for %s: certificate not" + " yet valid", PURPOSE, TLScontext->namaddr); + break; + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + msg_info("%s certificate verification failed for %s: certificate has" + " expired", PURPOSE, TLScontext->namaddr); + break; + case X509_V_ERR_INVALID_PURPOSE: + msg_info("certificate verification failed for %s: not designated for " + "use as a %s certificate", TLScontext->namaddr, PURPOSE); + break; + case X509_V_ERR_CERT_CHAIN_TOO_LONG: + msg_info("certificate verification failed for %s: " + "certificate chain longer than limit(%d)", + TLScontext->namaddr, depth - 1); + break; + default: + msg_info("%s certificate verification failed for %s: num=%d:%s", + PURPOSE, TLScontext->namaddr, err, + X509_verify_cert_error_string(err)); + break; + } +} + +#ifndef DONT_GRIPE +#define DONT_GRIPE 0 +#define DO_GRIPE 1 +#endif + +/* tls_text_name - extract certificate property value by name */ + +static char *tls_text_name(X509_NAME *name, int nid, const char *label, + const TLS_SESS_STATE *TLScontext, int gripe) +{ + const char *myname = "tls_text_name"; + int pos; + X509_NAME_ENTRY *entry; + ASN1_STRING *entry_str; + int asn1_type; + int utf8_length; + unsigned char *utf8_value; + int ch; + unsigned char *cp; + + if (name == 0 || (pos = X509_NAME_get_index_by_NID(name, nid, -1)) < 0) { + if (gripe != DONT_GRIPE) { + msg_warn("%s: %s: peer certificate has no %s", + myname, TLScontext->namaddr, label); + tls_print_errors(); + } + return (0); + } +#if 0 + + /* + * If the match is required unambiguous, insist that that no other values + * be present. + */ + if (X509_NAME_get_index_by_NID(name, nid, pos) >= 0) { + msg_warn("%s: %s: multiple %ss in peer certificate", + myname, TLScontext->namaddr, label); + return (0); + } +#endif + + if ((entry = X509_NAME_get_entry(name, pos)) == 0) { + /* This should not happen */ + msg_warn("%s: %s: error reading peer certificate %s entry", + myname, TLScontext->namaddr, label); + tls_print_errors(); + return (0); + } + if ((entry_str = X509_NAME_ENTRY_get_data(entry)) == 0) { + /* This should not happen */ + msg_warn("%s: %s: error reading peer certificate %s data", + myname, TLScontext->namaddr, label); + tls_print_errors(); + return (0); + } + + /* + * XXX Convert everything into UTF-8. This is a super-set of ASCII, so we + * don't have to bother with separate code paths for ASCII-like content. + * If the payload is ASCII then we won't waste lots of CPU cycles + * converting it into UTF-8. It's up to OpenSSL to do something + * reasonable when converting ASCII formats that contain non-ASCII + * content. + * + * XXX Don't bother optimizing the string length error check. It is not + * worth the complexity. + */ + asn1_type = ASN1_STRING_type(entry_str); + if ((utf8_length = ASN1_STRING_to_UTF8(&utf8_value, entry_str)) < 0) { + msg_warn("%s: %s: error decoding peer %s of ASN.1 type=%d", + myname, TLScontext->namaddr, label, asn1_type); + tls_print_errors(); + return (0); + } + + /* + * No returns without cleaning up. A good optimizer will replace multiple + * blocks of identical code by jumps to just one such block. + */ +#define TLS_TEXT_NAME_RETURN(x) do { \ + char *__tls_text_name_temp = (x); \ + OPENSSL_free(utf8_value); \ + return (__tls_text_name_temp); \ + } while (0) + + /* + * Remove trailing null characters. They would give false alarms with the + * length check and with the embedded null check. + */ +#define TRIM0(s, l) do { while ((l) > 0 && (s)[(l)-1] == 0) --(l); } while (0) + + TRIM0(utf8_value, utf8_length); + + /* + * Enforce the length limit, because the caller will copy the result into + * a fixed-length buffer. + */ + if (utf8_length >= CCERT_BUFSIZ) { + msg_warn("%s: %s: peer %s too long: %d", + myname, TLScontext->namaddr, label, utf8_length); + TLS_TEXT_NAME_RETURN(0); + } + + /* + * Reject embedded nulls in ASCII or UTF-8 names. OpenSSL is responsible + * for producing properly-formatted UTF-8. + */ + if (utf8_length != strlen((char *) utf8_value)) { + msg_warn("%s: %s: NULL character in peer %s", + myname, TLScontext->namaddr, label); + TLS_TEXT_NAME_RETURN(0); + } + + /* + * Reject non-printable ASCII characters in UTF-8 content. + * + * Note: the code below does not find control characters in illegal UTF-8 + * sequences. It's OpenSSL's job to produce valid UTF-8, and reportedly, + * it does validation. + */ + for (cp = utf8_value; (ch = *cp) != 0; cp++) { + if (ISASCII(ch) && !ISPRINT(ch)) { + msg_warn("%s: %s: non-printable content in peer %s", + myname, TLScontext->namaddr, label); + TLS_TEXT_NAME_RETURN(0); + } + } + TLS_TEXT_NAME_RETURN(mystrdup((char *) utf8_value)); +} + +/* tls_peer_CN - extract peer common name from certificate */ + +char *tls_peer_CN(X509 *peercert, const TLS_SESS_STATE *TLScontext) +{ + char *cn; + const char *san; + + /* Absent a commonName, return a validated DNS-ID SAN */ + cn = tls_text_name(X509_get_subject_name(peercert), NID_commonName, + "subject CN", TLScontext, DONT_GRIPE); + if (cn == 0 && (san = SSL_get0_peername(TLScontext->con)) != 0) + cn = mystrdup(san); + return (cn ? cn : mystrdup("")); +} + +/* tls_issuer_CN - extract issuer common name from certificate */ + +char *tls_issuer_CN(X509 *peer, const TLS_SESS_STATE *TLScontext) +{ + X509_NAME *name; + char *cn; + + name = X509_get_issuer_name(peer); + + /* + * If no issuer CN field, use Organization instead. CA certs without a CN + * are common, so we only complain if the organization is also missing. + */ + if ((cn = tls_text_name(name, NID_commonName, + "issuer CN", TLScontext, DONT_GRIPE)) == 0) + cn = tls_text_name(name, NID_organizationName, + "issuer Organization", TLScontext, DONT_GRIPE); + return (cn ? cn : mystrdup("")); +} + +#endif diff --git a/src/tls/warn-mixed-multi-key.pem b/src/tls/warn-mixed-multi-key.pem new file mode 100644 index 0000000..ab21d3a --- /dev/null +++ b/src/tls/warn-mixed-multi-key.pem @@ -0,0 +1,51 @@ +subject=/CN=mx1.example.com +issuer=/CN=EC issuer CA +-----BEGIN CERTIFICATE----- +MIIBoTCCAUagAwIBAgIBAjAKBggqhkjOPQQDAjAXMRUwEwYDVQQDDAxFQyBpc3N1 +ZXIgQ0EwIBcNMTkwMTA4MDY0NjE0WhgPMjExOTAxMDkwNjQ2MTRaMBoxGDAWBgNV +BAMMD214MS5leGFtcGxlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABBOO +wif9FUdKXAiD9T1bpr4RH01IOOhdxHZ4kwf3gmdEB0wK6jeaUVTXcBpH4qHSI6al +VORejU7dxLLviwq8DPejfjB8MB0GA1UdDgQWBBQXbO9McKUU3NbzBCPTD84CgDOa +XzAfBgNVHSMEGDAWgBSU9MnNFunfhaJ7NB97/8cPhfSv5DAJBgNVHRMEAjAAMBMG +A1UdJQQMMAoGCCsGAQUFBwMBMBoGA1UdEQQTMBGCD214MS5leGFtcGxlLmNvbTAK +BggqhkjOPQQDAgNJADBGAiEA6gE5EIg1Ap+Gmr+xC7CPKZQ60XNwm8wUJyaCDIk7 +CqMCIQCBCJ/BKwfJgmQ7KpTztuEdincvgyN7O6HM3FPfl+Fndg== +-----END CERTIFICATE----- + +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgkiPSKH+bHsvO1+AH +S555wf6m/Ii2VxJpyqbGqu8j5myhRANCAAQTjsIn/RVHSlwIg/U9W6a+ER9NSDjo +XcR2eJMH94JnRAdMCuo3mlFU13AaR+Kh0iOmpVTkXo1O3cSy74sKvAz3 +-----END PRIVATE KEY----- + +subject=/CN=EC issuer CA +issuer=/CN=EC root CA +-----BEGIN CERTIFICATE----- +MIIBcDCCARagAwIBAgIBAjAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2NDUyNFoYDzIxMTkwMTA5MDY0NTI0WjAXMRUwEwYDVQQD +DAxFQyBpc3N1ZXIgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARYcKaWmm2M +ulstiE3b15jhdyZA+Fwyb0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfs +G614fKHGUYzVo1MwUTAdBgNVHQ4EFgQUlPTJzRbp34WiezQfe//HD4X0r+QwHwYD +VR0jBBgwFoAU1HqQiWes42EpdB5ooHRxYr8z4V4wDwYDVR0TAQH/BAUwAwEB/zAK +BggqhkjOPQQDAgNIADBFAiBALgvWsycg6I1MZq6XKAlgNukQXPT/IgPiC+7Vn80U +WQIhAPqpsF4oPV7VKgFgVNmNm6MDKuuDQ47Jt/Lk79qkwqM7 +-----END CERTIFICATE----- + +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvhC73shHi6WonWdC +rbMwDzIrjYIejrvmkNQo+7hFnp2hRANCAARYcKaWmm2MulstiE3b15jhdyZA+Fwy +b0dE2W8pWKDSP/qsXTs7NvH5H8JjNZmDY4q1Nq7FRXfsG614fKHGUYzV +-----END PRIVATE KEY----- + +subject=/CN=EC root CA +issuer=/CN=EC root CA +-----BEGIN CERTIFICATE----- +MIIBbzCCARSgAwIBAgIBATAKBggqhkjOPQQDAjAVMRMwEQYDVQQDDApFQyByb290 +IENBMCAXDTE5MDEwODA2MTk1OFoYDzIxMTkwMTA5MDYxOTU4WjAVMRMwEQYDVQQD +DApFQyByb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfDF746gzVwSo +e/UsVtFqSX1ASKbYMdccE9pOB5D/fK5aPqZsy9/YKSE9T+/FcCgZa6uEROyR583Z +UMyTQFoHRqNTMFEwHQYDVR0OBBYEFNR6kIlnrONhKXQeaKB0cWK/M+FeMB8GA1Ud +IwQYMBaAFNR6kIlnrONhKXQeaKB0cWK/M+FeMA8GA1UdEwEB/wQFMAMBAf8wCgYI +KoZIzj0EAwIDSQAwRgIhALZ4ZLphG1ebIkAX+UnxJBVtDKxkq8qz0uBI0N7AEW7X +AiEAx8PLYRJjlGiWUdadeuCuavx0gNxd3wpOWTz+lC3nu/o= +-----END CERTIFICATE----- diff --git a/src/tls/warn-mixed-multi-key.pem.ref b/src/tls/warn-mixed-multi-key.pem.ref new file mode 100644 index 0000000..c9ce8db --- /dev/null +++ b/src/tls/warn-mixed-multi-key.pem.ref @@ -0,0 +1,13 @@ +unknown: warning: ignoring 2nd key at index 4 in warn-mixed-multi-key.pem after 1st at 2 +depth = 0 +issuer = /CN=EC issuer CA +subject = /CN=mx1.example.com + +depth = 1 +issuer = /CN=EC root CA +subject = /CN=EC issuer CA + +depth = 2 +issuer = /CN=EC root CA +subject = /CN=EC root CA + diff --git a/src/tlsmgr/.indent.pro b/src/tlsmgr/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/tlsmgr/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/tlsmgr/Makefile.in b/src/tlsmgr/Makefile.in new file mode 100644 index 0000000..8e7aab6 --- /dev/null +++ b/src/tlsmgr/Makefile.in @@ -0,0 +1,100 @@ +SHELL = /bin/sh +SRCS = tlsmgr.c +OBJS = tlsmgr.o +HDRS = +TESTSRC = smtpd_token_test.c +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = tlsmgr +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk *.db *.out *.tmp + rm -rf printfck + +tidy: clean + +tests: + +root_tests: + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +tlsmgr.o: ../../include/argv.h +tlsmgr.o: ../../include/attr.h +tlsmgr.o: ../../include/check_arg.h +tlsmgr.o: ../../include/data_redirect.h +tlsmgr.o: ../../include/dict.h +tlsmgr.o: ../../include/dns.h +tlsmgr.o: ../../include/events.h +tlsmgr.o: ../../include/htable.h +tlsmgr.o: ../../include/iostuff.h +tlsmgr.o: ../../include/mail_conf.h +tlsmgr.o: ../../include/mail_params.h +tlsmgr.o: ../../include/mail_proto.h +tlsmgr.o: ../../include/mail_server.h +tlsmgr.o: ../../include/mail_version.h +tlsmgr.o: ../../include/master_proto.h +tlsmgr.o: ../../include/msg.h +tlsmgr.o: ../../include/myaddrinfo.h +tlsmgr.o: ../../include/myflock.h +tlsmgr.o: ../../include/mymalloc.h +tlsmgr.o: ../../include/name_code.h +tlsmgr.o: ../../include/name_mask.h +tlsmgr.o: ../../include/nvtable.h +tlsmgr.o: ../../include/set_eugid.h +tlsmgr.o: ../../include/sock_addr.h +tlsmgr.o: ../../include/stringops.h +tlsmgr.o: ../../include/sys_defs.h +tlsmgr.o: ../../include/tls.h +tlsmgr.o: ../../include/tls_mgr.h +tlsmgr.o: ../../include/tls_prng.h +tlsmgr.o: ../../include/tls_scache.h +tlsmgr.o: ../../include/vbuf.h +tlsmgr.o: ../../include/vstream.h +tlsmgr.o: ../../include/vstring.h +tlsmgr.o: ../../include/vstring_vstream.h +tlsmgr.o: ../../include/warn_stat.h +tlsmgr.o: tlsmgr.c diff --git a/src/tlsmgr/tlsmgr.c b/src/tlsmgr/tlsmgr.c new file mode 100644 index 0000000..28ca961 --- /dev/null +++ b/src/tlsmgr/tlsmgr.c @@ -0,0 +1,1115 @@ +/*++ +/* NAME +/* tlsmgr 8 +/* SUMMARY +/* Postfix TLS session cache and PRNG manager +/* SYNOPSIS +/* \fBtlsmgr\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBtlsmgr\fR(8) manages the Postfix TLS session caches. +/* It stores and retrieves cache entries on request by +/* \fBsmtpd\fR(8) and \fBsmtp\fR(8) processes, and periodically +/* removes entries that have expired. +/* +/* The \fBtlsmgr\fR(8) also manages the PRNG (pseudo random number +/* generator) pool. It answers queries by the \fBsmtpd\fR(8) +/* and \fBsmtp\fR(8) +/* processes to seed their internal PRNG pools. +/* +/* The \fBtlsmgr\fR(8)'s PRNG pool is initially seeded from +/* an external source (EGD, /dev/urandom, or regular file). +/* It is updated at configurable pseudo-random intervals with +/* data from the external source. It is updated periodically +/* with data from TLS session cache entries and with the time +/* of day, and is updated with the time of day whenever a +/* process requests \fBtlsmgr\fR(8) service. +/* +/* The \fBtlsmgr\fR(8) saves the PRNG state to an exchange file +/* periodically and when the process terminates, and reads +/* the exchange file when initializing its PRNG. +/* SECURITY +/* .ad +/* .fi +/* The \fBtlsmgr\fR(8) is not security-sensitive. The code that maintains +/* the external and internal PRNG pools does not "trust" the +/* data that it manipulates, and the code that maintains the +/* TLS session cache does not touch the contents of the cached +/* entries, except for seeding its internal PRNG pool. +/* +/* The \fBtlsmgr\fR(8) can be run chrooted and with reduced privileges. +/* At process startup it connects to the entropy source and +/* exchange file, and creates or truncates the optional TLS +/* session cache files. +/* +/* With Postfix version 2.5 and later, the \fBtlsmgr\fR(8) no +/* longer uses root privileges when opening cache files. These +/* files should now be stored under the Postfix-owned +/* \fBdata_directory\fR. As a migration aid, an attempt to +/* open a cache file under a non-Postfix directory is redirected +/* to the Postfix-owned \fBdata_directory\fR, and a warning +/* is logged. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* There is no automatic means to limit the number of entries in the +/* TLS session caches and/or the size of the TLS cache files. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are not picked up automatically, +/* because \fBtlsmgr\fR(8) is a persistent processes. Use the +/* command "\fBpostfix reload\fR" after a configuration change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* TLS SESSION CACHE +/* .ad +/* .fi +/* .IP "\fBlmtp_tls_loglevel (0)\fR" +/* The LMTP-specific version of the smtp_tls_loglevel +/* configuration parameter. +/* .IP "\fBlmtp_tls_session_cache_database (empty)\fR" +/* The LMTP-specific version of the smtp_tls_session_cache_database +/* configuration parameter. +/* .IP "\fBlmtp_tls_session_cache_timeout (3600s)\fR" +/* The LMTP-specific version of the smtp_tls_session_cache_timeout +/* configuration parameter. +/* .IP "\fBsmtp_tls_loglevel (0)\fR" +/* Enable additional Postfix SMTP client logging of TLS activity. +/* .IP "\fBsmtp_tls_session_cache_database (empty)\fR" +/* Name of the file containing the optional Postfix SMTP client +/* TLS session cache. +/* .IP "\fBsmtp_tls_session_cache_timeout (3600s)\fR" +/* The expiration time of Postfix SMTP client TLS session cache +/* information. +/* .IP "\fBsmtpd_tls_loglevel (0)\fR" +/* Enable additional Postfix SMTP server logging of TLS activity. +/* .IP "\fBsmtpd_tls_session_cache_database (empty)\fR" +/* Name of the file containing the optional Postfix SMTP server +/* TLS session cache. +/* .IP "\fBsmtpd_tls_session_cache_timeout (3600s)\fR" +/* The expiration time of Postfix SMTP server TLS session cache +/* information. +/* PSEUDO RANDOM NUMBER GENERATOR +/* .ad +/* .fi +/* .IP "\fBtls_random_source (see 'postconf -d' output)\fR" +/* The external entropy source for the in-memory \fBtlsmgr\fR(8) pseudo +/* random number generator (PRNG) pool. +/* .IP "\fBtls_random_bytes (32)\fR" +/* The number of bytes that \fBtlsmgr\fR(8) reads from $tls_random_source +/* when (re)seeding the in-memory pseudo random number generator (PRNG) +/* pool. +/* .IP "\fBtls_random_exchange_name (see 'postconf -d' output)\fR" +/* Name of the pseudo random number generator (PRNG) state file +/* that is maintained by \fBtlsmgr\fR(8). +/* .IP "\fBtls_random_prng_update_period (3600s)\fR" +/* The time between attempts by \fBtlsmgr\fR(8) to save the state of +/* the pseudo random number generator (PRNG) to the file specified +/* with $tls_random_exchange_name. +/* .IP "\fBtls_random_reseed_period (3600s)\fR" +/* The maximal time between attempts by \fBtlsmgr\fR(8) to re-seed the +/* in-memory pseudo random number generator (PRNG) pool from external +/* sources. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdata_directory (see 'postconf -d' output)\fR" +/* The directory with Postfix-writable data files (for example: +/* caches, pseudo-random numbers). +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* smtp(8), Postfix SMTP client +/* smtpd(8), Postfix SMTP server +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* TLS_README, Postfix TLS configuration and operation +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* This service was introduced with Postfix version 2.2. +/* AUTHOR(S) +/* Lutz Jaenicke +/* BTU Cottbus +/* Allgemeine Elektrotechnik +/* Universitaetsplatz 3-4 +/* D-03044 Cottbus, Germany +/* +/* Adapted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include /* gettimeofday, not POSIX */ +#include + +#ifndef UCHAR_MAX +#define UCHAR_MAX 0xff +#endif + +/* OpenSSL library. */ + +#ifdef USE_TLS +#include /* For the PRNG */ +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Master process interface. */ + +#include +#include + +/* TLS library. */ + +#ifdef USE_TLS +#include +#define TLS_INTERNAL +#include /* TLS_MGR_SCACHE_ */ +#include +#include + +/* Application-specific. */ + + /* + * Tunables. + */ +char *var_tls_rand_source; +int var_tls_rand_bytes; +int var_tls_reseed_period; +int var_tls_prng_exch_period; +char *var_smtpd_tls_loglevel; +char *var_smtpd_tls_scache_db; +int var_smtpd_tls_scache_timeout; +char *var_smtp_tls_loglevel; +char *var_smtp_tls_scache_db; +int var_smtp_tls_scache_timeout; +char *var_lmtp_tls_loglevel; +char *var_lmtp_tls_scache_db; +int var_lmtp_tls_scache_timeout; +char *var_tls_rand_exch_name; + + /* + * Bound the time that we are willing to wait for an I/O operation. This + * produces better error messages than waiting until the watchdog timer + * kills the process. + */ +#define TLS_MGR_TIMEOUT 10 + + /* + * State for updating the PRNG exchange file. + */ +static TLS_PRNG_SRC *rand_exch; + + /* + * State for seeding the internal PRNG from external source. + */ +static TLS_PRNG_SRC *rand_source_dev; +static TLS_PRNG_SRC *rand_source_egd; +static TLS_PRNG_SRC *rand_source_file; + + /* + * The external entropy source type is encoded in the source name. The + * obvious alternative is to have separate configuration parameters per + * source type, so that one process can query multiple external sources. + */ +#define DEV_PREF "dev:" +#define DEV_PREF_LEN (sizeof((DEV_PREF)) - 1) +#define DEV_PATH(dev) ((dev) + EGD_PREF_LEN) + +#define EGD_PREF "egd:" +#define EGD_PREF_LEN (sizeof((EGD_PREF)) - 1) +#define EGD_PATH(egd) ((egd) + EGD_PREF_LEN) + + /* + * State for TLS session caches. + */ +typedef struct { + char *cache_label; /* cache short-hand name */ + TLS_SCACHE *cache_info; /* cache handle */ + int cache_active; /* cache status */ + char **cache_db; /* main.cf parameter value */ + const char *log_param; /* main.cf parameter name */ + char **log_level; /* main.cf parameter value */ + int *cache_timeout; /* main.cf parameter value */ +} TLSMGR_SCACHE; + +static TLSMGR_SCACHE cache_table[] = { + TLS_MGR_SCACHE_SMTPD, 0, 0, &var_smtpd_tls_scache_db, + VAR_SMTPD_TLS_LOGLEVEL, + &var_smtpd_tls_loglevel, &var_smtpd_tls_scache_timeout, + TLS_MGR_SCACHE_SMTP, 0, 0, &var_smtp_tls_scache_db, + VAR_SMTP_TLS_LOGLEVEL, + &var_smtp_tls_loglevel, &var_smtp_tls_scache_timeout, + TLS_MGR_SCACHE_LMTP, 0, 0, &var_lmtp_tls_scache_db, + VAR_LMTP_TLS_LOGLEVEL, + &var_lmtp_tls_loglevel, &var_lmtp_tls_scache_timeout, + 0, +}; + +#define smtpd_cache (cache_table[0]) + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) +#define STREQ(x, y) (strcmp((x), (y)) == 0) + +/* tlsmgr_prng_exch_event - update PRNG exchange file */ + +static void tlsmgr_prng_exch_event(int unused_event, void *dummy) +{ + const char *myname = "tlsmgr_prng_exch_event"; + unsigned char randbyte; + int next_period; + struct stat st; + + if (msg_verbose) + msg_info("%s: update PRNG exchange file", myname); + + /* + * Sanity check. If the PRNG exchange file was removed, there is no point + * updating it further. Restart the process and update the new file. + */ + if (fstat(rand_exch->fd, &st) < 0) + msg_fatal("cannot fstat() the PRNG exchange file: %m"); + if (st.st_nlink == 0) { + msg_warn("PRNG exchange file was removed -- exiting to reopen"); + sleep(1); + exit(0); + } + tls_prng_exch_update(rand_exch); + + /* + * Make prediction difficult for outsiders and calculate the time for the + * next execution randomly. + */ + RAND_bytes(&randbyte, 1); + next_period = (var_tls_prng_exch_period * randbyte) / UCHAR_MAX; + event_request_timer(tlsmgr_prng_exch_event, dummy, next_period); +} + +/* tlsmgr_reseed_event - re-seed the internal PRNG pool */ + +static void tlsmgr_reseed_event(int unused_event, void *dummy) +{ + int next_period; + unsigned char randbyte; + int must_exit = 0; + + /* + * Reseed the internal PRNG from external source. Errors are recoverable. + * We simply restart and reconnect without making a fuss. This is OK + * because we do require that exchange file updates succeed. The exchange + * file is the only entropy source that really matters in the long term. + * + * If the administrator specifies an external randomness source that we + * could not open upon start-up, restart to see if we can open it now + * (and log a nagging warning if we can't). + */ + if (*var_tls_rand_source) { + + /* + * Source is a random device. + */ + if (rand_source_dev) { + if (tls_prng_dev_read(rand_source_dev, var_tls_rand_bytes) <= 0) { + msg_info("cannot read from entropy device %s: %m -- " + "exiting to reopen", DEV_PATH(var_tls_rand_source)); + must_exit = 1; + } + } + + /* + * Source is an EGD compatible socket. + */ + else if (rand_source_egd) { + if (tls_prng_egd_read(rand_source_egd, var_tls_rand_bytes) <= 0) { + msg_info("lost connection to EGD server %s -- " + "exiting to reconnect", EGD_PATH(var_tls_rand_source)); + must_exit = 1; + } + } + + /* + * Source is a regular file. Read the content once and close the + * file. + */ + else if (rand_source_file) { + if (tls_prng_file_read(rand_source_file, var_tls_rand_bytes) <= 0) + msg_warn("cannot read from entropy file %s: %m", + var_tls_rand_source); + tls_prng_file_close(rand_source_file); + rand_source_file = 0; + var_tls_rand_source[0] = 0; + } + + /* + * Could not open the external source upon start-up. See if we can + * open it this time. Save PRNG state before we exit. + */ + else { + msg_info("exiting to reopen external entropy source %s", + var_tls_rand_source); + must_exit = 1; + } + } + + /* + * Save PRNG state in case we must exit. + */ + if (must_exit) { + if (rand_exch) + tls_prng_exch_update(rand_exch); + sleep(1); + exit(0); + } + + /* + * Make prediction difficult for outsiders and calculate the time for the + * next execution randomly. + */ + RAND_bytes(&randbyte, 1); + next_period = (var_tls_reseed_period * randbyte) / UCHAR_MAX; + event_request_timer(tlsmgr_reseed_event, dummy, next_period); +} + +/* tlsmgr_cache_run_event - start TLS session cache scan */ + +static void tlsmgr_cache_run_event(int unused_event, void *ctx) +{ + const char *myname = "tlsmgr_cache_run_event"; + TLSMGR_SCACHE *cache = (TLSMGR_SCACHE *) ctx; + + /* + * This routine runs when it is time for another TLS session cache scan. + * Make sure this routine gets called again in the future. + * + * Don't start a new scan when the timer goes off while cache cleanup is + * still in progress. + */ + if (cache->cache_info->verbose) + msg_info("%s: start TLS %s session cache cleanup", + myname, cache->cache_label); + + if (cache->cache_active == 0) + cache->cache_active = + tls_scache_sequence(cache->cache_info, DICT_SEQ_FUN_FIRST, + TLS_SCACHE_SEQUENCE_NOTHING); + + event_request_timer(tlsmgr_cache_run_event, (void *) cache, + cache->cache_info->timeout); +} + +/* tlsmgr_key - return matching or current RFC 5077 session ticket keys */ + +static int tlsmgr_key(VSTRING *buffer, int timeout) +{ + TLS_TICKET_KEY *key; + TLS_TICKET_KEY tmp; + unsigned char *name; + time_t now = time((time_t *) 0); + + /* In tlsmgr requests we encode null key names as empty strings. */ + name = LEN(buffer) ? (unsigned char *) STR(buffer) : 0; + + /* + * Each key's encrypt and subsequent decrypt-only timeout is half of the + * total session timeout. + */ + timeout /= 2; + + /* Attempt to locate existing key */ + if ((key = tls_scache_key(name, now, timeout)) == 0) { + if (name == 0) { + /* Create new encryption key */ + if (RAND_bytes(tmp.name, TLS_TICKET_NAMELEN) <= 0 + || RAND_bytes(tmp.bits, TLS_TICKET_KEYLEN) <= 0 + || RAND_bytes(tmp.hmac, TLS_TICKET_MACLEN) <= 0) + return (TLS_MGR_STAT_ERR); + tmp.tout = now + timeout - 1; + key = tls_scache_key_rotate(&tmp); + } else { + /* No matching decryption key found */ + return (TLS_MGR_STAT_ERR); + } + } + /* Return value overwrites name buffer */ + vstring_memcpy(buffer, (char *) key, sizeof(*key)); + return (TLS_MGR_STAT_OK); +} + +/* tlsmgr_loop - TLS manager main loop */ + +static int tlsmgr_loop(char *unused_name, char **unused_argv) +{ + struct timeval tv; + int active = 0; + TLSMGR_SCACHE *ent; + + /* + * Update the PRNG pool with the time of day. We do it here after every + * event (including internal timer events and external client request + * events), instead of doing it in individual event call-back routines. + */ + GETTIMEOFDAY(&tv); + RAND_seed(&tv, sizeof(struct timeval)); + + /* + * This routine runs as part of the event handling loop, after the event + * manager has delivered a timer or I/O event, or after it has waited for + * a specified amount of time. The result value of tlsmgr_loop() + * specifies how long the event manager should wait for the next event. + * + * We use this loop to interleave TLS session cache cleanup with other + * activity. Interleaved processing is needed when we use a client-server + * protocol for entropy and session state exchange with smtp(8) and + * smtpd(8) processes. + */ +#define DONT_WAIT 0 +#define WAIT_FOR_EVENT (-1) + + for (ent = cache_table; ent->cache_label; ++ent) { + if (ent->cache_info && ent->cache_active) + active |= ent->cache_active = + tls_scache_sequence(ent->cache_info, DICT_SEQ_FUN_NEXT, + TLS_SCACHE_SEQUENCE_NOTHING); + } + + return (active ? DONT_WAIT : WAIT_FOR_EVENT); +} + +/* tlsmgr_request_receive - receive request */ + +static int tlsmgr_request_receive(VSTREAM *client_stream, VSTRING *request) +{ + int count; + + /* + * Kluge: choose the protocol depending on the request size. + */ + if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) { + msg_warn("timeout while waiting for data from %s", + VSTREAM_PATH(client_stream)); + return (-1); + } + if ((count = peekfd(vstream_fileno(client_stream))) < 0) { + msg_warn("cannot examine read buffer of %s: %m", + VSTREAM_PATH(client_stream)); + return (-1); + } + + /* + * Short request: master trigger. Use the string+null protocol. + */ + if (count <= 2) { + if (vstring_get_null(request, client_stream) == VSTREAM_EOF) { + msg_warn("end-of-input while reading request from %s: %m", + VSTREAM_PATH(client_stream)); + return (-1); + } + } + + /* + * Long request: real tlsmgr client. Use the attribute list protocol. + */ + else { + if (attr_scan(client_stream, + ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_MGR_ATTR_REQ, request), + ATTR_TYPE_END) != 1) { + return (-1); + } + } + return (0); +} + +/* tlsmgr_service - respond to external request */ + +static void tlsmgr_service(VSTREAM *client_stream, char *unused_service, + char **argv) +{ + static VSTRING *request = 0; + static VSTRING *cache_type = 0; + static VSTRING *cache_id = 0; + static VSTRING *buffer = 0; + int len; + static char wakeup[] = { /* master wakeup request */ + TRIGGER_REQ_WAKEUP, + 0, + }; + TLSMGR_SCACHE *ent; + int status = TLS_MGR_STAT_FAIL; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Initialize. We're select threaded, so we can use static buffers. + */ + if (request == 0) { + request = vstring_alloc(10); + cache_type = vstring_alloc(10); + cache_id = vstring_alloc(10); + buffer = vstring_alloc(10); + } + + /* + * This routine runs whenever a client connects to the socket dedicated + * to the tlsmgr service (including wake up events sent by the master). + * All connection-management stuff is handled by the common code in + * multi_server.c. + */ + if (tlsmgr_request_receive(client_stream, request) == 0) { + + /* + * Load session from cache. + */ + if (STREQ(STR(request), TLS_MGR_REQ_LOOKUP)) { + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id), + ATTR_TYPE_END) == 2) { + for (ent = cache_table; ent->cache_label; ++ent) + if (strcmp(ent->cache_label, STR(cache_type)) == 0) + break; + if (ent->cache_label == 0) { + msg_warn("bogus cache type \"%s\" in \"%s\" request", + STR(cache_type), TLS_MGR_REQ_LOOKUP); + VSTRING_RESET(buffer); + } else if (ent->cache_info == 0) { + + /* + * Cache type valid, but not enabled + */ + VSTRING_RESET(buffer); + } else { + status = tls_scache_lookup(ent->cache_info, + STR(cache_id), buffer) ? + TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + SEND_ATTR_DATA(TLS_MGR_ATTR_SESSION, + LEN(buffer), STR(buffer)), + ATTR_TYPE_END); + } + + /* + * Save session to cache. + */ + else if (STREQ(STR(request), TLS_MGR_REQ_UPDATE)) { + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id), + RECV_ATTR_DATA(TLS_MGR_ATTR_SESSION, buffer), + ATTR_TYPE_END) == 3) { + for (ent = cache_table; ent->cache_label; ++ent) + if (strcmp(ent->cache_label, STR(cache_type)) == 0) + break; + if (ent->cache_label == 0) { + msg_warn("bogus cache type \"%s\" in \"%s\" request", + STR(cache_type), TLS_MGR_REQ_UPDATE); + } else if (ent->cache_info != 0) { + status = + tls_scache_update(ent->cache_info, STR(cache_id), + STR(buffer), LEN(buffer)) ? + TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + ATTR_TYPE_END); + } + + /* + * Delete session from cache. + */ + else if (STREQ(STR(request), TLS_MGR_REQ_DELETE)) { + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_ID, cache_id), + ATTR_TYPE_END) == 2) { + for (ent = cache_table; ent->cache_label; ++ent) + if (strcmp(ent->cache_label, STR(cache_type)) == 0) + break; + if (ent->cache_label == 0) { + msg_warn("bogus cache type \"%s\" in \"%s\" request", + STR(cache_type), TLS_MGR_REQ_DELETE); + } else if (ent->cache_info != 0) { + status = tls_scache_delete(ent->cache_info, + STR(cache_id)) ? + TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR; + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + ATTR_TYPE_END); + } + + /* + * RFC 5077 TLS session ticket keys + */ + else if (STREQ(STR(request), TLS_MGR_REQ_TKTKEY)) { + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_DATA(TLS_MGR_ATTR_KEYNAME, buffer), + ATTR_TYPE_END) == 1) { + if (LEN(buffer) != 0 && LEN(buffer) != TLS_TICKET_NAMELEN) { + msg_warn("invalid session ticket key name length: %ld", + (long) LEN(buffer)); + VSTRING_RESET(buffer); + } else if (*smtpd_cache.cache_timeout <= 0) { + status = TLS_MGR_STAT_ERR; + VSTRING_RESET(buffer); + } else { + status = tlsmgr_key(buffer, *smtpd_cache.cache_timeout); + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + SEND_ATTR_DATA(TLS_MGR_ATTR_KEYBUF, + LEN(buffer), STR(buffer)), + ATTR_TYPE_END); + } + + /* + * Entropy request. + */ + else if (STREQ(STR(request), TLS_MGR_REQ_SEED)) { + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_INT(TLS_MGR_ATTR_SIZE, &len), + ATTR_TYPE_END) == 1) { + VSTRING_RESET(buffer); + if (len <= 0 || len > 255) { + msg_warn("bogus seed length \"%d\" in \"%s\" request", + len, TLS_MGR_REQ_SEED); + } else { + VSTRING_SPACE(buffer, len); + RAND_bytes((unsigned char *) STR(buffer), len); + vstring_set_payload_size(buffer, len); + status = TLS_MGR_STAT_OK; + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + SEND_ATTR_DATA(TLS_MGR_ATTR_SEED, + LEN(buffer), STR(buffer)), + ATTR_TYPE_END); + } + + /* + * Caching policy request. + */ + else if (STREQ(STR(request), TLS_MGR_REQ_POLICY)) { + int cachable = 0; + int timeout = 0; + + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_MGR_ATTR_CACHE_TYPE, cache_type), + ATTR_TYPE_END) == 1) { + for (ent = cache_table; ent->cache_label; ++ent) + if (strcmp(ent->cache_label, STR(cache_type)) == 0) + break; + if (ent->cache_label == 0) { + msg_warn("bogus cache type \"%s\" in \"%s\" request", + STR(cache_type), TLS_MGR_REQ_POLICY); + } else { + cachable = (ent->cache_info != 0) ? 1 : 0; + timeout = *ent->cache_timeout; + status = TLS_MGR_STAT_OK; + } + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, status), + SEND_ATTR_INT(TLS_MGR_ATTR_CACHABLE, cachable), + SEND_ATTR_INT(TLS_MGR_ATTR_SESSTOUT, timeout), + ATTR_TYPE_END); + } + + /* + * Master trigger. Normally, these triggers arrive only after some + * other process requested the tlsmgr's service. The purpose is to + * restart the tlsmgr after it aborted due to a fatal run-time error, + * so that it can continue its housekeeping even while nothing is + * using TLS. + * + * XXX Which begs the question, if TLS isn't used often, do we need a + * tlsmgr background process? It could terminate when the session + * caches are empty. + */ + else if (STREQ(STR(request), wakeup)) { + if (msg_verbose) + msg_info("received master trigger"); + multi_server_disconnect(client_stream); + return; /* NOT: vstream_fflush */ + } + } + + /* + * Protocol error. + */ + else { + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, TLS_MGR_STAT_FAIL), + ATTR_TYPE_END); + } + vstream_fflush(client_stream); +} + +/* tlsmgr_pre_init - pre-jail initialization */ + +static void tlsmgr_pre_init(char *unused_name, char **unused_argv) +{ + char *path; + struct timeval tv; + TLSMGR_SCACHE *ent; + VSTRING *redirect; + HTABLE *dup_filter; + const char *dup_label; + + /* + * If nothing else works then at least this will get us a few bits of + * entropy. + * + * XXX This is our first call into the OpenSSL library. We should find out + * if this can be moved to the post-jail initialization phase, without + * breaking compatibility with existing installations. + */ + GETTIMEOFDAY(&tv); + tv.tv_sec ^= getpid(); + RAND_seed(&tv, sizeof(struct timeval)); + + /* + * Open the external entropy source. We will not be able to open it again + * after we are sent to chroot jail, so we keep it open. Errors are not + * fatal. The exchange file (see below) is the only entropy source that + * really matters in the long run. + * + * Security note: we open the entropy source while privileged, but we don't + * access the source until after we release privileges. This way, none of + * the OpenSSL code gets to execute while we are privileged. + */ + if (*var_tls_rand_source) { + + /* + * Source is a random device. + */ + if (!strncmp(var_tls_rand_source, DEV_PREF, DEV_PREF_LEN)) { + path = DEV_PATH(var_tls_rand_source); + rand_source_dev = tls_prng_dev_open(path, TLS_MGR_TIMEOUT); + if (rand_source_dev == 0) + msg_warn("cannot open entropy device %s: %m", path); + } + + /* + * Source is an EGD compatible socket. + */ + else if (!strncmp(var_tls_rand_source, EGD_PREF, EGD_PREF_LEN)) { + path = EGD_PATH(var_tls_rand_source); + rand_source_egd = tls_prng_egd_open(path, TLS_MGR_TIMEOUT); + if (rand_source_egd == 0) + msg_warn("cannot connect to EGD server %s: %m", path); + } + + /* + * Source is regular file. We read this only once. + */ + else { + rand_source_file = + tls_prng_file_open(var_tls_rand_source, TLS_MGR_TIMEOUT); + } + } else { + msg_warn("no entropy source specified with parameter %s", + VAR_TLS_RAND_SOURCE); + msg_warn("encryption keys etc. may be predictable"); + } + + /* + * Security: don't create root-owned files that contain untrusted data. + * And don't create Postfix-owned files in root-owned directories, + * either. We want a correct relationship between (file/directory) + * ownership and (file/directory) content. + */ + SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid); + redirect = vstring_alloc(100); + + /* + * Open the PRNG exchange file before going to jail, but don't use root + * privileges. Start the exchange file read/update pseudo thread after + * dropping privileges. + */ + if (*var_tls_rand_exch_name) { + rand_exch = + tls_prng_exch_open(data_redirect_file(redirect, + var_tls_rand_exch_name)); + if (rand_exch == 0) + msg_fatal("cannot open PRNG exchange file %s: %m", + var_tls_rand_exch_name); + } + + /* + * Open the session cache files and discard old information before going + * to jail, but don't use root privilege. Start the cache maintenance + * pseudo threads after dropping privileges. + */ + dup_filter = htable_create(sizeof(cache_table) / sizeof(cache_table[0])); + for (ent = cache_table; ent->cache_label; ++ent) { + /* Sanitize session timeout */ + if (*ent->cache_timeout > 0) { + if (*ent->cache_timeout < TLS_SESSION_LIFEMIN) + *ent->cache_timeout = TLS_SESSION_LIFEMIN; + } else { + *ent->cache_timeout = 0; + } + /* External cache database disabled if timeout is non-positive */ + if (*ent->cache_timeout > 0 && **ent->cache_db) { + if ((dup_label = htable_find(dup_filter, *ent->cache_db)) != 0) + msg_fatal("do not use the same TLS cache file %s for %s and %s", + *ent->cache_db, dup_label, ent->cache_label); + htable_enter(dup_filter, *ent->cache_db, ent->cache_label); + ent->cache_info = + tls_scache_open(data_redirect_map(redirect, *ent->cache_db), + ent->cache_label, + tls_log_mask(ent->log_param, + *ent->log_level) & TLS_LOG_CACHE, + *ent->cache_timeout); + } + } + htable_free(dup_filter, (void (*) (void *)) 0); + + /* + * Clean up and restore privilege. + */ + vstring_free(redirect); + RESTORE_SAVED_EUGID(); +} + +/* tlsmgr_post_init - post-jail initialization */ + +static void tlsmgr_post_init(char *unused_name, char **unused_argv) +{ + TLSMGR_SCACHE *ent; + +#define NULL_EVENT (0) +#define NULL_CONTEXT ((char *) 0) + + /* + * This routine runs after the skeleton code has entered the chroot jail, + * but before any client requests are serviced. Prevent automatic process + * suicide after a limited number of client requests or after a limited + * amount of idle time. + */ + var_use_limit = 0; + var_idle_limit = 0; + + /* + * Start the internal PRNG re-seeding pseudo thread first. + */ + if (*var_tls_rand_source) { + if (var_tls_reseed_period > INT_MAX / UCHAR_MAX) + var_tls_reseed_period = INT_MAX / UCHAR_MAX; + tlsmgr_reseed_event(NULL_EVENT, NULL_CONTEXT); + } + + /* + * Start the exchange file read/update pseudo thread. + */ + if (*var_tls_rand_exch_name) { + if (var_tls_prng_exch_period > INT_MAX / UCHAR_MAX) + var_tls_prng_exch_period = INT_MAX / UCHAR_MAX; + tlsmgr_prng_exch_event(NULL_EVENT, NULL_CONTEXT); + } + + /* + * Start the cache maintenance pseudo threads last. Strictly speaking + * there is nothing to clean up after we truncate the database to zero + * length, but early cleanup makes verbose logging more informative (we + * get positive confirmation that the cleanup threads are running). + */ + for (ent = cache_table; ent->cache_label; ++ent) + if (ent->cache_info) + tlsmgr_cache_run_event(NULL_EVENT, (void *) ent); +} + +/* tlsmgr_post_accept - announce our protocol */ + +static void tlsmgr_post_accept(VSTREAM *stream, char *unused_name, + char **unused_argv, HTABLE *unused_table) +{ + + /* + * Announce the protocol. + */ + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSMGR), + ATTR_TYPE_END); + (void) vstream_fflush(stream); +} + + +/* tlsmgr_before_exit - save PRNG state before exit */ + +static void tlsmgr_before_exit(char *unused_service_name, char **unused_argv) +{ + + /* + * Save state before we exit after "postfix reload". + */ + if (rand_exch) + tls_prng_exch_update(rand_exch); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static const CONFIG_STR_TABLE str_table[] = { + VAR_TLS_RAND_SOURCE, DEF_TLS_RAND_SOURCE, &var_tls_rand_source, 0, 0, + VAR_TLS_RAND_EXCH_NAME, DEF_TLS_RAND_EXCH_NAME, &var_tls_rand_exch_name, 0, 0, + VAR_SMTPD_TLS_SCACHE_DB, DEF_SMTPD_TLS_SCACHE_DB, &var_smtpd_tls_scache_db, 0, 0, + VAR_SMTP_TLS_SCACHE_DB, DEF_SMTP_TLS_SCACHE_DB, &var_smtp_tls_scache_db, 0, 0, + VAR_LMTP_TLS_SCACHE_DB, DEF_LMTP_TLS_SCACHE_DB, &var_lmtp_tls_scache_db, 0, 0, + VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0, + VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_LMTP_TLS_LOGLEVEL, DEF_LMTP_TLS_LOGLEVEL, &var_lmtp_tls_loglevel, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_TLS_RESEED_PERIOD, DEF_TLS_RESEED_PERIOD, &var_tls_reseed_period, 1, 0, + VAR_TLS_PRNG_UPD_PERIOD, DEF_TLS_PRNG_UPD_PERIOD, &var_tls_prng_exch_period, 1, 0, + VAR_SMTPD_TLS_SCACHTIME, DEF_SMTPD_TLS_SCACHTIME, &var_smtpd_tls_scache_timeout, 0, MAX_SMTPD_TLS_SCACHETIME, + VAR_SMTP_TLS_SCACHTIME, DEF_SMTP_TLS_SCACHTIME, &var_smtp_tls_scache_timeout, 0, MAX_SMTP_TLS_SCACHETIME, + VAR_LMTP_TLS_SCACHTIME, DEF_LMTP_TLS_SCACHTIME, &var_lmtp_tls_scache_timeout, 0, MAX_LMTP_TLS_SCACHETIME, + 0, + }; + static const CONFIG_INT_TABLE int_table[] = { + VAR_TLS_RAND_BYTES, DEF_TLS_RAND_BYTES, &var_tls_rand_bytes, 1, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Use the multi service skeleton, and require that no-one else is + * monitoring our service port while this process runs. + */ + multi_server_main(argc, argv, tlsmgr_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_PRE_INIT(tlsmgr_pre_init), + CA_MAIL_SERVER_POST_INIT(tlsmgr_post_init), + CA_MAIL_SERVER_POST_ACCEPT(tlsmgr_post_accept), + CA_MAIL_SERVER_EXIT(tlsmgr_before_exit), + CA_MAIL_SERVER_LOOP(tlsmgr_loop), + CA_MAIL_SERVER_SOLITARY, + 0); +} + +#else + +/* tlsmgr_service - respond to external trigger(s), non-TLS version */ + +static void tlsmgr_service(VSTREAM *unused_stream, char *unused_service, + char **unused_argv) +{ + msg_info("TLS support is not compiled in -- exiting"); +} + +/* main - the main program, non-TLS version */ + +int main(int argc, char **argv) +{ + + /* + * 200411 We can't simply use msg_fatal() here, because the logging + * hasn't been initialized. The text would disappear because stderr is + * redirected to /dev/null. + * + * We invoke multi_server_main() to complete program initialization + * (including logging) and then invoke the tlsmgr_service() routine to + * log the message that says why this program will not run. + */ + multi_server_main(argc, argv, tlsmgr_service, + 0); +} + +#endif diff --git a/src/tlsproxy/.indent.pro b/src/tlsproxy/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/tlsproxy/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/tlsproxy/Makefile.in b/src/tlsproxy/Makefile.in new file mode 100644 index 0000000..c72aa81 --- /dev/null +++ b/src/tlsproxy/Makefile.in @@ -0,0 +1,117 @@ +SHELL = /bin/sh +SRCS = tlsproxy.c tlsproxy_state.c +OBJS = tlsproxy.o tlsproxy_state.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = tlsproxy +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)tls$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)dns$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: test + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +tlsproxy.o: ../../include/argv.h +tlsproxy.o: ../../include/attr.h +tlsproxy.o: ../../include/been_here.h +tlsproxy.o: ../../include/check_arg.h +tlsproxy.o: ../../include/dns.h +tlsproxy.o: ../../include/events.h +tlsproxy.o: ../../include/htable.h +tlsproxy.o: ../../include/iostuff.h +tlsproxy.o: ../../include/mail_conf.h +tlsproxy.o: ../../include/mail_params.h +tlsproxy.o: ../../include/mail_proto.h +tlsproxy.o: ../../include/mail_server.h +tlsproxy.o: ../../include/mail_version.h +tlsproxy.o: ../../include/msg.h +tlsproxy.o: ../../include/myaddrinfo.h +tlsproxy.o: ../../include/mymalloc.h +tlsproxy.o: ../../include/name_code.h +tlsproxy.o: ../../include/name_mask.h +tlsproxy.o: ../../include/nbbio.h +tlsproxy.o: ../../include/nvtable.h +tlsproxy.o: ../../include/sock_addr.h +tlsproxy.o: ../../include/split_at.h +tlsproxy.o: ../../include/sys_defs.h +tlsproxy.o: ../../include/tls.h +tlsproxy.o: ../../include/tls_proxy.h +tlsproxy.o: ../../include/vbuf.h +tlsproxy.o: ../../include/vstream.h +tlsproxy.o: ../../include/vstring.h +tlsproxy.o: tlsproxy.c +tlsproxy.o: tlsproxy.h +tlsproxy_state.o: ../../include/argv.h +tlsproxy_state.o: ../../include/attr.h +tlsproxy_state.o: ../../include/check_arg.h +tlsproxy_state.o: ../../include/dns.h +tlsproxy_state.o: ../../include/events.h +tlsproxy_state.o: ../../include/htable.h +tlsproxy_state.o: ../../include/mail_conf.h +tlsproxy_state.o: ../../include/mail_server.h +tlsproxy_state.o: ../../include/msg.h +tlsproxy_state.o: ../../include/myaddrinfo.h +tlsproxy_state.o: ../../include/mymalloc.h +tlsproxy_state.o: ../../include/name_code.h +tlsproxy_state.o: ../../include/name_mask.h +tlsproxy_state.o: ../../include/nbbio.h +tlsproxy_state.o: ../../include/nvtable.h +tlsproxy_state.o: ../../include/sock_addr.h +tlsproxy_state.o: ../../include/sys_defs.h +tlsproxy_state.o: ../../include/tls.h +tlsproxy_state.o: ../../include/tls_proxy.h +tlsproxy_state.o: ../../include/vbuf.h +tlsproxy_state.o: ../../include/vstream.h +tlsproxy_state.o: ../../include/vstring.h +tlsproxy_state.o: tlsproxy.h +tlsproxy_state.o: tlsproxy_state.c diff --git a/src/tlsproxy/tlsproxy.c b/src/tlsproxy/tlsproxy.c new file mode 100644 index 0000000..ccb3804 --- /dev/null +++ b/src/tlsproxy/tlsproxy.c @@ -0,0 +1,1968 @@ +/*++ +/* NAME +/* tlsproxy 8 +/* SUMMARY +/* Postfix TLS proxy +/* SYNOPSIS +/* \fBtlsproxy\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBtlsproxy\fR(8) server implements a two-way TLS proxy. It +/* is used by the \fBpostscreen\fR(8) server to talk SMTP-over-TLS +/* with remote SMTP clients that are not allowlisted (including +/* clients whose allowlist status has expired), and by the +/* \fBsmtp\fR(8) client to support TLS connection reuse, but it +/* should also work for non-SMTP protocols. +/* +/* Although one \fBtlsproxy\fR(8) process can serve multiple +/* sessions at the same time, it is a good idea to allow the +/* number of processes to increase with load, so that the +/* service remains responsive. +/* PROTOCOL EXAMPLE +/* .ad +/* .fi +/* The example below concerns \fBpostscreen\fR(8). However, +/* the \fBtlsproxy\fR(8) server is agnostic of the application +/* protocol, and the example is easily adapted to other +/* applications. +/* +/* After receiving a valid remote SMTP client STARTTLS command, +/* the \fBpostscreen\fR(8) server sends the remote SMTP client +/* endpoint string, the requested role (server), and the +/* requested timeout to \fBtlsproxy\fR(8). \fBpostscreen\fR(8) +/* then receives a "TLS available" indication from \fBtlsproxy\fR(8). +/* If the TLS service is available, \fBpostscreen\fR(8) sends +/* the remote SMTP client file descriptor to \fBtlsproxy\fR(8), +/* and sends the plaintext 220 greeting to the remote SMTP +/* client. This triggers TLS negotiations between the remote +/* SMTP client and \fBtlsproxy\fR(8). Upon completion of the +/* TLS-level handshake, \fBtlsproxy\fR(8) translates between +/* plaintext from/to \fBpostscreen\fR(8) and ciphertext to/from +/* the remote SMTP client. +/* SECURITY +/* .ad +/* .fi +/* The \fBtlsproxy\fR(8) server is moderately security-sensitive. +/* It talks to untrusted clients on the network. The process +/* can be run chrooted at fixed low privilege. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are not picked up automatically, +/* as \fBtlsproxy\fR(8) processes may run for a long time +/* depending on mail server load. Use the command "\fBpostfix +/* reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* STARTTLS GLOBAL CONTROLS +/* .ad +/* .fi +/* The following settings are global and therefore cannot be +/* overruled by information specified in a \fBtlsproxy\fR(8) +/* client request. +/* .IP "\fBtls_append_default_CA (no)\fR" +/* Append the system-supplied default Certification Authority +/* certificates to the ones specified with *_tls_CApath or *_tls_CAfile. +/* .IP "\fBtls_daemon_random_bytes (32)\fR" +/* The number of pseudo-random bytes that an \fBsmtp\fR(8) or \fBsmtpd\fR(8) +/* process requests from the \fBtlsmgr\fR(8) server in order to seed its +/* internal pseudo random number generator (PRNG). +/* .IP "\fBtls_high_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "high" grade ciphers. +/* .IP "\fBtls_medium_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "medium" or higher grade ciphers. +/* .IP "\fBtls_low_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "low" or higher grade ciphers. +/* .IP "\fBtls_export_cipherlist (see 'postconf -d' output)\fR" +/* The OpenSSL cipherlist for "export" or higher grade ciphers. +/* .IP "\fBtls_null_cipherlist (eNULL:!aNULL)\fR" +/* The OpenSSL cipherlist for "NULL" grade ciphers that provide +/* authentication without encryption. +/* .IP "\fBtls_eecdh_strong_curve (prime256v1)\fR" +/* The elliptic curve used by the Postfix SMTP server for sensibly +/* strong +/* ephemeral ECDH key exchange. +/* .IP "\fBtls_eecdh_ultra_curve (secp384r1)\fR" +/* The elliptic curve used by the Postfix SMTP server for maximally +/* strong +/* ephemeral ECDH key exchange. +/* .IP "\fBtls_disable_workarounds (see 'postconf -d' output)\fR" +/* List or bit-mask of OpenSSL bug work-arounds to disable. +/* .IP "\fBtls_preempt_cipherlist (no)\fR" +/* With SSLv3 and later, use the Postfix SMTP server's cipher +/* preference order instead of the remote client's cipher preference +/* order. +/* .PP +/* Available in Postfix version 2.9 and later: +/* .IP "\fBtls_legacy_public_key_fingerprints (no)\fR" +/* A temporary migration aid for sites that use certificate +/* \fIpublic-key\fR fingerprints with Postfix 2.9.0..2.9.5, which use +/* an incorrect algorithm. +/* .PP +/* Available in Postfix version 2.11-3.1: +/* .IP "\fBtls_dane_digest_agility (on)\fR" +/* Configure RFC7671 DANE TLSA digest algorithm agility. +/* .IP "\fBtls_dane_trust_anchor_digest_enable (yes)\fR" +/* Enable support for RFC 6698 (DANE TLSA) DNS records that contain +/* digests of trust-anchors with certificate usage "2". +/* .PP +/* Available in Postfix version 2.11 and later: +/* .IP "\fBtlsmgr_service_name (tlsmgr)\fR" +/* The name of the \fBtlsmgr\fR(8) service entry in master.cf. +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBtls_session_ticket_cipher (Postfix >= 3.0: aes-256-cbc, Postfix < 3.0: aes-128-cbc)\fR" +/* Algorithm used to encrypt RFC5077 TLS session tickets. +/* .IP "\fBopenssl_path (openssl)\fR" +/* The location of the OpenSSL command line program \fBopenssl\fR(1). +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBtls_eecdh_auto_curves (see 'postconf -d' output)\fR" +/* The prioritized list of elliptic curves supported by the Postfix +/* SMTP client and server. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBtls_server_sni_maps (empty)\fR" +/* Optional lookup tables that map names received from remote SMTP +/* clients via the TLS Server Name Indication (SNI) extension to the +/* appropriate keys and certificate chains. +/* .PP +/* Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later: +/* .IP "\fBtls_fast_shutdown_enable (yes)\fR" +/* A workaround for implementations that hang Postfix while shutting +/* down a TLS session, until Postfix times out. +/* .PP +/* Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later: +/* .IP "\fBtls_config_file (default)\fR" +/* Optional configuration file with baseline OpenSSL settings. +/* .IP "\fBtls_config_name (empty)\fR" +/* The application name passed by Postfix to OpenSSL library +/* initialization functions. +/* STARTTLS SERVER CONTROLS +/* .ad +/* .fi +/* These settings are clones of Postfix SMTP server settings. +/* They allow \fBtlsproxy\fR(8) to load the same certificate +/* and private key information as the Postfix SMTP server, +/* before dropping privileges, so that the key files can be +/* kept read-only for root. These settings can currently not +/* be overruled by information in a \fBtlsproxy\fR(8) client +/* request, but that limitation may be removed in a future +/* version. +/* .IP "\fBtlsproxy_tls_CAfile ($smtpd_tls_CAfile)\fR" +/* A file containing (PEM format) CA certificates of root CAs +/* trusted to sign either remote SMTP client certificates or intermediate +/* CA certificates. +/* .IP "\fBtlsproxy_tls_CApath ($smtpd_tls_CApath)\fR" +/* A directory containing (PEM format) CA certificates of root CAs +/* trusted to sign either remote SMTP client certificates or intermediate +/* CA certificates. +/* .IP "\fBtlsproxy_tls_always_issue_session_ids ($smtpd_tls_always_issue_session_ids)\fR" +/* Force the Postfix \fBtlsproxy\fR(8) server to issue a TLS session id, +/* even when TLS session caching is turned off. +/* .IP "\fBtlsproxy_tls_ask_ccert ($smtpd_tls_ask_ccert)\fR" +/* Ask a remote SMTP client for a client certificate. +/* .IP "\fBtlsproxy_tls_ccert_verifydepth ($smtpd_tls_ccert_verifydepth)\fR" +/* The verification depth for remote SMTP client certificates. +/* .IP "\fBtlsproxy_tls_cert_file ($smtpd_tls_cert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server RSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_tls_ciphers ($smtpd_tls_ciphers)\fR" +/* The minimum TLS cipher grade that the Postfix \fBtlsproxy\fR(8) server +/* will use with opportunistic TLS encryption. +/* .IP "\fBtlsproxy_tls_dcert_file ($smtpd_tls_dcert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server DSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_tls_dh1024_param_file ($smtpd_tls_dh1024_param_file)\fR" +/* File with DH parameters that the Postfix \fBtlsproxy\fR(8) server +/* should use with non-export EDH ciphers. +/* .IP "\fBtlsproxy_tls_dh512_param_file ($smtpd_tls_dh512_param_file)\fR" +/* File with DH parameters that the Postfix \fBtlsproxy\fR(8) server +/* should use with export-grade EDH ciphers. +/* .IP "\fBtlsproxy_tls_dkey_file ($smtpd_tls_dkey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server DSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_tls_eccert_file ($smtpd_tls_eccert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server ECDSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_tls_eckey_file ($smtpd_tls_eckey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server ECDSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_tls_eecdh_grade ($smtpd_tls_eecdh_grade)\fR" +/* The Postfix \fBtlsproxy\fR(8) server security grade for ephemeral +/* elliptic-curve Diffie-Hellman (EECDH) key exchange. +/* .IP "\fBtlsproxy_tls_exclude_ciphers ($smtpd_tls_exclude_ciphers)\fR" +/* List of ciphers or cipher types to exclude from the \fBtlsproxy\fR(8) +/* server cipher list at all TLS security levels. +/* .IP "\fBtlsproxy_tls_fingerprint_digest ($smtpd_tls_fingerprint_digest)\fR" +/* The message digest algorithm to construct remote SMTP +/* client-certificate +/* fingerprints. +/* .IP "\fBtlsproxy_tls_key_file ($smtpd_tls_key_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) server RSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_tls_loglevel ($smtpd_tls_loglevel)\fR" +/* Enable additional Postfix \fBtlsproxy\fR(8) server logging of TLS +/* activity. +/* .IP "\fBtlsproxy_tls_mandatory_ciphers ($smtpd_tls_mandatory_ciphers)\fR" +/* The minimum TLS cipher grade that the Postfix \fBtlsproxy\fR(8) server +/* will use with mandatory TLS encryption. +/* .IP "\fBtlsproxy_tls_mandatory_exclude_ciphers ($smtpd_tls_mandatory_exclude_ciphers)\fR" +/* Additional list of ciphers or cipher types to exclude from the +/* \fBtlsproxy\fR(8) server cipher list at mandatory TLS security levels. +/* .IP "\fBtlsproxy_tls_mandatory_protocols ($smtpd_tls_mandatory_protocols)\fR" +/* The SSL/TLS protocols accepted by the Postfix \fBtlsproxy\fR(8) server +/* with mandatory TLS encryption. +/* .IP "\fBtlsproxy_tls_protocols ($smtpd_tls_protocols)\fR" +/* List of TLS protocols that the Postfix \fBtlsproxy\fR(8) server will +/* exclude or include with opportunistic TLS encryption. +/* .IP "\fBtlsproxy_tls_req_ccert ($smtpd_tls_req_ccert)\fR" +/* With mandatory TLS encryption, require a trusted remote SMTP +/* client certificate in order to allow TLS connections to proceed. +/* .IP "\fBtlsproxy_tls_security_level ($smtpd_tls_security_level)\fR" +/* The SMTP TLS security level for the Postfix \fBtlsproxy\fR(8) server; +/* when a non-empty value is specified, this overrides the obsolete +/* parameters smtpd_use_tls and smtpd_enforce_tls. +/* .IP "\fBtlsproxy_tls_chain_files ($smtpd_tls_chain_files)\fR" +/* Files with the Postfix \fBtlsproxy\fR(8) server keys and certificate +/* chains in PEM format. +/* STARTTLS CLIENT CONTROLS +/* .ad +/* .fi +/* These settings are clones of Postfix SMTP client settings. +/* They allow \fBtlsproxy\fR(8) to load the same certificate +/* and private key information as the Postfix SMTP client, +/* before dropping privileges, so that the key files can be +/* kept read-only for root. Some settings may be overruled by +/* information in a \fBtlsproxy\fR(8) client request. +/* .PP +/* Available in Postfix version 3.4 and later: +/* .IP "\fBtlsproxy_client_CAfile ($smtp_tls_CAfile)\fR" +/* A file containing CA certificates of root CAs trusted to sign +/* either remote TLS server certificates or intermediate CA certificates. +/* .IP "\fBtlsproxy_client_CApath ($smtp_tls_CApath)\fR" +/* Directory with PEM format Certification Authority certificates +/* that the Postfix \fBtlsproxy\fR(8) client uses to verify a remote TLS +/* server certificate. +/* .IP "\fBtlsproxy_client_chain_files ($smtp_tls_chain_files)\fR" +/* Files with the Postfix \fBtlsproxy\fR(8) client keys and certificate +/* chains in PEM format. +/* .IP "\fBtlsproxy_client_cert_file ($smtp_tls_cert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client RSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_client_key_file ($smtp_tls_key_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client RSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_client_dcert_file ($smtp_tls_dcert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client DSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_client_dkey_file ($smtp_tls_dkey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client DSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_client_eccert_file ($smtp_tls_eccert_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client ECDSA certificate in PEM +/* format. +/* .IP "\fBtlsproxy_client_eckey_file ($smtp_tls_eckey_file)\fR" +/* File with the Postfix \fBtlsproxy\fR(8) client ECDSA private key in PEM +/* format. +/* .IP "\fBtlsproxy_client_fingerprint_digest ($smtp_tls_fingerprint_digest)\fR" +/* The message digest algorithm used to construct remote TLS server +/* certificate fingerprints. +/* .IP "\fBtlsproxy_client_loglevel ($smtp_tls_loglevel)\fR" +/* Enable additional Postfix \fBtlsproxy\fR(8) client logging of TLS +/* activity. +/* .IP "\fBtlsproxy_client_loglevel_parameter (smtp_tls_loglevel)\fR" +/* The name of the parameter that provides the tlsproxy_client_loglevel +/* value. +/* .IP "\fBtlsproxy_client_scert_verifydepth ($smtp_tls_scert_verifydepth)\fR" +/* The verification depth for remote TLS server certificates. +/* .IP "\fBtlsproxy_client_use_tls ($smtp_use_tls)\fR" +/* Opportunistic mode: use TLS when a remote server announces TLS +/* support. +/* .IP "\fBtlsproxy_client_enforce_tls ($smtp_enforce_tls)\fR" +/* Enforcement mode: require that SMTP servers use TLS encryption. +/* .IP "\fBtlsproxy_client_per_site ($smtp_tls_per_site)\fR" +/* Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS +/* usage policy by next-hop destination and by remote TLS server +/* hostname. +/* .PP +/* Available in Postfix version 3.4-3.6: +/* .IP "\fBtlsproxy_client_level ($smtp_tls_security_level)\fR" +/* The default TLS security level for the Postfix \fBtlsproxy\fR(8) +/* client. +/* .IP "\fBtlsproxy_client_policy ($smtp_tls_policy_maps)\fR" +/* Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS +/* security policy by next-hop destination. +/* .PP +/* Available in Postfix version 3.7 and later: +/* .IP "\fBtlsproxy_client_security_level ($smtp_tls_security_level)\fR" +/* The default TLS security level for the Postfix \fBtlsproxy\fR(8) +/* client. +/* .IP "\fBtlsproxy_client_policy_maps ($smtp_tls_policy_maps)\fR" +/* Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS +/* security policy by next-hop destination. +/* OBSOLETE STARTTLS SUPPORT CONTROLS +/* .ad +/* .fi +/* These parameters are supported for compatibility with +/* \fBsmtpd\fR(8) legacy parameters. +/* .IP "\fBtlsproxy_use_tls ($smtpd_use_tls)\fR" +/* Opportunistic TLS: announce STARTTLS support to remote SMTP clients, +/* but do not require that clients use TLS encryption. +/* .IP "\fBtlsproxy_enforce_tls ($smtpd_enforce_tls)\fR" +/* Mandatory TLS: announce STARTTLS support to remote SMTP clients, and +/* require that clients use TLS encryption. +/* .IP "\fBtlsproxy_client_use_tls ($smtp_use_tls)\fR" +/* Opportunistic mode: use TLS when a remote server announces TLS +/* support. +/* .IP "\fBtlsproxy_client_enforce_tls ($smtp_enforce_tls)\fR" +/* Enforcement mode: require that SMTP servers use TLS encryption. +/* RESOURCE CONTROLS +/* .ad +/* .fi +/* .IP "\fBtlsproxy_watchdog_timeout (10s)\fR" +/* How much time a \fBtlsproxy\fR(8) process may take to process local +/* or remote I/O before it is terminated by a built-in watchdog timer. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* postscreen(8), Postfix zombie blocker +/* smtpd(8), Postfix SMTP server +/* postconf(5), configuration parameters +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* This service was introduced with Postfix version 2.8. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include + + /* + * Master library. + */ +#include + + /* + * TLS library. + */ +#ifdef USE_TLS +#define TLS_INTERNAL /* XXX */ +#include +#include + + /* + * Application-specific. + */ +#include + + /* + * Tunable parameters. We define our clones of the smtpd(8) parameters to + * avoid any confusion about which parameters are used by this program. + */ +int var_smtpd_tls_ccert_vd; +char *var_smtpd_tls_loglevel; +bool var_smtpd_use_tls; +bool var_smtpd_enforce_tls; +bool var_smtpd_tls_ask_ccert; +bool var_smtpd_tls_req_ccert; +bool var_smtpd_tls_set_sessid; +char *var_smtpd_relay_ccerts; +char *var_smtpd_tls_chain_files; +char *var_smtpd_tls_cert_file; +char *var_smtpd_tls_key_file; +char *var_smtpd_tls_dcert_file; +char *var_smtpd_tls_dkey_file; +char *var_smtpd_tls_eccert_file; +char *var_smtpd_tls_eckey_file; +char *var_smtpd_tls_CAfile; +char *var_smtpd_tls_CApath; +char *var_smtpd_tls_ciph; +char *var_smtpd_tls_mand_ciph; +char *var_smtpd_tls_excl_ciph; +char *var_smtpd_tls_mand_excl; +char *var_smtpd_tls_proto; +char *var_smtpd_tls_mand_proto; +char *var_smtpd_tls_dh512_param_file; +char *var_smtpd_tls_dh1024_param_file; +char *var_smtpd_tls_eecdh; +char *var_smtpd_tls_fpt_dgst; +char *var_smtpd_tls_level; + +int var_tlsp_tls_ccert_vd; +char *var_tlsp_tls_loglevel; +bool var_tlsp_use_tls; +bool var_tlsp_enforce_tls; +bool var_tlsp_tls_ask_ccert; +bool var_tlsp_tls_req_ccert; +bool var_tlsp_tls_set_sessid; +char *var_tlsp_tls_chain_files; +char *var_tlsp_tls_cert_file; +char *var_tlsp_tls_key_file; +char *var_tlsp_tls_dcert_file; +char *var_tlsp_tls_dkey_file; +char *var_tlsp_tls_eccert_file; +char *var_tlsp_tls_eckey_file; +char *var_tlsp_tls_CAfile; +char *var_tlsp_tls_CApath; +char *var_tlsp_tls_ciph; +char *var_tlsp_tls_mand_ciph; +char *var_tlsp_tls_excl_ciph; +char *var_tlsp_tls_mand_excl; +char *var_tlsp_tls_proto; +char *var_tlsp_tls_mand_proto; +char *var_tlsp_tls_dh512_param_file; +char *var_tlsp_tls_dh1024_param_file; +char *var_tlsp_tls_eecdh; +char *var_tlsp_tls_fpt_dgst; +char *var_tlsp_tls_level; + +int var_tlsp_watchdog; + + /* + * Defaults for tlsp_clnt_*. + */ +char *var_smtp_tls_loglevel; +int var_smtp_tls_scert_vd; +char *var_smtp_tls_chain_files; +char *var_smtp_tls_cert_file; +char *var_smtp_tls_key_file; +char *var_smtp_tls_dcert_file; +char *var_smtp_tls_dkey_file; +char *var_smtp_tls_eccert_file; +char *var_smtp_tls_eckey_file; +char *var_smtp_tls_CAfile; +char *var_smtp_tls_CApath; +char *var_smtp_tls_fpt_dgst; +char *var_smtp_tls_level; +bool var_smtp_use_tls; +bool var_smtp_enforce_tls; +char *var_smtp_tls_per_site; +char *var_smtp_tls_policy; + +char *var_tlsp_clnt_loglevel; +char *var_tlsp_clnt_logparam; +int var_tlsp_clnt_scert_vd; +char *var_tlsp_clnt_chain_files; +char *var_tlsp_clnt_cert_file; +char *var_tlsp_clnt_key_file; +char *var_tlsp_clnt_dcert_file; +char *var_tlsp_clnt_dkey_file; +char *var_tlsp_clnt_eccert_file; +char *var_tlsp_clnt_eckey_file; +char *var_tlsp_clnt_CAfile; +char *var_tlsp_clnt_CApath; +char *var_tlsp_clnt_fpt_dgst; +char *var_tlsp_clnt_level; +bool var_tlsp_clnt_use_tls; +bool var_tlsp_clnt_enforce_tls; +char *var_tlsp_clnt_per_site; +char *var_tlsp_clnt_policy; + + /* + * TLS per-process status. + */ +static TLS_APPL_STATE *tlsp_server_ctx; +static bool tlsp_pre_jail_done; +static int ask_client_cert; +static char *tlsp_pre_jail_client_param_key; /* pre-jail global params */ +static char *tlsp_pre_jail_client_init_key; /* pre-jail init props */ + + /* + * TLS per-client status. + */ +static HTABLE *tlsp_client_app_cache; /* per-client init props */ +static BH_TABLE *tlsp_params_mismatch_filter; /* per-client nag filter */ + + /* + * Error handling: if a function detects an error, then that function is + * responsible for destroying TLSP_STATE. Exceptions to this principle are + * indicated in the code. + */ + + /* + * Internal status API. + */ +#define TLSP_STAT_OK 0 +#define TLSP_STAT_ERR (-1) + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + + /* + * The code that implements the TLS engine looks simpler than expected. That + * is the result of a great deal of effort, mainly in design and analysis. + * + * The initial use case was to provide TLS support for postscreen(8). + * + * By design, postscreen(8) is an event-driven server that must scale up to a + * large number of clients. This means that postscreen(8) must avoid doing + * CPU-intensive operations such as those in OpenSSL. + * + * tlsproxy(8) runs the OpenSSL code on behalf of postscreen(8), translating + * plaintext SMTP messages from postscreen(8) into SMTP-over-TLS messages to + * the remote SMTP client, and vice versa. As long as postscreen(8) does not + * receive email messages, the cost of doing TLS operations will be modest. + * + * Like postscreen(8), one tlsproxy(8) process services multiple remote SMTP + * clients. Unlike postscreen(8), there can be more than one tlsproxy(8) + * process, although their number is meant to be much smaller than the + * number of remote SMTP clients that talk TLS. + * + * As with postscreen(8), all I/O must be event-driven: encrypted traffic + * between tlsproxy(8) and remote SMTP clients, and plaintext traffic + * between tlsproxy(8) and postscreen(8). Event-driven plaintext I/O is + * straightforward enough that it could be abstracted away with the nbbio(3) + * module. + * + * The event-driven TLS I/O implementation is founded on on-line OpenSSL + * documentation, supplemented by statements from OpenSSL developers on + * public mailing lists. After some field experience with this code, we may + * be able to factor it out as a library module, like nbbio(3), that can + * become part of the TLS library. + * + * Later in the life cycle, tlsproxy(8) has also become an enabler for TLS + * connection reuse across different SMTP client processes. + */ + +static void tlsp_ciphertext_event(int, void *); + +#define TLSP_INIT_TIMEOUT 100 + +static void tlsp_plaintext_event(int event, void *context); + +/* tlsp_drain - delayed exit after "postfix reload" */ + +static void tlsp_drain(char *unused_service, char **unused_argv) +{ + int count; + + /* + * After "postfix reload", complete work-in-progress in the background, + * instead of dropping already-accepted connections on the floor. + * + * All error retry counts shall be limited. Instead of blocking here, we + * could retry failed fork() operations in the event call-back routines, + * but we don't need perfection. The host system is severely overloaded + * and service levels are already way down. + */ + for (count = 0; /* see below */ ; count++) { + if (count >= 5) { + msg_fatal("fork: %m"); + } else if (event_server_drain() != 0) { + msg_warn("fork: %m"); + sleep(1); + continue; + } else { + return; + } + } +} + +/* tlsp_eval_tls_error - translate TLS "error" result into action */ + +static int tlsp_eval_tls_error(TLSP_STATE *state, int err) +{ + int ciphertext_fd = state->ciphertext_fd; + + /* + * The ciphertext file descriptor is in non-blocking mode, meaning that + * each SSL_accept/connect/read/write/shutdown request may return an + * "error" indication that it needs to read or write more ciphertext. The + * purpose of this routine is to translate those "error" indications into + * the appropriate read/write/timeout event requests. + */ + switch (err) { + + /* + * No error means a successful SSL_accept/connect/shutdown request or + * sequence of SSL_read/write requests. Disable read/write events on + * the ciphertext stream. Keep the ciphertext stream timer alive as a + * safety mechanism for the case that the plaintext pseudothreads get + * stuck. + */ + case SSL_ERROR_NONE: + if (state->ssl_last_err != SSL_ERROR_NONE) { + event_disable_readwrite(ciphertext_fd); + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + state->ssl_last_err = SSL_ERROR_NONE; + } + return (TLSP_STAT_OK); + + /* + * The TLS engine wants to write to the network. Turn on + * write/timeout events on the ciphertext stream. + */ + case SSL_ERROR_WANT_WRITE: + if (state->ssl_last_err == SSL_ERROR_WANT_READ) + event_disable_readwrite(ciphertext_fd); + if (state->ssl_last_err != SSL_ERROR_WANT_WRITE) { + event_enable_write(ciphertext_fd, tlsp_ciphertext_event, + (void *) state); + state->ssl_last_err = SSL_ERROR_WANT_WRITE; + } + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + return (TLSP_STAT_OK); + + /* + * The TLS engine wants to read from the network. Turn on + * read/timeout events on the ciphertext stream. + */ + case SSL_ERROR_WANT_READ: + if (state->ssl_last_err == SSL_ERROR_WANT_WRITE) + event_disable_readwrite(ciphertext_fd); + if (state->ssl_last_err != SSL_ERROR_WANT_READ) { + event_enable_read(ciphertext_fd, tlsp_ciphertext_event, + (void *) state); + state->ssl_last_err = SSL_ERROR_WANT_READ; + } + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + return (TLSP_STAT_OK); + + /* + * Some error. Self-destruct. This automagically cleans up all + * pending read/write and timeout event requests, making state a + * dangling pointer. + */ + case SSL_ERROR_SSL: + tls_print_errors(); + /* FALLTHROUGH */ + default: + + /* + * Allow buffered-up plaintext output to trickle out. Permanently + * disable read/write activity on the ciphertext stream, so that this + * function will no longer be called. Keep the ciphertext stream + * timer alive as a safety mechanism for the case that the plaintext + * pseudothreads get stuck. Return into tlsp_strategy(), which will + * enable plaintext write events. + */ +#define TLSP_CAN_TRICKLE_OUT_PLAINTEXT(buf) \ + ((buf) && !NBBIO_ERROR_FLAGS(buf) && NBBIO_WRITE_PEND(buf)) + + if (TLSP_CAN_TRICKLE_OUT_PLAINTEXT(state->plaintext_buf)) { + event_disable_readwrite(ciphertext_fd); + event_request_timer(tlsp_ciphertext_event, (void *) state, + state->timeout); + state->flags |= TLSP_FLAG_NO_MORE_CIPHERTEXT_IO; + return (TLSP_STAT_OK); + } + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } +} + +/* tlsp_post_handshake - post-handshake processing */ + +static int tlsp_post_handshake(TLSP_STATE *state) +{ + + /* + * Do not assume that tls_server_post_accept() and + * tls_client_post_connect() will always succeed. + */ + if (state->is_server_role) + state->tls_context = tls_server_post_accept(state->tls_context); + else + state->tls_context = tls_client_post_connect(state->tls_context, + state->client_start_props); + if (state->tls_context == 0) { + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } + + /* + * Report TLS handshake results to the tlsproxy client. + * + * Security: this sends internal data over the same local plaintext stream + * that will also be used for sending decrypted remote content from an + * arbitrary remote peer. For this reason we enable decrypted I/O only + * after reporting the TLS handshake results. The Postfix attribute + * protocol is robust enough that an attacker cannot append content. + */ + if ((state->req_flags & TLS_PROXY_FLAG_SEND_CONTEXT) != 0 + && (attr_print(state->plaintext_stream, ATTR_FLAG_NONE, + SEND_ATTR_FUNC(tls_proxy_context_print, + (void *) state->tls_context), + ATTR_TYPE_END) != 0 + || vstream_fflush(state->plaintext_stream) != 0)) { + msg_warn("cannot send TLS context: %m"); + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } + + /* + * Initialize plaintext-related session state. Once we have this behind + * us, the TLSP_STATE destructor will automagically clean up requests for + * plaintext read/write/timeout events, which makes error recovery + * easier. + */ + state->plaintext_buf = + nbbio_create(vstream_fileno(state->plaintext_stream), + VSTREAM_BUFSIZE, state->server_id, + tlsp_plaintext_event, + (void *) state); + return (TLSP_STAT_OK); +} + +/* tlsp_strategy - decide what to read or write next. */ + +static void tlsp_strategy(TLSP_STATE *state) +{ + TLS_SESS_STATE *tls_context = state->tls_context; + NBBIO *plaintext_buf; + int ssl_stat; + int ssl_read_err; + int ssl_write_err; + int handshake_err; + + /* + * This function is called after every ciphertext or plaintext event, to + * schedule new ciphertext or plaintext I/O. + */ + + /* + * Try to make an SSL I/O request. If this fails with SSL_ERROR_WANT_READ + * or SSL_ERROR_WANT_WRITE, enable ciphertext read or write events, and + * retry the SSL I/O request in a later tlsp_strategy() call. + */ + if ((state->flags & TLSP_FLAG_NO_MORE_CIPHERTEXT_IO) == 0) { + + /* + * Do not enable plain-text I/O before completing the TLS handshake. + * Otherwise the remote peer can prepend plaintext to the optional + * TLS_SESS_STATE object. + */ + if (state->flags & TLSP_FLAG_DO_HANDSHAKE) { + state->timeout = state->handshake_timeout; + ERR_clear_error(); + if (state->is_server_role) + ssl_stat = SSL_accept(tls_context->con); + else + ssl_stat = SSL_connect(tls_context->con); + if (ssl_stat != 1) { + handshake_err = SSL_get_error(tls_context->con, ssl_stat); + tlsp_eval_tls_error(state, handshake_err); + /* At this point, state could be a dangling pointer. */ + return; + } + state->flags &= ~TLSP_FLAG_DO_HANDSHAKE; + state->timeout = state->session_timeout; + if (tlsp_post_handshake(state) != TLSP_STAT_OK) { + /* At this point, state is a dangling pointer. */ + return; + } + } + + /* + * Shutdown and self-destruct after NBBIO error. This automagically + * cleans up all pending read/write and timeout event requests. + * Before shutting down TLS, we stop all plain-text I/O events but + * keep the NBBIO error flags. + */ + plaintext_buf = state->plaintext_buf; + if (NBBIO_ERROR_FLAGS(plaintext_buf)) { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf)) + nbbio_disable_readwrite(state->plaintext_buf); + ERR_clear_error(); + if (!SSL_in_init(tls_context->con) + && (ssl_stat = SSL_shutdown(tls_context->con)) < 0) { + handshake_err = SSL_get_error(tls_context->con, ssl_stat); + tlsp_eval_tls_error(state, handshake_err); + /* At this point, state could be a dangling pointer. */ + return; + } + tlsp_state_free(state); + return; + } + + /* + * Try to move data from the plaintext input buffer to the TLS + * engine. + * + * XXX We're supposed to repeat the exact same SSL_write() call + * arguments after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE + * result. Rumor has it that this is because each SSL_write() call + * reads from the buffer incrementally, and returns > 0 only after + * the final byte is processed. Rumor also has it that setting + * SSL_MODE_ENABLE_PARTIAL_WRITE and + * SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER voids this requirement, and + * that repeating the request with an increased request size is OK. + * Unfortunately all this is not or poorly documented, and one has to + * rely on statements from OpenSSL developers in public mailing + * archives. + */ + ssl_write_err = SSL_ERROR_NONE; + while (NBBIO_READ_PEND(plaintext_buf) > 0) { + ERR_clear_error(); + ssl_stat = SSL_write(tls_context->con, NBBIO_READ_BUF(plaintext_buf), + NBBIO_READ_PEND(plaintext_buf)); + ssl_write_err = SSL_get_error(tls_context->con, ssl_stat); + if (ssl_write_err != SSL_ERROR_NONE) + break; + /* Allow the plaintext pseudothread to read more data. */ + NBBIO_READ_PEND(plaintext_buf) -= ssl_stat; + if (NBBIO_READ_PEND(plaintext_buf) > 0) + memmove(NBBIO_READ_BUF(plaintext_buf), + NBBIO_READ_BUF(plaintext_buf) + ssl_stat, + NBBIO_READ_PEND(plaintext_buf)); + } + + /* + * Try to move data from the TLS engine to the plaintext output + * buffer. Note: data may arrive as a side effect of calling + * SSL_write(), therefore we call SSL_read() after calling + * SSL_write(). + * + * XXX We're supposed to repeat the exact same SSL_read() call arguments + * after an SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE result. This + * supposedly means that our plaintext writer must not memmove() the + * plaintext output buffer until after the SSL_read() call succeeds. + * For now I'll ignore this, because 1) SSL_read() is documented to + * return the bytes available, instead of returning > 0 only after + * the entire buffer is processed like SSL_write() does; and 2) there + * is no "read" equivalent of the SSL_R_BAD_WRITE_RETRY, + * SSL_MODE_ENABLE_PARTIAL_WRITE or + * SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER features. + */ + ssl_read_err = SSL_ERROR_NONE; + while (NBBIO_WRITE_PEND(state->plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) { + ERR_clear_error(); + ssl_stat = SSL_read(tls_context->con, + NBBIO_WRITE_BUF(plaintext_buf) + + NBBIO_WRITE_PEND(state->plaintext_buf), + NBBIO_BUFSIZE(plaintext_buf) + - NBBIO_WRITE_PEND(state->plaintext_buf)); + ssl_read_err = SSL_get_error(tls_context->con, ssl_stat); + if (ssl_read_err != SSL_ERROR_NONE) + break; + NBBIO_WRITE_PEND(plaintext_buf) += ssl_stat; + } + + /* + * Try to enable/disable ciphertext read/write events. If SSL_write() + * was satisfied, see if SSL_read() wants to do some work. In case of + * an unrecoverable error, this automagically destroys the session + * state after cleaning up all pending read/write and timeout event + * requests. + */ + if (tlsp_eval_tls_error(state, ssl_write_err != SSL_ERROR_NONE ? + ssl_write_err : ssl_read_err) < 0) + /* At this point, state is a dangling pointer. */ + return; + } + + /* + * Destroy state when the ciphertext I/O was permanently disabled and we + * can no longer trickle out plaintext. + */ + else { + plaintext_buf = state->plaintext_buf; + if (!TLSP_CAN_TRICKLE_OUT_PLAINTEXT(plaintext_buf)) { + tlsp_state_free(state); + return; + } + } + + /* + * Try to enable/disable plaintext read/write events. Basically, if we + * have nothing to write to the plaintext stream, see if there is + * something to read. If the write buffer is empty and the read buffer is + * full, suspend plaintext I/O until conditions change (but keep the + * timer active, as a safety mechanism in case ciphertext I/O gets + * stuck). + * + * XXX In theory, if the ciphertext peer keeps writing fast enough then we + * would never read from the plaintext stream and cause the latter to + * block. In practice, postscreen(8) limits the number of client + * commands, and thus postscreen(8)'s output will fit in a kernel buffer. + * A remote SMTP server is not supposed to flood the local SMTP client + * with massive replies; if it does, then the local SMTP client should + * deal with it. + */ + if (NBBIO_WRITE_PEND(plaintext_buf) > 0) { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_READ) + nbbio_disable_readwrite(plaintext_buf); + nbbio_enable_write(plaintext_buf, state->timeout); + } else if (NBBIO_READ_PEND(plaintext_buf) < NBBIO_BUFSIZE(plaintext_buf)) { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf) & NBBIO_FLAG_WRITE) + nbbio_disable_readwrite(plaintext_buf); + nbbio_enable_read(plaintext_buf, state->timeout); + } else { + if (NBBIO_ACTIVE_FLAGS(plaintext_buf)) + nbbio_slumber(plaintext_buf, state->timeout); + } +} + +/* tlsp_plaintext_event - plaintext was read/written */ + +static void tlsp_plaintext_event(int event, void *context) +{ + TLSP_STATE *state = (TLSP_STATE *) context; + + /* + * Safety alert: the plaintext pseudothreads have "slumbered" for too + * long (see code above). This means that the ciphertext pseudothreads + * are stuck. + */ + if ((NBBIO_ERROR_FLAGS(state->plaintext_buf) & NBBIO_FLAG_TIMEOUT) != 0 + && NBBIO_ACTIVE_FLAGS(state->plaintext_buf) == 0) + msg_warn("deadlock on ciphertext stream for %s", state->remote_endpt); + + /* + * This is easy, because the NBBIO layer has already done the event + * decoding and plaintext I/O for us. All we need to do is decide if we + * want to read or write more plaintext. + */ + tlsp_strategy(state); + /* At this point, state could be a dangling pointer. */ +} + +/* tlsp_ciphertext_event - ciphertext is ready to read/write */ + +static void tlsp_ciphertext_event(int event, void *context) +{ + TLSP_STATE *state = (TLSP_STATE *) context; + + /* + * Without a TLS equivalent of the NBBIO layer, we must decode the events + * ourselves and do the ciphertext I/O. Then, we can decide if we want to + * read or write more ciphertext. + */ + if (event == EVENT_READ || event == EVENT_WRITE) { + tlsp_strategy(state); + /* At this point, state could be a dangling pointer. */ + } else { + if (event == EVENT_TIME && state->ssl_last_err == SSL_ERROR_NONE) + msg_warn("deadlock on plaintext stream for %s", + state->remote_endpt); + else + msg_warn("ciphertext read/write %s for %s", + event == EVENT_TIME ? "timeout" : "error", + state->remote_endpt); + tlsp_state_free(state); + } +} + +/* tlsp_client_start_pre_handshake - turn on TLS or force disconnect */ + +static int tlsp_client_start_pre_handshake(TLSP_STATE *state) +{ + state->client_start_props->ctx = state->appl_state; + state->client_start_props->fd = state->ciphertext_fd; + state->tls_context = tls_client_start(state->client_start_props); + if (state->tls_context != 0) + return (TLSP_STAT_OK); + + tlsp_state_free(state); + return (TLSP_STAT_ERR); +} + +/* tlsp_server_start_pre_handshake - turn on TLS or force disconnect */ + +static int tlsp_server_start_pre_handshake(TLSP_STATE *state) +{ + TLS_SERVER_START_PROPS props; + static char *cipher_grade; + static VSTRING *cipher_exclusions; + + /* + * The code in this routine is pasted literally from smtpd(8). I am not + * going to sanitize this because doing so surely will break things in + * unexpected ways. + */ + + /* + * Perform the before-handshake portion of per-session initialization. + * Pass a null VSTREAM to indicate that this program will do the + * ciphertext I/O, not libtls. + * + * The cipher grade and exclusions don't change between sessions. Compute + * just once and cache. + */ +#define ADD_EXCLUDE(vstr, str) \ + do { \ + if (*(str)) \ + vstring_sprintf_append((vstr), "%s%s", \ + VSTRING_LEN(vstr) ? " " : "", (str)); \ + } while (0) + + if (cipher_grade == 0) { + cipher_grade = + var_tlsp_enforce_tls ? var_tlsp_tls_mand_ciph : var_tlsp_tls_ciph; + cipher_exclusions = vstring_alloc(10); + ADD_EXCLUDE(cipher_exclusions, var_tlsp_tls_excl_ciph); + if (var_tlsp_enforce_tls) + ADD_EXCLUDE(cipher_exclusions, var_tlsp_tls_mand_excl); + if (ask_client_cert) + ADD_EXCLUDE(cipher_exclusions, "aNULL"); + } + state->tls_context = + TLS_SERVER_START(&props, + ctx = tlsp_server_ctx, + stream = (VSTREAM *) 0,/* unused */ + fd = state->ciphertext_fd, + timeout = 0, /* unused */ + requirecert = (var_tlsp_tls_req_ccert + && var_tlsp_enforce_tls), + serverid = state->server_id, + namaddr = state->remote_endpt, + cipher_grade = cipher_grade, + cipher_exclusions = STR(cipher_exclusions), + mdalg = var_tlsp_tls_fpt_dgst); + + if (state->tls_context == 0) { + tlsp_state_free(state); + return (TLSP_STAT_ERR); + } + + /* + * XXX Do we care about TLS session rate limits? Good postscreen(8) + * clients will occasionally require the tlsproxy to renew their + * allowlist status, but bad clients hammering the server can suck up + * lots of CPU cycles. Per-client concurrency limits in postscreen(8) + * will divert only naive security "researchers". + */ + return (TLSP_STAT_OK); +} + + /* + * From here on down is low-level code that sets up the plumbing before + * passing control to the TLS engine above. + */ + +/* tlsp_request_read_event - pre-handshake event boiler plate */ + +static void tlsp_request_read_event(int fd, EVENT_NOTIFY_FN handler, + int timeout, void *context) +{ + event_enable_read(fd, handler, context); + event_request_timer(handler, context, timeout); +} + +/* tlsp_accept_event - pre-handshake event boiler plate */ + +static void tlsp_accept_event(int event, EVENT_NOTIFY_FN handler, + void *context) +{ + if (event != EVENT_TIME) + event_cancel_timer(handler, context); + else + errno = ETIMEDOUT; + /* tlsp_state_free() disables pre-handshake plaintext I/O events. */ +} + +/* tlsp_get_fd_event - receive final connection hand-off information */ + +static void tlsp_get_fd_event(int event, void *context) +{ + const char *myname = "tlsp_get_fd_event"; + TLSP_STATE *state = (TLSP_STATE *) context; + int plaintext_fd = vstream_fileno(state->plaintext_stream); + int status; + + /* + * At this point we still manually manage plaintext read/write/timeout + * events. Disable I/O events on the plaintext stream until the TLS + * handshake is completed. Every code path must either destroy state, or + * request the next event, otherwise we have a file and memory leak. + */ + tlsp_accept_event(event, tlsp_get_fd_event, (void *) state); + event_disable_readwrite(plaintext_fd); + + if (event != EVENT_READ + || (state->ciphertext_fd = LOCAL_RECV_FD(plaintext_fd)) < 0) { + msg_warn("%s: receive remote SMTP peer file descriptor: %m", myname); + tlsp_state_free(state); + return; + } + + /* + * This is a bit early, to ensure that timer events for this file handle + * are guaranteed to be turned off by the TLSP_STATE destructor. + */ + state->ciphertext_timer = tlsp_ciphertext_event; + non_blocking(state->ciphertext_fd, NON_BLOCKING); + + /* + * Perform the TLS layer before-handshake initialization. We perform the + * remainder after the actual TLS handshake completes. + */ + if (state->is_server_role) + status = tlsp_server_start_pre_handshake(state); + else + status = tlsp_client_start_pre_handshake(state); + if (status != TLSP_STAT_OK) + /* At this point, state is a dangling pointer. */ + return; + + /* + * Trigger the initial proxy server I/Os. + */ + tlsp_strategy(state); + /* At this point, state could be a dangling pointer. */ +} + +/* tlsp_config_diff - report server-client config differences */ + +static void tlsp_log_config_diff(const char *server_cfg, const char *client_cfg) +{ + VSTRING *diff_summary = vstring_alloc(100); + char *saved_server = mystrdup(server_cfg); + char *saved_client = mystrdup(client_cfg); + char *server_field; + char *client_field; + char *server_next; + char *client_next; + + /* + * Not using argv_split(), because it would treat multiple consecutive + * newline characters as one. + */ + for (server_field = saved_server, client_field = saved_client; + server_field && client_field; + server_field = server_next, client_field = client_next) { + server_next = split_at(server_field, '\n'); + client_next = split_at(client_field, '\n'); + if (strcmp(server_field, client_field) != 0) { + if (LEN(diff_summary) > 0) + vstring_sprintf_append(diff_summary, "; "); + vstring_sprintf_append(diff_summary, + "(server) '%s' != (client) '%s'", + server_field, client_field); + } + } + msg_warn("%s", STR(diff_summary)); + + vstring_free(diff_summary); + myfree(saved_client); + myfree(saved_server); +} + +/* tlsp_client_init - initialize a TLS client engine */ + +static TLS_APPL_STATE *tlsp_client_init(TLS_CLIENT_PARAMS *tls_params, + TLS_CLIENT_INIT_PROPS *init_props) +{ + TLS_APPL_STATE *appl_state; + VSTRING *param_buf; + char *param_key; + VSTRING *init_buf; + char *init_key; + int log_hints = 0; + + /* + * Use one TLS_APPL_STATE object for all requests that specify the same + * TLS_CLIENT_INIT_PROPS. Each TLS_APPL_STATE owns an SSL_CTX, which is + * expensive to create. Bug: TLS_CLIENT_PARAMS are not used when creating + * a TLS_APPL_STATE instance. + * + * First, compute the TLS_APPL_STATE cache lookup key. Save a copy of the + * pre-jail request TLS_CLIENT_PARAMS and TLSPROXY_CLIENT_INIT_PROPS + * settings, so that we can detect post-jail requests that do not match. + */ + param_buf = vstring_alloc(100); + param_key = tls_proxy_client_param_serialize(attr_print_plain, param_buf, + tls_params); + init_buf = vstring_alloc(100); + init_key = tls_proxy_client_init_serialize(attr_print_plain, init_buf, + init_props); + if (tlsp_pre_jail_done == 0) { + if (tlsp_pre_jail_client_param_key == 0 + || tlsp_pre_jail_client_init_key == 0) { + tlsp_pre_jail_client_param_key = mystrdup(param_key); + tlsp_pre_jail_client_init_key = mystrdup(init_key); + } else if (strcmp(tlsp_pre_jail_client_param_key, param_key) != 0 + || strcmp(tlsp_pre_jail_client_init_key, init_key) != 0) { + msg_panic("tlsp_client_init: too many pre-jail calls"); + } + } + + /* + * Log a warning if a post-jail request uses unexpected TLS_CLIENT_PARAMS + * settings. Bug: TLS_CLIENT_PARAMS settings are not used when creating a + * TLS_APPL_STATE instance; this makes a mismatch of TLS_CLIENT_PARAMS + * settings problematic. + */ + if (tlsp_pre_jail_done + && !been_here_fixed(tlsp_params_mismatch_filter, param_key) + && strcmp(tlsp_pre_jail_client_param_key, param_key) != 0) { + msg_warn("request from tlsproxy client with unexpected settings"); + tlsp_log_config_diff(tlsp_pre_jail_client_param_key, param_key); + log_hints = 1; + } + + /* + * Look up the cached TLS_APPL_STATE for this tls_client_init request. + */ + if ((appl_state = (TLS_APPL_STATE *) + htable_find(tlsp_client_app_cache, init_key)) == 0) { + + /* + * Before creating a TLS_APPL_STATE instance, log a warning if a + * post-jail request differs from the saved pre-jail request AND the + * post-jail request specifies file/directory pathname arguments. + * Unexpected requests containing pathnames are problematic after + * chroot (pathname resolution) and after dropping privileges (key + * files must be root read-only). Unexpected requests are not a + * problem as long as they contain no pathnames (for example a + * tls_loglevel change). + * + * We could eliminate some of this complication by adding code that + * opens a cert/key lookup table at pre-jail time, and by reading + * cert/key info on-the-fly from that table. But then all requests + * would still have to specify the same table. + */ +#define NOT_EMPTY(x) ((x) && *(x)) + + if (tlsp_pre_jail_done + && strcmp(tlsp_pre_jail_client_init_key, init_key) != 0 + && (NOT_EMPTY(init_props->chain_files) + || NOT_EMPTY(init_props->cert_file) + || NOT_EMPTY(init_props->key_file) + || NOT_EMPTY(init_props->dcert_file) + || NOT_EMPTY(init_props->dkey_file) + || NOT_EMPTY(init_props->eccert_file) + || NOT_EMPTY(init_props->eckey_file) + || NOT_EMPTY(init_props->CAfile) + || NOT_EMPTY(init_props->CApath))) { + msg_warn("request from tlsproxy client with unexpected settings"); + tlsp_log_config_diff(tlsp_pre_jail_client_init_key, init_key); + log_hints = 1; + } + } + if (log_hints) + msg_warn("to avoid this warning, 1) identify the tlsproxy " + "client that is making this request, 2) configure " + "a custom tlsproxy service with settings that " + "match that tlsproxy client, and 3) configure " + "that tlsproxy client with a tlsproxy_service_name " + "setting that resolves to that custom tlsproxy " + "service"); + + /* + * TLS_APPL_STATE creation may fail when a post-jail request specifies + * unexpected cert/key information, but that is OK because we already + * logged a warning with configuration suggestions. + */ + if (appl_state == 0 + && (appl_state = tls_client_init(init_props)) != 0) { + (void) htable_enter(tlsp_client_app_cache, init_key, + (void *) appl_state); + + /* + * To maintain sanity, allow partial SSL_write() operations, and + * allow SSL_write() buffer pointers to change after a WANT_READ or + * WANT_WRITE result. This is based on OpenSSL developers talking on + * a mailing list, but is not supported by documentation. If this + * code stops working then no-one can be held responsible. + */ + SSL_CTX_set_mode(appl_state->ssl_ctx, + SSL_MODE_ENABLE_PARTIAL_WRITE + | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + } + vstring_free(init_buf); + vstring_free(param_buf); + return (appl_state); +} + +/* tlsp_close_event - pre-handshake plaintext-client close event */ + +static void tlsp_close_event(int event, void *context) +{ + TLSP_STATE *state = (TLSP_STATE *) context; + + tlsp_accept_event(event, tlsp_close_event, (void *) state); + tlsp_state_free(state); +} + +/* tlsp_get_request_event - receive initial hand-off info */ + +static void tlsp_get_request_event(int event, void *context) +{ + const char *myname = "tlsp_get_request_event"; + TLSP_STATE *state = (TLSP_STATE *) context; + VSTREAM *plaintext_stream = state->plaintext_stream; + int plaintext_fd = vstream_fileno(plaintext_stream); + static VSTRING *remote_endpt; + static VSTRING *server_id; + int req_flags; + int handshake_timeout; + int session_timeout; + int ready = 0; + + /* + * At this point we still manually manage plaintext read/write/timeout + * events. Every code path must either destroy state or request the next + * event, otherwise this pseudo-thread is idle until the client goes + * away. + */ + tlsp_accept_event(event, tlsp_get_request_event, (void *) state); + + /* + * One-time initialization. + */ + if (remote_endpt == 0) { + remote_endpt = vstring_alloc(10); + server_id = vstring_alloc(10); + } + + /* + * Receive the initial request attributes. Receive the remainder after we + * figure out what role we are expected to play. + * + * The tlsproxy server does not enforce per-request read/write deadlines or + * minimal data rates. Instead, the tlsproxy server relies on the + * tlsproxy client to enforce these context-dependent limits. When a + * tlsproxy client decides to time out, it will close its end of the + * tlsproxy stream, and the tlsproxy server will handle that immediately. + */ + if (event != EVENT_READ + || attr_scan(plaintext_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(TLS_ATTR_REMOTE_ENDPT, remote_endpt), + RECV_ATTR_INT(TLS_ATTR_FLAGS, &req_flags), + RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &handshake_timeout), + RECV_ATTR_INT(TLS_ATTR_TIMEOUT, &session_timeout), + RECV_ATTR_STR(TLS_ATTR_SERVERID, server_id), + ATTR_TYPE_END) != 5) { + msg_warn("%s: receive request attributes: %m", myname); + tlsp_state_free(state); + return; + } + + /* + * XXX We use the same fixed timeout throughout the entire session for + * both plaintext and ciphertext communication. This timeout is just a + * safety feature; the real timeout will be enforced by our plaintext + * peer (except during TLS the handshake, when we intentionally disable + * plaintext I/O). + */ + state->remote_endpt = mystrdup(STR(remote_endpt)); + state->server_id = mystrdup(STR(server_id)); + msg_info("CONNECT %s %s", + (req_flags & TLS_PROXY_FLAG_ROLE_SERVER) ? "from" : + (req_flags & TLS_PROXY_FLAG_ROLE_CLIENT) ? "to" : + "(bogus_direction)", state->remote_endpt); + state->req_flags = req_flags; + /* state->is_server_role is set below. */ + state->handshake_timeout = handshake_timeout; + state->session_timeout = session_timeout + 10; /* XXX */ + + /* + * Receive the TLS preferences now, to reduce the number of protocol + * roundtrips. + */ + switch (req_flags & (TLS_PROXY_FLAG_ROLE_CLIENT | TLS_PROXY_FLAG_ROLE_SERVER)) { + case TLS_PROXY_FLAG_ROLE_CLIENT: + state->is_server_role = 0; + if (attr_scan(plaintext_stream, ATTR_FLAG_STRICT, + RECV_ATTR_FUNC(tls_proxy_client_param_scan, + (void *) &state->tls_params), + RECV_ATTR_FUNC(tls_proxy_client_init_scan, + (void *) &state->client_init_props), + RECV_ATTR_FUNC(tls_proxy_client_start_scan, + (void *) &state->client_start_props), + ATTR_TYPE_END) != 3) { + msg_warn("%s: receive client TLS settings: %m", myname); + tlsp_state_free(state); + return; + } + state->appl_state = tlsp_client_init(state->tls_params, + state->client_init_props); + ready = state->appl_state != 0; + break; + case TLS_PROXY_FLAG_ROLE_SERVER: + state->is_server_role = 1; + ready = (tlsp_server_ctx != 0); + break; + default: + state->is_server_role = 0; + msg_warn("%s: bad request flags: 0x%x", myname, req_flags); + ready = 0; + } + + /* + * For portability we must send some data, after receiving the request + * attributes and before receiving the remote file descriptor. + * + * If the requested TLS engine is unavailable, hang up after making sure + * that the plaintext peer has received our "sorry" indication. + */ + if (attr_print(plaintext_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, ready), + ATTR_TYPE_END) != 0 + || vstream_fflush(plaintext_stream) != 0 + || ready == 0) { + tlsp_request_read_event(plaintext_fd, tlsp_close_event, + TLSP_INIT_TIMEOUT, (void *) state); + return; + } else { + tlsp_request_read_event(plaintext_fd, tlsp_get_fd_event, + TLSP_INIT_TIMEOUT, (void *) state); + return; + } +} + +/* tlsp_service - handle new client connection */ + +static void tlsp_service(VSTREAM *plaintext_stream, + char *service, + char **argv) +{ + TLSP_STATE *state; + int plaintext_fd = vstream_fileno(plaintext_stream); + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This program handles multiple connections, so it must not block. We + * use event-driven code for all operations that introduce latency. + * Except that attribute lists are sent/received synchronously, once the + * socket is found to be ready for transmission. + */ + non_blocking(plaintext_fd, NON_BLOCKING); + vstream_control(plaintext_stream, + CA_VSTREAM_CTL_PATH("plaintext"), + CA_VSTREAM_CTL_TIMEOUT(5), + CA_VSTREAM_CTL_END); + + (void) attr_print(plaintext_stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TLSPROXY), + ATTR_TYPE_END); + if (vstream_fflush(plaintext_stream) != 0) + msg_warn("write %s attribute: %m", MAIL_ATTR_PROTO); + + /* + * Receive postscreen's remote SMTP client address/port and socket. + */ + state = tlsp_state_create(service, plaintext_stream); + tlsp_request_read_event(plaintext_fd, tlsp_get_request_event, + TLSP_INIT_TIMEOUT, (void *) state); +} + +/* pre_jail_init_server - pre-jail initialization */ + +static void pre_jail_init_server(void) +{ + TLS_SERVER_INIT_PROPS props; + const char *cert_file; + int have_server_cert; + int no_server_cert_ok; + int require_server_cert; + + /* + * The code in this routine is pasted literally from smtpd(8). I am not + * going to sanitize this because doing so surely will break things in + * unexpected ways. + */ + if (*var_tlsp_tls_level) { + switch (tls_level_lookup(var_tlsp_tls_level)) { + default: + msg_fatal("Invalid TLS level \"%s\"", var_tlsp_tls_level); + /* NOTREACHED */ + break; + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_FPRINT: + msg_warn("%s: unsupported TLS level \"%s\", using \"encrypt\"", + VAR_TLSP_TLS_LEVEL, var_tlsp_tls_level); + /* FALLTHROUGH */ + case TLS_LEV_ENCRYPT: + var_tlsp_enforce_tls = var_tlsp_use_tls = 1; + break; + case TLS_LEV_MAY: + var_tlsp_enforce_tls = 0; + var_tlsp_use_tls = 1; + break; + case TLS_LEV_NONE: + var_tlsp_enforce_tls = var_tlsp_use_tls = 0; + break; + } + } + var_tlsp_use_tls = var_tlsp_use_tls || var_tlsp_enforce_tls; + if (!var_tlsp_use_tls) { + msg_warn("TLS server role is disabled with %s or %s", + VAR_TLSP_TLS_LEVEL, VAR_TLSP_USE_TLS); + return; + } + + /* + * Load TLS keys before dropping privileges. + * + * Can't use anonymous ciphers if we want client certificates. Must use + * anonymous ciphers if we have no certificates. + */ + ask_client_cert = require_server_cert = + (var_tlsp_tls_ask_ccert + || (var_tlsp_enforce_tls && var_tlsp_tls_req_ccert)); + if (strcasecmp(var_tlsp_tls_cert_file, "none") == 0) { + no_server_cert_ok = 1; + cert_file = ""; + } else { + no_server_cert_ok = 0; + cert_file = var_tlsp_tls_cert_file; + } + have_server_cert = + (*cert_file || *var_tlsp_tls_dcert_file || *var_tlsp_tls_eccert_file); + + if (*var_tlsp_tls_chain_files != 0) { + if (!have_server_cert) + have_server_cert = 1; + else + msg_warn("Both %s and one or more of the legacy " + " %s, %s or %s are non-empty; the legacy " + " parameters will be ignored", + VAR_TLSP_TLS_CHAIN_FILES, + VAR_TLSP_TLS_CERT_FILE, + VAR_TLSP_TLS_ECCERT_FILE, + VAR_TLSP_TLS_DCERT_FILE); + } + /* Some TLS configuration errors are not show stoppers. */ + if (!have_server_cert && require_server_cert) + msg_warn("Need a server cert to request client certs"); + if (!var_tlsp_enforce_tls && var_tlsp_tls_req_ccert) + msg_warn("Can't require client certs unless TLS is required"); + /* After a show-stopper error, log a warning. */ + if (have_server_cert || (no_server_cert_ok && !require_server_cert)) { + + tls_pre_jail_init(TLS_ROLE_SERVER); + + /* + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + */ + tlsp_server_ctx = + TLS_SERVER_INIT(&props, + log_param = VAR_TLSP_TLS_LOGLEVEL, + log_level = var_tlsp_tls_loglevel, + verifydepth = var_tlsp_tls_ccert_vd, + cache_type = TLS_MGR_SCACHE_SMTPD, + set_sessid = var_tlsp_tls_set_sessid, + chain_files = var_tlsp_tls_chain_files, + cert_file = cert_file, + key_file = var_tlsp_tls_key_file, + dcert_file = var_tlsp_tls_dcert_file, + dkey_file = var_tlsp_tls_dkey_file, + eccert_file = var_tlsp_tls_eccert_file, + eckey_file = var_tlsp_tls_eckey_file, + CAfile = var_tlsp_tls_CAfile, + CApath = var_tlsp_tls_CApath, + dh1024_param_file + = var_tlsp_tls_dh1024_param_file, + dh512_param_file + = var_tlsp_tls_dh512_param_file, + eecdh_grade = var_tlsp_tls_eecdh, + protocols = var_tlsp_enforce_tls ? + var_tlsp_tls_mand_proto : + var_tlsp_tls_proto, + ask_ccert = ask_client_cert, + mdalg = var_tlsp_tls_fpt_dgst); + } else { + msg_warn("No server certs available. TLS can't be enabled"); + } + + /* + * To maintain sanity, allow partial SSL_write() operations, and allow + * SSL_write() buffer pointers to change after a WANT_READ or WANT_WRITE + * result. This is based on OpenSSL developers talking on a mailing list, + * but is not supported by documentation. If this code stops working then + * no-one can be held responsible. + */ + if (tlsp_server_ctx) + SSL_CTX_set_mode(tlsp_server_ctx->ssl_ctx, + SSL_MODE_ENABLE_PARTIAL_WRITE + | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); +} + +/* pre_jail_init_client - pre-jail initialization */ + +static void pre_jail_init_client(void) +{ + int clnt_use_tls; + + /* + * The cache with TLS_APPL_STATE instances for different TLS_CLIENT_INIT + * configurations. + */ + tlsp_client_app_cache = htable_create(10); + + /* + * Most sites don't use TLS client certs/keys. In that case, enabling + * tlsproxy-based connection caching is trivial. + * + * But some sites do use TLS client certs/keys, and that is challenging when + * tlsproxy runs in a post-jail environment: chroot breaks pathname + * resolution, and an unprivileged process should not be able to open + * files with secrets. The workaround: assume that most of those sites + * will use a fixed TLS client identity. In that case, tlsproxy can load + * the corresponding certs/keys at pre-jail time, so that secrets can + * remain read-only for root. As long as the tlsproxy pre-jail TLS client + * configuration with cert or key pathnames is the same as the one used + * in the Postfix SMTP client, sites can selectively or globally enable + * tlsproxy-based connection caching without additional TLS + * configuration. + * + * Loading one TLS client configuration at pre-jail time is not sufficient + * for the minority of sites that want to use TLS connection caching with + * multiple TLS client identities. To alert the operator, tlsproxy will + * log a warning when a TLS_CLIENT_INIT message specifies a different + * configuration than the tlsproxy pre-jail client configuration, and + * that different configuration specifies file/directory pathname + * arguments. The workaround is to have one tlsproxy process per TLS + * client identity. + * + * The general solution for single-identity or multi-identity clients is to + * stop loading certs and keys from individual files. Instead, have a + * cert/key map, indexed by client identity, read-only by root. After + * opening the map as root at pre-jail time, tlsproxy can read certs/keys + * on-the-fly as an unprivileged process at post-jail time. This is the + * approach that was already proposed for server-side SNI support, and it + * could be reused here. It would also end the proliferation of RSA + * cert/key parameters, DSA cert/key parameters, EC cert/key parameters, + * and so on. + * + * Horror: In order to create the same pre-jail TLS client context as the + * one used in the Postfix SMTP client, we have to duplicate intricate + * SMTP client code, including a handful configuration parameters that + * tlsproxy does not need. We must duplicate the logic, so that we only + * load certs and keys when the SMTP client would load them. + */ + if (*var_tlsp_clnt_level != 0) + switch (tls_level_lookup(var_tlsp_clnt_level)) { + case TLS_LEV_SECURE: + case TLS_LEV_VERIFY: + case TLS_LEV_DANE_ONLY: + case TLS_LEV_FPRINT: + case TLS_LEV_ENCRYPT: + var_tlsp_clnt_use_tls = var_tlsp_clnt_enforce_tls = 1; + break; + case TLS_LEV_DANE: + case TLS_LEV_MAY: + var_tlsp_clnt_use_tls = 1; + var_tlsp_clnt_enforce_tls = 0; + break; + case TLS_LEV_NONE: + var_tlsp_clnt_use_tls = var_tlsp_clnt_enforce_tls = 0; + break; + default: + /* tls_level_lookup() logs no warning. */ + /* session_tls_init() assumes that var_tlsp_clnt_level is sane. */ + msg_fatal("Invalid TLS level \"%s\"", var_tlsp_clnt_level); + } + clnt_use_tls = (var_tlsp_clnt_use_tls || var_tlsp_clnt_enforce_tls); + + /* + * Initialize the TLS data before entering the chroot jail. + */ + if (clnt_use_tls || var_tlsp_clnt_per_site[0] || var_tlsp_clnt_policy[0]) { + TLS_CLIENT_PARAMS tls_params; + TLS_CLIENT_INIT_PROPS init_props; + + tls_pre_jail_init(TLS_ROLE_CLIENT); + + /* + * We get stronger type safety and a cleaner interface by combining + * the various parameters into a single tls_client_props structure. + * + * Large parameter lists are error-prone, so we emulate a language + * feature that C does not have natively: named parameter lists. + */ + (void) tls_proxy_client_param_from_config(&tls_params); + (void) TLS_CLIENT_INIT_ARGS(&init_props, + log_param = var_tlsp_clnt_logparam, + log_level = var_tlsp_clnt_loglevel, + verifydepth = var_tlsp_clnt_scert_vd, + cache_type = TLS_MGR_SCACHE_SMTP, + chain_files = var_tlsp_clnt_chain_files, + cert_file = var_tlsp_clnt_cert_file, + key_file = var_tlsp_clnt_key_file, + dcert_file = var_tlsp_clnt_dcert_file, + dkey_file = var_tlsp_clnt_dkey_file, + eccert_file = var_tlsp_clnt_eccert_file, + eckey_file = var_tlsp_clnt_eckey_file, + CAfile = var_tlsp_clnt_CAfile, + CApath = var_tlsp_clnt_CApath, + mdalg = var_tlsp_clnt_fpt_dgst); + if (tlsp_client_init(&tls_params, &init_props) == 0) + msg_warn("TLS client initialization failed"); + } +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Initialize roles separately. + */ + pre_jail_init_server(); + pre_jail_init_client(); + + /* + * tlsp_client_init() needs to know if it is called pre-jail or + * post-jail. + */ + tlsp_pre_jail_done = 1; + + /* + * Bug: TLS_CLIENT_PARAMS attributes are not used when creating a + * TLS_APPL_STATE instance; we can only warn about attribute mismatches. + */ + tlsp_params_mismatch_filter = been_here_init(BH_BOUND_NONE, BH_FLAG_NONE); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - the main program */ + +int main(int argc, char **argv) +{ + + /* + * Each table below initializes the named variables to their implicit + * default value, or to the explicit value in main.cf or master.cf. Here, + * "compat" means that a table initializes a variable "smtpd_blah" or + * "smtp_blah" that provides the implicit default value for variable + * "tlsproxy_blah" which is initialized by a different table. To make + * this work, the variables in a "compat" table must be initialized + * before the variables in the corresponding non-compat table. + */ + static const CONFIG_INT_TABLE compat_int_table[] = { + VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, + VAR_SMTP_TLS_SCERT_VD, DEF_SMTP_TLS_SCERT_VD, &var_smtp_tls_scert_vd, 0, 0, + 0, + }; + static const CONFIG_NINT_TABLE nint_table[] = { + VAR_TLSP_TLS_CCERT_VD, DEF_TLSP_TLS_CCERT_VD, &var_tlsp_tls_ccert_vd, 0, 0, + VAR_TLSP_CLNT_SCERT_VD, DEF_TLSP_CLNT_SCERT_VD, &var_tlsp_clnt_scert_vd, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_TLSP_WATCHDOG, DEF_TLSP_WATCHDOG, &var_tlsp_watchdog, 10, 0, + 0, + }; + static const CONFIG_BOOL_TABLE compat_bool_table[] = { + VAR_SMTPD_USE_TLS, DEF_SMTPD_USE_TLS, &var_smtpd_use_tls, + VAR_SMTPD_ENFORCE_TLS, DEF_SMTPD_ENFORCE_TLS, &var_smtpd_enforce_tls, + VAR_SMTPD_TLS_ACERT, DEF_SMTPD_TLS_ACERT, &var_smtpd_tls_ask_ccert, + VAR_SMTPD_TLS_RCERT, DEF_SMTPD_TLS_RCERT, &var_smtpd_tls_req_ccert, + VAR_SMTPD_TLS_SET_SESSID, DEF_SMTPD_TLS_SET_SESSID, &var_smtpd_tls_set_sessid, + VAR_SMTP_USE_TLS, DEF_SMTP_USE_TLS, &var_smtp_use_tls, + VAR_SMTP_ENFORCE_TLS, DEF_SMTP_ENFORCE_TLS, &var_smtp_enforce_tls, + 0, + }; + static const CONFIG_NBOOL_TABLE nbool_table[] = { + VAR_TLSP_USE_TLS, DEF_TLSP_USE_TLS, &var_tlsp_use_tls, + VAR_TLSP_ENFORCE_TLS, DEF_TLSP_ENFORCE_TLS, &var_tlsp_enforce_tls, + VAR_TLSP_TLS_ACERT, DEF_TLSP_TLS_ACERT, &var_tlsp_tls_ask_ccert, + VAR_TLSP_TLS_RCERT, DEF_TLSP_TLS_RCERT, &var_tlsp_tls_req_ccert, + VAR_TLSP_TLS_SET_SESSID, DEF_TLSP_TLS_SET_SESSID, &var_tlsp_tls_set_sessid, + VAR_TLSP_CLNT_USE_TLS, DEF_TLSP_CLNT_USE_TLS, &var_tlsp_clnt_use_tls, + VAR_TLSP_CLNT_ENFORCE_TLS, DEF_TLSP_CLNT_ENFORCE_TLS, &var_tlsp_clnt_enforce_tls, + 0, + }; + static const CONFIG_STR_TABLE compat_str_table[] = { + VAR_SMTPD_TLS_CHAIN_FILES, DEF_SMTPD_TLS_CHAIN_FILES, &var_smtpd_tls_chain_files, 0, 0, + VAR_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CERT_FILE, &var_smtpd_tls_cert_file, 0, 0, + VAR_SMTPD_TLS_KEY_FILE, DEF_SMTPD_TLS_KEY_FILE, &var_smtpd_tls_key_file, 0, 0, + VAR_SMTPD_TLS_DCERT_FILE, DEF_SMTPD_TLS_DCERT_FILE, &var_smtpd_tls_dcert_file, 0, 0, + VAR_SMTPD_TLS_DKEY_FILE, DEF_SMTPD_TLS_DKEY_FILE, &var_smtpd_tls_dkey_file, 0, 0, + VAR_SMTPD_TLS_ECCERT_FILE, DEF_SMTPD_TLS_ECCERT_FILE, &var_smtpd_tls_eccert_file, 0, 0, + VAR_SMTPD_TLS_ECKEY_FILE, DEF_SMTPD_TLS_ECKEY_FILE, &var_smtpd_tls_eckey_file, 0, 0, + VAR_SMTPD_TLS_CA_FILE, DEF_SMTPD_TLS_CA_FILE, &var_smtpd_tls_CAfile, 0, 0, + VAR_SMTPD_TLS_CA_PATH, DEF_SMTPD_TLS_CA_PATH, &var_smtpd_tls_CApath, 0, 0, + VAR_SMTPD_TLS_CIPH, DEF_SMTPD_TLS_CIPH, &var_smtpd_tls_ciph, 1, 0, + VAR_SMTPD_TLS_MAND_CIPH, DEF_SMTPD_TLS_MAND_CIPH, &var_smtpd_tls_mand_ciph, 1, 0, + VAR_SMTPD_TLS_EXCL_CIPH, DEF_SMTPD_TLS_EXCL_CIPH, &var_smtpd_tls_excl_ciph, 0, 0, + VAR_SMTPD_TLS_MAND_EXCL, DEF_SMTPD_TLS_MAND_EXCL, &var_smtpd_tls_mand_excl, 0, 0, + VAR_SMTPD_TLS_PROTO, DEF_SMTPD_TLS_PROTO, &var_smtpd_tls_proto, 0, 0, + VAR_SMTPD_TLS_MAND_PROTO, DEF_SMTPD_TLS_MAND_PROTO, &var_smtpd_tls_mand_proto, 0, 0, + VAR_SMTPD_TLS_512_FILE, DEF_SMTPD_TLS_512_FILE, &var_smtpd_tls_dh512_param_file, 0, 0, + VAR_SMTPD_TLS_1024_FILE, DEF_SMTPD_TLS_1024_FILE, &var_smtpd_tls_dh1024_param_file, 0, 0, + VAR_SMTPD_TLS_EECDH, DEF_SMTPD_TLS_EECDH, &var_smtpd_tls_eecdh, 1, 0, + VAR_SMTPD_TLS_FPT_DGST, DEF_SMTPD_TLS_FPT_DGST, &var_smtpd_tls_fpt_dgst, 1, 0, + VAR_SMTPD_TLS_LOGLEVEL, DEF_SMTPD_TLS_LOGLEVEL, &var_smtpd_tls_loglevel, 0, 0, + VAR_SMTPD_TLS_LEVEL, DEF_SMTPD_TLS_LEVEL, &var_smtpd_tls_level, 0, 0, + VAR_SMTP_TLS_CHAIN_FILES, DEF_SMTP_TLS_CHAIN_FILES, &var_smtp_tls_chain_files, 0, 0, + VAR_SMTP_TLS_CERT_FILE, DEF_SMTP_TLS_CERT_FILE, &var_smtp_tls_cert_file, 0, 0, + VAR_SMTP_TLS_KEY_FILE, DEF_SMTP_TLS_KEY_FILE, &var_smtp_tls_key_file, 0, 0, + VAR_SMTP_TLS_DCERT_FILE, DEF_SMTP_TLS_DCERT_FILE, &var_smtp_tls_dcert_file, 0, 0, + VAR_SMTP_TLS_DKEY_FILE, DEF_SMTP_TLS_DKEY_FILE, &var_smtp_tls_dkey_file, 0, 0, + VAR_SMTP_TLS_CA_FILE, DEF_SMTP_TLS_CA_FILE, &var_smtp_tls_CAfile, 0, 0, + VAR_SMTP_TLS_CA_PATH, DEF_SMTP_TLS_CA_PATH, &var_smtp_tls_CApath, 0, 0, + VAR_SMTP_TLS_FPT_DGST, DEF_SMTP_TLS_FPT_DGST, &var_smtp_tls_fpt_dgst, 1, 0, + VAR_SMTP_TLS_ECCERT_FILE, DEF_SMTP_TLS_ECCERT_FILE, &var_smtp_tls_eccert_file, 0, 0, + VAR_SMTP_TLS_ECKEY_FILE, DEF_SMTP_TLS_ECKEY_FILE, &var_smtp_tls_eckey_file, 0, 0, + VAR_SMTP_TLS_LOGLEVEL, DEF_SMTP_TLS_LOGLEVEL, &var_smtp_tls_loglevel, 0, 0, + VAR_SMTP_TLS_PER_SITE, DEF_SMTP_TLS_PER_SITE, &var_smtp_tls_per_site, 0, 0, + VAR_SMTP_TLS_LEVEL, DEF_SMTP_TLS_LEVEL, &var_smtp_tls_level, 0, 0, + VAR_SMTP_TLS_POLICY, DEF_SMTP_TLS_POLICY, &var_smtp_tls_policy, 0, 0, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_TLSP_TLS_CHAIN_FILES, DEF_TLSP_TLS_CHAIN_FILES, &var_tlsp_tls_chain_files, 0, 0, + VAR_TLSP_TLS_CERT_FILE, DEF_TLSP_TLS_CERT_FILE, &var_tlsp_tls_cert_file, 0, 0, + VAR_TLSP_TLS_KEY_FILE, DEF_TLSP_TLS_KEY_FILE, &var_tlsp_tls_key_file, 0, 0, + VAR_TLSP_TLS_DCERT_FILE, DEF_TLSP_TLS_DCERT_FILE, &var_tlsp_tls_dcert_file, 0, 0, + VAR_TLSP_TLS_DKEY_FILE, DEF_TLSP_TLS_DKEY_FILE, &var_tlsp_tls_dkey_file, 0, 0, + VAR_TLSP_TLS_ECCERT_FILE, DEF_TLSP_TLS_ECCERT_FILE, &var_tlsp_tls_eccert_file, 0, 0, + VAR_TLSP_TLS_ECKEY_FILE, DEF_TLSP_TLS_ECKEY_FILE, &var_tlsp_tls_eckey_file, 0, 0, + VAR_TLSP_TLS_CA_FILE, DEF_TLSP_TLS_CA_FILE, &var_tlsp_tls_CAfile, 0, 0, + VAR_TLSP_TLS_CA_PATH, DEF_TLSP_TLS_CA_PATH, &var_tlsp_tls_CApath, 0, 0, + VAR_TLSP_TLS_CIPH, DEF_TLSP_TLS_CIPH, &var_tlsp_tls_ciph, 1, 0, + VAR_TLSP_TLS_MAND_CIPH, DEF_TLSP_TLS_MAND_CIPH, &var_tlsp_tls_mand_ciph, 1, 0, + VAR_TLSP_TLS_EXCL_CIPH, DEF_TLSP_TLS_EXCL_CIPH, &var_tlsp_tls_excl_ciph, 0, 0, + VAR_TLSP_TLS_MAND_EXCL, DEF_TLSP_TLS_MAND_EXCL, &var_tlsp_tls_mand_excl, 0, 0, + VAR_TLSP_TLS_PROTO, DEF_TLSP_TLS_PROTO, &var_tlsp_tls_proto, 0, 0, + VAR_TLSP_TLS_MAND_PROTO, DEF_TLSP_TLS_MAND_PROTO, &var_tlsp_tls_mand_proto, 0, 0, + VAR_TLSP_TLS_512_FILE, DEF_TLSP_TLS_512_FILE, &var_tlsp_tls_dh512_param_file, 0, 0, + VAR_TLSP_TLS_1024_FILE, DEF_TLSP_TLS_1024_FILE, &var_tlsp_tls_dh1024_param_file, 0, 0, + VAR_TLSP_TLS_EECDH, DEF_TLSP_TLS_EECDH, &var_tlsp_tls_eecdh, 1, 0, + VAR_TLSP_TLS_FPT_DGST, DEF_TLSP_TLS_FPT_DGST, &var_tlsp_tls_fpt_dgst, 1, 0, + VAR_TLSP_TLS_LOGLEVEL, DEF_TLSP_TLS_LOGLEVEL, &var_tlsp_tls_loglevel, 0, 0, + VAR_TLSP_TLS_LEVEL, DEF_TLSP_TLS_LEVEL, &var_tlsp_tls_level, 0, 0, + VAR_TLSP_CLNT_LOGLEVEL, DEF_TLSP_CLNT_LOGLEVEL, &var_tlsp_clnt_loglevel, 0, 0, + VAR_TLSP_CLNT_LOGPARAM, DEF_TLSP_CLNT_LOGPARAM, &var_tlsp_clnt_logparam, 0, 0, + VAR_TLSP_CLNT_CHAIN_FILES, DEF_TLSP_CLNT_CHAIN_FILES, &var_tlsp_clnt_chain_files, 0, 0, + VAR_TLSP_CLNT_CERT_FILE, DEF_TLSP_CLNT_CERT_FILE, &var_tlsp_clnt_cert_file, 0, 0, + VAR_TLSP_CLNT_KEY_FILE, DEF_TLSP_CLNT_KEY_FILE, &var_tlsp_clnt_key_file, 0, 0, + VAR_TLSP_CLNT_DCERT_FILE, DEF_TLSP_CLNT_DCERT_FILE, &var_tlsp_clnt_dcert_file, 0, 0, + VAR_TLSP_CLNT_DKEY_FILE, DEF_TLSP_CLNT_DKEY_FILE, &var_tlsp_clnt_dkey_file, 0, 0, + VAR_TLSP_CLNT_ECCERT_FILE, DEF_TLSP_CLNT_ECCERT_FILE, &var_tlsp_clnt_eccert_file, 0, 0, + VAR_TLSP_CLNT_ECKEY_FILE, DEF_TLSP_CLNT_ECKEY_FILE, &var_tlsp_clnt_eckey_file, 0, 0, + VAR_TLSP_CLNT_CAFILE, DEF_TLSP_CLNT_CAFILE, &var_tlsp_clnt_CAfile, 0, 0, + VAR_TLSP_CLNT_CAPATH, DEF_TLSP_CLNT_CAPATH, &var_tlsp_clnt_CApath, 0, 0, + VAR_TLSP_CLNT_FPT_DGST, DEF_TLSP_CLNT_FPT_DGST, &var_tlsp_clnt_fpt_dgst, 1, 0, + VAR_TLSP_CLNT_LEVEL, DEF_TLSP_CLNT_LEVEL, &var_tlsp_clnt_level, 0, 0, + VAR_TLSP_CLNT_PER_SITE, DEF_TLSP_CLNT_PER_SITE, &var_tlsp_clnt_per_site, 0, 0, + VAR_TLSP_CLNT_POLICY, DEF_TLSP_CLNT_POLICY, &var_tlsp_clnt_policy, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * Pass control to the event-driven service skeleton. + */ + event_server_main(argc, argv, tlsp_service, + CA_MAIL_SERVER_INT_TABLE(compat_int_table), + CA_MAIL_SERVER_NINT_TABLE(nint_table), + CA_MAIL_SERVER_STR_TABLE(compat_str_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_BOOL_TABLE(compat_bool_table), + CA_MAIL_SERVER_NBOOL_TABLE(nbool_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_SLOW_EXIT(tlsp_drain), + CA_MAIL_SERVER_RETIRE_ME, + CA_MAIL_SERVER_WATCHDOG(&var_tlsp_watchdog), + CA_MAIL_SERVER_UNLIMITED, + 0); +} + +#else + +/* tlsp_service - respond to external trigger(s), non-TLS version */ + +static void tlsp_service(VSTREAM *stream, char *unused_service, + char **unused_argv) +{ + msg_info("TLS support is not compiled in -- exiting"); + event_server_disconnect(stream); +} + +/* main - the main program */ + +int main(int argc, char **argv) +{ + + /* + * We can't simply use msg_fatal() here, because the logging hasn't been + * initialized. The text would disappear because stderr is redirected to + * /dev/null. + * + * We invoke event_server_main() to complete program initialization + * (including logging) and then invoke the tlsp_service() routine to log + * the message that says why this program will not run. + */ + event_server_main(argc, argv, tlsp_service, + 0); +} + +#endif diff --git a/src/tlsproxy/tlsproxy.h b/src/tlsproxy/tlsproxy.h new file mode 100644 index 0000000..eacbb1f --- /dev/null +++ b/src/tlsproxy/tlsproxy.h @@ -0,0 +1,69 @@ +/*++ +/* NAME +/* tlsproxy 3h +/* SUMMARY +/* tlsproxy internal interfaces +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * TLS library. + */ +#include + + /* + * Internal interface. + */ +typedef struct { + int flags; /* see below */ + int req_flags; /* request flags, see tls_proxy.h */ + int is_server_role; /* avoid clumsy handler code */ + char *service; /* argv[0] */ + VSTREAM *plaintext_stream; /* local peer: postscreen(8), etc. */ + NBBIO *plaintext_buf; /* plaintext buffer */ + int ciphertext_fd; /* remote peer */ + EVENT_NOTIFY_FN ciphertext_timer; /* kludge */ + int timeout; /* read/write time limit */ + int handshake_timeout; /* in-handshake time limit */ + int session_timeout; /* post-handshake time limit */ + char *remote_endpt; /* printable remote endpoint */ + char *server_id; /* cache management */ + TLS_APPL_STATE *appl_state; /* libtls state */ + TLS_SESS_STATE *tls_context; /* libtls state */ + int ssl_last_err; /* TLS I/O state */ + TLS_CLIENT_PARAMS *tls_params; /* globals not part of init_props */ + TLS_SERVER_INIT_PROPS *server_init_props; + TLS_SERVER_START_PROPS *server_start_props; + TLS_CLIENT_INIT_PROPS *client_init_props; + TLS_CLIENT_START_PROPS *client_start_props; +} TLSP_STATE; + +#define TLSP_FLAG_DO_HANDSHAKE (1<<0) +#define TLSP_FLAG_NO_MORE_CIPHERTEXT_IO (1<<1) /* overrides DO_HANDSHAKE */ + +extern TLSP_STATE *tlsp_state_create(const char *, VSTREAM *); +extern void tlsp_state_free(TLSP_STATE *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/tlsproxy/tlsproxy_state.c b/src/tlsproxy/tlsproxy_state.c new file mode 100644 index 0000000..df6cbda --- /dev/null +++ b/src/tlsproxy/tlsproxy_state.c @@ -0,0 +1,169 @@ +/*++ +/* NAME +/* tlsproxy_state 3 +/* SUMMARY +/* Postfix SMTP server +/* SYNOPSIS +/* #include +/* +/* TLSP_STATE *tlsp_state_create(service, plaintext_stream) +/* const char *service; +/* VSTREAM *plaintext_stream; +/* +/* void tlsp_state_free(state) +/* TLSP_STATE *state; +/* DESCRIPTION +/* This module provides TLSP_STATE constructor and destructor +/* routines. +/* +/* tlsp_state_create() initializes session context. +/* +/* tlsp_state_free() destroys session context. If the handshake +/* was in progress, it logs a 'handshake failed' message. +/* +/* Arguments: +/* .IP service +/* The service name for the TLS library. This argument is copied. +/* The destructor will automatically destroy the string. +/* .IP plaintext_stream +/* The VSTREAM between postscreen(8) and tlsproxy(8). +/* The destructor will automatically close the stream. +/* .PP +/* Other structure members are set by the application. The +/* text below describes how the TLSP_STATE destructor +/* disposes of them. +/* .IP plaintext_buf +/* NBBIO for plaintext I/O. +/* The destructor will automatically turn off read/write/timeout +/* events and destroy the NBBIO. +/* .IP ciphertext_fd +/* The file handle for the remote SMTP client socket. +/* The destructor will automatically turn off read/write events +/* and close the file handle. +/* .IP ciphertext_timer +/* The destructor will automatically turn off this time event. +/* .IP timeout +/* Time limit for plaintext and ciphertext I/O. +/* .IP remote_endpt +/* Printable remote endpoint name. +/* The destructor will automatically destroy the string. +/* .IP server_id +/* TLS session cache identifier. +/* The destructor will automatically destroy the string. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Master library. + */ +#include + + /* + * TLS library. + */ +#ifdef USE_TLS +#define TLS_INTERNAL /* XXX */ +#include +#include + + /* + * Application-specific. + */ +#include + +/* tlsp_state_create - create TLS proxy state object */ + +TLSP_STATE *tlsp_state_create(const char *service, + VSTREAM *plaintext_stream) +{ + TLSP_STATE *state = (TLSP_STATE *) mymalloc(sizeof(*state)); + + state->flags = TLSP_FLAG_DO_HANDSHAKE; + state->service = mystrdup(service); + state->plaintext_stream = plaintext_stream; + state->plaintext_buf = 0; + state->ciphertext_fd = -1; + state->ciphertext_timer = 0; + state->timeout = -1; + state->remote_endpt = 0; + state->server_id = 0; + state->tls_context = 0; + state->tls_params = 0; + state->server_init_props = 0; + state->server_start_props = 0; + state->client_init_props = 0; + state->client_start_props = 0; + + return (state); +} + +/* tlsp_state_free - destroy state objects, connection and events */ + +void tlsp_state_free(TLSP_STATE *state) +{ + /* Don't log failure after plaintext EOF. */ + if (state->remote_endpt && state->server_id + && (state->flags & TLSP_FLAG_DO_HANDSHAKE)) + msg_info("TLS handshake failed for service=%s peer=%s", + state->server_id, state->remote_endpt); + myfree(state->service); + if (state->plaintext_buf) /* turns off plaintext events */ + nbbio_free(state->plaintext_buf); + else + event_disable_readwrite(vstream_fileno(state->plaintext_stream)); + event_server_disconnect(state->plaintext_stream); + if (state->ciphertext_fd >= 0) { + event_disable_readwrite(state->ciphertext_fd); + (void) close(state->ciphertext_fd); + } + if (state->ciphertext_timer) + event_cancel_timer(state->ciphertext_timer, (void *) state); + if (state->remote_endpt) { + msg_info("DISCONNECT %s", state->remote_endpt); + myfree(state->remote_endpt); + } + if (state->server_id) + myfree(state->server_id); + if (state->tls_context) + tls_free_context(state->tls_context); + if (state->tls_params) + tls_proxy_client_param_free(state->tls_params); + if (state->server_init_props) + tls_proxy_server_init_free(state->server_init_props); + if (state->server_start_props) + tls_proxy_server_start_free(state->server_start_props); + if (state->client_init_props) + tls_proxy_client_init_free(state->client_init_props); + if (state->client_start_props) + tls_proxy_client_start_free(state->client_start_props); + myfree((void *) state); +} + +#endif diff --git a/src/trivial-rewrite/.indent.pro b/src/trivial-rewrite/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/trivial-rewrite/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/trivial-rewrite/.printfck b/src/trivial-rewrite/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/trivial-rewrite/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/trivial-rewrite/Makefile.in b/src/trivial-rewrite/Makefile.in new file mode 100644 index 0000000..92fa666 --- /dev/null +++ b/src/trivial-rewrite/Makefile.in @@ -0,0 +1,199 @@ +SHELL = /bin/sh +SRCS = trivial-rewrite.c rewrite.c resolve.c transport.c +OBJS = trivial-rewrite.o rewrite.o resolve.o transport.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +LIB = +TESTPROG= transport +PROG = trivial-rewrite +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) +LIB_DIR = ../../lib +INC_DIR = ../../include +BIN_DIR = ../../libexec + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +all: $(PROG) $(LIB) + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: transport_test + +root_tests: + +$(BIN_DIR)/$(PROG): $(PROG) + cp $(PROG) $@ + +update: $(BIN_DIR)/$(PROG) + +transport: transport.c $(LIB) $(LIBS) + -mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +transport_test: transport transport.in transport.ref + $(SHLIB_ENV) sh transport.in >transport.tmp 2>&1 + diff transport.ref transport.tmp + rm -f transport.tmp + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core trivial-rewrite $(TESTPROG) junk $(LIB) + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +resolve.o: ../../include/argv.h +resolve.o: ../../include/attr.h +resolve.o: ../../include/check_arg.h +resolve.o: ../../include/dict.h +resolve.o: ../../include/domain_list.h +resolve.o: ../../include/htable.h +resolve.o: ../../include/iostuff.h +resolve.o: ../../include/mail_addr_find.h +resolve.o: ../../include/mail_addr_form.h +resolve.o: ../../include/mail_conf.h +resolve.o: ../../include/mail_params.h +resolve.o: ../../include/mail_proto.h +resolve.o: ../../include/maps.h +resolve.o: ../../include/match_list.h +resolve.o: ../../include/match_parent_style.h +resolve.o: ../../include/msg.h +resolve.o: ../../include/myflock.h +resolve.o: ../../include/mymalloc.h +resolve.o: ../../include/nvtable.h +resolve.o: ../../include/quote_822_local.h +resolve.o: ../../include/quote_flags.h +resolve.o: ../../include/resolve_clnt.h +resolve.o: ../../include/resolve_local.h +resolve.o: ../../include/split_at.h +resolve.o: ../../include/string_list.h +resolve.o: ../../include/stringops.h +resolve.o: ../../include/sys_defs.h +resolve.o: ../../include/tok822.h +resolve.o: ../../include/valid_hostname.h +resolve.o: ../../include/valid_mailhost_addr.h +resolve.o: ../../include/valid_utf8_hostname.h +resolve.o: ../../include/vbuf.h +resolve.o: ../../include/vstream.h +resolve.o: ../../include/vstring.h +resolve.o: ../../include/vstring_vstream.h +resolve.o: resolve.c +resolve.o: transport.h +resolve.o: trivial-rewrite.h +rewrite.o: ../../include/argv.h +rewrite.o: ../../include/attr.h +rewrite.o: ../../include/check_arg.h +rewrite.o: ../../include/dict.h +rewrite.o: ../../include/htable.h +rewrite.o: ../../include/iostuff.h +rewrite.o: ../../include/mail_conf.h +rewrite.o: ../../include/mail_params.h +rewrite.o: ../../include/mail_proto.h +rewrite.o: ../../include/maps.h +rewrite.o: ../../include/msg.h +rewrite.o: ../../include/myflock.h +rewrite.o: ../../include/mymalloc.h +rewrite.o: ../../include/nvtable.h +rewrite.o: ../../include/resolve_clnt.h +rewrite.o: ../../include/resolve_local.h +rewrite.o: ../../include/split_at.h +rewrite.o: ../../include/sys_defs.h +rewrite.o: ../../include/tok822.h +rewrite.o: ../../include/vbuf.h +rewrite.o: ../../include/vstream.h +rewrite.o: ../../include/vstring.h +rewrite.o: ../../include/vstring_vstream.h +rewrite.o: rewrite.c +rewrite.o: trivial-rewrite.h +transport.o: ../../include/argv.h +transport.o: ../../include/attr.h +transport.o: ../../include/check_arg.h +transport.o: ../../include/dict.h +transport.o: ../../include/events.h +transport.o: ../../include/htable.h +transport.o: ../../include/iostuff.h +transport.o: ../../include/mail_addr_find.h +transport.o: ../../include/mail_addr_form.h +transport.o: ../../include/mail_params.h +transport.o: ../../include/mail_proto.h +transport.o: ../../include/maps.h +transport.o: ../../include/match_list.h +transport.o: ../../include/match_parent_style.h +transport.o: ../../include/msg.h +transport.o: ../../include/myflock.h +transport.o: ../../include/mymalloc.h +transport.o: ../../include/nvtable.h +transport.o: ../../include/split_at.h +transport.o: ../../include/stringops.h +transport.o: ../../include/strip_addr.h +transport.o: ../../include/sys_defs.h +transport.o: ../../include/vbuf.h +transport.o: ../../include/vstream.h +transport.o: ../../include/vstring.h +transport.o: transport.c +transport.o: transport.h +trivial-rewrite.o: ../../include/argv.h +trivial-rewrite.o: ../../include/attr.h +trivial-rewrite.o: ../../include/check_arg.h +trivial-rewrite.o: ../../include/dict.h +trivial-rewrite.o: ../../include/events.h +trivial-rewrite.o: ../../include/htable.h +trivial-rewrite.o: ../../include/iostuff.h +trivial-rewrite.o: ../../include/mail_addr.h +trivial-rewrite.o: ../../include/mail_conf.h +trivial-rewrite.o: ../../include/mail_params.h +trivial-rewrite.o: ../../include/mail_proto.h +trivial-rewrite.o: ../../include/mail_server.h +trivial-rewrite.o: ../../include/mail_version.h +trivial-rewrite.o: ../../include/maps.h +trivial-rewrite.o: ../../include/msg.h +trivial-rewrite.o: ../../include/myflock.h +trivial-rewrite.o: ../../include/mymalloc.h +trivial-rewrite.o: ../../include/nvtable.h +trivial-rewrite.o: ../../include/resolve_clnt.h +trivial-rewrite.o: ../../include/resolve_local.h +trivial-rewrite.o: ../../include/rewrite_clnt.h +trivial-rewrite.o: ../../include/split_at.h +trivial-rewrite.o: ../../include/stringops.h +trivial-rewrite.o: ../../include/sys_defs.h +trivial-rewrite.o: ../../include/tok822.h +trivial-rewrite.o: ../../include/vbuf.h +trivial-rewrite.o: ../../include/vstream.h +trivial-rewrite.o: ../../include/vstring.h +trivial-rewrite.o: ../../include/vstring_vstream.h +trivial-rewrite.o: transport.h +trivial-rewrite.o: trivial-rewrite.c +trivial-rewrite.o: trivial-rewrite.h diff --git a/src/trivial-rewrite/resolve.c b/src/trivial-rewrite/resolve.c new file mode 100644 index 0000000..40e6aa5 --- /dev/null +++ b/src/trivial-rewrite/resolve.c @@ -0,0 +1,828 @@ +/*++ +/* NAME +/* resolve 3 +/* SUMMARY +/* mail address resolver +/* SYNOPSIS +/* #include "trivial-rewrite.h" +/* +/* void resolve_init(void) +/* +/* int resolve_class(domain) +/* const char *domain; +/* +/* void resolve_proto(context, stream) +/* RES_CONTEXT *context; +/* VSTREAM *stream; +/* DESCRIPTION +/* This module implements the trivial address resolving engine. +/* It distinguishes between local and remote mail, and optionally +/* consults one or more transport tables that map a destination +/* to a transport, nexthop pair. +/* +/* resolve_init() initializes data structures that are private +/* to this module. It should be called once before using the +/* actual resolver routines. +/* +/* resolve_class() returns the address class for the specified +/* domain, or -1 in case of error. +/* +/* resolve_proto() implements the client-server protocol: +/* read one address in FQDN form, reply with a (transport, +/* nexthop, internalized recipient) triple. +/* STANDARDS +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "trivial-rewrite.h" +#include "transport.h" + + /* + * The job of the address resolver is to map one recipient address to a + * triple of (channel, nexthop, recipient). The channel is the name of the + * delivery service specified in master.cf, the nexthop is (usually) a + * description of the next host to deliver to, and recipient is the final + * recipient address. The latter may differ from the input address as the + * result of stripping multiple layers of sender-specified routing. + * + * Addresses are resolved by their domain name. Known domain names are + * categorized into classes: local, virtual alias, virtual mailbox, relay, + * and everything else. Finding the address domain class is a matter of + * table lookups. + * + * Different address domain classes generally use different delivery channels, + * and may use class dependent ways to arrive at the corresponding nexthop + * information. With classes that do final delivery, the nexthop is + * typically the local machine hostname. + * + * The transport lookup table provides a means to override the domain class + * channel and/or nexhop information for specific recipients or for entire + * domain hierarchies. + * + * This works well in the general case. The only bug in this approach is that + * the structure of the nexthop information is transport dependent. + * Typically, the nexthop specifies a hostname, hostname + TCP Port, or the + * pathname of a UNIX-domain socket. However, with the error transport the + * nexthop field contains free text with the reason for non-delivery. + * + * Therefore, a transport map entry that overrides the channel but not the + * nexthop information (or vice versa) may produce surprising results. In + * particular, the free text nexthop information for the error transport is + * likely to confuse regular delivery agents; and conversely, a hostname or + * socket pathname is not an adequate text as reason for non-delivery. + * + * In the code below, rcpt_domain specifies the domain name that we will use + * when the transport table specifies a non-default channel but no nexthop + * information (we use a generic text when that non-default channel is the + * error transport). + */ + +#define STR vstring_str +#define LEN VSTRING_LEN + + /* + * Some of the lists that define the address domain classes. + */ +static DOMAIN_LIST *relay_domains; +static STRING_LIST *virt_alias_doms; +static STRING_LIST *virt_mailbox_doms; + +static MAPS *relocated_maps; + +/* resolve_class - determine domain address class */ + +int resolve_class(const char *domain) +{ + int ret; + + /* + * Same order as in resolve_addr(). + */ + if ((ret = resolve_local(domain)) != 0) + return (ret > 0 ? RESOLVE_CLASS_LOCAL : -1); + if (virt_alias_doms) { + if (string_list_match(virt_alias_doms, domain)) + return (RESOLVE_CLASS_ALIAS); + if (virt_alias_doms->error) + return (-1); + } + if (virt_mailbox_doms) { + if (string_list_match(virt_mailbox_doms, domain)) + return (RESOLVE_CLASS_VIRTUAL); + if (virt_mailbox_doms->error) + return (-1); + } + if (relay_domains) { + if (string_list_match(relay_domains, domain)) + return (RESOLVE_CLASS_RELAY); + if (relay_domains->error) + return (-1); + } + return (RESOLVE_CLASS_DEFAULT); +} + +/* resolve_addr - resolve address according to rule set */ + +static void resolve_addr(RES_CONTEXT *rp, char *sender, char *addr, + VSTRING *channel, VSTRING *nexthop, + VSTRING *nextrcpt, int *flags) +{ + const char *myname = "resolve_addr"; + VSTRING *addr_buf = vstring_alloc(100); + TOK822 *tree = 0; + TOK822 *saved_domain = 0; + TOK822 *domain = 0; + char *destination; + const char *blame = 0; + const char *rcpt_domain; + ssize_t addr_len; + ssize_t loop_count; + ssize_t loop_max; + char *local; + char *oper; + char *junk; + const char *relay; + const char *xport; + const char *sender_key; + int rc; + + *flags = 0; + vstring_strcpy(channel, "CHANNEL NOT UPDATED"); + vstring_strcpy(nexthop, "NEXTHOP NOT UPDATED"); + vstring_strcpy(nextrcpt, "NEXTRCPT NOT UPDATED"); + + /* + * The address is in internalized (unquoted) form. + * + * In an ideal world we would parse the externalized address form as given + * to us by the sender. + * + * However, in the real world we have to look for routing characters like + * %@! in the address local-part, even when that information is quoted + * due to the presence of special characters or whitespace. Although + * technically incorrect, this is needed to stop user@domain@domain relay + * attempts when forwarding mail to a Sendmail MX host. + * + * This suggests that we parse the address in internalized (unquoted) form. + * Unfortunately, if we do that, the unparser generates incorrect white + * space between adjacent non-operator tokens. Example: ``first last'' + * needs white space, but ``stuff[stuff]'' does not. This is not a + * problem when unparsing the result from parsing externalized forms, + * because the parser/unparser were designed for valid externalized forms + * where ``stuff[stuff]'' does not happen. + * + * As a workaround we start with the quoted form and then dequote the + * local-part only where needed. This will do the right thing in most + * (but not all) cases. + */ + addr_len = strlen(addr); + quote_822_local(addr_buf, addr); + tree = tok822_scan_addr(vstring_str(addr_buf)); + + /* + * The optimizer will eliminate tests that always fail, and will replace + * multiple expansions of this macro by a GOTO to a single instance. + */ +#define FREE_MEMORY_AND_RETURN { \ + if (saved_domain) \ + tok822_free_tree(saved_domain); \ + if(tree) \ + tok822_free_tree(tree); \ + if (addr_buf) \ + vstring_free(addr_buf); \ + return; \ + } + + /* + * Preliminary resolver: strip off all instances of the local domain. + * Terminate when no destination domain is left over, or when the + * destination domain is remote. + * + * XXX To whom it may concern. If you change the resolver loop below, or + * quote_822_local.c, or tok822_parse.c, be sure to re-run the tests + * under "make resolve_clnt_test" in the global directory. + */ +#define RESOLVE_LOCAL(domain) \ + resolve_local(STR(tok822_internalize(addr_buf, domain, TOK822_STR_DEFL))) + + for (loop_count = 0, loop_max = addr_len + 100; /* void */ ; loop_count++) { + + /* + * XXX Should never happen, but if this happens with some + * pathological address, then that is not sufficient reason to + * disrupt the operation of an MTA. + */ + if (loop_count > loop_max) { + msg_warn("resolve_addr: <%s>: giving up after %ld iterations", + addr, (long) loop_count); + *flags |= RESOLVE_FLAG_FAIL; + FREE_MEMORY_AND_RETURN; + break; + } + + /* + * Strip trailing dot at end of domain, but not dot-dot or at-dot. + * This merely makes diagnostics more accurate by leaving bogus + * addresses alone. + */ + if (tree->tail + && tree->tail->type == '.' + && tok822_rfind_type(tree->tail, '@') != 0 + && tree->tail->prev->type != '.' + && tree->tail->prev->type != '@') + tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); + + /* + * Strip trailing @. + */ + if (var_resolve_nulldom + && tree->tail + && tree->tail->type == '@') + tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); + + /* + * Strip (and save) @domain if local. + * + * Grr. resolve_local() table lookups may fail. It may be OK for local + * file lookup code to abort upon failure, but with network-based + * tables it is preferable to return an error indication to the + * requestor. + */ + if ((domain = tok822_rfind_type(tree->tail, '@')) != 0) { + if (domain->next && (rc = RESOLVE_LOCAL(domain->next)) <= 0) { + if (rc < 0) { + *flags |= RESOLVE_FLAG_FAIL; + FREE_MEMORY_AND_RETURN; + } + break; + } + tok822_sub_keep_before(tree, domain); + if (saved_domain) + tok822_free_tree(saved_domain); + saved_domain = domain; + domain = 0; /* safety for future change */ + } + + /* + * After stripping the local domain, if any, replace foo%bar by + * foo@bar, site!user by user@site, rewrite to canonical form, and + * retry. + */ + if (tok822_rfind_type(tree->tail, '@') + || (var_swap_bangpath && tok822_rfind_type(tree->tail, '!')) + || (var_percent_hack && tok822_rfind_type(tree->tail, '%'))) { + rewrite_tree(&local_context, tree); + continue; + } + + /* + * If the local-part is a quoted string, crack it open when we're + * permitted to do so and look for routing operators. This is + * technically incorrect, but is needed to stop relaying problems. + * + * XXX Do another feeble attempt to keep local-part info quoted. + */ + if (var_resolve_dequoted + && tree->head && tree->head == tree->tail + && tree->head->type == TOK822_QSTRING + && ((oper = strrchr(local = STR(tree->head->vstr), '@')) != 0 + || (var_percent_hack && (oper = strrchr(local, '%')) != 0) + || (var_swap_bangpath && (oper = strrchr(local, '!')) != 0))) { + if (*oper == '%') + *oper = '@'; + tok822_internalize(addr_buf, tree->head, TOK822_STR_DEFL); + if (*oper == '@') { + junk = mystrdup(STR(addr_buf)); + quote_822_local(addr_buf, junk); + myfree(junk); + } + tok822_free(tree->head); + tree->head = tok822_scan(STR(addr_buf), &tree->tail); + rewrite_tree(&local_context, tree); + continue; + } + + /* + * An empty local-part or an empty quoted string local-part becomes + * the local MAILER-DAEMON, for consistency with our own From: + * message headers. + */ + if (tree->head && tree->head == tree->tail + && tree->head->type == TOK822_QSTRING + && VSTRING_LEN(tree->head->vstr) == 0) { + tok822_free(tree->head); + tree->head = 0; + } + /* XXX Re-resolve the surrogate, in case already in user@domain form. */ + if (tree->head == 0) { + tree->head = tok822_scan(var_empty_addr, &tree->tail); + continue; + } + /* XXX Re-resolve with @$myhostname for backwards compatibility. */ + if (domain == 0 && saved_domain == 0) { + tok822_sub_append(tree, tok822_alloc('@', (char *) 0)); + tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0)); + continue; + } + + /* + * We're done. There are no domains left to strip off the address, + * and all null local-part information is sanitized. + */ + domain = 0; + break; + } + + vstring_free(addr_buf); + addr_buf = 0; + + /* + * Make sure the resolved envelope recipient has the user@domain form. If + * no domain was specified in the address, assume the local machine. See + * above for what happens with an empty address. + */ + if (domain == 0) { + if (saved_domain) { + tok822_sub_append(tree, saved_domain); + saved_domain = 0; + } else { + tok822_sub_append(tree, tok822_alloc('@', (char *) 0)); + tok822_sub_append(tree, tok822_scan(var_myhostname, (TOK822 **) 0)); + } + } + + /* + * Transform the recipient address back to internal form. + * + * XXX This may produce incorrect results if we cracked open a quoted + * local-part with routing operators; see discussion above at the top of + * the big loop. + * + * XXX We explicitly disallow domain names in bare network address form. A + * network address destination should be formatted according to RFC 2821: + * it should be enclosed in [], and an IPv6 address should have an IPv6: + * prefix. + */ + tok822_internalize(nextrcpt, tree, TOK822_STR_DEFL); + rcpt_domain = strrchr(STR(nextrcpt), '@') + 1; + if (rcpt_domain == (char *) 1) + msg_panic("no @ in address: \"%s\"", STR(nextrcpt)); + if (*rcpt_domain == '[') { + if (!valid_mailhost_literal(rcpt_domain, DONT_GRIPE)) + *flags |= RESOLVE_FLAG_ERROR; + } else if (var_smtputf8_enable + && valid_utf8_string(STR(nextrcpt), LEN(nextrcpt)) == 0) { + *flags |= RESOLVE_FLAG_ERROR; + } else if (!valid_utf8_hostname(var_smtputf8_enable, rcpt_domain, + DONT_GRIPE)) { + if (var_resolve_num_dom && valid_hostaddr(rcpt_domain, DONT_GRIPE)) { + vstring_insert(nextrcpt, rcpt_domain - STR(nextrcpt), "[", 1); + vstring_strcat(nextrcpt, "]"); + rcpt_domain = strrchr(STR(nextrcpt), '@') + 1; + if ((rc = resolve_local(rcpt_domain)) > 0) /* XXX */ + domain = 0; + else if (rc < 0) { + *flags |= RESOLVE_FLAG_FAIL; + FREE_MEMORY_AND_RETURN; + } + } else { + *flags |= RESOLVE_FLAG_ERROR; + } + } + tok822_free_tree(tree); + tree = 0; + + /* + * XXX Short-cut invalid address forms. + */ + if (*flags & RESOLVE_FLAG_ERROR) { + *flags |= RESOLVE_CLASS_DEFAULT; + FREE_MEMORY_AND_RETURN; + } + + /* + * Recognize routing operators in the local-part, even when we do not + * recognize ! or % as valid routing operators locally. This is needed to + * prevent backup MX hosts from relaying third-party destinations through + * primary MX hosts, otherwise the backup host could end up on black + * lists. Ignore local swap_bangpath and percent_hack settings because we + * can't know how the next MX host is set up. + */ + if (strcmp(STR(nextrcpt) + strcspn(STR(nextrcpt), "@!%") + 1, rcpt_domain)) + *flags |= RESOLVE_FLAG_ROUTED; + + /* + * With local, virtual, relay, or other non-local destinations, give the + * highest precedence to transport associated nexthop information. + * + * Otherwise, with relay or other non-local destinations, the relayhost + * setting overrides the recipient domain name, and the sender-dependent + * relayhost overrides both. + * + * XXX Nag if the recipient domain is listed in multiple domain lists. The + * result is implementation defined, and may break when internals change. + * + * For now, we distinguish only a fixed number of address classes. + * Eventually this may become extensible, so that new classes can be + * configured with their own domain list, delivery transport, and + * recipient table. + */ +#define STREQ(x,y) (strcmp((x), (y)) == 0) + + if (domain != 0) { + + /* + * Virtual alias domain. + */ + if (virt_alias_doms + && string_list_match(virt_alias_doms, rcpt_domain)) { + if (var_helpful_warnings) { + if (virt_mailbox_doms + && string_list_match(virt_mailbox_doms, rcpt_domain)) + msg_warn("do not list domain %s in BOTH %s and %s", + rcpt_domain, VAR_VIRT_ALIAS_DOMS, + VAR_VIRT_MAILBOX_DOMS); + if (relay_domains + && domain_list_match(relay_domains, rcpt_domain)) + msg_warn("do not list domain %s in BOTH %s and %s", + rcpt_domain, VAR_VIRT_ALIAS_DOMS, + VAR_RELAY_DOMAINS); +#if 0 + if (strcasecmp_utf8(rcpt_domain, var_myorigin) == 0) + msg_warn("do not list $%s (%s) in %s", + VAR_MYORIGIN, var_myorigin, VAR_VIRT_ALIAS_DOMS); +#endif + } + vstring_strcpy(channel, MAIL_SERVICE_ERROR); + vstring_sprintf(nexthop, "5.1.1 User unknown%s", + var_show_unk_rcpt_table ? + " in virtual alias table" : ""); + *flags |= RESOLVE_CLASS_ALIAS; + } else if (virt_alias_doms && virt_alias_doms->error != 0) { + msg_warn("%s lookup failure", VAR_VIRT_ALIAS_DOMS); + *flags |= RESOLVE_FLAG_FAIL; + FREE_MEMORY_AND_RETURN; + } + + /* + * Virtual mailbox domain. + */ + else if (virt_mailbox_doms + && string_list_match(virt_mailbox_doms, rcpt_domain)) { + if (var_helpful_warnings) { + if (relay_domains + && domain_list_match(relay_domains, rcpt_domain)) + msg_warn("do not list domain %s in BOTH %s and %s", + rcpt_domain, VAR_VIRT_MAILBOX_DOMS, + VAR_RELAY_DOMAINS); + } + vstring_strcpy(channel, RES_PARAM_VALUE(rp->virt_transport)); + vstring_strcpy(nexthop, rcpt_domain); + blame = rp->virt_transport_name; + *flags |= RESOLVE_CLASS_VIRTUAL; + } else if (virt_mailbox_doms && virt_mailbox_doms->error != 0) { + msg_warn("%s lookup failure", VAR_VIRT_MAILBOX_DOMS); + *flags |= RESOLVE_FLAG_FAIL; + FREE_MEMORY_AND_RETURN; + } else { + + /* + * Off-host relay destination. + */ + if (relay_domains + && domain_list_match(relay_domains, rcpt_domain)) { + vstring_strcpy(channel, RES_PARAM_VALUE(rp->relay_transport)); + blame = rp->relay_transport_name; + *flags |= RESOLVE_CLASS_RELAY; + } else if (relay_domains && relay_domains->error != 0) { + msg_warn("%s lookup failure", VAR_RELAY_DOMAINS); + *flags |= RESOLVE_FLAG_FAIL; + FREE_MEMORY_AND_RETURN; + } + + /* + * Other off-host destination. + */ + else { + if (rp->snd_def_xp_info + && (xport = mail_addr_find(rp->snd_def_xp_info, + sender_key = (*sender ? sender : + var_null_def_xport_maps_key), + (char **) 0)) != 0) { + if (*xport == 0) { + msg_warn("%s: ignoring null lookup result for %s", + rp->snd_def_xp_maps_name, sender_key); + xport = "DUNNO"; + } + vstring_strcpy(channel, strcasecmp(xport, "DUNNO") == 0 ? + RES_PARAM_VALUE(rp->def_transport) : xport); + blame = rp->snd_def_xp_maps_name; + } else if (rp->snd_def_xp_info + && rp->snd_def_xp_info->error != 0) { + msg_warn("%s lookup failure", rp->snd_def_xp_maps_name); + *flags |= RESOLVE_FLAG_FAIL; + FREE_MEMORY_AND_RETURN; + } else { + vstring_strcpy(channel, RES_PARAM_VALUE(rp->def_transport)); + blame = rp->def_transport_name; + } + *flags |= RESOLVE_CLASS_DEFAULT; + } + + /* + * With off-host delivery, sender-dependent or global relayhost + * override the recipient domain. + */ + if (rp->snd_relay_info + && (relay = mail_addr_find(rp->snd_relay_info, + sender_key = (*sender ? sender : + var_null_relay_maps_key), + (char **) 0)) != 0) { + if (*relay == 0) { + msg_warn("%s: ignoring null lookup result for %s", + rp->snd_relay_maps_name, sender_key); + relay = 0; + } else if (strcasecmp_utf8(relay, "DUNNO") == 0) + relay = 0; + } else if (rp->snd_relay_info + && rp->snd_relay_info->error != 0) { + msg_warn("%s lookup failure", rp->snd_relay_maps_name); + *flags |= RESOLVE_FLAG_FAIL; + FREE_MEMORY_AND_RETURN; + } else { + relay = 0; + } + /* Enforce all the relayhost precedences in one place. */ + if (relay != 0) { + vstring_strcpy(nexthop, relay); + } else if (*RES_PARAM_VALUE(rp->relayhost)) + vstring_strcpy(nexthop, RES_PARAM_VALUE(rp->relayhost)); + else + vstring_strcpy(nexthop, rcpt_domain); + } + } + + /* + * Local delivery. + * + * XXX Nag if the domain is listed in multiple domain lists. The effect is + * implementation defined, and may break when internals change. + */ + else { + if (var_helpful_warnings) { + if (virt_alias_doms + && string_list_match(virt_alias_doms, rcpt_domain)) + msg_warn("do not list domain %s in BOTH %s and %s", + rcpt_domain, VAR_MYDEST, VAR_VIRT_ALIAS_DOMS); + if (virt_mailbox_doms + && string_list_match(virt_mailbox_doms, rcpt_domain)) + msg_warn("do not list domain %s in BOTH %s and %s", + rcpt_domain, VAR_MYDEST, VAR_VIRT_MAILBOX_DOMS); + } + vstring_strcpy(channel, RES_PARAM_VALUE(rp->local_transport)); + vstring_strcpy(nexthop, rcpt_domain); + blame = rp->local_transport_name; + *flags |= RESOLVE_CLASS_LOCAL; + } + + /* + * An explicit main.cf transport:nexthop setting overrides the nexthop. + * + * XXX We depend on this mechanism to enforce per-recipient concurrencies + * for local recipients. With "local_transport = local:$myhostname" we + * force mail for any domain in $mydestination/${proxy,inet}_interfaces + * to share the same queue. + */ + if ((destination = split_at(STR(channel), ':')) != 0 && *destination) + vstring_strcpy(nexthop, destination); + + /* + * Sanity checks. + */ + if (*STR(channel) == 0) { + if (blame == 0) + msg_panic("%s: null blame", myname); + msg_warn("file %s/%s: parameter %s: null transport is not allowed", + var_config_dir, MAIN_CONF_FILE, blame); + *flags |= RESOLVE_FLAG_FAIL; + FREE_MEMORY_AND_RETURN; + } + if (*STR(nexthop) == 0) + msg_panic("%s: null nexthop", myname); + + /* + * The transport map can selectively override any transport and/or + * nexthop host info that is set up above. Unfortunately, the syntax for + * nexthop information is transport specific. We therefore need sane and + * intuitive semantics for transport map entries that specify a channel + * but no nexthop. + * + * With non-error transports, the initial nexthop information is the + * recipient domain. However, specific main.cf transport definitions may + * specify a transport-specific destination, such as a host + TCP socket, + * or the pathname of a UNIX-domain socket. With less precedence than + * main.cf transport definitions, a main.cf relayhost definition may also + * override nexthop information for off-host deliveries. + * + * With the error transport, the nexthop information is free text that + * specifies the reason for non-delivery. + * + * Because nexthop syntax is transport specific we reset the nexthop + * information to the recipient domain when the transport table specifies + * a transport without also specifying the nexthop information. + * + * Subtle note: reset nexthop even when the transport table does not change + * the transport. Otherwise it is hard to get rid of main.cf specified + * nexthop information. + * + * XXX Don't override the virtual alias class (error:User unknown) result. + */ + if (rp->transport_info && !(*flags & RESOLVE_CLASS_ALIAS)) { + if (transport_lookup(rp->transport_info, STR(nextrcpt), + rcpt_domain, channel, nexthop) == 0 + && rp->transport_info->transport_path->error != 0) { + msg_warn("%s lookup failure", rp->transport_maps_name); + *flags |= RESOLVE_FLAG_FAIL; + FREE_MEMORY_AND_RETURN; + } + } + + /* + * Bounce recipients that have moved, regardless of domain address class. + * We do this last, in anticipation of transport maps that can override + * the recipient address. + * + * The downside of not doing this in delivery agents is that this table has + * no effect on local alias expansion results. Such mail will have to + * make almost an entire iteration through the mail system. + */ +#define IGNORE_ADDR_EXTENSION ((char **) 0) + + if (relocated_maps != 0) { + const char *newloc; + + if ((newloc = mail_addr_find(relocated_maps, STR(nextrcpt), + IGNORE_ADDR_EXTENSION)) != 0) { + vstring_strcpy(channel, MAIL_SERVICE_ERROR); + /* 5.1.6 is the closest match, but not perfect. */ + vstring_sprintf(nexthop, "5.1.6 User has moved to %s", newloc); + } else if (relocated_maps->error != 0) { + msg_warn("%s lookup failure", VAR_RELOCATED_MAPS); + *flags |= RESOLVE_FLAG_FAIL; + FREE_MEMORY_AND_RETURN; + } + } + + /* + * Bounce recipient addresses that start with `-'. External commands may + * misinterpret such addresses as command-line options. + * + * In theory I could say people should always carefully set up their + * master.cf pipe mailer entries with `--' before the first non-option + * argument, but mistakes will happen regardless. + * + * Therefore the protection is put in place here, where it cannot be + * bypassed. + */ + if (var_allow_min_user == 0 && STR(nextrcpt)[0] == '-') { + *flags |= RESOLVE_FLAG_ERROR; + FREE_MEMORY_AND_RETURN; + } + + /* + * Clean up. + */ + FREE_MEMORY_AND_RETURN; +} + +/* Static, so they can be used by the network protocol interface only. */ + +static VSTRING *channel; +static VSTRING *nexthop; +static VSTRING *nextrcpt; +static VSTRING *query; +static VSTRING *sender; + +/* resolve_proto - read request and send reply */ + +int resolve_proto(RES_CONTEXT *context, VSTREAM *stream) +{ + int flags; + + if (attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_SENDER, sender), + RECV_ATTR_STR(MAIL_ATTR_ADDR, query), + ATTR_TYPE_END) != 2) + return (-1); + + resolve_addr(context, STR(sender), STR(query), + channel, nexthop, nextrcpt, &flags); + + if (msg_verbose) + msg_info("`%s' -> `%s' -> (`%s' `%s' `%s' `%d')", + STR(sender), STR(query), STR(channel), + STR(nexthop), STR(nextrcpt), flags); + + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_FLAGS, server_flags), + SEND_ATTR_STR(MAIL_ATTR_TRANSPORT, STR(channel)), + SEND_ATTR_STR(MAIL_ATTR_NEXTHOP, STR(nexthop)), + SEND_ATTR_STR(MAIL_ATTR_RECIP, STR(nextrcpt)), + SEND_ATTR_INT(MAIL_ATTR_FLAGS, flags), + ATTR_TYPE_END); + + if (vstream_fflush(stream) != 0) { + msg_warn("write resolver reply: %m"); + return (-1); + } + return (0); +} + +/* resolve_init - module initializations */ + +void resolve_init(void) +{ + sender = vstring_alloc(100); + query = vstring_alloc(100); + channel = vstring_alloc(100); + nexthop = vstring_alloc(100); + nextrcpt = vstring_alloc(100); + + if (*var_virt_alias_doms) + virt_alias_doms = + string_list_init(VAR_VIRT_ALIAS_DOMS, MATCH_FLAG_RETURN, + var_virt_alias_doms); + + if (*var_virt_mailbox_doms) + virt_mailbox_doms = + string_list_init(VAR_VIRT_MAILBOX_DOMS, MATCH_FLAG_RETURN, + var_virt_mailbox_doms); + + if (*var_relay_domains) + relay_domains = + domain_list_init(VAR_RELAY_DOMAINS, MATCH_FLAG_RETURN + | match_parent_style(VAR_RELAY_DOMAINS), + var_relay_domains); + + if (*var_relocated_maps) + relocated_maps = + maps_create(VAR_RELOCATED_MAPS, var_relocated_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_UTF8_REQUEST); +} diff --git a/src/trivial-rewrite/rewrite.c b/src/trivial-rewrite/rewrite.c new file mode 100644 index 0000000..483463c --- /dev/null +++ b/src/trivial-rewrite/rewrite.c @@ -0,0 +1,303 @@ +/*++ +/* NAME +/* rewrite 3 +/* SUMMARY +/* mail address rewriter +/* SYNOPSIS +/* #include "trivial-rewrite.h" +/* +/* void rewrite_init(void) +/* +/* void rewrite_proto(stream) +/* VSTREAM *stream; +/* +/* void rewrite_addr(context, addr, result) +/* RWR_CONTEXT *context; +/* char *addr; +/* VSTRING *result; +/* +/* void rewrite_tree(context, tree) +/* RWR_CONTEXT *context; +/* TOK822 *tree; +/* +/* RWR_CONTEXT local_context; +/* RWR_CONTEXT remote_context; +/* DESCRIPTION +/* This module implements the trivial address rewriting engine. +/* +/* rewrite_init() initializes data structures that are private +/* to this module. It should be called once before using the +/* actual rewriting routines. +/* +/* rewrite_proto() implements the client-server protocol: read +/* one rule set name and one address in external (quoted) form, +/* reply with the rewritten address in external form. +/* +/* rewrite_addr() rewrites an address string to another string. +/* Both input and output are in external (quoted) form. +/* +/* rewrite_tree() rewrites a parse tree with a single address to +/* another tree. A tree is a dummy node on top of a token list. +/* +/* local_context and remote_context provide domain names for +/* completing incomplete address forms. +/* STANDARDS +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* SEE ALSO +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "trivial-rewrite.h" + +RWR_CONTEXT local_context = { + VAR_MYORIGIN, &var_myorigin, + VAR_MYDOMAIN, &var_mydomain, +}; + +RWR_CONTEXT remote_context = { + VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain, + VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain, +}; + +static VSTRING *ruleset; +static VSTRING *address; +static VSTRING *result; + +/* rewrite_tree - rewrite address according to rule set */ + +void rewrite_tree(RWR_CONTEXT *context, TOK822 *tree) +{ + TOK822 *colon; + TOK822 *domain; + TOK822 *bang; + TOK822 *local; + VSTRING *vstringval; + + /* + * XXX If you change this module, quote_822_local.c, or tok822_parse.c, + * be sure to re-run the tests under "make rewrite_clnt_test" and "make + * resolve_clnt_test" in the global directory. + */ + + /* + * Sanity check. + */ + if (tree->head == 0) + msg_panic("rewrite_tree: empty tree"); + + /* + * An empty address is a special case. + */ + if (tree->head == tree->tail + && tree->tail->type == TOK822_QSTRING + && VSTRING_LEN(tree->tail->vstr) == 0) + return; + + /* + * Treat a lone @ as if it were an empty address. + */ + if (tree->head == tree->tail + && tree->tail->type == '@') { + tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); + tok822_sub_append(tree, tok822_alloc(TOK822_QSTRING, "")); + return; + } + + /* + * Strip source route. + */ + if (tree->head->type == '@' + && (colon = tok822_find_type(tree->head, ':')) != 0 + && colon != tree->tail) + tok822_free_tree(tok822_sub_keep_after(tree, colon)); + + /* + * Optionally, transform address forms without @. + */ + if ((domain = tok822_rfind_type(tree->tail, '@')) == 0) { + + /* + * Swap domain!user to user@domain. + */ + if (var_swap_bangpath != 0 + && (bang = tok822_find_type(tree->head, '!')) != 0) { + tok822_sub_keep_before(tree, bang); + local = tok822_cut_after(bang); + tok822_free(bang); + tok822_sub_prepend(tree, tok822_alloc('@', (char *) 0)); + if (local) + tok822_sub_prepend(tree, local); + } + + /* + * Promote user%domain to user@domain. + */ + else if (var_percent_hack != 0 + && (domain = tok822_rfind_type(tree->tail, '%')) != 0) { + domain->type = '@'; + } + + /* + * Append missing @origin + */ + else if (var_append_at_myorigin != 0 + && REW_PARAM_VALUE(context->origin) != 0 + && REW_PARAM_VALUE(context->origin)[0] != 0) { + domain = tok822_sub_append(tree, tok822_alloc('@', (char *) 0)); + tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->origin), + (TOK822 **) 0)); + } + } + + /* + * Append missing .domain, but leave broken forms ending in @ alone. This + * merely makes diagnostics more accurate by leaving bogus addresses + * alone. + * + * Backwards-compatibility warning: warn for "user@localhost" when there is + * no "localhost" in mydestination or in any other address class with an + * explicit domain list. + */ + if (var_append_dot_mydomain != 0 + && REW_PARAM_VALUE(context->domain) != 0 + && REW_PARAM_VALUE(context->domain)[0] != 0 + && (domain = tok822_rfind_type(tree->tail, '@')) != 0 + && domain != tree->tail + && tok822_find_type(domain, TOK822_DOMLIT) == 0 + && tok822_find_type(domain, '.') == 0) { + if (warn_compat_break_app_dot_mydomain + && (vstringval = domain->next->vstr) != 0) { + if (strcasecmp(vstring_str(vstringval), "localhost") != 0) { + msg_info("using backwards-compatible default setting " + VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to " + "\"%s.%s\"", vstring_str(vstringval), + vstring_str(vstringval), var_mydomain); + } else if (resolve_class("localhost") == RESOLVE_CLASS_DEFAULT) { + msg_info("using backwards-compatible default setting " + VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to " + "\"%s.%s\"; please add \"localhost\" to " + "mydestination or other address class", + vstring_str(vstringval), vstring_str(vstringval), + var_mydomain); + } + } + tok822_sub_append(tree, tok822_alloc('.', (char *) 0)); + tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->domain), + (TOK822 **) 0)); + } + + /* + * Strip trailing dot at end of domain, but not dot-dot or @-dot. This + * merely makes diagnostics more accurate by leaving bogus addresses + * alone. + */ + if (tree->tail->type == '.' + && tree->tail->prev + && tree->tail->prev->type != '.' + && tree->tail->prev->type != '@') + tok822_free_tree(tok822_sub_keep_before(tree, tree->tail)); +} + +/* rewrite_proto - read request and send reply */ + +int rewrite_proto(VSTREAM *stream) +{ + RWR_CONTEXT *context; + TOK822 *tree; + + if (attr_scan(stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_RULE, ruleset), + RECV_ATTR_STR(MAIL_ATTR_ADDR, address), + ATTR_TYPE_END) != 2) + return (-1); + + if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_LOCAL) == 0) + context = &local_context; + else if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_REMOTE) == 0) + context = &remote_context; + else { + msg_warn("unknown context: %s", vstring_str(ruleset)); + return (-1); + } + + /* + * Sanity check. An address is supposed to be in externalized form. + */ + if (*vstring_str(address) == 0) { + msg_warn("rewrite_addr: null address"); + vstring_strcpy(result, vstring_str(address)); + } + + /* + * Convert the address from externalized (quoted) form to token list, + * rewrite it, and convert back. + */ + else { + tree = tok822_scan_addr(vstring_str(address)); + rewrite_tree(context, tree); + tok822_externalize(result, tree, TOK822_STR_DEFL); + tok822_free_tree(tree); + } + if (msg_verbose) + msg_info("`%s' `%s' -> `%s'", vstring_str(ruleset), + vstring_str(address), vstring_str(result)); + + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_FLAGS, server_flags), + SEND_ATTR_STR(MAIL_ATTR_ADDR, vstring_str(result)), + ATTR_TYPE_END); + + if (vstream_fflush(stream) != 0) { + msg_warn("write rewrite reply: %m"); + return (-1); + } + return (0); +} + +/* rewrite_init - module initializations */ + +void rewrite_init(void) +{ + ruleset = vstring_alloc(100); + address = vstring_alloc(100); + result = vstring_alloc(100); +} diff --git a/src/trivial-rewrite/transport.c b/src/trivial-rewrite/transport.c new file mode 100644 index 0000000..90a2c4f --- /dev/null +++ b/src/trivial-rewrite/transport.c @@ -0,0 +1,438 @@ +/*++ +/* NAME +/* transport 3 +/* SUMMARY +/* transport mapping +/* SYNOPSIS +/* #include "transport.h" +/* +/* TRANSPORT_INFO *transport_pre_init(maps_name, maps) +/* const char *maps_name; +/* const char *maps; +/* +/* void transport_post_init(info) +/* TRANSPORT_INFO *info; +/* +/* int transport_lookup(info, address, rcpt_domain, channel, nexthop) +/* TRANSPORT_INFO *info; +/* const char *address; +/* const char *rcpt_domain; +/* VSTRING *channel; +/* VSTRING *nexthop; +/* +/* void transport_free(info); +/* TRANSPORT_INFO * info; +/* DESCRIPTION +/* This module implements access to the table that maps transport +/* user@domain addresses to (channel, nexthop) tuples. +/* +/* transport_pre_init() performs initializations that should be +/* done before the process enters the chroot jail, and +/* before calling transport_lookup(). +/* +/* transport_post_init() can be invoked after entering the chroot +/* jail, and must be called before calling transport_lookup(). +/* +/* transport_lookup() finds the channel and nexthop for the given +/* domain, and returns 1 if something was found. Otherwise, 0 +/* is returned. +/* DIAGNOSTICS +/* info->transport_path->error is non-zero when the lookup +/* should be tried again. +/* SEE ALSO +/* maps(3), multi-dictionary search +/* strip_addr(3), strip extension from address +/* transport(5), format of transport map +/* CONFIGURATION PARAMETERS +/* transport_maps, names of maps to be searched. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "transport.h" + +static int transport_match_parent_style; + +#define STR(x) vstring_str(x) + +static void transport_wildcard_init(TRANSPORT_INFO *); + +/* transport_pre_init - pre-jail initialization */ + +TRANSPORT_INFO *transport_pre_init(const char *transport_maps_name, + const char *transport_maps) +{ + TRANSPORT_INFO *tp; + + tp = (TRANSPORT_INFO *) mymalloc(sizeof(*tp)); + tp->transport_path = maps_create(transport_maps_name, transport_maps, + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_NO_REGSUB + | DICT_FLAG_UTF8_REQUEST); + tp->wildcard_channel = tp->wildcard_nexthop = 0; + tp->wildcard_errno = 0; + tp->expire = 0; + return (tp); +} + +/* transport_post_init - post-jail initialization */ + +void transport_post_init(TRANSPORT_INFO *tp) +{ + transport_match_parent_style = match_parent_style(VAR_TRANSPORT_MAPS); + transport_wildcard_init(tp); +} + +/* transport_free - destroy transport info */ + +void transport_free(TRANSPORT_INFO *tp) +{ + if (tp->transport_path) + maps_free(tp->transport_path); + if (tp->wildcard_channel) + vstring_free(tp->wildcard_channel); + if (tp->wildcard_nexthop) + vstring_free(tp->wildcard_nexthop); + myfree((void *) tp); +} + +/* update_entry - update from transport table entry */ + +static void update_entry(const char *new_channel, const char *new_nexthop, + const char *rcpt_domain, VSTRING *channel, + VSTRING *nexthop) +{ + + /* + * :[nexthop] means don't change the channel, and don't change the + * nexthop unless a non-default nexthop is specified. Thus, a right-hand + * side of ":" is the transport table equivalent of a NOOP. + */ + if (*new_channel == 0) { /* :[nexthop] */ + if (*new_nexthop != 0) + vstring_strcpy(nexthop, new_nexthop); + } + + /* + * transport[:[nexthop]] means change the channel, and reset the nexthop + * to the default unless a non-default nexthop is specified. + */ + else { + vstring_strcpy(channel, new_channel); + if (*new_nexthop != 0) + vstring_strcpy(nexthop, new_nexthop); + else if (strcmp(STR(channel), MAIL_SERVICE_ERROR) != 0 + && strcmp(STR(channel), MAIL_SERVICE_RETRY) != 0) + vstring_strcpy(nexthop, rcpt_domain); + else + vstring_strcpy(nexthop, "Address is undeliverable"); + } +} + +/* parse_transport_entry - parse transport table entry */ + +static void parse_transport_entry(const char *value, const char *rcpt_domain, + VSTRING *channel, VSTRING *nexthop) +{ + char *saved_value; + const char *host; + +#define FOUND 1 +#define NOTFOUND 0 + + /* + * It would be great if we could specify a recipient address in the + * lookup result. Unfortunately, we cannot simply run the result through + * a parser that recognizes "transport:user@domain" because the lookup + * result can have arbitrary content (especially in the case of the error + * mailer). + */ + saved_value = mystrdup(value); + host = split_at(saved_value, ':'); + update_entry(saved_value, host ? host : "", rcpt_domain, channel, nexthop); + myfree(saved_value); +} + +/* transport_wildcard_init - (re) initialize wild-card lookup result */ + +static void transport_wildcard_init(TRANSPORT_INFO *tp) +{ + VSTRING *channel = vstring_alloc(10); + VSTRING *nexthop = vstring_alloc(10); + const char *value; + + /* + * Both channel and nexthop may be zero-length strings. Therefore we must + * use something else to represent "wild-card does not exist". We use + * null VSTRING pointers, for historical reasons. + */ + if (tp->wildcard_channel) + vstring_free(tp->wildcard_channel); + if (tp->wildcard_nexthop) + vstring_free(tp->wildcard_nexthop); + + /* + * Technically, the wildcard lookup pattern is redundant. A static map + * (keys always match, result is fixed string) could achieve the same: + * + * transport_maps = hash:/etc/postfix/transport static:xxx:yyy + * + * But the user interface of such an approach would be less intuitive. We + * tolerate the continued existence of wildcard lookup patterns because + * of human interface considerations. + */ +#define WILDCARD "*" +#define FULL 0 +#define PARTIAL DICT_FLAG_FIXED + + if ((value = maps_find(tp->transport_path, WILDCARD, FULL)) != 0) { + parse_transport_entry(value, "", channel, nexthop); + tp->wildcard_errno = 0; + tp->wildcard_channel = channel; + tp->wildcard_nexthop = nexthop; + if (msg_verbose) + msg_info("wildcard_{chan:hop}={%s:%s}", + vstring_str(channel), vstring_str(nexthop)); + } else { + tp->wildcard_errno = tp->transport_path->error; + vstring_free(channel); + vstring_free(nexthop); + tp->wildcard_channel = 0; + tp->wildcard_nexthop = 0; + } + tp->expire = event_time() + 30; /* XXX make configurable */ +} + +/* transport_lookup - map a transport domain */ + +int transport_lookup(TRANSPORT_INFO *tp, const char *addr, + const char *rcpt_domain, + VSTRING *channel, VSTRING *nexthop) +{ + char *ratsign = 0; + const char *value; + +#define STREQ(x,y) (strcmp((x), (y)) == 0) +#define DISCARD_EXTENSION ((char **) 0) + + /* + * The null recipient is rewritten to the local mailer daemon address. + */ + if (*addr == 0) { + msg_warn("transport_lookup: null address - skipping table lookup"); + return (NOTFOUND); + } + + /* + * Look up the full and extension-stripped address, then match the domain + * and subdomains. Try the external form before the backwards-compatible + * internal form. + */ +#define LOOKUP_STRATEGY \ + (MA_FIND_FULL | MA_FIND_NOEXT | MA_FIND_DOMAIN | \ + (transport_match_parent_style == MATCH_FLAG_PARENT ? \ + MA_FIND_PDMS : MA_FIND_PDDMDS)) + + if ((ratsign = strrchr(addr, '@')) == 0 || ratsign[1] == 0) + msg_panic("transport_lookup: bad address: \"%s\"", addr); + + if ((value = mail_addr_find_strategy(tp->transport_path, addr, (char **) 0, + LOOKUP_STRATEGY)) != 0) { + parse_transport_entry(value, rcpt_domain, channel, nexthop); + return (FOUND); + } + if (tp->transport_path->error != 0) + return (NOTFOUND); + + /* + * Fall back to the wild-card entry. + */ + if (tp->wildcard_errno || event_time() > tp->expire) + transport_wildcard_init(tp); + if (tp->wildcard_errno) { + tp->transport_path->error = tp->wildcard_errno; + return (NOTFOUND); + } else if (tp->wildcard_channel) { + update_entry(STR(tp->wildcard_channel), STR(tp->wildcard_nexthop), + rcpt_domain, channel, nexthop); + return (FOUND); + } + + /* + * We really did not find it. + */ + return (NOTFOUND); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. Read an address from stdin, and spit out + * the lookup result. + */ + +#include + +#include +#include +#include + +static NORETURN usage(const char *progname) +{ + msg_fatal("usage: %s [-v] database", progname); +} + +int main(int argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(100); + VSTRING *channel = vstring_alloc(100); + VSTRING *nexthop = vstring_alloc(100); + TRANSPORT_INFO *tp; + char *bp; + char *addr_field; + char *rcpt_domain; + char *expect_channel; + char *expect_nexthop; + int status; + int ch; + int errs = 0; + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "v")) > 0) { + switch (ch) { + case 'v': + msg_verbose++; + break; + default: + usage(argv[0]); + } + } + if (argc != optind + 1) + usage(argv[0]); + + /* + * Initialize. + */ +#define UPDATE(var, val) do { myfree(var); var = mystrdup(val); } while (0) + + mail_conf_read(); /* XXX eliminate dependency. */ + UPDATE(var_rcpt_delim, "+"); + UPDATE(var_mydomain, "localdomain"); + UPDATE(var_myorigin, "localhost.localdomain"); + UPDATE(var_mydest, "localhost.localdomain"); + + tp = transport_pre_init("transport map", argv[optind]); + transport_post_init(tp); + + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + bp = STR(buffer); + + /* + * Parse the input and expectations. XXX We can't expect empty + * fields, so require '-' instead. + */ + if ((addr_field = mystrtok(&bp, ":")) == 0) + msg_fatal("no address field"); + if ((rcpt_domain = strrchr(addr_field, '@')) == 0) + msg_fatal("no recipient domain"); + rcpt_domain += 1; + expect_channel = mystrtok(&bp, ":"); + expect_nexthop = mystrtok(&bp, ":"); + if ((expect_channel != 0) != (expect_nexthop != 0)) + msg_fatal("specify both channel and nexthop, or specify neither"); + if (expect_channel) { + if (strcmp(expect_channel, "-") == 0) + *expect_channel = 0; + if (strcmp(expect_nexthop, "-") == 0) + *expect_nexthop = 0; + vstring_strcpy(channel, "DEFAULT"); + vstring_strcpy(nexthop, rcpt_domain); + } + if (mystrtok(&bp, ":") != 0) + msg_fatal("garbage after nexthop field"); + + /* + * Lookups. + */ + status = transport_lookup(tp, addr_field, rcpt_domain, + channel, nexthop); + + /* + * Enforce expectations. + */ + if (expect_nexthop && status) { + vstream_printf("%s:%s -> %s:%s \n", + addr_field, rcpt_domain, + STR(channel), STR(nexthop)); + vstream_fflush(VSTREAM_OUT); + if (strcmp(expect_channel, STR(channel)) != 0) { + msg_warn("expect channel '%s' but got '%s'", + expect_channel, STR(channel)); + errs = 1; + } + if (strcmp(expect_nexthop, STR(nexthop)) != 0) { + msg_warn("expect nexthop '%s' but got '%s'", + expect_nexthop, STR(nexthop)); + errs = 1; + } + } else if (expect_nexthop && !status) { + vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain, + tp->transport_path->error ? + "(try again)" : "(not found)"); + vstream_fflush(VSTREAM_OUT); + msg_warn("expect channel '%s' but got none", expect_channel); + msg_warn("expect nexthop '%s' but got none", expect_nexthop); + errs = 1; + } else if (!status) { + vstream_printf("%s:%s -> %s\n", addr_field, rcpt_domain, + tp->transport_path->error ? + "(try again)" : "(not found)"); + } + } + transport_free(tp); + vstring_free(nexthop); + vstring_free(channel); + vstring_free(buffer); + exit(errs != 0); +} + +#endif diff --git a/src/trivial-rewrite/transport.h b/src/trivial-rewrite/transport.h new file mode 100644 index 0000000..7db0b50 --- /dev/null +++ b/src/trivial-rewrite/transport.h @@ -0,0 +1,51 @@ +/*++ +/* NAME +/* transport 3h +/* SUMMARY +/* transport mapping +/* SYNOPSIS +/* #include "transport.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include + + /* + * Global library. + */ +#include + + /* + * External interface. + */ +typedef struct TRANSPORT_INFO { + MAPS *transport_path; + VSTRING *wildcard_channel; + VSTRING *wildcard_nexthop; + int wildcard_errno; + time_t expire; +} TRANSPORT_INFO; + +extern TRANSPORT_INFO *transport_pre_init(const char *, const char *); +extern void transport_post_init(TRANSPORT_INFO *); +extern int transport_lookup(TRANSPORT_INFO *, const char *, const char *, VSTRING *, VSTRING *); +extern void transport_free(TRANSPORT_INFO *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/trivial-rewrite/transport.in b/src/trivial-rewrite/transport.in new file mode 100644 index 0000000..1ed3a53 --- /dev/null +++ b/src/trivial-rewrite/transport.in @@ -0,0 +1,45 @@ +#!/bin/sh + +# Format: address:expected_channel:expected_nexthop +# No expectation means no match is expected. +# Specify "-" to expect an empty string. + +echo ==== no wildcard +${VALGRIND} ./transport 'inline:{rcpt1@example1.com=channel1:nexthop1, rcpt2@example2=channel2:, example3=channel3}' <<'EOF' +rcpt1@example1.com:channel1:nexthop1 +rcpt1+ext1@example1.com:channel1:nexthop1 +rcpt2@example2:channel2:example2 +rcpt@example3:channel3:example3 +EOF + +echo ==== with wildcard channel and nexthop +${VALGRIND} ./transport 'inline:{*=channel0:nexthop0, rcpt1@example1.com=channel1:nexthop1}' <<'EOF' +rcpt1@example1.com:channel1:nexthop1 +rcpt2@example2:channel0:nexthop0 +EOF + +echo ==== with wildcard channel only +${VALGRIND} ./transport 'inline:{*=channel0, rcpt1@example1.com=channel1:nexthop1}' <<'EOF' +rcpt1@example1.com:channel1:nexthop1 +rcpt2@example2:channel0:example2 +EOF + +echo ==== with wildcard nexthop only +${VALGRIND} ./transport 'inline:{*=:nexthop0, rcpt1@example1.com=channel1:nexthop1}' <<'EOF' +rcpt1@example1.com:channel1:nexthop1 +rcpt2@example2:DEFAULT:nexthop0 +EOF + +echo ==== with wildcard empty fields. +${VALGRIND} ./transport 'inline:{*=:, rcpt1@example1.com=channel1:nexthop1}' <<'EOF' +rcpt1@example1.com:channel1:nexthop1 +rcpt2@example2:DEFAULT:example2 +EOF + +echo === subdomain test +${VALGRIND} ./transport 'inline:{example=:example-result,.example=:dot-example-result}' <<'EOF' +plain1+ext@other-example: +foo@example:DEFAULT:example-result +foo@sub.example:DEFAULT:dot-example-result +foo@sub.sub.example:DEFAULT:dot-example-result +EOF diff --git a/src/trivial-rewrite/transport.ref b/src/trivial-rewrite/transport.ref new file mode 100644 index 0000000..47ab07b --- /dev/null +++ b/src/trivial-rewrite/transport.ref @@ -0,0 +1,22 @@ +==== no wildcard +rcpt1@example1.com:example1.com -> channel1:nexthop1 +rcpt1+ext1@example1.com:example1.com -> channel1:nexthop1 +rcpt2@example2:example2 -> channel2:example2 +rcpt@example3:example3 -> channel3:example3 +==== with wildcard channel and nexthop +rcpt1@example1.com:example1.com -> channel1:nexthop1 +rcpt2@example2:example2 -> channel0:nexthop0 +==== with wildcard channel only +rcpt1@example1.com:example1.com -> channel1:nexthop1 +rcpt2@example2:example2 -> channel0:example2 +==== with wildcard nexthop only +rcpt1@example1.com:example1.com -> channel1:nexthop1 +rcpt2@example2:example2 -> DEFAULT:nexthop0 +==== with wildcard empty fields. +rcpt1@example1.com:example1.com -> channel1:nexthop1 +rcpt2@example2:example2 -> DEFAULT:example2 +=== subdomain test +plain1+ext@other-example:other-example -> (not found) +foo@example:example -> DEFAULT:example-result +foo@sub.example:sub.example -> DEFAULT:dot-example-result +foo@sub.sub.example:sub.sub.example -> DEFAULT:dot-example-result diff --git a/src/trivial-rewrite/trivial-rewrite.c b/src/trivial-rewrite/trivial-rewrite.c new file mode 100644 index 0000000..675af80 --- /dev/null +++ b/src/trivial-rewrite/trivial-rewrite.c @@ -0,0 +1,667 @@ +/*++ +/* NAME +/* trivial-rewrite 8 +/* SUMMARY +/* Postfix address rewriting and resolving daemon +/* SYNOPSIS +/* \fBtrivial-rewrite\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBtrivial-rewrite\fR(8) daemon processes three types of client +/* service requests: +/* .IP "\fBrewrite \fIcontext address\fR" +/* Rewrite an address to standard form, according to the +/* address rewriting context: +/* .RS +/* .IP \fBlocal\fR +/* Append the domain names specified with \fB$myorigin\fR or +/* \fB$mydomain\fR to incomplete addresses; do \fBswap_bangpath\fR +/* and \fBallow_percent_hack\fR processing as described below, and +/* strip source routed addresses (\fI@site,@site:user@domain\fR) +/* to \fIuser@domain\fR form. +/* .IP \fBremote\fR +/* Append the domain name specified with +/* \fB$remote_header_rewrite_domain\fR to incomplete +/* addresses. Otherwise the result is identical to that of +/* the \fBlocal\fR address rewriting context. This prevents +/* Postfix from appending the local domain to spam from poorly +/* written remote clients. +/* .RE +/* .IP "\fBresolve \fIsender\fR \fIaddress\fR" +/* Resolve the address to a (\fItransport\fR, \fInexthop\fR, +/* \fIrecipient\fR, \fIflags\fR) quadruple. The meaning of +/* the results is as follows: +/* .RS +/* .IP \fItransport\fR +/* The delivery agent to use. This is the first field of an entry +/* in the \fBmaster.cf\fR file. +/* .IP \fInexthop\fR +/* The host to send to and optional delivery method information. +/* .IP \fIrecipient\fR +/* The envelope recipient address that is passed on to \fInexthop\fR. +/* .IP \fIflags\fR +/* The address class, whether the address requires relaying, +/* whether the address has problems, and whether the request failed. +/* .RE +/* .IP "\fBverify \fIsender\fR \fIaddress\fR" +/* Resolve the address for address verification purposes. +/* SERVER PROCESS MANAGEMENT +/* .ad +/* .fi +/* The \fBtrivial-rewrite\fR(8) servers run under control by +/* the Postfix master(8) +/* server. Each server can handle multiple simultaneous connections. +/* When all servers are busy while a client connects, the master +/* creates a new server process, provided that the trivial-rewrite +/* server process limit is not exceeded. +/* Each trivial-rewrite server terminates after +/* serving at least \fB$max_use\fR clients of after \fB$max_idle\fR +/* seconds of idle time. +/* STANDARDS +/* .ad +/* .fi +/* None. The command does not interact with the outside world. +/* SECURITY +/* .ad +/* .fi +/* The \fBtrivial-rewrite\fR(8) daemon is not security sensitive. +/* By default, this daemon does not talk to remote or local users. +/* It can run at a fixed low privilege in a chrooted environment. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* On busy mail systems a long time may pass before a \fBmain.cf\fR +/* change affecting \fBtrivial-rewrite\fR(8) is picked up. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* COMPATIBILITY CONTROLS +/* .ad +/* .fi +/* .IP "\fBresolve_dequoted_address (yes)\fR" +/* Resolve a recipient address safely instead of correctly, by +/* looking inside quotes. +/* .PP +/* Available with Postfix version 2.1 and later: +/* .IP "\fBresolve_null_domain (no)\fR" +/* Resolve an address that ends in the "@" null domain as if the +/* local hostname were specified, instead of rejecting the address as +/* invalid. +/* .PP +/* Available with Postfix version 2.3 and later: +/* .IP "\fBresolve_numeric_domain (no)\fR" +/* Resolve "user@ipaddress" as "user@[ipaddress]", instead of +/* rejecting the address as invalid. +/* .PP +/* Available with Postfix version 2.5 and later: +/* .IP "\fBallow_min_user (no)\fR" +/* Allow a sender or recipient address to have `-' as the first +/* character. +/* ADDRESS REWRITING CONTROLS +/* .ad +/* .fi +/* .IP "\fBmyorigin ($myhostname)\fR" +/* The domain name that locally-posted mail appears to come +/* from, and that locally posted mail is delivered to. +/* .IP "\fBallow_percent_hack (yes)\fR" +/* Enable the rewriting of the form "user%domain" to "user@domain". +/* .IP "\fBappend_at_myorigin (yes)\fR" +/* With locally submitted mail, append the string "@$myorigin" to mail +/* addresses without domain information. +/* .IP "\fBappend_dot_mydomain (Postfix >= 3.0: no, Postfix < 3.0: yes)\fR" +/* With locally submitted mail, append the string ".$mydomain" to +/* addresses that have no ".domain" information. +/* .IP "\fBrecipient_delimiter (empty)\fR" +/* The set of characters that can separate an email address +/* localpart, user name, or a .forward file name from its extension. +/* .IP "\fBswap_bangpath (yes)\fR" +/* Enable the rewriting of "site!user" into "user@site". +/* .PP +/* Available in Postfix 2.2 and later: +/* .IP "\fBremote_header_rewrite_domain (empty)\fR" +/* Don't rewrite message headers from remote clients at all when +/* this parameter is empty; otherwise, rewrite message headers and +/* append the specified domain name to incomplete addresses. +/* ROUTING CONTROLS +/* .ad +/* .fi +/* The following is applicable to Postfix version 2.0 and later. +/* Earlier versions do not have support for: virtual_transport, +/* relay_transport, virtual_alias_domains, virtual_mailbox_domains +/* or proxy_interfaces. +/* .IP "\fBlocal_transport (local:$myhostname)\fR" +/* The default mail delivery transport and next-hop destination +/* for final delivery to domains listed with mydestination, and for +/* [ipaddress] destinations that match $inet_interfaces or $proxy_interfaces. +/* .IP "\fBvirtual_transport (virtual)\fR" +/* The default mail delivery transport and next-hop destination for +/* final delivery to domains listed with $virtual_mailbox_domains. +/* .IP "\fBrelay_transport (relay)\fR" +/* The default mail delivery transport and next-hop destination for +/* remote delivery to domains listed with $relay_domains. +/* .IP "\fBdefault_transport (smtp)\fR" +/* The default mail delivery transport and next-hop destination for +/* destinations that do not match $mydestination, $inet_interfaces, +/* $proxy_interfaces, $virtual_alias_domains, $virtual_mailbox_domains, +/* or $relay_domains. +/* .IP "\fBparent_domain_matches_subdomains (see 'postconf -d' output)\fR" +/* A list of Postfix features where the pattern "example.com" also +/* matches subdomains of example.com, +/* instead of requiring an explicit ".example.com" pattern. +/* .IP "\fBrelayhost (empty)\fR" +/* The next-hop destination(s) for non-local mail; overrides non-local +/* domains in recipient addresses. +/* .IP "\fBtransport_maps (empty)\fR" +/* Optional lookup tables with mappings from recipient address to +/* (message delivery transport, next-hop destination). +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBsender_dependent_relayhost_maps (empty)\fR" +/* A sender-dependent override for the global relayhost parameter +/* setting. +/* .PP +/* Available in Postfix version 2.5 and later: +/* .IP "\fBempty_address_relayhost_maps_lookup_key (<>)\fR" +/* The sender_dependent_relayhost_maps search string that will be +/* used instead of the null sender address. +/* .PP +/* Available in Postfix version 2.7 and later: +/* .IP "\fBempty_address_default_transport_maps_lookup_key (<>)\fR" +/* The sender_dependent_default_transport_maps search string that +/* will be used instead of the null sender address. +/* .IP "\fBsender_dependent_default_transport_maps (empty)\fR" +/* A sender-dependent override for the global default_transport +/* parameter setting. +/* ADDRESS VERIFICATION CONTROLS +/* .ad +/* .fi +/* Postfix version 2.1 introduces sender and recipient address verification. +/* This feature is implemented by sending probe email messages that +/* are not actually delivered. +/* By default, address verification probes use the same route +/* as regular mail. To override specific aspects of message +/* routing for address verification probes, specify one or more +/* of the following: +/* .IP "\fBaddress_verify_local_transport ($local_transport)\fR" +/* Overrides the local_transport parameter setting for address +/* verification probes. +/* .IP "\fBaddress_verify_virtual_transport ($virtual_transport)\fR" +/* Overrides the virtual_transport parameter setting for address +/* verification probes. +/* .IP "\fBaddress_verify_relay_transport ($relay_transport)\fR" +/* Overrides the relay_transport parameter setting for address +/* verification probes. +/* .IP "\fBaddress_verify_default_transport ($default_transport)\fR" +/* Overrides the default_transport parameter setting for address +/* verification probes. +/* .IP "\fBaddress_verify_relayhost ($relayhost)\fR" +/* Overrides the relayhost parameter setting for address verification +/* probes. +/* .IP "\fBaddress_verify_transport_maps ($transport_maps)\fR" +/* Overrides the transport_maps parameter setting for address verification +/* probes. +/* .PP +/* Available in Postfix version 2.3 and later: +/* .IP "\fBaddress_verify_sender_dependent_relayhost_maps ($sender_dependent_relayhost_maps)\fR" +/* Overrides the sender_dependent_relayhost_maps parameter setting for address +/* verification probes. +/* .PP +/* Available in Postfix version 2.7 and later: +/* .IP "\fBaddress_verify_sender_dependent_default_transport_maps ($sender_dependent_default_transport_maps)\fR" +/* Overrides the sender_dependent_default_transport_maps parameter +/* setting for address verification probes. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBempty_address_recipient (MAILER-DAEMON)\fR" +/* The recipient of mail addressed to the null address. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBrelocated_maps (empty)\fR" +/* Optional lookup tables with new contact information for users or +/* domains that no longer exist. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBshow_user_unknown_table_name (yes)\fR" +/* Display the name of the recipient table in the "User unknown" +/* responses. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 2.0 and later: +/* .IP "\fBhelpful_warnings (yes)\fR" +/* Log warnings about problematic configuration settings, and provide +/* helpful suggestions. +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* postconf(5), configuration parameters +/* transport(5), transport table format +/* relocated(5), format of the "user has moved" table +/* master(8), process manager +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* ADDRESS_CLASS_README, Postfix address classes howto +/* ADDRESS_VERIFICATION_README, Postfix address verification +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Multi server skeleton. */ + +#include + +/* Application-specific. */ + +#include +#include + +static VSTRING *command; + + /* + * Tunable parameters. + */ +char *var_transport_maps; +bool var_swap_bangpath; +bool var_append_dot_mydomain; +bool var_append_at_myorigin; +bool var_percent_hack; +char *var_local_transport; +char *var_virt_transport; +char *var_relay_transport; +int var_resolve_dequoted; +char *var_virt_alias_maps; /* XXX virtual_alias_domains */ +char *var_virt_mailbox_maps; /* XXX virtual_mailbox_domains */ +char *var_virt_alias_doms; +char *var_virt_mailbox_doms; +char *var_relocated_maps; +char *var_def_transport; +char *var_snd_def_xport_maps; +char *var_empty_addr; +int var_show_unk_rcpt_table; +int var_resolve_nulldom; +char *var_remote_rwr_domain; +char *var_snd_relay_maps; +char *var_null_relay_maps_key; +char *var_null_def_xport_maps_key; +int var_resolve_num_dom; +bool var_allow_min_user; + + /* + * Shadow personality for address verification. + */ +char *var_vrfy_xport_maps; +char *var_vrfy_local_xport; +char *var_vrfy_virt_xport; +char *var_vrfy_relay_xport; +char *var_vrfy_def_xport; +char *var_vrfy_snd_def_xport_maps; +char *var_vrfy_relayhost; +char *var_vrfy_relay_maps; + + /* + * Different resolver personalities depending on the kind of request. + */ +RES_CONTEXT resolve_regular = { + VAR_LOCAL_TRANSPORT, &var_local_transport, + VAR_VIRT_TRANSPORT, &var_virt_transport, + VAR_RELAY_TRANSPORT, &var_relay_transport, + VAR_DEF_TRANSPORT, &var_def_transport, + VAR_SND_DEF_XPORT_MAPS, &var_snd_def_xport_maps, 0, + VAR_RELAYHOST, &var_relayhost, + VAR_SND_RELAY_MAPS, &var_snd_relay_maps, 0, + VAR_TRANSPORT_MAPS, &var_transport_maps, 0 +}; + +RES_CONTEXT resolve_verify = { + VAR_VRFY_LOCAL_XPORT, &var_vrfy_local_xport, + VAR_VRFY_VIRT_XPORT, &var_vrfy_virt_xport, + VAR_VRFY_RELAY_XPORT, &var_vrfy_relay_xport, + VAR_VRFY_DEF_XPORT, &var_vrfy_def_xport, + VAR_VRFY_SND_DEF_XPORT_MAPS, &var_vrfy_snd_def_xport_maps, 0, + VAR_VRFY_RELAYHOST, &var_vrfy_relayhost, + VAR_VRFY_RELAY_MAPS, &var_vrfy_relay_maps, 0, + VAR_VRFY_XPORT_MAPS, &var_vrfy_xport_maps, 0 +}; + + /* + * Connection management. When file-based lookup tables change we should + * restart at our convenience, but avoid client read errors. We restart + * rather than reopen, because the process may be chrooted (and if it isn't + * we still need code that handles the chrooted case anyway). + * + * Three variants are implemented. Only one should be used. + * + * ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT + * + * This code detaches the trivial-rewrite process from the master, stops + * accepting new clients, and handles established clients in the background, + * asking them to reconnect the next time they send a request. The master + * creates a new process that accepts connections. This is reasonably safe + * because the number of trivial-rewrite server processes is small compared + * to the number of trivial-rewrite client processes. The few extra + * background processes should not make a difference in Postfix's footprint. + * However, once a daemon detaches from the master, its exit status will be + * lost, and abnormal termination may remain undetected. Timely restart is + * achieved by checking the table changed status every 10 seconds or so + * before responding to a client request. + * + * ifdef CHECK_TABLE_STATS_PERIODICALLY + * + * This code runs every 10 seconds and terminates the process when lookup + * tables have changed. This is subject to race conditions when established + * clients send a request while the server exits; those clients may read EOF + * instead of a server reply. If the experience with the oldest option + * (below) is anything to go by, however, then this is unlikely to be a + * problem during real deployment. + * + * ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT + * + * This is the old code. It checks the table changed status when a new client + * connects (i.e. before the server calls accept()), and terminates + * immediately. This is invisible for the connecting client, but is subject + * to race conditions when established clients send a request while the + * server exits; those clients may read EOF instead of a server reply. This + * has, however, not been a problem in real deployment. With the old code, + * timely restart is achieved by setting the ipc_ttl parameter to 60 + * seconds, so that the table change status is checked several times a + * minute. + */ +int server_flags; + + /* + * Define exactly one of these. + */ +/* #define DETACH_AND_ASK_CLIENTS_TO_RECONNECT /* correct and complex */ +#define CHECK_TABLE_STATS_PERIODICALLY /* quick */ +/* #define CHECK_TABLE_STATS_BEFORE_ACCEPT /* slow */ + +/* rewrite_service - read request and send reply */ + +static void rewrite_service(VSTREAM *stream, char *unused_service, char **argv) +{ + int status = -1; + +#ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT + static time_t last; + time_t now; + const char *table; + +#endif + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Client connections are long-lived. Be sure to refesh timely. + */ +#ifdef DETACH_AND_ASK_CLIENTS_TO_RECONNECT + if (server_flags == 0 && (now = event_time()) - last > 10) { + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + if (multi_server_drain() == 0) + server_flags = 1; + } + last = now; + } +#endif + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * dedicated to address rewriting. All connection-management stuff is + * handled by the common code in multi_server.c. + */ + if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, + RECV_ATTR_STR(MAIL_ATTR_REQ, command), + ATTR_TYPE_END) == 1) { + if (strcmp(vstring_str(command), REWRITE_ADDR) == 0) { + status = rewrite_proto(stream); + } else if (strcmp(vstring_str(command), RESOLVE_REGULAR) == 0) { + status = resolve_proto(&resolve_regular, stream); + } else if (strcmp(vstring_str(command), RESOLVE_VERIFY) == 0) { + status = resolve_proto(&resolve_verify, stream); + } else { + msg_warn("bad command %.30s", printable(vstring_str(command), '?')); + } + } + if (status < 0) + multi_server_disconnect(stream); +} + +/* pre_accept - see if tables have changed */ + +#ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +#endif + +/* post_accept - announce our protocol name */ + +static void post_accept(VSTREAM *stream, char *unused_name, char **unused_argv, + HTABLE *unused_attr) +{ + + /* + * Announce the protocol. + */ + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TRIVIAL), + ATTR_TYPE_END); + (void) vstream_fflush(stream); +} + +static void check_table_stats(int unused_event, void *unused_context) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } + event_request_timer(check_table_stats, (void *) 0, 10); +} + +/* pre_jail_init - initialize before entering chroot jail */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + command = vstring_alloc(100); + rewrite_init(); + resolve_init(); + if (*RES_PARAM_VALUE(resolve_regular.transport_maps)) + resolve_regular.transport_info = + transport_pre_init(resolve_regular.transport_maps_name, + RES_PARAM_VALUE(resolve_regular.transport_maps)); + if (*RES_PARAM_VALUE(resolve_verify.transport_maps)) + resolve_verify.transport_info = + transport_pre_init(resolve_verify.transport_maps_name, + RES_PARAM_VALUE(resolve_verify.transport_maps)); + if (*RES_PARAM_VALUE(resolve_regular.snd_relay_maps)) + resolve_regular.snd_relay_info = + maps_create(resolve_regular.snd_relay_maps_name, + RES_PARAM_VALUE(resolve_regular.snd_relay_maps), + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST); + if (*RES_PARAM_VALUE(resolve_verify.snd_relay_maps)) + resolve_verify.snd_relay_info = + maps_create(resolve_verify.snd_relay_maps_name, + RES_PARAM_VALUE(resolve_verify.snd_relay_maps), + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST); + if (*RES_PARAM_VALUE(resolve_regular.snd_def_xp_maps)) + resolve_regular.snd_def_xp_info = + maps_create(resolve_regular.snd_def_xp_maps_name, + RES_PARAM_VALUE(resolve_regular.snd_def_xp_maps), + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST); + if (*RES_PARAM_VALUE(resolve_verify.snd_def_xp_maps)) + resolve_verify.snd_def_xp_info = + maps_create(resolve_verify.snd_def_xp_maps_name, + RES_PARAM_VALUE(resolve_verify.snd_def_xp_maps), + DICT_FLAG_LOCK | DICT_FLAG_FOLD_FIX + | DICT_FLAG_NO_REGSUB | DICT_FLAG_UTF8_REQUEST); +} + +/* post_jail_init - initialize after entering chroot jail */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + if (resolve_regular.transport_info) + transport_post_init(resolve_regular.transport_info); + if (resolve_verify.transport_info) + transport_post_init(resolve_verify.transport_info); + check_table_stats(0, (void *) 0); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the multi-threaded skeleton code */ + +int main(int argc, char **argv) +{ + static const CONFIG_STR_TABLE str_table[] = { + VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0, + VAR_LOCAL_TRANSPORT, DEF_LOCAL_TRANSPORT, &var_local_transport, 1, 0, + VAR_VIRT_TRANSPORT, DEF_VIRT_TRANSPORT, &var_virt_transport, 1, 0, + VAR_RELAY_TRANSPORT, DEF_RELAY_TRANSPORT, &var_relay_transport, 1, 0, + VAR_DEF_TRANSPORT, DEF_DEF_TRANSPORT, &var_def_transport, 1, 0, + VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0, + VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0, + VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0, + VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0, + VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0, + VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0, + VAR_VRFY_XPORT_MAPS, DEF_VRFY_XPORT_MAPS, &var_vrfy_xport_maps, 0, 0, + VAR_VRFY_LOCAL_XPORT, DEF_VRFY_LOCAL_XPORT, &var_vrfy_local_xport, 1, 0, + VAR_VRFY_VIRT_XPORT, DEF_VRFY_VIRT_XPORT, &var_vrfy_virt_xport, 1, 0, + VAR_VRFY_RELAY_XPORT, DEF_VRFY_RELAY_XPORT, &var_vrfy_relay_xport, 1, 0, + VAR_VRFY_DEF_XPORT, DEF_VRFY_DEF_XPORT, &var_vrfy_def_xport, 1, 0, + VAR_VRFY_RELAYHOST, DEF_VRFY_RELAYHOST, &var_vrfy_relayhost, 0, 0, + VAR_REM_RWR_DOMAIN, DEF_REM_RWR_DOMAIN, &var_remote_rwr_domain, 0, 0, + VAR_SND_RELAY_MAPS, DEF_SND_RELAY_MAPS, &var_snd_relay_maps, 0, 0, + VAR_NULL_RELAY_MAPS_KEY, DEF_NULL_RELAY_MAPS_KEY, &var_null_relay_maps_key, 1, 0, + VAR_VRFY_RELAY_MAPS, DEF_VRFY_RELAY_MAPS, &var_vrfy_relay_maps, 0, 0, + VAR_SND_DEF_XPORT_MAPS, DEF_SND_DEF_XPORT_MAPS, &var_snd_def_xport_maps, 0, 0, + VAR_NULL_DEF_XPORT_MAPS_KEY, DEF_NULL_DEF_XPORT_MAPS_KEY, &var_null_def_xport_maps_key, 1, 0, + VAR_VRFY_SND_DEF_XPORT_MAPS, DEF_VRFY_SND_DEF_XPORT_MAPS, &var_vrfy_snd_def_xport_maps, 0, 0, + 0, + }; + static const CONFIG_BOOL_TABLE bool_table[] = { + VAR_SWAP_BANGPATH, DEF_SWAP_BANGPATH, &var_swap_bangpath, + VAR_APP_AT_MYORIGIN, DEF_APP_AT_MYORIGIN, &var_append_at_myorigin, + VAR_PERCENT_HACK, DEF_PERCENT_HACK, &var_percent_hack, + VAR_RESOLVE_DEQUOTED, DEF_RESOLVE_DEQUOTED, &var_resolve_dequoted, + VAR_SHOW_UNK_RCPT_TABLE, DEF_SHOW_UNK_RCPT_TABLE, &var_show_unk_rcpt_table, + VAR_RESOLVE_NULLDOM, DEF_RESOLVE_NULLDOM, &var_resolve_nulldom, + VAR_RESOLVE_NUM_DOM, DEF_RESOLVE_NUM_DOM, &var_resolve_num_dom, + VAR_ALLOW_MIN_USER, DEF_ALLOW_MIN_USER, &var_allow_min_user, + 0, + }; + static const CONFIG_NBOOL_TABLE nbool_table[] = { + VAR_APP_DOT_MYDOMAIN, DEF_APP_DOT_MYDOMAIN, &var_append_dot_mydomain, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + multi_server_main(argc, argv, rewrite_service, + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_BOOL_TABLE(bool_table), + CA_MAIL_SERVER_NBOOL_TABLE(nbool_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_POST_INIT(post_jail_init), +#ifdef CHECK_TABLE_STATS_BEFORE_ACCEPT + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), +#endif + CA_MAIL_SERVER_POST_ACCEPT(post_accept), + 0); +} diff --git a/src/trivial-rewrite/trivial-rewrite.h b/src/trivial-rewrite/trivial-rewrite.h new file mode 100644 index 0000000..e27dd00 --- /dev/null +++ b/src/trivial-rewrite/trivial-rewrite.h @@ -0,0 +1,92 @@ +/*++ +/* NAME +/* trivial-rewrite 3h +/* SUMMARY +/* mail address rewriter and resolver +/* SYNOPSIS +/* #include "trivial-rewrite.h" +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include + + /* + * Connection management. + */ +extern int server_flags; + + /* + * rewrite.c + */ +typedef struct { + const char *origin_name; /* name of variable */ + char **origin; /* default origin */ + const char *domain_name; /* name of variable */ + char **domain; /* default domain */ +} RWR_CONTEXT; + +#define REW_PARAM_VALUE(x) (*(x)) /* make it easy to do it right */ + +extern void rewrite_init(void); +extern int rewrite_proto(VSTREAM *); +extern void rewrite_addr(RWR_CONTEXT *, char *, VSTRING *); +extern void rewrite_tree(RWR_CONTEXT *, TOK822 *); +extern RWR_CONTEXT local_context; +extern RWR_CONTEXT inval_context; + + /* + * resolve.c + */ +typedef struct { + const char *local_transport_name; /* name of variable */ + char **local_transport; /* local transport:nexthop */ + const char *virt_transport_name; /* name of variable */ + char **virt_transport; /* virtual mailbox transport:nexthop */ + const char *relay_transport_name; /* name of variable */ + char **relay_transport; /* relay transport:nexthop */ + const char *def_transport_name; /* name of variable */ + char **def_transport; /* default transport:nexthop */ + const char *snd_def_xp_maps_name; /* name of variable */ + char **snd_def_xp_maps; /* maptype:mapname */ + MAPS *snd_def_xp_info; /* handle */ + const char *relayhost_name; /* name of variable */ + char **relayhost; /* for relay and default transport */ + const char *snd_relay_maps_name; /* name of variable */ + char **snd_relay_maps; /* maptype:mapname */ + MAPS *snd_relay_info; /* handle */ + const char *transport_maps_name; /* name of variable */ + char **transport_maps; /* maptype:mapname */ + struct TRANSPORT_INFO *transport_info; /* handle */ +} RES_CONTEXT; + +#define RES_PARAM_VALUE(x) (*(x)) /* make it easy to do it right */ + +extern void resolve_init(void); +extern int resolve_proto(RES_CONTEXT *, VSTREAM *); +extern int resolve_class(const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/util/.indent.pro b/src/util/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/util/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/util/.printfck b/src/util/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/util/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/util/Makefile.in b/src/util/Makefile.in new file mode 100644 index 0000000..c59cdf9 --- /dev/null +++ b/src/util/Makefile.in @@ -0,0 +1,2756 @@ +SHELL = /bin/sh +SRCS = alldig.c allprint.c argv.c argv_split.c attr_clnt.c attr_print0.c \ + attr_print64.c attr_print_plain.c attr_scan0.c attr_scan64.c \ + attr_scan_plain.c auto_clnt.c base64_code.c basename.c binhash.c \ + chroot_uid.c cidr_match.c clean_env.c close_on_exec.c concatenate.c \ + ctable.c dict.c dict_alloc.c dict_cdb.c dict_cidr.c dict_db.c \ + dict_dbm.c dict_debug.c dict_env.c dict_ht.c dict_lmdb.c dict_ni.c dict_nis.c \ + dict_nisplus.c dict_open.c dict_pcre.c dict_regexp.c dict_sdbm.c \ + dict_static.c dict_tcp.c dict_unix.c dir_forest.c doze.c dummy_read.c \ + dummy_write.c duplex_pipe.c environ.c events.c exec_command.c \ + fifo_listen.c fifo_trigger.c file_limit.c find_inet.c fsspace.c \ + fullname.c get_domainname.c get_hostname.c hex_code.c hex_quote.c \ + host_port.c htable.c inet_addr_host.c inet_addr_list.c \ + inet_addr_local.c inet_connect.c inet_listen.c inet_proto.c \ + inet_trigger.c line_wrap.c lowercase.c lstat_as.c mac_expand.c \ + mac_parse.c make_dirs.c mask_addr.c match_list.c match_ops.c msg.c \ + msg_output.c msg_syslog.c msg_vstream.c mvect.c myaddrinfo.c myflock.c \ + mymalloc.c myrand.c mystrtok.c name_code.c name_mask.c netstring.c \ + neuter.c non_blocking.c nvtable.c open_as.c open_limit.c open_lock.c \ + peekfd.c posix_signals.c printable.c rand_sleep.c \ + readlline.c ring.c safe_getenv.c safe_open.c \ + sane_accept.c sane_connect.c sane_link.c sane_rename.c \ + sane_socketpair.c sane_time.c scan_dir.c set_eugid.c set_ugid.c \ + load_lib.c \ + sigdelay.c skipblanks.c sock_addr.c spawn_command.c split_at.c \ + split_nameval.c stat_as.c strcasecmp.c stream_connect.c \ + stream_listen.c stream_recv_fd.c stream_send_fd.c stream_trigger.c \ + sys_compat.c timed_connect.c timed_read.c timed_wait.c timed_write.c \ + translit.c trimblanks.c unescape.c unix_connect.c unix_listen.c \ + unix_recv_fd.c unix_send_fd.c unix_trigger.c unsafe.c uppercase.c \ + username.c valid_hostname.c vbuf.c vbuf_print.c vstream.c \ + vstream_popen.c vstring.c vstring_vstream.c watchdog.c \ + write_buf.c sane_basename.c format_tv.c allspace.c \ + allascii.c load_file.c killme_after.c vstream_tweak.c \ + pass_trigger.c edit_file.c inet_windowsize.c \ + unix_pass_fd_fix.c dict_cache.c valid_utf8_string.c dict_thash.c \ + ip_match.c nbbio.c base32_code.c dict_test.c \ + dict_fail.c msg_rate_delay.c dict_surrogate.c warn_stat.c \ + dict_sockmap.c line_number.c recv_pass_attr.c pass_accept.c \ + poll_fd.c timecmp.c slmdb.c dict_pipe.c dict_random.c \ + valid_utf8_hostname.c midna_domain.c argv_splitq.c balpar.c dict_union.c \ + extpar.c dict_inline.c casefold.c dict_utf8.c strcasecmp_utf8.c \ + split_qnameval.c argv_attr_print.c argv_attr_scan.c dict_file.c \ + msg_logger.c logwriter.c unix_dgram_connect.c unix_dgram_listen.c \ + byte_mask.c known_tcp_ports.c argv_split_at.c dict_stream.c \ + sane_strtol.c hash_fnv.c ldseed.c +OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \ + attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \ + attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \ + chroot_uid.o cidr_match.o clean_env.o close_on_exec.o concatenate.o \ + ctable.o dict.o dict_alloc.o dict_cidr.o dict_db.o \ + dict_dbm.o dict_debug.o dict_env.o dict_ht.o dict_ni.o dict_nis.o \ + dict_nisplus.o dict_open.o dict_regexp.o \ + dict_static.o dict_tcp.o dict_unix.o dir_forest.o doze.o dummy_read.o \ + dummy_write.o duplex_pipe.o environ.o events.o exec_command.o \ + fifo_listen.o fifo_trigger.o file_limit.o find_inet.o fsspace.o \ + fullname.o get_domainname.o get_hostname.o hex_code.o hex_quote.o \ + host_port.o htable.o inet_addr_host.o inet_addr_list.o \ + inet_addr_local.o inet_connect.o inet_listen.o inet_proto.o \ + inet_trigger.o line_wrap.o lowercase.o lstat_as.o mac_expand.o \ + load_lib.o \ + mac_parse.o make_dirs.o mask_addr.o match_list.o match_ops.o msg.o \ + msg_output.o msg_syslog.o msg_vstream.o mvect.o myaddrinfo.o myflock.o \ + mymalloc.o myrand.o mystrtok.o name_code.o name_mask.o netstring.o \ + neuter.o non_blocking.o nvtable.o open_as.o open_limit.o open_lock.o \ + peekfd.o posix_signals.o printable.o rand_sleep.o \ + readlline.o ring.o safe_getenv.o safe_open.o \ + sane_accept.o sane_connect.o sane_link.o sane_rename.o \ + sane_socketpair.o sane_time.o scan_dir.o set_eugid.o set_ugid.o \ + sigdelay.o skipblanks.o sock_addr.o spawn_command.o split_at.o \ + split_nameval.o stat_as.o $(STRCASE) stream_connect.o \ + stream_listen.o stream_recv_fd.o stream_send_fd.o stream_trigger.o \ + sys_compat.o timed_connect.o timed_read.o timed_wait.o timed_write.o \ + translit.o trimblanks.o unescape.o unix_connect.o unix_listen.o \ + unix_recv_fd.o unix_send_fd.o unix_trigger.o unsafe.o uppercase.o \ + username.o valid_hostname.o vbuf.o vbuf_print.o vstream.o \ + vstream_popen.o vstring.o vstring_vstream.o watchdog.o \ + write_buf.o sane_basename.o format_tv.o allspace.o \ + allascii.o load_file.o killme_after.o vstream_tweak.o \ + pass_trigger.o edit_file.o inet_windowsize.o \ + unix_pass_fd_fix.o dict_cache.o valid_utf8_string.o dict_thash.o \ + ip_match.o nbbio.o base32_code.o dict_test.o \ + dict_fail.o msg_rate_delay.o dict_surrogate.o warn_stat.o \ + dict_sockmap.o line_number.o recv_pass_attr.o pass_accept.o \ + poll_fd.o timecmp.o $(NON_PLUGIN_MAP_OBJ) dict_pipe.o dict_random.o \ + valid_utf8_hostname.o midna_domain.o argv_splitq.o balpar.o dict_union.o \ + extpar.o dict_inline.o casefold.o dict_utf8.o strcasecmp_utf8.o \ + split_qnameval.o argv_attr_print.o argv_attr_scan.o dict_file.o \ + msg_logger.o logwriter.o unix_dgram_connect.o unix_dgram_listen.o \ + byte_mask.o known_tcp_ports.o argv_split_at.o dict_stream.o \ + sane_strtol.o hash_fnv.o ldseed.o +# MAP_OBJ is for maps that may be dynamically loaded with dynamicmaps.cf. +# When hard-linking these, makedefs sets NON_PLUGIN_MAP_OBJ=$(MAP_OBJ), +# otherwise it sets the PLUGIN_* macros. +MAP_OBJ = dict_pcre.o $(LIB_MAP_OBJ) +LIB_MAP_OBJ = dict_cdb.o dict_lmdb.o dict_sdbm.o slmdb.o +HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \ + chroot_uid.h cidr_match.h clean_env.h connect.h ctable.h dict.h \ + dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_env.h dict_ht.h \ + dict_lmdb.h dict_ni.h dict_nis.h dict_nisplus.h dict_pcre.h dict_regexp.h \ + dict_sdbm.h dict_static.h dict_tcp.h dict_unix.h dir_forest.h \ + events.h exec_command.h find_inet.h fsspace.h fullname.h \ + get_domainname.h get_hostname.h hex_code.h hex_quote.h host_port.h \ + htable.h inet_addr_host.h inet_addr_list.h inet_addr_local.h \ + inet_proto.h iostuff.h line_wrap.h listen.h lstat_as.h mac_expand.h \ + mac_parse.h make_dirs.h mask_addr.h match_list.h msg.h \ + msg_output.h msg_syslog.h msg_vstream.h mvect.h myaddrinfo.h myflock.h \ + mymalloc.h myrand.h name_code.h name_mask.h netstring.h nvtable.h \ + open_as.h open_lock.h posix_signals.h readlline.h ring.h \ + safe.h safe_open.h sane_accept.h sane_connect.h sane_fsops.h \ + load_lib.h \ + sane_socketpair.h sane_time.h scan_dir.h set_eugid.h set_ugid.h \ + sigdelay.h sock_addr.h spawn_command.h split_at.h stat_as.h \ + stringops.h sys_defs.h timed_connect.h timed_wait.h trigger.h \ + username.h valid_hostname.h vbuf.h vbuf_print.h vstream.h vstring.h \ + vstring_vstream.h watchdog.h format_tv.h load_file.h killme_after.h \ + edit_file.h dict_cache.h dict_thash.h ip_match.h nbbio.h base32_code.h \ + dict_fail.h warn_stat.h dict_sockmap.h line_number.h timecmp.h \ + slmdb.h compat_va_copy.h dict_pipe.h dict_random.h \ + valid_utf8_hostname.h midna_domain.h dict_union.h dict_inline.h \ + check_arg.h argv_attr.h msg_logger.h logwriter.h byte_mask.h \ + known_tcp_ports.h sane_strtol.h hash_fnv.h ldseed.h +TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \ + stream_test.c dup2_pass_on_exec.c +DEFS = -I. -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +FILES = Makefile $(SRCS) $(HDRS) +INCL = +LIB = lib$(LIB_PREFIX)util$(LIB_SUFFIX) +TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \ + fifo_rdonly_bug fifo_rdwr_bug fifo_trigger fsspace fullname \ + inet_addr_host inet_addr_local mac_parse make_dirs msg_syslog \ + mystrtok sigdelay translit valid_hostname vstream_popen \ + vstring vstring_vstream doze select_bug stream_test mac_expand \ + watchdog unescape hex_quote name_mask rand_sleep sane_time ctable \ + inet_addr_list attr_print64 attr_scan64 base64_code attr_print0 \ + attr_scan0 host_port attr_scan_plain attr_print_plain htable \ + unix_recv_fd unix_send_fd stream_recv_fd stream_send_fd hex_code \ + myaddrinfo myaddrinfo4 inet_proto sane_basename format_tv \ + valid_utf8_string ip_match base32_code msg_rate_delay netstring \ + vstream timecmp dict_cache midna_domain casefold strcasecmp_utf8 \ + vbuf_print split_qnameval vstream msg_logger byte_mask \ + known_tcp_ports dict_stream find_inet binhash +PLUGIN_MAP_SO = $(LIB_PREFIX)pcre$(LIB_SUFFIX) +HTABLE_FIX = NORANDOMIZE=1 +LIB_DIR = ../../lib +INC_DIR = ../../include + +.c.o:; $(CC) $(SHLIB_CFLAGS) $(CFLAGS) -c $*.c + +all: $(LIB) $(PLUGIN_MAP_SO_MAKE) $(PLUGIN_MAP_OBJ) + +$(OBJS) $(PLUGIN_MAP_OBJ): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +$(LIB): $(OBJS) + $(AR) $(ARFL) $(LIB) $? + $(RANLIB) $(LIB) + $(SHLIB_LD) $(SHLIB_RPATH) -o $(LIB) $(OBJS) $(SHLIB_SYSLIBS) + +$(LIB_DIR)/$(LIB): $(LIB) + cp $(LIB) $(LIB_DIR) + $(RANLIB) $(LIB_DIR)/$(LIB) + +plugin_map_so_make: $(PLUGIN_MAP_SO) + +$(LIB_PREFIX)pcre$(LIB_SUFFIX): dict_pcre.o + $(PLUGIN_LD) $(SHLIB_RPATH) -o $@ dict_pcre.o $(AUXLIBS_PCRE) + +update: $(LIB_DIR)/$(LIB) $(HDRS) $(PLUGIN_MAP_SO_UPDATE) \ + $(PLUGIN_MAP_OBJ_UPDATE) + -for i in $(HDRS); \ + do \ + cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \ + done + cd $(INC_DIR); chmod 644 $(HDRS) + +plugin_map_so_update: $(PLUGIN_MAP_SO) + -for i in $(PLUGIN_MAP_SO); \ + do \ + for type in $(DEFINED_MAP_TYPES); do \ + case $$i in $(LIB_PREFIX)$$type$(LIB_SUFFIX)) \ + cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \ + continue 2;; \ + esac; \ + done; \ + rm -f $(LIB_DIR)/$$i; \ + done + +plugin_map_obj_update: $(LIB_MAP_OBJ) + -for i in $(LIB_MAP_OBJ); \ + do \ + cmp -s $$i $(LIB_DIR)/$$i 2>/dev/null || cp $$i $(LIB_DIR); \ + done + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +shar: + @shar $(FILES) + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o $(LIB) *core $(TESTPROG) junk $(MAKES) *.tmp + rm -rf printfck + +tidy: clean + +dup2_pass_on_exec: dup2_pass_on_exec.c + $(CC) $(CFLAGS) -o $@ $@.c $(SYSLIBS) + +vstring: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +msg_logger: msg_logger.c $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +msg_syslog: msg_syslog.c $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +vstring_vstream: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +valid_hostname: valid_hostname.c $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +events: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +dict_open: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +fullname: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +inet_addr_local: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +inet_addr_host: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +fifo_open: fifo_open.c + $(CC) $(CFLAGS) -o $@ $@.c $(SYSLIBS) + +sigdelay: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +mystrtok: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +fifo_rdwr_bug: fifo_rdwr_bug.c $(LIB) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) + +fifo_rdonly_bug: fifo_rdonly_bug.c $(LIB) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) + +select_bug: select_bug.c $(LIB) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) + +translit: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +fsspace: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +exec_command: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +make_dirs: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +mac_parse: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +vstream_popen: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +fifo_trigger: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +doze: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +mac_expand: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +watchdog: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +unescape: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +hex_quote: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +name_mask: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +byte_mask: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +rand_sleep: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +sane_time: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +ctable: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +inet_addr_list: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_print64: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_scan64: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +base64_code: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_print0: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_scan0: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +host_port: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_scan_plain: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +attr_print_plain: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +htable: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +binhash: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +unix_recv_fd: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +unix_send_fd: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +stream_recv_fd: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +stream_send_fd: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +hex_code: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +myaddrinfo: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +myaddrinfo4: $(LIB) + mv myaddrinfo.o junk + $(CC) $(CFLAGS) -DTEST -DEMULATE_IPV4_ADDRINFO -o $@ myaddrinfo.c $(LIB) $(SYSLIBS) + mv junk myaddrinfo.o + +inet_proto: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +sane_basename: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +find_inet: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +stream_test: stream_test.c $(LIB) + $(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS) + +gcctest: gccw.c gccw.ref + rm -f gccw.o + make gccw.o 2>&1 | sed "s/\`/'/g; s/return-/return /" | sort >gccw.tmp + diff gccw.ref gccw.tmp + rm -f gccw.o gccw.tmp + +format_tv: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +valid_utf8_string: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +ip_match: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +base32_code: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +msg_rate_delay: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +netstring: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +vstream: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +timecmp: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +dict_cache: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +midna_domain: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +casefold: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +strcasecmp_utf8: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +vbuf_print: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +split_qnameval: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +known_tcp_ports: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +dict_stream: $(LIB) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS) + mv junk $@.o + +tests: all valid_hostname_test mac_expand_test dict_test unescape_test \ + hex_quote_test ctable_test inet_addr_list_test base64_code_test \ + attr_scan64_test attr_scan0_test dict_pcre_tests host_port_test \ + dict_cidr_test attr_scan_plain_test htable_test hex_code_test \ + myaddrinfo_test format_tv_test ip_match_test name_mask_tests \ + base32_code_test dict_thash_test surrogate_test timecmp_test \ + dict_static_test dict_inline_test midna_domain_test casefold_test \ + dict_utf8_test strcasecmp_utf8_test vbuf_print_test dict_regexp_test \ + dict_union_test dict_pipe_test miss_endif_cidr_test \ + miss_endif_regexp_test split_qnameval_test vstring_test \ + vstream_test dict_regexp_file_test dict_cidr_file_test \ + dict_static_file_test dict_random_test dict_random_file_test \ + dict_inline_file_test byte_mask_tests mystrtok_test \ + known_tcp_ports_test dict_stream_test dict_inline_regexp_test \ + dict_inline_cidr_test binhash_test + +dict_pcre_tests: dict_pcre_test miss_endif_pcre_test dict_pcre_file_test \ + dict_inline_pcre_test + +root_tests: + +valid_hostname_test: valid_hostname valid_hostname.in valid_hostname.ref + $(SHLIB_ENV) ${VALGRIND} ./valid_hostname valid_hostname.tmp + diff valid_hostname.ref valid_hostname.tmp + rm -f valid_hostname.tmp + +mac_expand_test: mac_expand mac_expand.in mac_expand.ref + $(SHLIB_ENV) ${VALGRIND} ./mac_expand mac_expand.tmp 2>&1 + diff mac_expand.ref mac_expand.tmp + rm -f mac_expand.tmp + +unescape_test: unescape unescape.in unescape.ref + $(SHLIB_ENV) ${VALGRIND} ./unescape unescape.tmp + diff -b unescape.ref unescape.tmp +# $(SHLIB_ENV) ${VALGRIND} ./unescape unescape.tmp +# diff unescape.in unescape.tmp + rm -f unescape.tmp + +hex_quote_test: hex_quote + $(SHLIB_ENV) ${VALGRIND} ./hex_quote hex_quote.tmp + od -cb hex_quote.ref + cmp hex_quote.ref hex_quote.tmp + rm -f hex_quote.ref hex_quote.tmp + +ctable_test: ctable + $(SHLIB_ENV) ${VALGRIND} ./ctable ctable.tmp 2>&1 + diff ctable.ref ctable.tmp + rm -f ctable.tmp + +# On Linux, following test may require "modprobe ipv6" to enable IPv6. + +inet_addr_list_test: inet_addr_list + $(SHLIB_ENV) ${VALGRIND} ./inet_addr_list `cat inet_addr_list.in` >inet_addr_list.tmp 2>&1 + diff inet_addr_list.ref inet_addr_list.tmp + rm -f inet_addr_list.tmp + +sane_basename_test: sane_basename + $(SHLIB_ENV) ${VALGRIND} ./sane_basename sane_basename.tmp 2>&1 + diff sane_basename.ref sane_basename.tmp + rm -f sane_basename.tmp + +base64_code_test: base64_code + $(SHLIB_ENV) ${VALGRIND} ./base64_code + +attr_scan64_test: attr_print64 attr_scan64 attr_scan64.ref + ($(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./attr_print64 2>&3 | (sleep 1; $(HTABLE_FIX) $(SHLIB_ENV) ./attr_scan64)) >attr_scan64.tmp 2>&1 3>&1 + diff attr_scan64.ref attr_scan64.tmp + rm -f attr_scan64.tmp + +attr_scan0_test: attr_print0 attr_scan0 attr_scan0.ref + ($(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./attr_print0 2>&3 | (sleep 1; $(HTABLE_FIX) $(SHLIB_ENV) ./attr_scan0)) >attr_scan0.tmp 2>&1 3>&1 + diff attr_scan0.ref attr_scan0.tmp + rm -f attr_scan0.tmp + +dict_test: dict_open testdb dict_test.in dict_test.ref + rm -f testdb.db testdb.dir testdb.pag + $(SHLIB_ENV) ../postmap/postmap -N hash:testdb + $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp + diff dict_test.ref dict_test.tmp + $(SHLIB_ENV) ../postmap/postmap -n hash:testdb + $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb write < dict_test.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_test.tmp + diff dict_test.ref dict_test.tmp + rm -f testdb.db testdb.dir testdb.pag dict_test.tmp + +dict_pcre_test: dict_open dict_pcre.in dict_pcre.map dict_pcre.ref + $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:dict_pcre.map read \ + &1 | sed -e 's/uid=[0-9][0-9][0-9]*/uid=USER/' \ + -e 's/missing )/missing closing parenthesis/' >dict_pcre.tmp + diff dict_pcre.ref dict_pcre.tmp + rm -f dict_pcre.tmp + +dict_pcre_file_test: dict_open dict_pcre_file.in dict_pcre_file.map dict_pcre_file.ref + echo this-is-file1 > dict_pcre_file1 + echo this-is-file2 > dict_pcre_file2 + $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:dict_pcre_file.map \ + read src_rhs_is_file &1 | \ + sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_pcre_file.tmp + diff dict_pcre_file.ref dict_pcre_file.tmp + rm -f dict_pcre_file.tmp dict_pcre_file1 dict_pcre_file2 + +dict_regexp_test: dict_open dict_regexp.in dict_regexp.map dict_regexp.ref + $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:dict_regexp.map read &1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp.tmp + diff dict_regexp.ref dict_regexp.tmp + rm -f dict_regexp.tmp + +dict_regexp_file_test: dict_open dict_regexp_file.in dict_regexp_file.map dict_regexp_file.ref + echo this-is-file1 > dict_regexp_file1 + echo this-is-file2 > dict_regexp_file2 + $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:dict_regexp_file.map \ + read src_rhs_is_file &1 | \ + sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp_file.tmp + diff dict_regexp_file.ref dict_regexp_file.tmp + rm -f dict_regexp_file.tmp dict_regexp_file1 dict_regexp_file2 + +dict_cidr_test: dict_open dict_cidr.in dict_cidr.map dict_cidr.ref + $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:dict_cidr.map read &1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr.tmp + diff dict_cidr.ref dict_cidr.tmp + rm -f dict_cidr.tmp + +dict_cidr_file_test: dict_open dict_cidr_file.in dict_cidr_file.map dict_cidr_file.ref + echo this-is-file1 > dict_cidr_file1 + echo this-is-file2 > dict_cidr_file2 + $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:dict_cidr_file.map \ + read src_rhs_is_file &1 | \ + sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr_file.tmp + diff dict_cidr_file.ref dict_cidr_file.tmp + rm -f dict_cidr_file.tmp dict_cidr_file1 dict_cidr_file2 + +miss_endif_cidr_test: dict_open miss_endif_cidr.map miss_endif_cidr.ref + echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:miss_endif_cidr.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_cidr.tmp + diff miss_endif_cidr.ref dict_cidr.tmp + rm -f dict_cidr.tmp + +miss_endif_pcre_test: dict_open miss_endif_re.map miss_endif_pcre.ref + echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:miss_endif_re.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_pcre.tmp + diff miss_endif_pcre.ref dict_pcre.tmp + rm -f dict_pcre.tmp + +miss_endif_regexp_test: dict_open miss_endif_re.map miss_endif_regexp.ref + echo get 1.2.3.5 | $(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:miss_endif_re.map read 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' >dict_regexp.tmp + diff miss_endif_regexp.ref dict_regexp.tmp + rm -f dict_regexp.tmp + +split_qnameval_test: split_qnameval update + $(SHLIB_ENV) ${VALGRIND} ./split_qnameval + +dict_seq_test: dict_open testdb dict_seq.in dict_seq.ref + rm -f testdb.db testdb.dir testdb.pag + $(SHLIB_ENV) ${VALGRIND} ./dict_open hash:testdb create sync < dict_seq.in 2>&1 | sed 's/uid=[0-9][0-9][0-9]*/uid=USER/' > dict_seq.tmp + diff dict_seq.ref dict_seq.tmp + rm -f testdb.db testdb.dir testdb.pag dict_seq.tmp + +host_port_test: host_port host_port.in host_port.ref + $(SHLIB_ENV) ${VALGRIND} ./host_port host_port.tmp 2>&1 + diff host_port.ref host_port.tmp + rm -f host_port.tmp + +attr_scan_plain_test: attr_print_plain attr_scan_plain attr_scan_plain.ref + ($(HTABLE_FIX) $(SHLIB_ENV) ${VALGRIND} ./attr_print_plain 2>&3 | (sleep 1; $(HTABLE_FIX) $(SHLIB_ENV) ./attr_scan_plain)) >attr_scan_plain.tmp 2>&1 3>&1 + diff attr_scan_plain.ref attr_scan_plain.tmp + rm -f attr_scan_plain.tmp + +htable_test: htable /usr/share/dict/words + $(SHLIB_ENV) ${VALGRIND} ./htable < /usr/share/dict/words + +binhash_test: binhash /usr/share/dict/words + $(SHLIB_ENV) ${VALGRIND} ./binhash < /usr/share/dict/words + +hex_code_test: hex_code + $(SHLIB_ENV) ${VALGRIND} ./hex_code + +timecmp_test: timecmp + $(SHLIB_ENV) ${VALGRIND} ./timecmp + +myaddrinfo_test: myaddrinfo myaddrinfo.ref myaddrinfo.ref2 + $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all belly.porcupine.org 168.100.3.2 >myaddrinfo.tmp 2>&1 + diff myaddrinfo.ref myaddrinfo.tmp + rm -f myaddrinfo.tmp + $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo all null.porcupine.org 10.0.0.0 >myaddrinfo.tmp 2>&1 + diff myaddrinfo.ref2 myaddrinfo.tmp + rm -f myaddrinfo.tmp + +myaddrinfo4_test: myaddrinfo4 myaddrinfo4.ref myaddrinfo4.ref2 + $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all belly.porcupine.org 168.100.3.2 >myaddrinfo4.tmp 2>&1 + diff myaddrinfo4.ref myaddrinfo4.tmp + $(SHLIB_ENV) ${VALGRIND} ./myaddrinfo4 all null.porcupine.org 10.0.0.0 >myaddrinfo4.tmp 2>&1 + diff myaddrinfo4.ref2 myaddrinfo4.tmp + rm -f myaddrinfo4.tmp + +format_tv_test: format_tv format_tv.in format_tv.ref + $(SHLIB_ENV) ${VALGRIND} ./format_tv format_tv.tmp + diff format_tv.ref format_tv.tmp + rm -f format_tv.tmp + +ip_match_test: ip_match ip_match.in ip_match.ref + $(SHLIB_ENV) ${VALGRIND} ./ip_match ip_match.tmp + diff ip_match.ref ip_match.tmp + rm -f ip_match.tmp + +name_mask_tests: name_mask_test0 name_mask_test1 name_mask_test2 \ + name_mask_test3 name_mask_test4 name_mask_test5 name_mask_test6 \ + name_mask_test7 name_mask_test8 name_mask_test9 + +name_mask_test0: name_mask name_mask.in name_mask.ref0 + $(SHLIB_ENV) ${VALGRIND} ./name_mask IGNORE IGNORE < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref0 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test1: name_mask name_mask.in name_mask.ref1 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref1 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test2: name_mask name_mask.in name_mask.ref2 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,RETURN NUMBER < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref2 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test3: name_mask name_mask.in name_mask.ref3 + $(SHLIB_ENV) ${VALGRIND} ./name_mask WARN NUMBER < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref3 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test4: name_mask name_mask.in name_mask.ref4 + $(SHLIB_ENV) ${VALGRIND} ./name_mask RETURN NUMBER < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref4 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test5: name_mask name_mask.in name_mask.ref5 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN RETURN < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref5 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test6: name_mask name_mask.in name_mask.ref6 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN WARN < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref6 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test7: name_mask name_mask.in name_mask.ref7 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN IGNORE < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref7 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test8: name_mask name_mask.in name_mask.ref8 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER,COMMA < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref8 name_mask.tmp + rm -f name_mask.tmp + +name_mask_test9: name_mask name_mask.in name_mask.ref9 + $(SHLIB_ENV) ${VALGRIND} ./name_mask NUMBER,WARN NUMBER,PIPE < name_mask.in > name_mask.tmp 2>&1 + diff name_mask.ref9 name_mask.tmp + rm -f name_mask.tmp + +byte_mask_tests: byte_mask_test0 byte_mask_test1 byte_mask_test2 + +byte_mask_test0: byte_mask byte_mask.in byte_mask.ref0 + while read line; do \ + echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask IGNORE IGNORE; \ + done < byte_mask.in > byte_mask.tmp 2>&1 + diff byte_mask.ref0 byte_mask.tmp + rm -f byte_mask.tmp + +byte_mask_test1: byte_mask byte_mask.in byte_mask.ref1 + while read line; do \ + echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask WARN WARN; \ + done < byte_mask.in > byte_mask.tmp 2>&1 + diff byte_mask.ref1 byte_mask.tmp + rm -f byte_mask.tmp + +byte_mask_test2: byte_mask byte_mask.in byte_mask.ref2 + -while read line; do \ + echo "$$line" | $(SHLIB_ENV) ${VALGRIND} ./byte_mask FATAL FATAL; \ + done < byte_mask.in > byte_mask.tmp 2>&1 + diff byte_mask.ref2 byte_mask.tmp + rm -f byte_mask.tmp + +base32_code_test: base32_code + $(SHLIB_ENV) ${VALGRIND} ./base32_code + +dict_thash_test: ../postmap/postmap dict_thash.map dict_thash.in dict_thash.ref + $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.map 2>&1 | \ + LANG=C sort | diff dict_thash.map - + NORANDOMIZE=1 $(SHLIB_ENV) ${VALGRIND} ../postmap/postmap -fs texthash:dict_thash.in >dict_thash.tmp 2>&1 + diff dict_thash.ref dict_thash.tmp + rm -f dict_thash.tmp + +surrogate_test: dict_open surrogate.ref + cp /dev/null surrogate.tmp + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:/xx write >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open cidr:/xx read >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:/xx write >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open pcre:/xx read >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:/xx write >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open regexp:/xx read >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open unix:xx write >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open unix:xx read >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open texthash:/xx write >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open texthash:/xx read >>surrogate.tmp 2>&1 + echo get foo|$(SHLIB_ENV) ${VALGRIND} ./dict_open hash:/xx read >>surrogate.tmp 2>&1 + diff surrogate.ref surrogate.tmp + rm -f surrogate.tmp + +dict_static_test: dict_open dict_static.ref + (set -e; \ + (echo get foo; echo get bar) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open static:fooxx read; \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open static:'{ foo xx ' read dict_static.tmp 2>&1 + diff dict_static.ref dict_static.tmp + rm -f dict_static.tmp + +dict_static_file_test: dict_open dict_static_file.ref + echo this-is-file1 > dict_static_file1 + (set -e; \ + echo get foo | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open static:fooxx read src_rhs_is_file; \ + (echo get file1; echo get file2) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open static:'{ dict_static_file1 }' read src_rhs_is_file; \ + ) >dict_static_file.tmp 2>&1 + diff dict_static_file.ref dict_static_file.tmp + rm -f dict_static_file.tmp dict_static_file1 + +dict_random_test: dict_open dict_random.ref + (set -e; \ + (echo get foo; echo get bar) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open randmap:'{123 123}' read; \ + echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:123 read; \ + echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:'{ 123 ' read; \ + echo get foo | $(SHLIB_ENV) ${VALGRIND} ./dict_open randmap:'{ 123 }x' read; \ + ) >dict_random.tmp 2>&1 + diff dict_random.ref dict_random.tmp + rm -f dict_random.tmp + +dict_random_file_test: dict_open dict_random_file.ref + echo this-is-file1 > dict_random_file1 + (set -e; \ + echo get foo | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open randmap:'{fooxx}' read src_rhs_is_file; \ + (echo get foo; echo get bar) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open randmap:'{ dict_random_file1 }' read src_rhs_is_file; \ + ) >dict_random_file.tmp 2>&1 + diff dict_random_file.ref dict_random_file.tmp + rm -f dict_random_file.tmp dict_random_file1 + +dict_inline_test: dict_open dict_inline.ref + (set -e; \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open inline:'{ }' read dict_inline.tmp 2>&1 + diff dict_inline.ref dict_inline.tmp + rm -f dict_inline.tmp + +dict_inline_file_test: dict_open dict_inline_file.ref + (set -e; \ + echo get foo | $(SHLIB_ENV) ${VALGRIND} \ + ./dict_open inline:'{ foo=xx, bar=yy }' read src_rhs_is_file; \ + echo this-is-file1 > dict_inline_file1; \ + echo this-is-file2 > dict_inline_file2; \ + (echo get file1; echo get file2; echo get file3) | $(SHLIB_ENV) \ + ${VALGRIND} ./dict_open inline:'{ file1=dict_inline_file1, file2=dict_inline_file2}' read src_rhs_is_file; \ + ) >dict_inline_file.tmp 2>&1 + diff dict_inline_file.ref dict_inline_file.tmp + rm -f dict_inline_file.tmp dict_inline_file1 dict_inline_file2 + +midna_domain_test: midna_domain midna_domain_test.in midna_domain_test.ref + $(SHLIB_ENV) ${VALGRIND} ./midna_domain midna_domain_test.tmp 2>&1 + diff midna_domain_test.ref midna_domain_test.tmp + rm -f midna_domain_test.tmp + +casefold_test: casefold casefold_test.in casefold_test.ref + $(SHLIB_ENV) ${VALGRIND} ./casefold casefold_test.tmp 2>&1 + diff casefold_test.ref casefold_test.tmp + rm -f casefold_test.tmp + +dict_utf8_test: dict_open dict_utf8_test.in dict_utf8_test.ref + $(SHLIB_ENV) sh dict_utf8_test.in >dict_utf8_test.tmp 2>&1 + diff dict_utf8_test.ref dict_utf8_test.tmp + rm -f dict_utf8_test.tmp + +strcasecmp_utf8_test: strcasecmp_utf8 strcasecmp_utf8_test.in \ + strcasecmp_utf8_test.ref + $(SHLIB_ENV) ${VALGRIND} ./strcasecmp_utf8 strcasecmp_utf8_test.tmp 2>&1 + diff strcasecmp_utf8_test.ref strcasecmp_utf8_test.tmp + rm -f strcasecmp_utf8_test.tmp + +vbuf_print_test: vbuf_print vbuf_print_test.in vbuf_print_test.ref + $(SHLIB_ENV) ${VALGRIND} ./vbuf_print vbuf_print_test.tmp 2>&1 + diff vbuf_print_test.ref vbuf_print_test.tmp + rm -f vbuf_print_test.tmp + +dict_union_test: dict_open dict_union_test.in dict_union_test.ref + $(SHLIB_ENV) ${VALGRIND} sh -x dict_union_test.in >dict_union_test.tmp 2>&1 + diff dict_union_test.ref dict_union_test.tmp + rm -f dict_union_test.tmp + +dict_pipe_test: dict_open dict_pipe_test.in dict_pipe_test.ref + $(SHLIB_ENV) ${VALGRIND} sh -x dict_pipe_test.in >dict_pipe_test.tmp 2>&1 + diff dict_pipe_test.ref dict_pipe_test.tmp + rm -f dict_pipe_test.tmp + +vstring_test: dict_open vstring vstring_test.ref + $(SHLIB_ENV) ${VALGRIND} ./vstring one two three >vstring_test.tmp 2>&1 + diff vstring_test.ref vstring_test.tmp + rm -f vstring_test.tmp + +vstream_test: vstream vstream_test.in vstream_test.ref + $(SHLIB_ENV) ${VALGRIND} ./vstream vstream_test.tmp 2>&1 + diff vstream_test.ref vstream_test.tmp + rm -f vstream_test.tmp + +mystrtok_test: mystrtok mystrtok.ref + $(SHLIB_ENV) ${VALGRIND} ./mystrtok >mystrtok.tmp 2>&1 + diff mystrtok.ref mystrtok.tmp + rm -f mystrtok.tmp + +known_tcp_ports_test: known_tcp_ports known_tcp_ports.ref + $(SHLIB_ENV) ${VALGRIND} ./known_tcp_ports >known_tcp_ports.tmp 2>&1 + diff known_tcp_ports.ref known_tcp_ports.tmp + rm -f known_tcp_ports.tmp + +dict_stream_test: dict_stream dict_stream.ref + $(SHLIB_ENV) ${VALGRIND} ./dict_stream >dict_stream.tmp 2>&1 + diff dict_stream.ref dict_stream.tmp + rm -f dict_stream.tmp + +dict_inline_pcre_test: dict_open dict_inline_pcre.ref + (echo get foo; echo get bar) | \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open 'pcre:{{/foo/ got foo}}' \ + read 2>&1 | grep -v uid= >dict_inline_pcre.tmp + diff dict_inline_pcre.ref dict_inline_pcre.tmp + rm -f dict_inline_pcre.tmp + +dict_inline_regexp_test: dict_open dict_inline_regexp.ref + (echo get foo; echo get bar) | \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open 'regexp:{{/foo/ got foo}}' \ + read 2>&1 | grep -v uid= >dict_inline_regexp.tmp + diff dict_inline_regexp.ref dict_inline_regexp.tmp + rm -f dict_inline_regexp.tmp + +dict_inline_cidr_test: dict_open dict_inline_cidr.ref + (echo get 1.2.3.4; echo get 4.3.2.1) | \ + $(SHLIB_ENV) ${VALGRIND} ./dict_open \ + 'cidr:{{1.2.3.4 got 1.2.3.4}}' \ + read 2>&1 | grep -v uid= >dict_inline_cidr.tmp + diff dict_inline_cidr.ref dict_inline_cidr.tmp + rm -f dict_inline_cidr.tmp + +find_inet_test: find_inet find_inet.ref + $(SHLIB_ENV) ${VALGRIND} ./find_inet >find_inet.tmp 2>&1 + diff find_inet.ref find_inet.tmp + rm -f find_inet.tmp + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +allascii.o: allascii.c +allascii.o: check_arg.h +allascii.o: stringops.h +allascii.o: sys_defs.h +allascii.o: vbuf.h +allascii.o: vstring.h +alldig.o: alldig.c +alldig.o: check_arg.h +alldig.o: stringops.h +alldig.o: sys_defs.h +alldig.o: vbuf.h +alldig.o: vstring.h +allprint.o: allprint.c +allprint.o: check_arg.h +allprint.o: stringops.h +allprint.o: sys_defs.h +allprint.o: vbuf.h +allprint.o: vstring.h +allspace.o: allspace.c +allspace.o: check_arg.h +allspace.o: stringops.h +allspace.o: sys_defs.h +allspace.o: vbuf.h +allspace.o: vstring.h +argv.o: argv.c +argv.o: argv.h +argv.o: msg.h +argv.o: mymalloc.h +argv.o: sys_defs.h +argv_attr_print.o: argv.h +argv_attr_print.o: argv_attr.h +argv_attr_print.o: argv_attr_print.c +argv_attr_print.o: attr.h +argv_attr_print.o: check_arg.h +argv_attr_print.o: htable.h +argv_attr_print.o: msg.h +argv_attr_print.o: mymalloc.h +argv_attr_print.o: nvtable.h +argv_attr_print.o: sys_defs.h +argv_attr_print.o: vbuf.h +argv_attr_print.o: vstream.h +argv_attr_print.o: vstring.h +argv_attr_scan.o: argv.h +argv_attr_scan.o: argv_attr.h +argv_attr_scan.o: argv_attr_scan.c +argv_attr_scan.o: attr.h +argv_attr_scan.o: check_arg.h +argv_attr_scan.o: htable.h +argv_attr_scan.o: msg.h +argv_attr_scan.o: mymalloc.h +argv_attr_scan.o: nvtable.h +argv_attr_scan.o: sys_defs.h +argv_attr_scan.o: vbuf.h +argv_attr_scan.o: vstream.h +argv_attr_scan.o: vstring.h +argv_split.o: argv.h +argv_split.o: argv_split.c +argv_split.o: check_arg.h +argv_split.o: msg.h +argv_split.o: mymalloc.h +argv_split.o: stringops.h +argv_split.o: sys_defs.h +argv_split.o: vbuf.h +argv_split.o: vstring.h +argv_split_at.o: argv.h +argv_split_at.o: argv_split_at.c +argv_split_at.o: check_arg.h +argv_split_at.o: msg.h +argv_split_at.o: mymalloc.h +argv_split_at.o: split_at.h +argv_split_at.o: stringops.h +argv_split_at.o: sys_defs.h +argv_split_at.o: vbuf.h +argv_split_at.o: vstring.h +argv_splitq.o: argv.h +argv_splitq.o: argv_splitq.c +argv_splitq.o: check_arg.h +argv_splitq.o: msg.h +argv_splitq.o: mymalloc.h +argv_splitq.o: stringops.h +argv_splitq.o: sys_defs.h +argv_splitq.o: vbuf.h +argv_splitq.o: vstring.h +attr_clnt.o: attr.h +attr_clnt.o: attr_clnt.c +attr_clnt.o: attr_clnt.h +attr_clnt.o: auto_clnt.h +attr_clnt.o: check_arg.h +attr_clnt.o: compat_va_copy.h +attr_clnt.o: htable.h +attr_clnt.o: iostuff.h +attr_clnt.o: msg.h +attr_clnt.o: mymalloc.h +attr_clnt.o: nvtable.h +attr_clnt.o: sys_defs.h +attr_clnt.o: vbuf.h +attr_clnt.o: vstream.h +attr_clnt.o: vstring.h +attr_print0.o: attr.h +attr_print0.o: attr_print0.c +attr_print0.o: base64_code.h +attr_print0.o: check_arg.h +attr_print0.o: htable.h +attr_print0.o: msg.h +attr_print0.o: mymalloc.h +attr_print0.o: nvtable.h +attr_print0.o: sys_defs.h +attr_print0.o: vbuf.h +attr_print0.o: vstream.h +attr_print0.o: vstring.h +attr_print64.o: attr.h +attr_print64.o: attr_print64.c +attr_print64.o: base64_code.h +attr_print64.o: check_arg.h +attr_print64.o: htable.h +attr_print64.o: msg.h +attr_print64.o: mymalloc.h +attr_print64.o: nvtable.h +attr_print64.o: sys_defs.h +attr_print64.o: vbuf.h +attr_print64.o: vstream.h +attr_print64.o: vstring.h +attr_print_plain.o: attr.h +attr_print_plain.o: attr_print_plain.c +attr_print_plain.o: base64_code.h +attr_print_plain.o: check_arg.h +attr_print_plain.o: htable.h +attr_print_plain.o: msg.h +attr_print_plain.o: mymalloc.h +attr_print_plain.o: nvtable.h +attr_print_plain.o: sys_defs.h +attr_print_plain.o: vbuf.h +attr_print_plain.o: vstream.h +attr_print_plain.o: vstring.h +attr_scan0.o: attr.h +attr_scan0.o: attr_scan0.c +attr_scan0.o: base64_code.h +attr_scan0.o: check_arg.h +attr_scan0.o: htable.h +attr_scan0.o: msg.h +attr_scan0.o: mymalloc.h +attr_scan0.o: nvtable.h +attr_scan0.o: stringops.h +attr_scan0.o: sys_defs.h +attr_scan0.o: vbuf.h +attr_scan0.o: vstream.h +attr_scan0.o: vstring.h +attr_scan0.o: vstring_vstream.h +attr_scan64.o: attr.h +attr_scan64.o: attr_scan64.c +attr_scan64.o: base64_code.h +attr_scan64.o: check_arg.h +attr_scan64.o: htable.h +attr_scan64.o: msg.h +attr_scan64.o: mymalloc.h +attr_scan64.o: nvtable.h +attr_scan64.o: stringops.h +attr_scan64.o: sys_defs.h +attr_scan64.o: vbuf.h +attr_scan64.o: vstream.h +attr_scan64.o: vstring.h +attr_scan_plain.o: attr.h +attr_scan_plain.o: attr_scan_plain.c +attr_scan_plain.o: base64_code.h +attr_scan_plain.o: check_arg.h +attr_scan_plain.o: htable.h +attr_scan_plain.o: msg.h +attr_scan_plain.o: mymalloc.h +attr_scan_plain.o: nvtable.h +attr_scan_plain.o: stringops.h +attr_scan_plain.o: sys_defs.h +attr_scan_plain.o: vbuf.h +attr_scan_plain.o: vstream.h +attr_scan_plain.o: vstring.h +auto_clnt.o: auto_clnt.c +auto_clnt.o: auto_clnt.h +auto_clnt.o: check_arg.h +auto_clnt.o: connect.h +auto_clnt.o: events.h +auto_clnt.o: iostuff.h +auto_clnt.o: msg.h +auto_clnt.o: mymalloc.h +auto_clnt.o: split_at.h +auto_clnt.o: sys_defs.h +auto_clnt.o: vbuf.h +auto_clnt.o: vstream.h +balpar.o: balpar.c +balpar.o: check_arg.h +balpar.o: stringops.h +balpar.o: sys_defs.h +balpar.o: vbuf.h +balpar.o: vstring.h +base32_code.o: base32_code.c +base32_code.o: base32_code.h +base32_code.o: check_arg.h +base32_code.o: msg.h +base32_code.o: mymalloc.h +base32_code.o: sys_defs.h +base32_code.o: vbuf.h +base32_code.o: vstring.h +base64_code.o: base64_code.c +base64_code.o: base64_code.h +base64_code.o: check_arg.h +base64_code.o: msg.h +base64_code.o: mymalloc.h +base64_code.o: sys_defs.h +base64_code.o: vbuf.h +base64_code.o: vstring.h +basename.o: basename.c +basename.o: check_arg.h +basename.o: stringops.h +basename.o: sys_defs.h +basename.o: vbuf.h +basename.o: vstring.h +binhash.o: binhash.c +binhash.o: binhash.h +binhash.o: hash_fnv.h +binhash.o: msg.h +binhash.o: mymalloc.h +binhash.o: sys_defs.h +byte_mask.o: byte_mask.c +byte_mask.o: byte_mask.h +byte_mask.o: check_arg.h +byte_mask.o: msg.h +byte_mask.o: mymalloc.h +byte_mask.o: stringops.h +byte_mask.o: sys_defs.h +byte_mask.o: vbuf.h +byte_mask.o: vstring.h +casefold.o: casefold.c +casefold.o: check_arg.h +casefold.o: msg.h +casefold.o: stringops.h +casefold.o: sys_defs.h +casefold.o: vbuf.h +casefold.o: vstring.h +chroot_uid.o: chroot_uid.c +chroot_uid.o: chroot_uid.h +chroot_uid.o: msg.h +chroot_uid.o: sys_defs.h +cidr_match.o: check_arg.h +cidr_match.o: cidr_match.c +cidr_match.o: cidr_match.h +cidr_match.o: mask_addr.h +cidr_match.o: msg.h +cidr_match.o: myaddrinfo.h +cidr_match.o: split_at.h +cidr_match.o: stringops.h +cidr_match.o: sys_defs.h +cidr_match.o: vbuf.h +cidr_match.o: vstring.h +clean_env.o: argv.h +clean_env.o: clean_env.c +clean_env.o: clean_env.h +clean_env.o: msg.h +clean_env.o: safe.h +clean_env.o: sys_defs.h +close_on_exec.o: close_on_exec.c +close_on_exec.o: iostuff.h +close_on_exec.o: msg.h +close_on_exec.o: sys_defs.h +concatenate.o: check_arg.h +concatenate.o: compat_va_copy.h +concatenate.o: concatenate.c +concatenate.o: mymalloc.h +concatenate.o: stringops.h +concatenate.o: sys_defs.h +concatenate.o: vbuf.h +concatenate.o: vstring.h +ctable.o: ctable.c +ctable.o: ctable.h +ctable.o: htable.h +ctable.o: msg.h +ctable.o: mymalloc.h +ctable.o: ring.h +ctable.o: sys_defs.h +dict.o: argv.h +dict.o: check_arg.h +dict.o: dict.c +dict.o: dict.h +dict.o: dict_ht.h +dict.o: htable.h +dict.o: iostuff.h +dict.o: line_number.h +dict.o: mac_expand.h +dict.o: mac_parse.h +dict.o: msg.h +dict.o: myflock.h +dict.o: mymalloc.h +dict.o: name_mask.h +dict.o: readlline.h +dict.o: stringops.h +dict.o: sys_defs.h +dict.o: vbuf.h +dict.o: vstream.h +dict.o: vstring.h +dict.o: warn_stat.h +dict_alloc.o: argv.h +dict_alloc.o: check_arg.h +dict_alloc.o: dict.h +dict_alloc.o: dict_alloc.c +dict_alloc.o: msg.h +dict_alloc.o: myflock.h +dict_alloc.o: mymalloc.h +dict_alloc.o: sys_defs.h +dict_alloc.o: vbuf.h +dict_alloc.o: vstream.h +dict_alloc.o: vstring.h +dict_cache.o: argv.h +dict_cache.o: check_arg.h +dict_cache.o: dict.h +dict_cache.o: dict_cache.c +dict_cache.o: dict_cache.h +dict_cache.o: events.h +dict_cache.o: msg.h +dict_cache.o: myflock.h +dict_cache.o: mymalloc.h +dict_cache.o: sys_defs.h +dict_cache.o: vbuf.h +dict_cache.o: vstream.h +dict_cache.o: vstring.h +dict_cdb.o: argv.h +dict_cdb.o: check_arg.h +dict_cdb.o: dict.h +dict_cdb.o: dict_cdb.c +dict_cdb.o: dict_cdb.h +dict_cdb.o: iostuff.h +dict_cdb.o: msg.h +dict_cdb.o: myflock.h +dict_cdb.o: mymalloc.h +dict_cdb.o: stringops.h +dict_cdb.o: sys_defs.h +dict_cdb.o: vbuf.h +dict_cdb.o: vstream.h +dict_cdb.o: vstring.h +dict_cdb.o: warn_stat.h +dict_cidr.o: argv.h +dict_cidr.o: check_arg.h +dict_cidr.o: cidr_match.h +dict_cidr.o: dict.h +dict_cidr.o: dict_cidr.c +dict_cidr.o: dict_cidr.h +dict_cidr.o: msg.h +dict_cidr.o: mvect.h +dict_cidr.o: myaddrinfo.h +dict_cidr.o: myflock.h +dict_cidr.o: mymalloc.h +dict_cidr.o: readlline.h +dict_cidr.o: stringops.h +dict_cidr.o: sys_defs.h +dict_cidr.o: vbuf.h +dict_cidr.o: vstream.h +dict_cidr.o: vstring.h +dict_cidr.o: warn_stat.h +dict_db.o: argv.h +dict_db.o: check_arg.h +dict_db.o: dict.h +dict_db.o: dict_db.c +dict_db.o: dict_db.h +dict_db.o: iostuff.h +dict_db.o: msg.h +dict_db.o: myflock.h +dict_db.o: mymalloc.h +dict_db.o: stringops.h +dict_db.o: sys_defs.h +dict_db.o: vbuf.h +dict_db.o: vstream.h +dict_db.o: vstring.h +dict_db.o: warn_stat.h +dict_dbm.o: dict_dbm.c +dict_dbm.o: sys_defs.h +dict_debug.o: argv.h +dict_debug.o: check_arg.h +dict_debug.o: dict.h +dict_debug.o: dict_debug.c +dict_debug.o: msg.h +dict_debug.o: myflock.h +dict_debug.o: mymalloc.h +dict_debug.o: sys_defs.h +dict_debug.o: vbuf.h +dict_debug.o: vstream.h +dict_debug.o: vstring.h +dict_env.o: argv.h +dict_env.o: check_arg.h +dict_env.o: dict.h +dict_env.o: dict_env.c +dict_env.o: dict_env.h +dict_env.o: msg.h +dict_env.o: myflock.h +dict_env.o: mymalloc.h +dict_env.o: safe.h +dict_env.o: stringops.h +dict_env.o: sys_defs.h +dict_env.o: vbuf.h +dict_env.o: vstream.h +dict_env.o: vstring.h +dict_fail.o: argv.h +dict_fail.o: check_arg.h +dict_fail.o: dict.h +dict_fail.o: dict_fail.c +dict_fail.o: dict_fail.h +dict_fail.o: msg.h +dict_fail.o: myflock.h +dict_fail.o: mymalloc.h +dict_fail.o: sys_defs.h +dict_fail.o: vbuf.h +dict_fail.o: vstream.h +dict_fail.o: vstring.h +dict_file.o: argv.h +dict_file.o: base64_code.h +dict_file.o: check_arg.h +dict_file.o: dict.h +dict_file.o: dict_file.c +dict_file.o: msg.h +dict_file.o: myflock.h +dict_file.o: mymalloc.h +dict_file.o: sys_defs.h +dict_file.o: vbuf.h +dict_file.o: vstream.h +dict_file.o: vstring.h +dict_ht.o: argv.h +dict_ht.o: check_arg.h +dict_ht.o: dict.h +dict_ht.o: dict_ht.c +dict_ht.o: dict_ht.h +dict_ht.o: htable.h +dict_ht.o: myflock.h +dict_ht.o: mymalloc.h +dict_ht.o: stringops.h +dict_ht.o: sys_defs.h +dict_ht.o: vbuf.h +dict_ht.o: vstream.h +dict_ht.o: vstring.h +dict_inline.o: argv.h +dict_inline.o: check_arg.h +dict_inline.o: dict.h +dict_inline.o: dict_ht.h +dict_inline.o: dict_inline.c +dict_inline.o: dict_inline.h +dict_inline.o: htable.h +dict_inline.o: msg.h +dict_inline.o: myflock.h +dict_inline.o: mymalloc.h +dict_inline.o: stringops.h +dict_inline.o: sys_defs.h +dict_inline.o: vbuf.h +dict_inline.o: vstream.h +dict_inline.o: vstring.h +dict_lmdb.o: argv.h +dict_lmdb.o: check_arg.h +dict_lmdb.o: dict.h +dict_lmdb.o: dict_lmdb.c +dict_lmdb.o: dict_lmdb.h +dict_lmdb.o: htable.h +dict_lmdb.o: iostuff.h +dict_lmdb.o: msg.h +dict_lmdb.o: myflock.h +dict_lmdb.o: mymalloc.h +dict_lmdb.o: slmdb.h +dict_lmdb.o: stringops.h +dict_lmdb.o: sys_defs.h +dict_lmdb.o: vbuf.h +dict_lmdb.o: vstream.h +dict_lmdb.o: vstring.h +dict_lmdb.o: warn_stat.h +dict_ni.o: dict_ni.c +dict_ni.o: sys_defs.h +dict_nis.o: argv.h +dict_nis.o: check_arg.h +dict_nis.o: dict.h +dict_nis.o: dict_nis.c +dict_nis.o: dict_nis.h +dict_nis.o: msg.h +dict_nis.o: myflock.h +dict_nis.o: mymalloc.h +dict_nis.o: stringops.h +dict_nis.o: sys_defs.h +dict_nis.o: vbuf.h +dict_nis.o: vstream.h +dict_nis.o: vstring.h +dict_nisplus.o: argv.h +dict_nisplus.o: check_arg.h +dict_nisplus.o: dict.h +dict_nisplus.o: dict_nisplus.c +dict_nisplus.o: dict_nisplus.h +dict_nisplus.o: msg.h +dict_nisplus.o: myflock.h +dict_nisplus.o: mymalloc.h +dict_nisplus.o: stringops.h +dict_nisplus.o: sys_defs.h +dict_nisplus.o: vbuf.h +dict_nisplus.o: vstream.h +dict_nisplus.o: vstring.h +dict_open.o: argv.h +dict_open.o: check_arg.h +dict_open.o: dict.h +dict_open.o: dict_cdb.h +dict_open.o: dict_cidr.h +dict_open.o: dict_db.h +dict_open.o: dict_dbm.h +dict_open.o: dict_env.h +dict_open.o: dict_fail.h +dict_open.o: dict_ht.h +dict_open.o: dict_inline.h +dict_open.o: dict_lmdb.h +dict_open.o: dict_ni.h +dict_open.o: dict_nis.h +dict_open.o: dict_nisplus.h +dict_open.o: dict_open.c +dict_open.o: dict_pcre.h +dict_open.o: dict_pipe.h +dict_open.o: dict_random.h +dict_open.o: dict_regexp.h +dict_open.o: dict_sdbm.h +dict_open.o: dict_sockmap.h +dict_open.o: dict_static.h +dict_open.o: dict_tcp.h +dict_open.o: dict_thash.h +dict_open.o: dict_union.h +dict_open.o: dict_unix.h +dict_open.o: htable.h +dict_open.o: msg.h +dict_open.o: myflock.h +dict_open.o: mymalloc.h +dict_open.o: split_at.h +dict_open.o: stringops.h +dict_open.o: sys_defs.h +dict_open.o: vbuf.h +dict_open.o: vstream.h +dict_open.o: vstring.h +dict_pcre.o: argv.h +dict_pcre.o: check_arg.h +dict_pcre.o: dict.h +dict_pcre.o: dict_pcre.c +dict_pcre.o: dict_pcre.h +dict_pcre.o: mac_parse.h +dict_pcre.o: msg.h +dict_pcre.o: mvect.h +dict_pcre.o: myflock.h +dict_pcre.o: mymalloc.h +dict_pcre.o: readlline.h +dict_pcre.o: safe.h +dict_pcre.o: stringops.h +dict_pcre.o: sys_defs.h +dict_pcre.o: vbuf.h +dict_pcre.o: vstream.h +dict_pcre.o: vstring.h +dict_pcre.o: warn_stat.h +dict_pipe.o: argv.h +dict_pipe.o: check_arg.h +dict_pipe.o: dict.h +dict_pipe.o: dict_pipe.c +dict_pipe.o: dict_pipe.h +dict_pipe.o: htable.h +dict_pipe.o: msg.h +dict_pipe.o: myflock.h +dict_pipe.o: mymalloc.h +dict_pipe.o: stringops.h +dict_pipe.o: sys_defs.h +dict_pipe.o: vbuf.h +dict_pipe.o: vstream.h +dict_pipe.o: vstring.h +dict_random.o: argv.h +dict_random.o: check_arg.h +dict_random.o: dict.h +dict_random.o: dict_random.c +dict_random.o: dict_random.h +dict_random.o: msg.h +dict_random.o: myflock.h +dict_random.o: mymalloc.h +dict_random.o: myrand.h +dict_random.o: stringops.h +dict_random.o: sys_defs.h +dict_random.o: vbuf.h +dict_random.o: vstream.h +dict_random.o: vstring.h +dict_regexp.o: argv.h +dict_regexp.o: check_arg.h +dict_regexp.o: dict.h +dict_regexp.o: dict_regexp.c +dict_regexp.o: dict_regexp.h +dict_regexp.o: mac_parse.h +dict_regexp.o: msg.h +dict_regexp.o: mvect.h +dict_regexp.o: myflock.h +dict_regexp.o: mymalloc.h +dict_regexp.o: readlline.h +dict_regexp.o: safe.h +dict_regexp.o: stringops.h +dict_regexp.o: sys_defs.h +dict_regexp.o: vbuf.h +dict_regexp.o: vstream.h +dict_regexp.o: vstring.h +dict_regexp.o: warn_stat.h +dict_sdbm.o: argv.h +dict_sdbm.o: check_arg.h +dict_sdbm.o: dict.h +dict_sdbm.o: dict_sdbm.c +dict_sdbm.o: dict_sdbm.h +dict_sdbm.o: htable.h +dict_sdbm.o: iostuff.h +dict_sdbm.o: msg.h +dict_sdbm.o: myflock.h +dict_sdbm.o: mymalloc.h +dict_sdbm.o: stringops.h +dict_sdbm.o: sys_defs.h +dict_sdbm.o: vbuf.h +dict_sdbm.o: vstream.h +dict_sdbm.o: vstring.h +dict_sdbm.o: warn_stat.h +dict_sockmap.o: argv.h +dict_sockmap.o: auto_clnt.h +dict_sockmap.o: check_arg.h +dict_sockmap.o: dict.h +dict_sockmap.o: dict_sockmap.c +dict_sockmap.o: dict_sockmap.h +dict_sockmap.o: htable.h +dict_sockmap.o: msg.h +dict_sockmap.o: myflock.h +dict_sockmap.o: mymalloc.h +dict_sockmap.o: netstring.h +dict_sockmap.o: split_at.h +dict_sockmap.o: stringops.h +dict_sockmap.o: sys_defs.h +dict_sockmap.o: vbuf.h +dict_sockmap.o: vstream.h +dict_sockmap.o: vstring.h +dict_static.o: argv.h +dict_static.o: check_arg.h +dict_static.o: dict.h +dict_static.o: dict_static.c +dict_static.o: dict_static.h +dict_static.o: msg.h +dict_static.o: myflock.h +dict_static.o: mymalloc.h +dict_static.o: stringops.h +dict_static.o: sys_defs.h +dict_static.o: vbuf.h +dict_static.o: vstream.h +dict_static.o: vstring.h +dict_stream.o: argv.h +dict_stream.o: check_arg.h +dict_stream.o: dict.h +dict_stream.o: dict_stream.c +dict_stream.o: msg.h +dict_stream.o: myflock.h +dict_stream.o: mymalloc.h +dict_stream.o: stringops.h +dict_stream.o: sys_defs.h +dict_stream.o: vbuf.h +dict_stream.o: vstream.h +dict_stream.o: vstring.h +dict_surrogate.o: argv.h +dict_surrogate.o: check_arg.h +dict_surrogate.o: compat_va_copy.h +dict_surrogate.o: dict.h +dict_surrogate.o: dict_surrogate.c +dict_surrogate.o: msg.h +dict_surrogate.o: myflock.h +dict_surrogate.o: mymalloc.h +dict_surrogate.o: sys_defs.h +dict_surrogate.o: vbuf.h +dict_surrogate.o: vstream.h +dict_surrogate.o: vstring.h +dict_tcp.o: argv.h +dict_tcp.o: check_arg.h +dict_tcp.o: connect.h +dict_tcp.o: dict.h +dict_tcp.o: dict_tcp.c +dict_tcp.o: dict_tcp.h +dict_tcp.o: hex_quote.h +dict_tcp.o: iostuff.h +dict_tcp.o: msg.h +dict_tcp.o: myflock.h +dict_tcp.o: mymalloc.h +dict_tcp.o: stringops.h +dict_tcp.o: sys_defs.h +dict_tcp.o: vbuf.h +dict_tcp.o: vstream.h +dict_tcp.o: vstring.h +dict_tcp.o: vstring_vstream.h +dict_test.o: argv.h +dict_test.o: check_arg.h +dict_test.o: dict.h +dict_test.o: dict_db.h +dict_test.o: dict_lmdb.h +dict_test.o: dict_test.c +dict_test.o: msg.h +dict_test.o: msg_vstream.h +dict_test.o: myflock.h +dict_test.o: stringops.h +dict_test.o: sys_defs.h +dict_test.o: vbuf.h +dict_test.o: vstream.h +dict_test.o: vstring.h +dict_test.o: vstring_vstream.h +dict_thash.o: argv.h +dict_thash.o: check_arg.h +dict_thash.o: dict.h +dict_thash.o: dict_ht.h +dict_thash.o: dict_thash.c +dict_thash.o: dict_thash.h +dict_thash.o: htable.h +dict_thash.o: iostuff.h +dict_thash.o: msg.h +dict_thash.o: myflock.h +dict_thash.o: mymalloc.h +dict_thash.o: readlline.h +dict_thash.o: stringops.h +dict_thash.o: sys_defs.h +dict_thash.o: vbuf.h +dict_thash.o: vstream.h +dict_thash.o: vstring.h +dict_union.o: argv.h +dict_union.o: check_arg.h +dict_union.o: dict.h +dict_union.o: dict_union.c +dict_union.o: dict_union.h +dict_union.o: htable.h +dict_union.o: msg.h +dict_union.o: myflock.h +dict_union.o: mymalloc.h +dict_union.o: stringops.h +dict_union.o: sys_defs.h +dict_union.o: vbuf.h +dict_union.o: vstream.h +dict_union.o: vstring.h +dict_unix.o: argv.h +dict_unix.o: check_arg.h +dict_unix.o: dict.h +dict_unix.o: dict_unix.c +dict_unix.o: dict_unix.h +dict_unix.o: msg.h +dict_unix.o: myflock.h +dict_unix.o: mymalloc.h +dict_unix.o: stringops.h +dict_unix.o: sys_defs.h +dict_unix.o: vbuf.h +dict_unix.o: vstream.h +dict_unix.o: vstring.h +dict_utf8.o: argv.h +dict_utf8.o: check_arg.h +dict_utf8.o: dict.h +dict_utf8.o: dict_utf8.c +dict_utf8.o: msg.h +dict_utf8.o: myflock.h +dict_utf8.o: mymalloc.h +dict_utf8.o: stringops.h +dict_utf8.o: sys_defs.h +dict_utf8.o: vbuf.h +dict_utf8.o: vstream.h +dict_utf8.o: vstring.h +dir_forest.o: check_arg.h +dir_forest.o: dir_forest.c +dir_forest.o: dir_forest.h +dir_forest.o: msg.h +dir_forest.o: sys_defs.h +dir_forest.o: vbuf.h +dir_forest.o: vstring.h +doze.o: doze.c +doze.o: iostuff.h +doze.o: msg.h +doze.o: sys_defs.h +dummy_read.o: dummy_read.c +dummy_read.o: iostuff.h +dummy_read.o: msg.h +dummy_read.o: sys_defs.h +dummy_write.o: dummy_write.c +dummy_write.o: iostuff.h +dummy_write.o: msg.h +dummy_write.o: sys_defs.h +dup2_pass_on_exec.o: dup2_pass_on_exec.c +duplex_pipe.o: duplex_pipe.c +duplex_pipe.o: iostuff.h +duplex_pipe.o: sane_socketpair.h +duplex_pipe.o: sys_defs.h +edit_file.o: check_arg.h +edit_file.o: edit_file.c +edit_file.o: edit_file.h +edit_file.o: msg.h +edit_file.o: myflock.h +edit_file.o: mymalloc.h +edit_file.o: stringops.h +edit_file.o: sys_defs.h +edit_file.o: vbuf.h +edit_file.o: vstream.h +edit_file.o: vstring.h +edit_file.o: warn_stat.h +environ.o: environ.c +environ.o: sys_defs.h +events.o: events.c +events.o: events.h +events.o: iostuff.h +events.o: msg.h +events.o: mymalloc.h +events.o: ring.h +events.o: sys_defs.h +exec_command.o: argv.h +exec_command.o: exec_command.c +exec_command.o: exec_command.h +exec_command.o: msg.h +exec_command.o: sys_defs.h +extpar.o: check_arg.h +extpar.o: extpar.c +extpar.o: stringops.h +extpar.o: sys_defs.h +extpar.o: vbuf.h +extpar.o: vstring.h +fifo_listen.o: fifo_listen.c +fifo_listen.o: htable.h +fifo_listen.o: iostuff.h +fifo_listen.o: listen.h +fifo_listen.o: msg.h +fifo_listen.o: sys_defs.h +fifo_listen.o: warn_stat.h +fifo_open.o: fifo_open.c +fifo_rdonly_bug.o: fifo_rdonly_bug.c +fifo_rdonly_bug.o: sys_defs.h +fifo_rdwr_bug.o: fifo_rdwr_bug.c +fifo_rdwr_bug.o: sys_defs.h +fifo_trigger.o: check_arg.h +fifo_trigger.o: fifo_trigger.c +fifo_trigger.o: iostuff.h +fifo_trigger.o: msg.h +fifo_trigger.o: safe_open.h +fifo_trigger.o: sys_defs.h +fifo_trigger.o: trigger.h +fifo_trigger.o: vbuf.h +fifo_trigger.o: vstream.h +fifo_trigger.o: vstring.h +file_limit.o: file_limit.c +file_limit.o: iostuff.h +file_limit.o: msg.h +file_limit.o: sys_defs.h +find_inet.o: check_arg.h +find_inet.o: find_inet.c +find_inet.o: find_inet.h +find_inet.o: known_tcp_ports.h +find_inet.o: msg.h +find_inet.o: stringops.h +find_inet.o: sys_defs.h +find_inet.o: vbuf.h +find_inet.o: vstring.h +format_tv.o: check_arg.h +format_tv.o: format_tv.c +format_tv.o: format_tv.h +format_tv.o: msg.h +format_tv.o: sys_defs.h +format_tv.o: vbuf.h +format_tv.o: vstring.h +fsspace.o: fsspace.c +fsspace.o: fsspace.h +fsspace.o: msg.h +fsspace.o: sys_defs.h +fullname.o: check_arg.h +fullname.o: fullname.c +fullname.o: fullname.h +fullname.o: safe.h +fullname.o: sys_defs.h +fullname.o: vbuf.h +fullname.o: vstring.h +gccw.o: gccw.c +get_domainname.o: get_domainname.c +get_domainname.o: get_domainname.h +get_domainname.o: get_hostname.h +get_domainname.o: mymalloc.h +get_domainname.o: sys_defs.h +get_hostname.o: get_hostname.c +get_hostname.o: get_hostname.h +get_hostname.o: msg.h +get_hostname.o: mymalloc.h +get_hostname.o: sys_defs.h +get_hostname.o: valid_hostname.h +hash_fnv.o: hash_fnv.c +hash_fnv.o: hash_fnv.h +hash_fnv.o: ldseed.h +hash_fnv.o: msg.h +hash_fnv.o: sys_defs.h +hex_code.o: check_arg.h +hex_code.o: hex_code.c +hex_code.o: hex_code.h +hex_code.o: msg.h +hex_code.o: mymalloc.h +hex_code.o: sys_defs.h +hex_code.o: vbuf.h +hex_code.o: vstring.h +hex_quote.o: check_arg.h +hex_quote.o: hex_quote.c +hex_quote.o: hex_quote.h +hex_quote.o: msg.h +hex_quote.o: sys_defs.h +hex_quote.o: vbuf.h +hex_quote.o: vstring.h +host_port.o: check_arg.h +host_port.o: host_port.c +host_port.o: host_port.h +host_port.o: msg.h +host_port.o: split_at.h +host_port.o: stringops.h +host_port.o: sys_defs.h +host_port.o: valid_hostname.h +host_port.o: valid_utf8_hostname.h +host_port.o: vbuf.h +host_port.o: vstring.h +htable.o: hash_fnv.h +htable.o: htable.c +htable.o: htable.h +htable.o: msg.h +htable.o: mymalloc.h +htable.o: sys_defs.h +inet_addr_host.o: inet_addr_host.c +inet_addr_host.o: inet_addr_host.h +inet_addr_host.o: inet_addr_list.h +inet_addr_host.o: inet_proto.h +inet_addr_host.o: msg.h +inet_addr_host.o: myaddrinfo.h +inet_addr_host.o: mymalloc.h +inet_addr_host.o: sock_addr.h +inet_addr_host.o: sys_defs.h +inet_addr_list.o: inet_addr_list.c +inet_addr_list.o: inet_addr_list.h +inet_addr_list.o: msg.h +inet_addr_list.o: myaddrinfo.h +inet_addr_list.o: mymalloc.h +inet_addr_list.o: sock_addr.h +inet_addr_list.o: sys_defs.h +inet_addr_local.o: check_arg.h +inet_addr_local.o: hex_code.h +inet_addr_local.o: inet_addr_list.h +inet_addr_local.o: inet_addr_local.c +inet_addr_local.o: inet_addr_local.h +inet_addr_local.o: mask_addr.h +inet_addr_local.o: msg.h +inet_addr_local.o: myaddrinfo.h +inet_addr_local.o: mymalloc.h +inet_addr_local.o: sock_addr.h +inet_addr_local.o: sys_defs.h +inet_addr_local.o: vbuf.h +inet_addr_local.o: vstring.h +inet_connect.o: connect.h +inet_connect.o: host_port.h +inet_connect.o: inet_connect.c +inet_connect.o: inet_proto.h +inet_connect.o: iostuff.h +inet_connect.o: msg.h +inet_connect.o: myaddrinfo.h +inet_connect.o: mymalloc.h +inet_connect.o: sane_connect.h +inet_connect.o: sock_addr.h +inet_connect.o: sys_defs.h +inet_connect.o: timed_connect.h +inet_listen.o: host_port.h +inet_listen.o: htable.h +inet_listen.o: inet_listen.c +inet_listen.o: inet_proto.h +inet_listen.o: iostuff.h +inet_listen.o: listen.h +inet_listen.o: msg.h +inet_listen.o: myaddrinfo.h +inet_listen.o: mymalloc.h +inet_listen.o: sane_accept.h +inet_listen.o: sock_addr.h +inet_listen.o: sys_defs.h +inet_proto.o: check_arg.h +inet_proto.o: inet_proto.c +inet_proto.o: inet_proto.h +inet_proto.o: msg.h +inet_proto.o: myaddrinfo.h +inet_proto.o: mymalloc.h +inet_proto.o: name_mask.h +inet_proto.o: sys_defs.h +inet_proto.o: vbuf.h +inet_proto.o: vstring.h +inet_trigger.o: connect.h +inet_trigger.o: events.h +inet_trigger.o: inet_trigger.c +inet_trigger.o: iostuff.h +inet_trigger.o: msg.h +inet_trigger.o: mymalloc.h +inet_trigger.o: sys_defs.h +inet_trigger.o: trigger.h +inet_windowsize.o: inet_windowsize.c +inet_windowsize.o: iostuff.h +inet_windowsize.o: msg.h +inet_windowsize.o: sys_defs.h +ip_match.o: check_arg.h +ip_match.o: ip_match.c +ip_match.o: ip_match.h +ip_match.o: msg.h +ip_match.o: mymalloc.h +ip_match.o: sys_defs.h +ip_match.o: vbuf.h +ip_match.o: vstring.h +killme_after.o: killme_after.c +killme_after.o: killme_after.h +killme_after.o: sys_defs.h +known_tcp_ports.o: check_arg.h +known_tcp_ports.o: htable.h +known_tcp_ports.o: known_tcp_ports.c +known_tcp_ports.o: known_tcp_ports.h +known_tcp_ports.o: mymalloc.h +known_tcp_ports.o: stringops.h +known_tcp_ports.o: sys_defs.h +known_tcp_ports.o: vbuf.h +known_tcp_ports.o: vstring.h +ldseed.o: iostuff.h +ldseed.o: ldseed.c +ldseed.o: ldseed.h +ldseed.o: msg.h +ldseed.o: sys_defs.h +line_number.o: check_arg.h +line_number.o: line_number.c +line_number.o: line_number.h +line_number.o: sys_defs.h +line_number.o: vbuf.h +line_number.o: vstring.h +line_wrap.o: line_wrap.c +line_wrap.o: line_wrap.h +line_wrap.o: sys_defs.h +load_file.o: check_arg.h +load_file.o: iostuff.h +load_file.o: load_file.c +load_file.o: load_file.h +load_file.o: msg.h +load_file.o: sys_defs.h +load_file.o: vbuf.h +load_file.o: vstream.h +load_file.o: warn_stat.h +load_lib.o: load_lib.c +load_lib.o: load_lib.h +load_lib.o: msg.h +load_lib.o: sys_defs.h +logwriter.o: check_arg.h +logwriter.o: iostuff.h +logwriter.o: logwriter.c +logwriter.o: logwriter.h +logwriter.o: msg.h +logwriter.o: mymalloc.h +logwriter.o: safe_open.h +logwriter.o: sys_defs.h +logwriter.o: vbuf.h +logwriter.o: vstream.h +logwriter.o: vstring.h +lowercase.o: check_arg.h +lowercase.o: lowercase.c +lowercase.o: stringops.h +lowercase.o: sys_defs.h +lowercase.o: vbuf.h +lowercase.o: vstring.h +lstat_as.o: lstat_as.c +lstat_as.o: lstat_as.h +lstat_as.o: msg.h +lstat_as.o: set_eugid.h +lstat_as.o: sys_defs.h +lstat_as.o: warn_stat.h +mac_expand.o: check_arg.h +mac_expand.o: htable.h +mac_expand.o: mac_expand.c +mac_expand.o: mac_expand.h +mac_expand.o: mac_parse.h +mac_expand.o: msg.h +mac_expand.o: mymalloc.h +mac_expand.o: name_code.h +mac_expand.o: sane_strtol.h +mac_expand.o: stringops.h +mac_expand.o: sys_defs.h +mac_expand.o: vbuf.h +mac_expand.o: vstring.h +mac_parse.o: check_arg.h +mac_parse.o: mac_parse.c +mac_parse.o: mac_parse.h +mac_parse.o: msg.h +mac_parse.o: sys_defs.h +mac_parse.o: vbuf.h +mac_parse.o: vstring.h +make_dirs.o: check_arg.h +make_dirs.o: make_dirs.c +make_dirs.o: make_dirs.h +make_dirs.o: msg.h +make_dirs.o: mymalloc.h +make_dirs.o: stringops.h +make_dirs.o: sys_defs.h +make_dirs.o: vbuf.h +make_dirs.o: vstring.h +make_dirs.o: warn_stat.h +mask_addr.o: mask_addr.c +mask_addr.o: mask_addr.h +mask_addr.o: msg.h +mask_addr.o: sys_defs.h +match_list.o: argv.h +match_list.o: check_arg.h +match_list.o: dict.h +match_list.o: match_list.c +match_list.o: match_list.h +match_list.o: msg.h +match_list.o: myflock.h +match_list.o: mymalloc.h +match_list.o: stringops.h +match_list.o: sys_defs.h +match_list.o: vbuf.h +match_list.o: vstream.h +match_list.o: vstring.h +match_list.o: vstring_vstream.h +match_ops.o: argv.h +match_ops.o: check_arg.h +match_ops.o: cidr_match.h +match_ops.o: dict.h +match_ops.o: match_list.h +match_ops.o: match_ops.c +match_ops.o: msg.h +match_ops.o: myaddrinfo.h +match_ops.o: myflock.h +match_ops.o: mymalloc.h +match_ops.o: split_at.h +match_ops.o: stringops.h +match_ops.o: sys_defs.h +match_ops.o: vbuf.h +match_ops.o: vstream.h +match_ops.o: vstring.h +midna_domain.o: check_arg.h +midna_domain.o: ctable.h +midna_domain.o: midna_domain.c +midna_domain.o: midna_domain.h +midna_domain.o: msg.h +midna_domain.o: mymalloc.h +midna_domain.o: name_mask.h +midna_domain.o: stringops.h +midna_domain.o: sys_defs.h +midna_domain.o: valid_hostname.h +midna_domain.o: vbuf.h +midna_domain.o: vstring.h +msg.o: msg.c +msg.o: msg.h +msg.o: msg_output.h +msg.o: sys_defs.h +msg_logger.o: check_arg.h +msg_logger.o: connect.h +msg_logger.o: iostuff.h +msg_logger.o: logwriter.h +msg_logger.o: msg.h +msg_logger.o: msg_logger.c +msg_logger.o: msg_logger.h +msg_logger.o: msg_output.h +msg_logger.o: mymalloc.h +msg_logger.o: safe.h +msg_logger.o: sys_defs.h +msg_logger.o: vbuf.h +msg_logger.o: vstream.h +msg_logger.o: vstring.h +msg_output.o: check_arg.h +msg_output.o: msg_output.c +msg_output.o: msg_output.h +msg_output.o: msg_vstream.h +msg_output.o: mymalloc.h +msg_output.o: stringops.h +msg_output.o: sys_defs.h +msg_output.o: vbuf.h +msg_output.o: vstream.h +msg_output.o: vstring.h +msg_rate_delay.o: check_arg.h +msg_rate_delay.o: events.h +msg_rate_delay.o: msg.h +msg_rate_delay.o: msg_rate_delay.c +msg_rate_delay.o: sys_defs.h +msg_rate_delay.o: vbuf.h +msg_rate_delay.o: vstring.h +msg_syslog.o: check_arg.h +msg_syslog.o: msg.h +msg_syslog.o: msg_output.h +msg_syslog.o: msg_syslog.c +msg_syslog.o: msg_syslog.h +msg_syslog.o: mymalloc.h +msg_syslog.o: safe.h +msg_syslog.o: stringops.h +msg_syslog.o: sys_defs.h +msg_syslog.o: vbuf.h +msg_syslog.o: vstring.h +msg_vstream.o: check_arg.h +msg_vstream.o: msg.h +msg_vstream.o: msg_output.h +msg_vstream.o: msg_vstream.c +msg_vstream.o: msg_vstream.h +msg_vstream.o: sys_defs.h +msg_vstream.o: vbuf.h +msg_vstream.o: vstream.h +mvect.o: mvect.c +mvect.o: mvect.h +mvect.o: mymalloc.h +mvect.o: sys_defs.h +myaddrinfo.o: check_arg.h +myaddrinfo.o: inet_proto.h +myaddrinfo.o: known_tcp_ports.h +myaddrinfo.o: msg.h +myaddrinfo.o: myaddrinfo.c +myaddrinfo.o: myaddrinfo.h +myaddrinfo.o: mymalloc.h +myaddrinfo.o: sock_addr.h +myaddrinfo.o: split_at.h +myaddrinfo.o: stringops.h +myaddrinfo.o: sys_defs.h +myaddrinfo.o: valid_hostname.h +myaddrinfo.o: vbuf.h +myaddrinfo.o: vstring.h +myflock.o: msg.h +myflock.o: myflock.c +myflock.o: myflock.h +myflock.o: sys_defs.h +mymalloc.o: msg.h +mymalloc.o: mymalloc.c +mymalloc.o: mymalloc.h +mymalloc.o: sys_defs.h +myrand.o: myrand.c +myrand.o: myrand.h +myrand.o: sys_defs.h +mystrtok.o: check_arg.h +mystrtok.o: mystrtok.c +mystrtok.o: stringops.h +mystrtok.o: sys_defs.h +mystrtok.o: vbuf.h +mystrtok.o: vstring.h +name_code.o: name_code.c +name_code.o: name_code.h +name_code.o: sys_defs.h +name_mask.o: check_arg.h +name_mask.o: msg.h +name_mask.o: mymalloc.h +name_mask.o: name_mask.c +name_mask.o: name_mask.h +name_mask.o: stringops.h +name_mask.o: sys_defs.h +name_mask.o: vbuf.h +name_mask.o: vstring.h +nbbio.o: events.h +nbbio.o: msg.h +nbbio.o: mymalloc.h +nbbio.o: nbbio.c +nbbio.o: nbbio.h +nbbio.o: sys_defs.h +netstring.o: check_arg.h +netstring.o: compat_va_copy.h +netstring.o: msg.h +netstring.o: netstring.c +netstring.o: netstring.h +netstring.o: sys_defs.h +netstring.o: vbuf.h +netstring.o: vstream.h +netstring.o: vstring.h +neuter.o: check_arg.h +neuter.o: neuter.c +neuter.o: stringops.h +neuter.o: sys_defs.h +neuter.o: vbuf.h +neuter.o: vstring.h +non_blocking.o: iostuff.h +non_blocking.o: msg.h +non_blocking.o: non_blocking.c +non_blocking.o: sys_defs.h +nvtable.o: htable.h +nvtable.o: mymalloc.h +nvtable.o: nvtable.c +nvtable.o: nvtable.h +nvtable.o: sys_defs.h +open_as.o: msg.h +open_as.o: open_as.c +open_as.o: open_as.h +open_as.o: set_eugid.h +open_as.o: sys_defs.h +open_limit.o: iostuff.h +open_limit.o: open_limit.c +open_limit.o: sys_defs.h +open_lock.o: check_arg.h +open_lock.o: msg.h +open_lock.o: myflock.h +open_lock.o: open_lock.c +open_lock.o: open_lock.h +open_lock.o: safe_open.h +open_lock.o: sys_defs.h +open_lock.o: vbuf.h +open_lock.o: vstream.h +open_lock.o: vstring.h +pass_accept.o: attr.h +pass_accept.o: check_arg.h +pass_accept.o: htable.h +pass_accept.o: iostuff.h +pass_accept.o: listen.h +pass_accept.o: msg.h +pass_accept.o: mymalloc.h +pass_accept.o: nvtable.h +pass_accept.o: pass_accept.c +pass_accept.o: sys_defs.h +pass_accept.o: vbuf.h +pass_accept.o: vstream.h +pass_accept.o: vstring.h +pass_trigger.o: connect.h +pass_trigger.o: events.h +pass_trigger.o: iostuff.h +pass_trigger.o: msg.h +pass_trigger.o: mymalloc.h +pass_trigger.o: pass_trigger.c +pass_trigger.o: sys_defs.h +pass_trigger.o: trigger.h +peekfd.o: iostuff.h +peekfd.o: peekfd.c +peekfd.o: sys_defs.h +poll_fd.o: iostuff.h +poll_fd.o: msg.h +poll_fd.o: poll_fd.c +poll_fd.o: sys_defs.h +posix_signals.o: posix_signals.c +posix_signals.o: posix_signals.h +posix_signals.o: sys_defs.h +printable.o: check_arg.h +printable.o: printable.c +printable.o: stringops.h +printable.o: sys_defs.h +printable.o: vbuf.h +printable.o: vstring.h +rand_sleep.o: iostuff.h +rand_sleep.o: msg.h +rand_sleep.o: myrand.h +rand_sleep.o: rand_sleep.c +rand_sleep.o: sys_defs.h +readlline.o: check_arg.h +readlline.o: msg.h +readlline.o: readlline.c +readlline.o: readlline.h +readlline.o: sys_defs.h +readlline.o: vbuf.h +readlline.o: vstream.h +readlline.o: vstring.h +recv_pass_attr.o: attr.h +recv_pass_attr.o: check_arg.h +recv_pass_attr.o: htable.h +recv_pass_attr.o: iostuff.h +recv_pass_attr.o: listen.h +recv_pass_attr.o: mymalloc.h +recv_pass_attr.o: nvtable.h +recv_pass_attr.o: recv_pass_attr.c +recv_pass_attr.o: sys_defs.h +recv_pass_attr.o: vbuf.h +recv_pass_attr.o: vstream.h +recv_pass_attr.o: vstring.h +ring.o: ring.c +ring.o: ring.h +safe_getenv.o: safe.h +safe_getenv.o: safe_getenv.c +safe_getenv.o: sys_defs.h +safe_open.o: check_arg.h +safe_open.o: msg.h +safe_open.o: safe_open.c +safe_open.o: safe_open.h +safe_open.o: stringops.h +safe_open.o: sys_defs.h +safe_open.o: vbuf.h +safe_open.o: vstream.h +safe_open.o: vstring.h +safe_open.o: warn_stat.h +sane_accept.o: msg.h +sane_accept.o: sane_accept.c +sane_accept.o: sane_accept.h +sane_accept.o: sys_defs.h +sane_basename.o: check_arg.h +sane_basename.o: sane_basename.c +sane_basename.o: stringops.h +sane_basename.o: sys_defs.h +sane_basename.o: vbuf.h +sane_basename.o: vstring.h +sane_connect.o: msg.h +sane_connect.o: sane_connect.c +sane_connect.o: sane_connect.h +sane_connect.o: sys_defs.h +sane_link.o: msg.h +sane_link.o: sane_fsops.h +sane_link.o: sane_link.c +sane_link.o: sys_defs.h +sane_link.o: warn_stat.h +sane_rename.o: msg.h +sane_rename.o: sane_fsops.h +sane_rename.o: sane_rename.c +sane_rename.o: sys_defs.h +sane_rename.o: warn_stat.h +sane_socketpair.o: msg.h +sane_socketpair.o: sane_socketpair.c +sane_socketpair.o: sane_socketpair.h +sane_socketpair.o: sys_defs.h +sane_strtol.o: sane_strtol.c +sane_strtol.o: sane_strtol.h +sane_strtol.o: sys_defs.h +sane_time.o: msg.h +sane_time.o: sane_time.c +sane_time.o: sane_time.h +sane_time.o: sys_defs.h +scan_dir.o: check_arg.h +scan_dir.o: msg.h +scan_dir.o: mymalloc.h +scan_dir.o: scan_dir.c +scan_dir.o: scan_dir.h +scan_dir.o: stringops.h +scan_dir.o: sys_defs.h +scan_dir.o: vbuf.h +scan_dir.o: vstring.h +select_bug.o: check_arg.h +select_bug.o: msg.h +select_bug.o: msg_vstream.h +select_bug.o: select_bug.c +select_bug.o: sys_defs.h +select_bug.o: vbuf.h +select_bug.o: vstream.h +set_eugid.o: msg.h +set_eugid.o: set_eugid.c +set_eugid.o: set_eugid.h +set_eugid.o: sys_defs.h +set_ugid.o: msg.h +set_ugid.o: set_ugid.c +set_ugid.o: set_ugid.h +set_ugid.o: sys_defs.h +sigdelay.o: msg.h +sigdelay.o: posix_signals.h +sigdelay.o: sigdelay.c +sigdelay.o: sigdelay.h +sigdelay.o: sys_defs.h +skipblanks.o: check_arg.h +skipblanks.o: skipblanks.c +skipblanks.o: stringops.h +skipblanks.o: sys_defs.h +skipblanks.o: vbuf.h +skipblanks.o: vstring.h +slmdb.o: check_arg.h +slmdb.o: slmdb.c +slmdb.o: slmdb.h +sock_addr.o: msg.h +sock_addr.o: sock_addr.c +sock_addr.o: sock_addr.h +sock_addr.o: sys_defs.h +spawn_command.o: argv.h +spawn_command.o: check_arg.h +spawn_command.o: clean_env.h +spawn_command.o: exec_command.h +spawn_command.o: msg.h +spawn_command.o: set_ugid.h +spawn_command.o: spawn_command.c +spawn_command.o: spawn_command.h +spawn_command.o: sys_defs.h +spawn_command.o: timed_wait.h +split_at.o: split_at.c +split_at.o: split_at.h +split_at.o: sys_defs.h +split_nameval.o: check_arg.h +split_nameval.o: msg.h +split_nameval.o: split_nameval.c +split_nameval.o: stringops.h +split_nameval.o: sys_defs.h +split_nameval.o: vbuf.h +split_nameval.o: vstring.h +split_qnameval.o: check_arg.h +split_qnameval.o: msg.h +split_qnameval.o: split_qnameval.c +split_qnameval.o: stringops.h +split_qnameval.o: sys_defs.h +split_qnameval.o: vbuf.h +split_qnameval.o: vstring.h +stat_as.o: msg.h +stat_as.o: set_eugid.h +stat_as.o: stat_as.c +stat_as.o: stat_as.h +stat_as.o: sys_defs.h +stat_as.o: warn_stat.h +strcasecmp.o: strcasecmp.c +strcasecmp.o: sys_defs.h +strcasecmp_utf8.o: check_arg.h +strcasecmp_utf8.o: strcasecmp_utf8.c +strcasecmp_utf8.o: stringops.h +strcasecmp_utf8.o: sys_defs.h +strcasecmp_utf8.o: vbuf.h +strcasecmp_utf8.o: vstring.h +stream_connect.o: connect.h +stream_connect.o: iostuff.h +stream_connect.o: msg.h +stream_connect.o: stream_connect.c +stream_connect.o: sys_defs.h +stream_listen.o: htable.h +stream_listen.o: iostuff.h +stream_listen.o: listen.h +stream_listen.o: msg.h +stream_listen.o: stream_listen.c +stream_listen.o: sys_defs.h +stream_recv_fd.o: iostuff.h +stream_recv_fd.o: msg.h +stream_recv_fd.o: stream_recv_fd.c +stream_recv_fd.o: sys_defs.h +stream_send_fd.o: iostuff.h +stream_send_fd.o: msg.h +stream_send_fd.o: stream_send_fd.c +stream_send_fd.o: sys_defs.h +stream_test.o: check_arg.h +stream_test.o: connect.h +stream_test.o: htable.h +stream_test.o: iostuff.h +stream_test.o: listen.h +stream_test.o: msg.h +stream_test.o: msg_vstream.h +stream_test.o: stream_test.c +stream_test.o: sys_defs.h +stream_test.o: vbuf.h +stream_test.o: vstream.h +stream_trigger.o: connect.h +stream_trigger.o: events.h +stream_trigger.o: iostuff.h +stream_trigger.o: msg.h +stream_trigger.o: mymalloc.h +stream_trigger.o: stream_trigger.c +stream_trigger.o: sys_defs.h +stream_trigger.o: trigger.h +sys_compat.o: sys_compat.c +sys_compat.o: sys_defs.h +timecmp.o: timecmp.c +timecmp.o: timecmp.h +timed_connect.o: iostuff.h +timed_connect.o: msg.h +timed_connect.o: sane_connect.h +timed_connect.o: sys_defs.h +timed_connect.o: timed_connect.c +timed_connect.o: timed_connect.h +timed_read.o: iostuff.h +timed_read.o: msg.h +timed_read.o: sys_defs.h +timed_read.o: timed_read.c +timed_wait.o: msg.h +timed_wait.o: posix_signals.h +timed_wait.o: sys_defs.h +timed_wait.o: timed_wait.c +timed_wait.o: timed_wait.h +timed_write.o: iostuff.h +timed_write.o: msg.h +timed_write.o: sys_defs.h +timed_write.o: timed_write.c +translit.o: check_arg.h +translit.o: stringops.h +translit.o: sys_defs.h +translit.o: translit.c +translit.o: vbuf.h +translit.o: vstring.h +trimblanks.o: check_arg.h +trimblanks.o: stringops.h +trimblanks.o: sys_defs.h +trimblanks.o: trimblanks.c +trimblanks.o: vbuf.h +trimblanks.o: vstring.h +unescape.o: check_arg.h +unescape.o: stringops.h +unescape.o: sys_defs.h +unescape.o: unescape.c +unescape.o: vbuf.h +unescape.o: vstring.h +unix_connect.o: connect.h +unix_connect.o: iostuff.h +unix_connect.o: msg.h +unix_connect.o: sane_connect.h +unix_connect.o: sys_defs.h +unix_connect.o: timed_connect.h +unix_connect.o: unix_connect.c +unix_dgram_connect.o: connect.h +unix_dgram_connect.o: iostuff.h +unix_dgram_connect.o: msg.h +unix_dgram_connect.o: sys_defs.h +unix_dgram_connect.o: unix_dgram_connect.c +unix_dgram_listen.o: htable.h +unix_dgram_listen.o: iostuff.h +unix_dgram_listen.o: listen.h +unix_dgram_listen.o: msg.h +unix_dgram_listen.o: sys_defs.h +unix_dgram_listen.o: unix_dgram_listen.c +unix_listen.o: htable.h +unix_listen.o: iostuff.h +unix_listen.o: listen.h +unix_listen.o: msg.h +unix_listen.o: sane_accept.h +unix_listen.o: sys_defs.h +unix_listen.o: unix_listen.c +unix_pass_fd_fix.o: check_arg.h +unix_pass_fd_fix.o: iostuff.h +unix_pass_fd_fix.o: name_mask.h +unix_pass_fd_fix.o: sys_defs.h +unix_pass_fd_fix.o: unix_pass_fd_fix.c +unix_pass_fd_fix.o: vbuf.h +unix_pass_fd_fix.o: vstring.h +unix_recv_fd.o: iostuff.h +unix_recv_fd.o: msg.h +unix_recv_fd.o: sys_defs.h +unix_recv_fd.o: unix_recv_fd.c +unix_send_fd.o: iostuff.h +unix_send_fd.o: msg.h +unix_send_fd.o: sys_defs.h +unix_send_fd.o: unix_send_fd.c +unix_trigger.o: connect.h +unix_trigger.o: events.h +unix_trigger.o: iostuff.h +unix_trigger.o: msg.h +unix_trigger.o: mymalloc.h +unix_trigger.o: sys_defs.h +unix_trigger.o: trigger.h +unix_trigger.o: unix_trigger.c +unsafe.o: safe.h +unsafe.o: sys_defs.h +unsafe.o: unsafe.c +uppercase.o: check_arg.h +uppercase.o: stringops.h +uppercase.o: sys_defs.h +uppercase.o: uppercase.c +uppercase.o: vbuf.h +uppercase.o: vstring.h +username.o: sys_defs.h +username.o: username.c +username.o: username.h +valid_hostname.o: check_arg.h +valid_hostname.o: msg.h +valid_hostname.o: mymalloc.h +valid_hostname.o: stringops.h +valid_hostname.o: sys_defs.h +valid_hostname.o: valid_hostname.c +valid_hostname.o: valid_hostname.h +valid_hostname.o: vbuf.h +valid_hostname.o: vstring.h +valid_utf8_hostname.o: check_arg.h +valid_utf8_hostname.o: midna_domain.h +valid_utf8_hostname.o: msg.h +valid_utf8_hostname.o: mymalloc.h +valid_utf8_hostname.o: stringops.h +valid_utf8_hostname.o: sys_defs.h +valid_utf8_hostname.o: valid_hostname.h +valid_utf8_hostname.o: valid_utf8_hostname.c +valid_utf8_hostname.o: valid_utf8_hostname.h +valid_utf8_hostname.o: vbuf.h +valid_utf8_hostname.o: vstring.h +valid_utf8_string.o: check_arg.h +valid_utf8_string.o: stringops.h +valid_utf8_string.o: sys_defs.h +valid_utf8_string.o: valid_utf8_string.c +valid_utf8_string.o: vbuf.h +valid_utf8_string.o: vstring.h +vbuf.o: sys_defs.h +vbuf.o: vbuf.c +vbuf.o: vbuf.h +vbuf_print.o: check_arg.h +vbuf_print.o: msg.h +vbuf_print.o: mymalloc.h +vbuf_print.o: sys_defs.h +vbuf_print.o: vbuf.h +vbuf_print.o: vbuf_print.c +vbuf_print.o: vbuf_print.h +vbuf_print.o: vstring.h +vstream.o: check_arg.h +vstream.o: iostuff.h +vstream.o: msg.h +vstream.o: mymalloc.h +vstream.o: sys_defs.h +vstream.o: vbuf.h +vstream.o: vbuf_print.h +vstream.o: vstream.c +vstream.o: vstream.h +vstream.o: vstring.h +vstream_popen.o: argv.h +vstream_popen.o: check_arg.h +vstream_popen.o: clean_env.h +vstream_popen.o: exec_command.h +vstream_popen.o: iostuff.h +vstream_popen.o: msg.h +vstream_popen.o: set_ugid.h +vstream_popen.o: sys_defs.h +vstream_popen.o: vbuf.h +vstream_popen.o: vstream.h +vstream_popen.o: vstream_popen.c +vstream_tweak.o: check_arg.h +vstream_tweak.o: msg.h +vstream_tweak.o: sys_defs.h +vstream_tweak.o: vbuf.h +vstream_tweak.o: vstream.h +vstream_tweak.o: vstream_tweak.c +vstring.o: check_arg.h +vstring.o: msg.h +vstring.o: mymalloc.h +vstring.o: sys_defs.h +vstring.o: vbuf.h +vstring.o: vbuf_print.h +vstring.o: vstring.c +vstring.o: vstring.h +vstring_vstream.o: check_arg.h +vstring_vstream.o: msg.h +vstring_vstream.o: sys_defs.h +vstring_vstream.o: vbuf.h +vstring_vstream.o: vstream.h +vstring_vstream.o: vstring.h +vstring_vstream.o: vstring_vstream.c +vstring_vstream.o: vstring_vstream.h +warn_stat.o: msg.h +warn_stat.o: sys_defs.h +warn_stat.o: warn_stat.c +warn_stat.o: warn_stat.h +watchdog.o: events.h +watchdog.o: iostuff.h +watchdog.o: killme_after.h +watchdog.o: msg.h +watchdog.o: mymalloc.h +watchdog.o: posix_signals.h +watchdog.o: sys_defs.h +watchdog.o: watchdog.c +watchdog.o: watchdog.h +write_buf.o: iostuff.h +write_buf.o: msg.h +write_buf.o: sys_defs.h +write_buf.o: write_buf.c diff --git a/src/util/allascii.c b/src/util/allascii.c new file mode 100644 index 0000000..4ac820e --- /dev/null +++ b/src/util/allascii.c @@ -0,0 +1,65 @@ +/*++ +/* NAME +/* allascii 3 +/* SUMMARY +/* predicate if string is all ASCII +/* SYNOPSIS +/* #include +/* +/* int allascii(buffer) +/* const char *buffer; +/* +/* int allascii_len(buffer, len) +/* const char *buffer; +/* ssize_t len; +/* DESCRIPTION +/* allascii() determines if its argument is an all-ASCII string. +/* +/* Arguments: +/* .IP buffer +/* The null-terminated input string. +/* .IP len +/* The string length, -1 to determine the length dynamically. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include "stringops.h" + +/* allascii_len - return true if string is all ASCII */ + +int allascii_len(const char *string, ssize_t len) +{ + const char *cp; + int ch; + + if (len < 0) + len = strlen(string); + if (len == 0) + return (0); + for (cp = string; cp < string + len + && (ch = *(unsigned char *) cp) != 0; cp++) + if (!ISASCII(ch)) + return (0); + return (1); +} diff --git a/src/util/alldig.c b/src/util/alldig.c new file mode 100644 index 0000000..cabe5c3 --- /dev/null +++ b/src/util/alldig.c @@ -0,0 +1,73 @@ +/*++ +/* NAME +/* alldig 3 +/* SUMMARY +/* predicate if string is all numerical +/* SYNOPSIS +/* #include +/* +/* int alldig(string) +/* const char *string; +/* +/* int allalnum(string) +/* const char *string; +/* DESCRIPTION +/* alldig() determines if its argument is an all-numerical string. +/* +/* allalnum() determines if its argument is an all-alphanumerical +/* string. +/* SEE ALSO +/* An alldig() routine appears in Brian W. Kernighan, P.J. Plauger: +/* "Software Tools", Addison-Wesley 1976. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include + +/* alldig - return true if string is all digits */ + +int alldig(const char *string) +{ + const char *cp; + + if (*string == 0) + return (0); + for (cp = string; *cp != 0; cp++) + if (!ISDIGIT(*cp)) + return (0); + return (1); +} + +/* allalnum - return true if string is all alphanum */ + +int allalnum(const char *string) +{ + const char *cp; + + if (*string == 0) + return (0); + for (cp = string; *cp != 0; cp++) + if (!ISALNUM(*cp)) + return (0); + return (1); +} diff --git a/src/util/allprint.c b/src/util/allprint.c new file mode 100644 index 0000000..6e9c519 --- /dev/null +++ b/src/util/allprint.c @@ -0,0 +1,50 @@ +/*++ +/* NAME +/* allprint 3 +/* SUMMARY +/* predicate if string is all printable +/* SYNOPSIS +/* #include +/* +/* int allprint(buffer) +/* const char *buffer; +/* DESCRIPTION +/* allprint() determines if its argument is an all-printable string. +/* +/* Arguments: +/* .IP buffer +/* The null-terminated input string. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include "stringops.h" + +/* allprint - return true if string is all printable */ + +int allprint(const char *string) +{ + const char *cp; + int ch; + + if (*string == 0) + return (0); + for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++) + if (!ISASCII(ch) || !ISPRINT(ch)) + return (0); + return (1); +} diff --git a/src/util/allspace.c b/src/util/allspace.c new file mode 100644 index 0000000..9a05347 --- /dev/null +++ b/src/util/allspace.c @@ -0,0 +1,50 @@ +/*++ +/* NAME +/* allspace 3 +/* SUMMARY +/* predicate if string is all space +/* SYNOPSIS +/* #include +/* +/* int allspace(buffer) +/* const char *buffer; +/* DESCRIPTION +/* allspace() determines if its argument is an all-space string. +/* +/* Arguments: +/* .IP buffer +/* The null-terminated input string. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include "stringops.h" + +/* allspace - return true if string is all space */ + +int allspace(const char *string) +{ + const char *cp; + int ch; + + if (*string == 0) + return (0); + for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++) + if (!ISASCII(ch) || !ISSPACE(ch)) + return (0); + return (1); +} diff --git a/src/util/argv.c b/src/util/argv.c new file mode 100644 index 0000000..a364e24 --- /dev/null +++ b/src/util/argv.c @@ -0,0 +1,326 @@ +/*++ +/* NAME +/* argv 3 +/* SUMMARY +/* string array utilities +/* SYNOPSIS +/* #include +/* +/* ARGV *argv_alloc(len) +/* ssize_t len; +/* +/* ARGV *argv_sort(argvp) +/* ARGV *argvp; +/* +/* ARGV *argv_free(argvp) +/* ARGV *argvp; +/* +/* void argv_add(argvp, arg, ..., ARGV_END) +/* ARGV *argvp; +/* char *arg; +/* +/* void argv_addn(argvp, arg, arg_len, ..., ARGV_END) +/* ARGV *argvp; +/* char *arg; +/* ssize_t arg_len; +/* +/* void argv_terminate(argvp); +/* ARGV *argvp; +/* +/* void argv_truncate(argvp, len); +/* ARGV *argvp; +/* ssize_t len; +/* +/* void argv_insert_one(argvp, pos, arg) +/* ARGV *argvp; +/* ssize_t pos; +/* const char *arg; +/* +/* void argv_replace_one(argvp, pos, arg) +/* ARGV *argvp; +/* ssize_t pos; +/* const char *arg; +/* +/* void argv_delete(argvp, pos, how_many) +/* ARGV *argvp; +/* ssize_t pos; +/* ssize_t how_many; +/* +/* void ARGV_FAKE_BEGIN(argv, arg) +/* const char *arg; +/* +/* void ARGV_FAKE_END +/* DESCRIPTION +/* The functions in this module manipulate arrays of string +/* pointers. An ARGV structure contains the following members: +/* .IP len +/* The length of the \fIargv\fR array member. +/* .IP argc +/* The number of \fIargv\fR elements used. +/* .IP argv +/* An array of pointers to null-terminated strings. +/* .PP +/* argv_alloc() returns an empty string array of the requested +/* length. The result is ready for use by argv_add(). The array +/* is null terminated. +/* +/* argv_sort() sorts the elements of argvp in place returning +/* the original array. +/* +/* argv_add() copies zero or more strings and adds them to the +/* specified string array. The array is null terminated. +/* Terminate the argument list with a null pointer. The manifest +/* constant ARGV_END provides a convenient notation for this. +/* +/* argv_addn() is like argv_add(), but each string is followed +/* by a string length argument. +/* +/* argv_free() releases storage for a string array, and conveniently +/* returns a null pointer. +/* +/* argv_terminate() null-terminates its string array argument. +/* +/* argv_truncate() truncates its argument to the specified +/* number of entries, but does not reallocate memory. The +/* result is null-terminated. +/* +/* argv_insert_one() inserts one string at the specified array +/* position. +/* +/* argv_replace_one() replaces one string at the specified +/* position. The old string is destroyed after the update is +/* made. +/* +/* argv_delete() deletes the specified number of elements +/* starting at the specified array position. The result is +/* null-terminated. +/* +/* ARGV_FAKE_BEGIN/END are an optimization for the case where +/* a single string needs to be passed into an ARGV-based +/* interface. ARGV_FAKE_BEGIN() opens a statement block and +/* allocates a stack-based ARGV structure named after the first +/* argument, that encapsulates the second argument. This +/* implementation allocates no heap memory and creates no copy +/* of the second argument. ARGV_FAKE_END closes the statement +/* block and thereby releases storage. +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include /* 44BSD stdarg.h uses abort() */ +#include +#include + +/* Application-specific. */ + +#include "mymalloc.h" +#include "msg.h" +#include "argv.h" + +/* argv_free - destroy string array */ + +ARGV *argv_free(ARGV *argvp) +{ + char **cpp; + + for (cpp = argvp->argv; cpp < argvp->argv + argvp->argc; cpp++) + myfree(*cpp); + myfree((void *) argvp->argv); + myfree((void *) argvp); + return (0); +} + +/* argv_alloc - initialize string array */ + +ARGV *argv_alloc(ssize_t len) +{ + ARGV *argvp; + ssize_t sane_len; + + /* + * Make sure that always argvp->argc < argvp->len. + */ + argvp = (ARGV *) mymalloc(sizeof(*argvp)); + argvp->len = 0; + sane_len = (len < 2 ? 2 : len); + argvp->argv = (char **) mymalloc((sane_len + 1) * sizeof(char *)); + argvp->len = sane_len; + argvp->argc = 0; + argvp->argv[0] = 0; + return (argvp); +} + +static int argv_cmp(const void *e1, const void *e2) +{ + const char *s1 = *(const char **) e1; + const char *s2 = *(const char **) e2; + + return strcmp(s1, s2); +} + +/* argv_sort - sort array in place */ + +ARGV *argv_sort(ARGV *argvp) +{ + qsort(argvp->argv, argvp->argc, sizeof(argvp->argv[0]), argv_cmp); + return (argvp); +} + +/* argv_extend - extend array */ + +static void argv_extend(ARGV *argvp) +{ + ssize_t new_len; + + new_len = argvp->len * 2; + argvp->argv = (char **) + myrealloc((void *) argvp->argv, (new_len + 1) * sizeof(char *)); + argvp->len = new_len; +} + +/* argv_add - add string to vector */ + +void argv_add(ARGV *argvp,...) +{ + char *arg; + va_list ap; + + /* + * Make sure that always argvp->argc < argvp->len. + */ +#define ARGV_SPACE_LEFT(a) ((a)->len - (a)->argc - 1) + + va_start(ap, argvp); + while ((arg = va_arg(ap, char *)) != 0) { + if (ARGV_SPACE_LEFT(argvp) <= 0) + argv_extend(argvp); + argvp->argv[argvp->argc++] = mystrdup(arg); + } + va_end(ap); + argvp->argv[argvp->argc] = 0; +} + +/* argv_addn - add string to vector */ + +void argv_addn(ARGV *argvp,...) +{ + char *arg; + ssize_t len; + va_list ap; + + /* + * Make sure that always argvp->argc < argvp->len. + */ + va_start(ap, argvp); + while ((arg = va_arg(ap, char *)) != 0) { + if ((len = va_arg(ap, ssize_t)) < 0) + msg_panic("argv_addn: bad string length %ld", (long) len); + if (ARGV_SPACE_LEFT(argvp) <= 0) + argv_extend(argvp); + argvp->argv[argvp->argc++] = mystrndup(arg, len); + } + va_end(ap); + argvp->argv[argvp->argc] = 0; +} + +/* argv_terminate - terminate string array */ + +void argv_terminate(ARGV *argvp) +{ + + /* + * Trust that argvp->argc < argvp->len. + */ + argvp->argv[argvp->argc] = 0; +} + +/* argv_truncate - truncate string array */ + +void argv_truncate(ARGV *argvp, ssize_t len) +{ + char **cpp; + + /* + * Sanity check. + */ + if (len < 0) + msg_panic("argv_truncate: bad length %ld", (long) len); + + if (len < argvp->argc) { + for (cpp = argvp->argv + len; cpp < argvp->argv + argvp->argc; cpp++) + myfree(*cpp); + argvp->argc = len; + argvp->argv[argvp->argc] = 0; + } +} + +/* argv_insert_one - insert one string into array */ + +void argv_insert_one(ARGV *argvp, ssize_t where, const char *arg) +{ + ssize_t pos; + + /* + * Sanity check. + */ + if (where < 0 || where > argvp->argc) + msg_panic("argv_insert_one bad position: %ld", (long) where); + + if (ARGV_SPACE_LEFT(argvp) <= 0) + argv_extend(argvp); + for (pos = argvp->argc; pos >= where; pos--) + argvp->argv[pos + 1] = argvp->argv[pos]; + argvp->argv[where] = mystrdup(arg); + argvp->argc += 1; +} + +/* argv_replace_one - replace one string in array */ + +void argv_replace_one(ARGV *argvp, ssize_t where, const char *arg) +{ + char *temp; + + /* + * Sanity check. + */ + if (where < 0 || where >= argvp->argc) + msg_panic("argv_replace_one bad position: %ld", (long) where); + + temp = argvp->argv[where]; + argvp->argv[where] = mystrdup(arg); + myfree(temp); +} + +/* argv_delete - remove string(s) from array */ + +void argv_delete(ARGV *argvp, ssize_t first, ssize_t how_many) +{ + ssize_t pos; + + /* + * Sanity check. + */ + if (first < 0 || how_many < 0 || first + how_many > argvp->argc) + msg_panic("argv_delete bad range: (start=%ld count=%ld)", + (long) first, (long) how_many); + + for (pos = first; pos < first + how_many; pos++) + myfree(argvp->argv[pos]); + for (pos = first; pos <= argvp->argc - how_many; pos++) + argvp->argv[pos] = argvp->argv[pos + how_many]; + argvp->argc -= how_many; +} diff --git a/src/util/argv.h b/src/util/argv.h new file mode 100644 index 0000000..1e3b467 --- /dev/null +++ b/src/util/argv.h @@ -0,0 +1,69 @@ +#ifndef _ARGV_H_INCLUDED_ +#define _ARGV_H_INCLUDED_ + +/*++ +/* NAME +/* argv 3h +/* SUMMARY +/* string array utilities +/* SYNOPSIS +/* #include "argv.h" +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +typedef struct ARGV { + ssize_t len; /* number of array elements */ + ssize_t argc; /* array elements in use */ + char **argv; /* string array */ +} ARGV; + +extern ARGV *argv_alloc(ssize_t); +extern ARGV *argv_sort(ARGV *); +extern void argv_add(ARGV *,...); +extern void argv_addn(ARGV *,...); +extern void argv_terminate(ARGV *); +extern void argv_truncate(ARGV *, ssize_t); +extern void argv_insert_one(ARGV *, ssize_t, const char *); +extern void argv_replace_one(ARGV *, ssize_t, const char *); +extern void argv_delete(ARGV *, ssize_t, ssize_t); +extern ARGV *argv_free(ARGV *); + +extern ARGV *argv_split(const char *, const char *); +extern ARGV *argv_split_count(const char *, const char *, ssize_t); +extern ARGV *argv_split_append(ARGV *, const char *, const char *); + +extern ARGV *argv_splitq(const char *, const char *, const char *); +extern ARGV *argv_splitq_count(const char *, const char *, const char *, ssize_t); +extern ARGV *argv_splitq_append(ARGV *, const char *, const char *, const char *); + +extern ARGV *argv_split_at(const char *, int); +extern ARGV *argv_split_at_count(const char *, int, ssize_t); +extern ARGV *argv_split_at_append(ARGV *, const char *, int); + +#define ARGV_FAKE_BEGIN(fake_argv, arg) { \ + ARGV fake_argv; \ + char *__fake_argv_args__[2]; \ + __fake_argv_args__[0] = (char *) (arg); \ + __fake_argv_args__[1] = 0; \ + fake_argv.argv = __fake_argv_args__; \ + fake_argv.argc = fake_argv.len = 1; + +#define ARGV_FAKE_END } + +#define ARGV_END ((char *) 0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/argv_attr.h b/src/util/argv_attr.h new file mode 100644 index 0000000..53c587f --- /dev/null +++ b/src/util/argv_attr.h @@ -0,0 +1,43 @@ +#ifndef _ARGV_ATTR_H_INCLUDED_ +#define _ARGV_ATTR_H_INCLUDED_ + +/*++ +/* NAME +/* argv_attr 3h +/* SUMMARY +/* argv serialization/deserialization +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * External API. + */ +#define ARGV_ATTR_SIZE "argv_size" +#define ARGV_ATTR_VALUE "argv_value" +#define ARGV_ATTR_MAX 1024 + +extern int argv_attr_print(ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); +extern int argv_attr_scan(ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/argv_attr_print.c b/src/util/argv_attr_print.c new file mode 100644 index 0000000..78e3315 --- /dev/null +++ b/src/util/argv_attr_print.c @@ -0,0 +1,73 @@ +/*++ +/* NAME +/* argv_attr_print +/* SUMMARY +/* write ARGV to stream +/* SYNOPSIS +/* #include +/* +/* int argv_attr_print(print_fn, stream, flags, ptr) +/* ATTR_PRINT_COMMON_FN print_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* DESCRIPTION +/* argv_attr_print() writes an ARGV to the named stream using +/* the specified attribute print routine. argv_attr_print() is meant +/* to be passed as a call-back to attr_print(), thusly: +/* +/* ... SEND_ATTR_FUNC(argv_attr_print, (const void *) argv), ... +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* The result value is zero in case of success, non-zero +/* otherwise. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + +/* argv_attr_print - write ARGV to stream */ + +int argv_attr_print(ATTR_PRINT_COMMON_FN print_fn, VSTREAM *fp, + int flags, const void *ptr) +{ + ARGV *argv = (ARGV *) ptr; + int n; + int ret; + int argc = argv ? argv->argc : 0; + + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_INT(ARGV_ATTR_SIZE, argc), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("argv_attr_print count=%d", argc); + for (n = 0; ret == 0 && n < argc; n++) + ret = print_fn(fp, flags | ATTR_FLAG_MORE, + SEND_ATTR_STR(ARGV_ATTR_VALUE, argv->argv[n]), + ATTR_TYPE_END); + if (msg_verbose) + msg_info("argv_attr_print ret=%d", ret); + /* Do not flush the stream. */ + return (ret); +} diff --git a/src/util/argv_attr_scan.c b/src/util/argv_attr_scan.c new file mode 100644 index 0000000..0987bf8 --- /dev/null +++ b/src/util/argv_attr_scan.c @@ -0,0 +1,93 @@ +/*++ +/* NAME +/* argv_attr_scan +/* SUMMARY +/* read ARGV from stream +/* SYNOPSIS +/* #include +/* +/* int argv_attr_scan(scan_fn, stream, flags, ptr) +/* ATTR_SCAN_COMMON_FN scan_fn; +/* VSTREAM *stream; +/* int flags; +/* void *ptr; +/* DESCRIPTION +/* argv_attr_scan() creates an ARGV and reads its contents +/* from the named stream using the specified attribute scan +/* routine. argv_attr_scan() is meant to be passed as a call-back +/* to attr_scan(), thusly: +/* +/* ARGV *argv = 0; +/* ... +/* ... RECV_ATTR_FUNC(argv_attr_scan, (void *) &argv), ... +/* ... +/* if (argv) +/* argv_free(argv); +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* In case of error, this function returns non-zero and creates +/* an ARGV null pointer. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + +/* argv_attr_scan - write ARGV to stream */ + +int argv_attr_scan(ATTR_PRINT_COMMON_FN scan_fn, VSTREAM *fp, + int flags, void *ptr) +{ + ARGV *argv = 0; + int size; + int ret; + + if ((ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_INT(ARGV_ATTR_SIZE, &size), + ATTR_TYPE_END)) == 1) { + if (msg_verbose) + msg_info("argv_attr_scan count=%d", size); + if (size < 0 || size > ARGV_ATTR_MAX) { + msg_warn("invalid size %d from %s while reading ARGV", + size, VSTREAM_PATH(fp)); + ret = -1; + } else if (size > 0) { + VSTRING *buffer = vstring_alloc(100); + + argv = argv_alloc(size); + while (ret == 1 && size-- > 0) { + if ((ret = scan_fn(fp, flags | ATTR_FLAG_MORE, + RECV_ATTR_STR(ARGV_ATTR_VALUE, buffer), + ATTR_TYPE_END)) == 1) + argv_add(argv, vstring_str(buffer), ARGV_END); + } + argv_terminate(argv); + vstring_free(buffer); + } + } + *(ARGV **) ptr = argv; + if (msg_verbose) + msg_info("argv_attr_scan ret=%d", ret); + return (ret); +} diff --git a/src/util/argv_split.c b/src/util/argv_split.c new file mode 100644 index 0000000..a9f3afb --- /dev/null +++ b/src/util/argv_split.c @@ -0,0 +1,112 @@ +/*++ +/* NAME +/* argv_split 3 +/* SUMMARY +/* string array utilities +/* SYNOPSIS +/* #include +/* +/* ARGV *argv_split(string, delim) +/* const char *string; +/* const char *delim; +/* +/* ARGV *argv_split_count(string, delim, count) +/* const char *string; +/* const char *delim; +/* ssize_t count; +/* +/* ARGV *argv_split_append(argv, string, delim) +/* ARGV *argv; +/* const char *string; +/* const char *delim; +/* DESCRIPTION +/* argv_split() breaks up \fIstring\fR into tokens according +/* to the delimiters specified in \fIdelim\fR. The result is +/* a null-terminated string array. +/* +/* argv_split_count() is like argv_split() but stops splitting +/* input after at most \fIcount\fR -1 times and leaves the +/* remainder, if any, in the last array element. It is an error +/* to specify a count < 1. +/* +/* argv_split_append() performs the same operation as argv_split(), +/* but appends the result to an existing string array. +/* SEE ALSO +/* mystrtok(), safe string splitter. +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include + +/* Application-specific. */ + +#include "mymalloc.h" +#include "stringops.h" +#include "argv.h" +#include "msg.h" + +/* argv_split - split string into token array */ + +ARGV *argv_split(const char *string, const char *delim) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = mystrtok(&bp, delim)) != 0) + argv_add(argvp, arg, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_split_count - split string into token array */ + +ARGV *argv_split_count(const char *string, const char *delim, ssize_t count) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + if (count < 1) + msg_panic("argv_split_count: bad count: %ld", (long) count); + while (count-- > 1 && (arg = mystrtok(&bp, delim)) != 0) + argv_add(argvp, arg, (char *) 0); + if (*bp) + bp += strspn(bp, delim); + if (*bp) + argv_add(argvp, bp, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_split_append - split string into token array, append to array */ + +ARGV *argv_split_append(ARGV *argvp, const char *string, const char *delim) +{ + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = mystrtok(&bp, delim)) != 0) + argv_add(argvp, arg, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} diff --git a/src/util/argv_split_at.c b/src/util/argv_split_at.c new file mode 100644 index 0000000..fcdb4ff --- /dev/null +++ b/src/util/argv_split_at.c @@ -0,0 +1,124 @@ +/*++ +/* NAME +/* argv_split_at 3 +/* SUMMARY +/* string array utilities +/* SYNOPSIS +/* #include +/* +/* ARGV *argv_split_at(string, sep) +/* const char *string; +/* int sep; +/* +/* ARGV *argv_split_at_count(string, sep, count) +/* const char *string; +/* int sep; +/* ssize_t count; +/* +/* ARGV *argv_split_at_append(argv, string, sep) +/* ARGV *argv; +/* const char *string; +/* int sep; +/* DESCRIPTION +/* argv_split_at() splits \fIstring\fR into fields using a +/* single separator specified in \fIsep\fR. The result is a +/* null-terminated string array. +/* +/* argv_split_at_count() is like argv_split_at() but stops +/* splitting input after at most \fIcount\fR -1 times and +/* leaves the remainder, if any, in the last array element. +/* It is an error to specify a count < 1. +/* +/* argv_split_at_append() performs the same operation as +/* argv_split_at(), but appends the result to an existing +/* string array. +/* SEE ALSO +/* split_at(), trivial string splitter. +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include +#include + +/* Application-specific. */ + +#include +#include +#include +#include +#include + +/* argv_split_at - split string into field array */ + +ARGV *argv_split_at(const char *string, int sep) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = split_at(bp, sep)) != 0) { + argv_add(argvp, bp, (char *) 0); + bp = arg; + } + argv_add(argvp, bp, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_split_at_count - split string into field array */ + +ARGV *argv_split_at_count(const char *string, int sep, ssize_t count) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + if (count < 1) + msg_panic("argv_split_at_count: bad count: %ld", (long) count); + while (count-- > 1 && (arg = split_at(bp, sep)) != 0) { + argv_add(argvp, bp, (char *) 0); + bp = arg; + } + argv_add(argvp, bp, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_split_at_append - split string into field array, append to array */ + +ARGV *argv_split_at_append(ARGV *argvp, const char *string, int sep) +{ + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = split_at(bp, sep)) != 0) { + argv_add(argvp, bp, (char *) 0); + bp = arg; + } + argv_add(argvp, bp, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} diff --git a/src/util/argv_splitq.c b/src/util/argv_splitq.c new file mode 100644 index 0000000..3900ee1 --- /dev/null +++ b/src/util/argv_splitq.c @@ -0,0 +1,118 @@ +/*++ +/* NAME +/* argv_splitq 3 +/* SUMMARY +/* string array utilities +/* SYNOPSIS +/* #include +/* +/* ARGV *argv_splitq(string, delim, parens) +/* const char *string; +/* const char *delim; +/* const char *parens; +/* +/* ARGV *argv_splitq_count(string, delim, parens, count) +/* const char *string; +/* const char *delim; +/* const char *parens; +/* ssize_t count; +/* +/* ARGV *argv_splitq_append(argv, string, delim, parens) +/* ARGV *argv; +/* const char *string; +/* const char *delim; +/* const char *parens; +/* DESCRIPTION +/* argv_splitq() breaks up \fIstring\fR into tokens according +/* to the delimiters specified in \fIdelim\fR, while avoiding +/* splitting text between matching parentheses. The result is +/* a null-terminated string array. +/* +/* argv_splitq_count() is like argv_splitq() but stops splitting +/* input after at most \fIcount\fR -1 times and leaves the +/* remainder, if any, in the last array element. It is an error +/* to specify a count < 1. +/* +/* argv_splitq_append() performs the same operation as argv_splitq(), +/* but appends the result to an existing string array. +/* SEE ALSO +/* mystrtokq(), safe string splitter. +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include + +/* Application-specific. */ + +#include "mymalloc.h" +#include "stringops.h" +#include "argv.h" +#include "msg.h" + +/* argv_splitq - split string into token array */ + +ARGV *argv_splitq(const char *string, const char *delim, const char *parens) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = mystrtokq(&bp, delim, parens)) != 0) + argv_add(argvp, arg, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_splitq_count - split string into token array */ + +ARGV *argv_splitq_count(const char *string, const char *delim, + const char *parens, ssize_t count) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + if (count < 1) + msg_panic("argv_splitq_count: bad count: %ld", (long) count); + while (count-- > 1 && (arg = mystrtokq(&bp, delim, parens)) != 0) + argv_add(argvp, arg, (char *) 0); + if (*bp) + bp += strspn(bp, delim); + if (*bp) + argv_add(argvp, bp, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} + +/* argv_splitq_append - split string into token array, append to array */ + +ARGV *argv_splitq_append(ARGV *argvp, const char *string, const char *delim, + const char *parens) +{ + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + + while ((arg = mystrtokq(&bp, delim, parens)) != 0) + argv_add(argvp, arg, (char *) 0); + argv_terminate(argvp); + myfree(saved_string); + return (argvp); +} diff --git a/src/util/attr.h b/src/util/attr.h new file mode 100644 index 0000000..067405f --- /dev/null +++ b/src/util/attr.h @@ -0,0 +1,184 @@ +#ifndef _ATTR_H_INCLUDED_ +#define _ATTR_H_INCLUDED_ + +/*++ +/* NAME +/* attr 3h +/* SUMMARY +/* attribute list manipulations +/* SYNOPSIS +/* #include "attr.h" + DESCRIPTION + .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * Delegation for better data abstraction. + */ +typedef int (*ATTR_SCAN_COMMON_FN) (VSTREAM *, int,...); +typedef int (*ATTR_SCAN_CUSTOM_FN) (ATTR_SCAN_COMMON_FN, VSTREAM *, int, void *); +typedef int (*ATTR_PRINT_COMMON_FN) (VSTREAM *, int,...); +typedef int (*ATTR_PRINT_CUSTOM_FN) (ATTR_PRINT_COMMON_FN, VSTREAM *, int, const void *); + + /* + * Attribute types. See attr_scan(3) for documentation. + */ +#define ATTR_TYPE_END 0 /* end of data */ +#define ATTR_TYPE_INT 1 /* Unsigned integer */ +#define ATTR_TYPE_NUM ATTR_TYPE_INT +#define ATTR_TYPE_STR 2 /* Character string */ +#define ATTR_TYPE_HASH 3 /* Hash table */ +#define ATTR_TYPE_NV 3 /* Name-value table */ +#define ATTR_TYPE_LONG 4 /* Unsigned long */ +#define ATTR_TYPE_DATA 5 /* Binary data */ +#define ATTR_TYPE_FUNC 6 /* Function pointer */ +#define ATTR_TYPE_STREQ 7 /* Requires (name, value) match */ + + /* + * Optional sender-specified grouping for hash or nameval tables. + */ +#define ATTR_TYPE_OPEN '{' +#define ATTR_TYPE_CLOSE '}' +#define ATTR_NAME_OPEN "{" +#define ATTR_NAME_CLOSE "}" + +#define ATTR_HASH_LIMIT 1024 /* Size of hash table */ + + /* + * Typechecking support for variadic function arguments. See check_arg(3h) + * for documentation. + */ +#define SEND_ATTR_INT(name, val) ATTR_TYPE_INT, CHECK_CPTR(ATTR, char, (name)), CHECK_VAL(ATTR, int, (val)) +#define SEND_ATTR_STR(name, val) ATTR_TYPE_STR, CHECK_CPTR(ATTR, char, (name)), CHECK_CPTR(ATTR, char, (val)) +#define SEND_ATTR_HASH(val) ATTR_TYPE_HASH, CHECK_CPTR(ATTR, HTABLE, (val)) +#define SEND_ATTR_NV(val) ATTR_TYPE_NV, CHECK_CPTR(ATTR, NVTABLE, (val)) +#define SEND_ATTR_LONG(name, val) ATTR_TYPE_LONG, CHECK_CPTR(ATTR, char, (name)), CHECK_VAL(ATTR, long, (val)) +#define SEND_ATTR_DATA(name, len, val) ATTR_TYPE_DATA, CHECK_CPTR(ATTR, char, (name)), CHECK_VAL(ATTR, ssize_t, (len)), CHECK_CPTR(ATTR, void, (val)) +#define SEND_ATTR_FUNC(func, val) ATTR_TYPE_FUNC, CHECK_VAL(ATTR, ATTR_PRINT_CUSTOM_FN, (func)), CHECK_CPTR(ATTR, void, (val)) + +#define RECV_ATTR_INT(name, val) ATTR_TYPE_INT, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, int, (val)) +#define RECV_ATTR_STR(name, val) ATTR_TYPE_STR, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, VSTRING, (val)) +#define RECV_ATTR_STREQ(name, val) ATTR_TYPE_STREQ, CHECK_CPTR(ATTR, char, (name)), CHECK_CPTR(ATTR, char, (val)) +#define RECV_ATTR_HASH(val) ATTR_TYPE_HASH, CHECK_PTR(ATTR, HTABLE, (val)) +#define RECV_ATTR_NV(val) ATTR_TYPE_NV, CHECK_PTR(ATTR, NVTABLE, (val)) +#define RECV_ATTR_LONG(name, val) ATTR_TYPE_LONG, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, long, (val)) +#define RECV_ATTR_DATA(name, val) ATTR_TYPE_DATA, CHECK_CPTR(ATTR, char, (name)), CHECK_PTR(ATTR, VSTRING, (val)) +#define RECV_ATTR_FUNC(func, val) ATTR_TYPE_FUNC, CHECK_VAL(ATTR, ATTR_SCAN_CUSTOM_FN, (func)), CHECK_PTR(ATTR, void, (val)) + +CHECK_VAL_HELPER_DCL(ATTR, ssize_t); +CHECK_VAL_HELPER_DCL(ATTR, long); +CHECK_VAL_HELPER_DCL(ATTR, int); +CHECK_PTR_HELPER_DCL(ATTR, void); +CHECK_PTR_HELPER_DCL(ATTR, long); +CHECK_PTR_HELPER_DCL(ATTR, int); +CHECK_PTR_HELPER_DCL(ATTR, VSTRING); +CHECK_PTR_HELPER_DCL(ATTR, NVTABLE); +CHECK_PTR_HELPER_DCL(ATTR, HTABLE); +CHECK_CPTR_HELPER_DCL(ATTR, void); +CHECK_CPTR_HELPER_DCL(ATTR, char); +CHECK_CPTR_HELPER_DCL(ATTR, NVTABLE); +CHECK_CPTR_HELPER_DCL(ATTR, HTABLE); +CHECK_VAL_HELPER_DCL(ATTR, ATTR_PRINT_CUSTOM_FN); +CHECK_VAL_HELPER_DCL(ATTR, ATTR_SCAN_CUSTOM_FN); + + /* + * Flags that control processing. See attr_scan(3) for documentation. + */ +#define ATTR_FLAG_NONE 0 +#define ATTR_FLAG_MISSING (1<<0) /* Flag missing attribute */ +#define ATTR_FLAG_EXTRA (1<<1) /* Flag spurious attribute */ +#define ATTR_FLAG_MORE (1<<2) /* Don't skip or terminate */ +#define ATTR_FLAG_PRINTABLE (1<<3) /* Sanitize received strings */ + +#define ATTR_FLAG_STRICT (ATTR_FLAG_MISSING | ATTR_FLAG_EXTRA) +#define ATTR_FLAG_ALL (017) + + /* + * Default to null-terminated, as opposed to base64-encoded. + */ +#define attr_print attr_print0 +#define attr_vprint attr_vprint0 +#define attr_scan attr_scan0 +#define attr_vscan attr_vscan0 +#define attr_scan_more attr_scan_more0 + + /* + * attr_print64.c. + */ +extern int attr_print64(VSTREAM *, int,...); +extern int attr_vprint64(VSTREAM *, int, va_list); + + /* + * attr_scan64.c. + */ +extern int WARN_UNUSED_RESULT attr_scan64(VSTREAM *, int,...); +extern int WARN_UNUSED_RESULT attr_vscan64(VSTREAM *, int, va_list); +extern int WARN_UNUSED_RESULT attr_scan_more64(VSTREAM *); + + /* + * attr_print0.c. + */ +extern int attr_print0(VSTREAM *, int,...); +extern int attr_vprint0(VSTREAM *, int, va_list); + + /* + * attr_scan0.c. + */ +extern int WARN_UNUSED_RESULT attr_scan0(VSTREAM *, int,...); +extern int WARN_UNUSED_RESULT attr_vscan0(VSTREAM *, int, va_list); +extern int WARN_UNUSED_RESULT attr_scan_more0(VSTREAM *); + + /* + * attr_scan_plain.c. + */ +extern int attr_print_plain(VSTREAM *, int,...); +extern int attr_vprint_plain(VSTREAM *, int, va_list); +extern int attr_scan_more_plain(VSTREAM *); + + /* + * attr_print_plain.c. + */ +extern int WARN_UNUSED_RESULT attr_scan_plain(VSTREAM *, int,...); +extern int WARN_UNUSED_RESULT attr_vscan_plain(VSTREAM *, int, va_list); + + /* + * Attribute names for testing the compatibility of the read and write + * routines. + */ +#ifdef TEST +#define ATTR_NAME_INT "number" +#define ATTR_NAME_STR "string" +#define ATTR_NAME_LONG "long_number" +#define ATTR_NAME_DATA "data" +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/attr_clnt.c b/src/util/attr_clnt.c new file mode 100644 index 0000000..d944be5 --- /dev/null +++ b/src/util/attr_clnt.c @@ -0,0 +1,300 @@ +/*++ +/* NAME +/* attr_clnt 3 +/* SUMMARY +/* attribute query-reply client +/* SYNOPSIS +/* #include +/* +/* typedef int (*ATTR_CLNT_PRINT_FN) (VSTREAM *, int, va_list); +/* typedef int (*ATTR_CLNT_SCAN_FN) (VSTREAM *, int, va_list); +/* typedef int (*ATTR_CLNT_HANDSHAKE_FN) (VSTREAM *); +/* +/* ATTR_CLNT *attr_clnt_create(server, timeout, max_idle, max_ttl) +/* const char *server; +/* int timeout; +/* int max_idle; +/* int max_ttl; +/* +/* int attr_clnt_request(client, +/* send_flags, send_type, send_name, ..., ATTR_TYPE_END, +/* recv_flags, recv_type, recv_name, ..., ATTR_TYPE_END) +/* ATTR_CLNT *client; +/* int send_flags; +/* int send_type; +/* const char *send_name; +/* int recv_flags; +/* int recv_type; +/* const char *recv_name; +/* +/* void attr_clnt_free(client) +/* ATTR_CLNT *client; +/* +/* void attr_clnt_control(client, name, value, ... ATTR_CLNT_CTL_END) +/* ATTR_CLNT *client; +/* int name; +/* DESCRIPTION +/* This module implements a client for a simple attribute-based +/* protocol. The default protocol is described in attr_scan_plain(3). +/* +/* attr_clnt_create() creates a client handle. See auto_clnt(3) for +/* a description of the arguments. +/* +/* attr_clnt_request() sends the specified request attributes and +/* receives a reply. The reply argument specifies a name-value table. +/* The other arguments are as described in attr_print_plain(3). The +/* result is the number of attributes received or -1 in case of trouble. +/* +/* attr_clnt_free() destroys a client handle and closes its connection. +/* +/* attr_clnt_control() allows the user to fine tune the behavior of +/* the specified client. The arguments are a list of (name, value) +/* terminated with ATTR_CLNT_CTL_END. +/* The following lists the names and the types of the corresponding +/* value arguments. +/* .IP "ATTR_CLNT_CTL_PROTO(ATTR_CLNT_PRINT_FN, ATTR_CLNT_SCAN_FN)" +/* Specifies alternatives for the attr_plain_print() and +/* attr_plain_scan() functions. +/* .IP "ATTR_CLNT_CTL_REQ_LIMIT(int)" +/* The maximal number of requests per connection (default: 0, +/* i.e. no limit). To enable the limit, specify a value greater +/* than zero. +/* .IP "ATTR_CLNT_CTL_TRY_LIMIT(int)" +/* The maximal number of attempts to send a request before +/* giving up (default: 2). To disable the limit, specify a +/* value equal to zero. +/* .IP "ATTR_CLNT_CTL_TRY_DELAY(int)" +/* The time in seconds between attempts to send a request +/* (default: 1). Specify a value greater than zero. +/* .IP "ATTR_CLNT_CTL_HANDSHAKE(VSTREAM *)" +/* A pointer to function that will be called at the start of a +/* new connection, and that returns 0 in case of success. +/* DIAGNOSTICS +/* Warnings: communication failure. +/* SEE ALSO +/* auto_clnt(3), client endpoint management +/* attr_scan_plain(3), attribute protocol +/* attr_print_plain(3), attribute protocol +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +struct ATTR_CLNT { + AUTO_CLNT *auto_clnt; + /* Remaining properties are set with attr_clnt_control(). */ + ATTR_CLNT_PRINT_FN print; + ATTR_CLNT_SCAN_FN scan; + int req_limit; + int req_count; + int try_limit; + int try_delay; +}; + +#define ATTR_CLNT_DEF_REQ_LIMIT (0) /* default per-session request limit */ +#define ATTR_CLNT_DEF_TRY_LIMIT (2) /* default request (re)try limit */ +#define ATTR_CLNT_DEF_TRY_DELAY (1) /* default request (re)try delay */ + +/* attr_clnt_free - destroy attribute client */ + +void attr_clnt_free(ATTR_CLNT *client) +{ + auto_clnt_free(client->auto_clnt); + myfree((void *) client); +} + +/* attr_clnt_create - create attribute client */ + +ATTR_CLNT *attr_clnt_create(const char *service, int timeout, + int max_idle, int max_ttl) +{ + ATTR_CLNT *client; + + client = (ATTR_CLNT *) mymalloc(sizeof(*client)); + client->auto_clnt = auto_clnt_create(service, timeout, max_idle, max_ttl); + client->scan = attr_vscan_plain; + client->print = attr_vprint_plain; + client->req_limit = ATTR_CLNT_DEF_REQ_LIMIT; + client->req_count = 0; + client->try_limit = ATTR_CLNT_DEF_TRY_LIMIT; + client->try_delay = ATTR_CLNT_DEF_TRY_DELAY; + return (client); +} + +/* attr_clnt_request - send query, receive reply */ + +int attr_clnt_request(ATTR_CLNT *client, int send_flags,...) +{ + const char *myname = "attr_clnt_request"; + VSTREAM *stream; + int count = 0; + va_list saved_ap; + va_list ap; + int type; + int recv_flags; + int err; + int ret; + + /* + * XXX If the stream is readable before we send anything, then assume the + * remote end disconnected. + * + * XXX For some reason we can't simply call the scan routine after the print + * routine, that messes up the argument list. + */ +#define SKIP_ARG(ap, type) { \ + (void) va_arg(ap, char *); \ + (void) va_arg(ap, type); \ + } +#define SKIP_ARG2(ap, t1, t2) { \ + SKIP_ARG(ap, t1); \ + (void) va_arg(ap, t2); \ + } + + /* Finalize argument lists before returning. */ + va_start(saved_ap, send_flags); + for (;;) { + errno = 0; + if ((stream = auto_clnt_access(client->auto_clnt)) != 0 + && readable(vstream_fileno(stream)) == 0) { + errno = 0; + VA_COPY(ap, saved_ap); + err = (client->print(stream, send_flags, ap) != 0 + || vstream_fflush(stream) != 0); + va_end(ap); + if (err == 0) { + VA_COPY(ap, saved_ap); + while ((type = va_arg(ap, int)) != ATTR_TYPE_END) { + switch (type) { + case ATTR_TYPE_STR: + SKIP_ARG(ap, char *); + break; + case ATTR_TYPE_DATA: + SKIP_ARG2(ap, ssize_t, char *); + break; + case ATTR_TYPE_INT: + SKIP_ARG(ap, int); + break; + case ATTR_TYPE_LONG: + SKIP_ARG(ap, long); + break; + case ATTR_TYPE_HASH: + (void) va_arg(ap, HTABLE *); + break; + default: + msg_panic("%s: unexpected attribute type %d", + myname, type); + } + } + recv_flags = va_arg(ap, int); + ret = client->scan(stream, recv_flags, ap); + va_end(ap); + /* Finalize argument lists before returning. */ + if (ret > 0) { + if (client->req_limit > 0 + && (client->req_count += 1) >= client->req_limit) { + auto_clnt_recover(client->auto_clnt); + client->req_count = 0; + } + break; + } + } + } + if ((++count >= client->try_limit && client->try_limit > 0) + || msg_verbose + || (errno && errno != EPIPE && errno != ENOENT && errno != ECONNRESET)) + msg_warn("problem talking to server %s: %m", + auto_clnt_name(client->auto_clnt)); + /* Finalize argument lists before returning. */ + if (count >= client->try_limit && client->try_limit > 0) { + ret = -1; + break; + } + sleep(client->try_delay); + auto_clnt_recover(client->auto_clnt); + client->req_count = 0; + } + /* Finalize argument lists before returning. */ + va_end(saved_ap); + return (ret); +} + +/* attr_clnt_control - fine control */ + +void attr_clnt_control(ATTR_CLNT *client, int name,...) +{ + const char *myname = "attr_clnt_control"; + va_list ap; + + for (va_start(ap, name); name != ATTR_CLNT_CTL_END; name = va_arg(ap, int)) { + switch (name) { + case ATTR_CLNT_CTL_PROTO: + client->print = va_arg(ap, ATTR_CLNT_PRINT_FN); + client->scan = va_arg(ap, ATTR_CLNT_SCAN_FN); + break; + case ATTR_CLNT_CTL_HANDSHAKE: + auto_clnt_control(client->auto_clnt, + AUTO_CLNT_CTL_HANDSHAKE, + va_arg(ap, ATTR_CLNT_HANDSHAKE_FN), + AUTO_CLNT_CTL_END); + break; + case ATTR_CLNT_CTL_REQ_LIMIT: + client->req_limit = va_arg(ap, int); + if (client->req_limit < 0) + msg_panic("%s: bad request limit: %d", + myname, client->req_limit); + if (msg_verbose) + msg_info("%s: new request limit %d", + myname, client->req_limit); + break; + case ATTR_CLNT_CTL_TRY_LIMIT: + client->try_limit = va_arg(ap, int); + if (client->try_limit < 0) + msg_panic("%s: bad retry limit: %d", myname, client->try_limit); + if (msg_verbose) + msg_info("%s: new retry limit %d", myname, client->try_limit); + break; + case ATTR_CLNT_CTL_TRY_DELAY: + client->try_delay = va_arg(ap, int); + if (client->try_delay <= 0) + msg_panic("%s: bad retry delay: %d", myname, client->try_delay); + if (msg_verbose) + msg_info("%s: new retry delay %d", myname, client->try_delay); + break; + default: + msg_panic("%s: bad name %d", myname, name); + } + } + va_end(ap); +} diff --git a/src/util/attr_clnt.h b/src/util/attr_clnt.h new file mode 100644 index 0000000..ca630cd --- /dev/null +++ b/src/util/attr_clnt.h @@ -0,0 +1,60 @@ +#ifndef _ATTR_CLNT_H_INCLUDED_ +#define _ATTR_CLNT_H_INCLUDED_ + +/*++ +/* NAME +/* attr_clnt 3h +/* SUMMARY +/* attribute query-reply client +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +typedef struct ATTR_CLNT ATTR_CLNT; +typedef int (*ATTR_CLNT_PRINT_FN) (VSTREAM *, int, va_list); +typedef int (*ATTR_CLNT_SCAN_FN) (VSTREAM *, int, va_list); +typedef int (*ATTR_CLNT_HANDSHAKE_FN) (VSTREAM *); + +extern ATTR_CLNT *attr_clnt_create(const char *, int, int, int); +extern int attr_clnt_request(ATTR_CLNT *, int,...); +extern void attr_clnt_free(ATTR_CLNT *); +extern void attr_clnt_control(ATTR_CLNT *, int,...); + +#define ATTR_CLNT_CTL_END 0 +#define ATTR_CLNT_CTL_PROTO 1 /* print/scan functions */ +#define ATTR_CLNT_CTL_REQ_LIMIT 2 /* requests per connection */ +#define ATTR_CLNT_CTL_TRY_LIMIT 3 /* attempts per request */ +#define ATTR_CLNT_CTL_TRY_DELAY 4 /* pause between requests */ +#define ATTR_CLNT_CTL_HANDSHAKE 5 /* handshake before first request */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/attr_print0.c b/src/util/attr_print0.c new file mode 100644 index 0000000..98c5118 --- /dev/null +++ b/src/util/attr_print0.c @@ -0,0 +1,256 @@ +/*++ +/* NAME +/* attr_print0 3 +/* SUMMARY +/* send attributes over byte stream +/* SYNOPSIS +/* #include +/* +/* int attr_print0(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vprint0(fp, flags, ap) +/* VSTREAM fp; +/* int flags; +/* va_list ap; +/* DESCRIPTION +/* attr_print0() takes zero or more (name, value) simple attributes +/* and converts its input to a byte stream that can be recovered with +/* attr_scan0(). The stream is not flushed. +/* +/* attr_vprint0() provides an alternate interface that is convenient +/* for calling from within variadic functions. +/* +/* Attributes are sent in the requested order as specified with the +/* attr_print0() argument list. This routine satisfies the formatting +/* rules as outlined in attr_scan0(3). +/* +/* Arguments: +/* .IP fp +/* Stream to write the result to. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MORE +/* After sending the requested attributes, leave the output stream in +/* a state that is usable for more attribute sending operations on +/* the same output attribute list. +/* By default, attr_print0() automatically appends an attribute list +/* terminator when it has sent the last requested attribute. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "SEND_ATTR_INT(const char *name, int value)" +/* The arguments are an attribute name and an integer. +/* .IP "SEND_ATTR_LONG(const char *name, long value)" +/* The arguments are an attribute name and a long integer. +/* .IP "SEND_ATTR_STR(const char *name, const char *value)" +/* The arguments are an attribute name and a null-terminated +/* string. +/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)" +/* The arguments are an attribute name, an attribute value +/* length, and an attribute value pointer. +/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)" +/* The arguments are a function pointer and generic data +/* pointer. The caller-specified function returns whatever the +/* specified attribute printing function returns. +/* .IP "SEND_ATTR_HASH(const HTABLE *table)" +/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)" +/* The content of the table is sent as a sequence of string-valued +/* attributes with names equal to the table lookup keys. +/* .IP ATTR_TYPE_END +/* This terminates the attribute list. +/* .RE +/* DIAGNOSTICS +/* The result value is 0 in case of success, VSTREAM_EOF in case +/* of trouble. +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_scan0(3) recover attributes from byte stream +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_vprint0 - send attribute list to stream */ + +int attr_vprint0(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_print0"; + int attr_type; + char *attr_name; + unsigned int_val; + unsigned long long_val; + char *str_val; + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + ssize_t len_val; + static VSTRING *base64_buf; + ATTR_PRINT_CUSTOM_FN print_fn; + void *print_arg; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * Iterate over all (type, name, value) triples, and produce output on + * the fly. + */ + while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) { + switch (attr_type) { + case ATTR_TYPE_INT: + attr_name = va_arg(ap, char *); + vstream_fwrite(fp, attr_name, strlen(attr_name) + 1); + int_val = va_arg(ap, int); + vstream_fprintf(fp, "%u", (unsigned) int_val); + VSTREAM_PUTC('\0', fp); + if (msg_verbose) + msg_info("send attr %s = %u", attr_name, int_val); + break; + case ATTR_TYPE_LONG: + attr_name = va_arg(ap, char *); + vstream_fwrite(fp, attr_name, strlen(attr_name) + 1); + long_val = va_arg(ap, unsigned long); + vstream_fprintf(fp, "%lu", (unsigned long) long_val); + VSTREAM_PUTC('\0', fp); + if (msg_verbose) + msg_info("send attr %s = %lu", attr_name, long_val); + break; + case ATTR_TYPE_STR: + attr_name = va_arg(ap, char *); + vstream_fwrite(fp, attr_name, strlen(attr_name) + 1); + str_val = va_arg(ap, char *); + vstream_fwrite(fp, str_val, strlen(str_val) + 1); + if (msg_verbose) + msg_info("send attr %s = %s", attr_name, str_val); + break; + case ATTR_TYPE_DATA: + attr_name = va_arg(ap, char *); + vstream_fwrite(fp, attr_name, strlen(attr_name) + 1); + len_val = va_arg(ap, ssize_t); + str_val = va_arg(ap, char *); + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + base64_encode(base64_buf, str_val, len_val); + vstream_fwrite(fp, STR(base64_buf), LEN(base64_buf) + 1); + if (msg_verbose) + msg_info("send attr %s = [data %ld bytes]", + attr_name, (long) len_val); + break; + case ATTR_TYPE_FUNC: + print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN); + print_arg = va_arg(ap, void *); + print_fn(attr_print0, fp, flags | ATTR_FLAG_MORE, print_arg); + break; + case ATTR_TYPE_HASH: + vstream_fwrite(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN)); + ht_info_list = htable_list(va_arg(ap, HTABLE *)); + for (ht = ht_info_list; *ht; ht++) { + vstream_fwrite(fp, ht[0]->key, strlen(ht[0]->key) + 1); + vstream_fwrite(fp, ht[0]->value, strlen(ht[0]->value) + 1); + if (msg_verbose) + msg_info("send attr name %s value %s", + ht[0]->key, (char *) ht[0]->value); + } + myfree((void *) ht_info_list); + vstream_fwrite(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE)); + break; + default: + msg_panic("%s: unknown type code: %d", myname, attr_type); + } + } + if ((flags & ATTR_FLAG_MORE) == 0) + VSTREAM_PUTC('\0', fp); + return (vstream_ferror(fp)); +} + +int attr_print0(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vprint0(fp, flags, ap); + va_end(ap); + return (ret); +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan0 test + * program. + */ +#include + +int main(int unused_argc, char **argv) +{ + HTABLE *table = htable_create(1); + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + htable_enter(table, "foo-name", mystrdup("foo-value")); + htable_enter(table, "bar-name", mystrdup("bar-value")); + attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + SEND_ATTR_HASH(table), + SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L), + ATTR_TYPE_END); + attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + ATTR_TYPE_END); + attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "not-test"), + ATTR_TYPE_END); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + htable_free(table, myfree); + return (0); +} + +#endif diff --git a/src/util/attr_print64.c b/src/util/attr_print64.c new file mode 100644 index 0000000..085ba33 --- /dev/null +++ b/src/util/attr_print64.c @@ -0,0 +1,297 @@ +/*++ +/* NAME +/* attr_print64 3 +/* SUMMARY +/* send attributes over byte stream +/* SYNOPSIS +/* #include +/* +/* int attr_print64(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vprint64(fp, flags, ap) +/* VSTREAM fp; +/* int flags; +/* va_list ap; +/* DESCRIPTION +/* attr_print64() takes zero or more (name, value) simple attributes +/* and converts its input to a byte stream that can be recovered with +/* attr_scan64(). The stream is not flushed. +/* +/* attr_vprint64() provides an alternate interface that is convenient +/* for calling from within variadic functions. +/* +/* Attributes are sent in the requested order as specified with the +/* attr_print64() argument list. This routine satisfies the formatting +/* rules as outlined in attr_scan64(3). +/* +/* Arguments: +/* .IP fp +/* Stream to write the result to. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MORE +/* After sending the requested attributes, leave the output stream in +/* a state that is usable for more attribute sending operations on +/* the same output attribute list. +/* By default, attr_print64() automatically appends an attribute list +/* terminator when it has sent the last requested attribute. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "SEND_ATTR_INT(const char *name, int value)" +/* The arguments are an attribute name and an integer. +/* .IP "SEND_ATTR_LONG(const char *name, long value)" +/* The arguments are an attribute name and a long integer. +/* .IP "SEND_ATTR_STR(const char *name, const char *value)" +/* The arguments are an attribute name and a null-terminated +/* string. +/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)" +/* The arguments are an attribute name, an attribute value +/* length, and an attribute value pointer. +/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)" +/* The arguments are a function pointer and generic data +/* pointer. The caller-specified function returns whatever the +/* specified attribute printing function returns. +/* .IP "SEND_ATTR_HASH(const HTABLE *table)" +/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)" +/* The content of the table is sent as a sequence of string-valued +/* attributes with names equal to the table lookup keys. +/* .IP ATTR_TYPE_END +/* This terminates the attribute list. +/* .RE +/* DIAGNOSTICS +/* The result value is 0 in case of success, VSTREAM_EOF in case +/* of trouble. +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_scan64(3) recover attributes from byte stream +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_print64_str - encode and send attribute information */ + +static void attr_print64_str(VSTREAM *fp, const char *str, ssize_t len) +{ + static VSTRING *base64_buf; + + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + + base64_encode(base64_buf, str, len); + vstream_fputs(STR(base64_buf), fp); +} + +static void attr_print64_num(VSTREAM *fp, unsigned num) +{ + static VSTRING *plain; + + if (plain == 0) + plain = vstring_alloc(10); + + vstring_sprintf(plain, "%u", num); + attr_print64_str(fp, STR(plain), LEN(plain)); +} + +static void attr_print64_long_num(VSTREAM *fp, unsigned long long_num) +{ + static VSTRING *plain; + + if (plain == 0) + plain = vstring_alloc(10); + + vstring_sprintf(plain, "%lu", long_num); + attr_print64_str(fp, STR(plain), LEN(plain)); +} + +/* attr_vprint64 - send attribute list to stream */ + +int attr_vprint64(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_print64"; + int attr_type; + char *attr_name; + unsigned int_val; + unsigned long long_val; + char *str_val; + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + ssize_t len_val; + ATTR_PRINT_CUSTOM_FN print_fn; + void *print_arg; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * Iterate over all (type, name, value) triples, and produce output on + * the fly. + */ + while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) { + switch (attr_type) { + case ATTR_TYPE_INT: + attr_name = va_arg(ap, char *); + attr_print64_str(fp, attr_name, strlen(attr_name)); + int_val = va_arg(ap, int); + VSTREAM_PUTC(':', fp); + attr_print64_num(fp, (unsigned) int_val); + VSTREAM_PUTC('\n', fp); + if (msg_verbose) + msg_info("send attr %s = %u", attr_name, int_val); + break; + case ATTR_TYPE_LONG: + attr_name = va_arg(ap, char *); + attr_print64_str(fp, attr_name, strlen(attr_name)); + long_val = va_arg(ap, long); + VSTREAM_PUTC(':', fp); + attr_print64_long_num(fp, (unsigned long) long_val); + VSTREAM_PUTC('\n', fp); + if (msg_verbose) + msg_info("send attr %s = %lu", attr_name, long_val); + break; + case ATTR_TYPE_STR: + attr_name = va_arg(ap, char *); + attr_print64_str(fp, attr_name, strlen(attr_name)); + str_val = va_arg(ap, char *); + VSTREAM_PUTC(':', fp); + attr_print64_str(fp, str_val, strlen(str_val)); + VSTREAM_PUTC('\n', fp); + if (msg_verbose) + msg_info("send attr %s = %s", attr_name, str_val); + break; + case ATTR_TYPE_DATA: + attr_name = va_arg(ap, char *); + attr_print64_str(fp, attr_name, strlen(attr_name)); + len_val = va_arg(ap, ssize_t); + str_val = va_arg(ap, char *); + VSTREAM_PUTC(':', fp); + attr_print64_str(fp, str_val, len_val); + VSTREAM_PUTC('\n', fp); + if (msg_verbose) + msg_info("send attr %s = [data %ld bytes]", + attr_name, (long) len_val); + break; + case ATTR_TYPE_FUNC: + print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN); + print_arg = va_arg(ap, void *); + print_fn(attr_print64, fp, flags | ATTR_FLAG_MORE, print_arg); + break; + case ATTR_TYPE_HASH: + attr_print64_str(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN) - 1); + VSTREAM_PUTC('\n', fp); + ht_info_list = htable_list(va_arg(ap, HTABLE *)); + for (ht = ht_info_list; *ht; ht++) { + attr_print64_str(fp, ht[0]->key, strlen(ht[0]->key)); + VSTREAM_PUTC(':', fp); + attr_print64_str(fp, ht[0]->value, strlen(ht[0]->value)); + VSTREAM_PUTC('\n', fp); + if (msg_verbose) + msg_info("send attr name %s value %s", + ht[0]->key, (char *) ht[0]->value); + } + myfree((void *) ht_info_list); + attr_print64_str(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE) - 1); + VSTREAM_PUTC('\n', fp); + break; + default: + msg_panic("%s: unknown type code: %d", myname, attr_type); + } + } + if ((flags & ATTR_FLAG_MORE) == 0) + VSTREAM_PUTC('\n', fp); + return (vstream_ferror(fp)); +} + +int attr_print64(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vprint64(fp, flags, ap); + va_end(ap); + return (ret); +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan64 test + * program. + */ +#include + +int main(int unused_argc, char **argv) +{ + HTABLE *table = htable_create(1); + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + htable_enter(table, "foo-name", mystrdup("foo-value")); + htable_enter(table, "bar-name", mystrdup("bar-value")); + attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + SEND_ATTR_HASH(table), + SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L), + ATTR_TYPE_END); + attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + ATTR_TYPE_END); + attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "not-test"), + ATTR_TYPE_END); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + htable_free(table, myfree); + return (0); +} + +#endif diff --git a/src/util/attr_print_plain.c b/src/util/attr_print_plain.c new file mode 100644 index 0000000..7d2d02f --- /dev/null +++ b/src/util/attr_print_plain.c @@ -0,0 +1,252 @@ +/*++ +/* NAME +/* attr_print_plain 3 +/* SUMMARY +/* send attributes over byte stream +/* SYNOPSIS +/* #include +/* +/* int attr_print_plain(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vprint_plain(fp, flags, ap) +/* VSTREAM fp; +/* int flags; +/* va_list ap; +/* DESCRIPTION +/* attr_print_plain() takes zero or more (name, value) simple attributes +/* and converts its input to a byte stream that can be recovered with +/* attr_scan_plain(). The stream is not flushed. +/* +/* attr_vprint_plain() provides an alternate interface that is convenient +/* for calling from within variadic functions. +/* +/* Attributes are sent in the requested order as specified with the +/* attr_print_plain() argument list. This routine satisfies the formatting +/* rules as outlined in attr_scan_plain(3). +/* +/* Arguments: +/* .IP fp +/* Stream to write the result to. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MORE +/* After sending the requested attributes, leave the output stream in +/* a state that is usable for more attribute sending operations on +/* the same output attribute list. +/* By default, attr_print_plain() automatically appends an attribute list +/* terminator when it has sent the last requested attribute. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "SEND_ATTR_INT(const char *name, int value)" +/* The arguments are an attribute name and an integer. +/* .IP "SEND_ATTR_LONG(const char *name, long value)" +/* The arguments are an attribute name and a long integer. +/* .IP "SEND_ATTR_STR(const char *name, const char *value)" +/* The arguments are an attribute name and a null-terminated +/* string. +/* .IP "SEND_ATTR_DATA(const char *name, ssize_t len, const void *value)" +/* The arguments are an attribute name, an attribute value +/* length, and an attribute value pointer. +/* .IP "SEND_ATTR_FUNC(ATTR_PRINT_CUSTOM_FN, const void *value)" +/* The arguments are a function pointer and generic data +/* pointer. The caller-specified function returns whatever the +/* specified attribute printing function returns. +/* .IP "SEND_ATTR_HASH(const HTABLE *table)" +/* .IP "SEND_ATTR_NAMEVAL(const NVTABLE *table)" +/* The content of the table is sent as a sequence of string-valued +/* attributes with names equal to the table lookup keys. +/* .IP ATTR_TYPE_END +/* This terminates the attribute list. +/* .RE +/* DIAGNOSTICS +/* The result value is 0 in case of success, VSTREAM_EOF in case +/* of trouble. +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_scan_plain(3) recover attributes from byte stream +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_vprint_plain - send attribute list to stream */ + +int attr_vprint_plain(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_print_plain"; + int attr_type; + char *attr_name; + unsigned int_val; + unsigned long long_val; + char *str_val; + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + static VSTRING *base64_buf; + ssize_t len_val; + ATTR_PRINT_CUSTOM_FN print_fn; + void *print_arg; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * Iterate over all (type, name, value) triples, and produce output on + * the fly. + */ + while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) { + switch (attr_type) { + case ATTR_TYPE_INT: + attr_name = va_arg(ap, char *); + int_val = va_arg(ap, int); + vstream_fprintf(fp, "%s=%u\n", attr_name, (unsigned) int_val); + if (msg_verbose) + msg_info("send attr %s = %u", attr_name, (unsigned) int_val); + break; + case ATTR_TYPE_LONG: + attr_name = va_arg(ap, char *); + long_val = va_arg(ap, long); + vstream_fprintf(fp, "%s=%lu\n", attr_name, long_val); + if (msg_verbose) + msg_info("send attr %s = %lu", attr_name, long_val); + break; + case ATTR_TYPE_STR: + attr_name = va_arg(ap, char *); + str_val = va_arg(ap, char *); + vstream_fprintf(fp, "%s=%s\n", attr_name, str_val); + if (msg_verbose) + msg_info("send attr %s = %s", attr_name, str_val); + break; + case ATTR_TYPE_DATA: + attr_name = va_arg(ap, char *); + len_val = va_arg(ap, ssize_t); + str_val = va_arg(ap, char *); + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + base64_encode(base64_buf, str_val, len_val); + vstream_fprintf(fp, "%s=%s\n", attr_name, STR(base64_buf)); + if (msg_verbose) + msg_info("send attr %s = [data %ld bytes]", + attr_name, (long) len_val); + break; + case ATTR_TYPE_FUNC: + print_fn = va_arg(ap, ATTR_PRINT_CUSTOM_FN); + print_arg = va_arg(ap, void *); + print_fn(attr_print_plain, fp, flags | ATTR_FLAG_MORE, print_arg); + break; + case ATTR_TYPE_HASH: + vstream_fwrite(fp, ATTR_NAME_OPEN, sizeof(ATTR_NAME_OPEN)); + VSTREAM_PUTC('\n', fp); + ht_info_list = htable_list(va_arg(ap, HTABLE *)); + for (ht = ht_info_list; *ht; ht++) { + vstream_fprintf(fp, "%s=%s\n", ht[0]->key, (char *) ht[0]->value); + if (msg_verbose) + msg_info("send attr name %s value %s", + ht[0]->key, (char *) ht[0]->value); + } + myfree((void *) ht_info_list); + vstream_fwrite(fp, ATTR_NAME_CLOSE, sizeof(ATTR_NAME_CLOSE)); + VSTREAM_PUTC('\n', fp); + break; + default: + msg_panic("%s: unknown type code: %d", myname, attr_type); + } + } + if ((flags & ATTR_FLAG_MORE) == 0) + VSTREAM_PUTC('\n', fp); + return (vstream_ferror(fp)); +} + +int attr_print_plain(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vprint_plain(fp, flags, ap); + va_end(ap); + return (ret); +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan_plain test + * program. + */ +#include + +int main(int unused_argc, char **argv) +{ + HTABLE *table = htable_create(1); + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + htable_enter(table, "foo-name", mystrdup("foo-value")); + htable_enter(table, "bar-name", mystrdup("bar-value")); + attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + SEND_ATTR_HASH(table), + SEND_ATTR_LONG(ATTR_NAME_LONG, 4321L), + ATTR_TYPE_END); + attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "test"), + SEND_ATTR_INT(ATTR_NAME_INT, 4711), + SEND_ATTR_LONG(ATTR_NAME_LONG, 1234L), + SEND_ATTR_STR(ATTR_NAME_STR, "whoopee"), + SEND_ATTR_DATA(ATTR_NAME_DATA, strlen("whoopee"), "whoopee"), + ATTR_TYPE_END); + attr_print_plain(VSTREAM_OUT, ATTR_FLAG_NONE, + SEND_ATTR_STR("protocol", "not-test"), + ATTR_TYPE_END); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + htable_free(table, myfree); + return (0); +} + +#endif diff --git a/src/util/attr_scan.ref b/src/util/attr_scan.ref new file mode 100644 index 0000000..cd06a27 --- /dev/null +++ b/src/util/attr_scan.ref @@ -0,0 +1,36 @@ +./attr_print: send attr number = 4711 +./attr_print: send attr string = whoopee +./attr_print: send attr name foo-name value foo-value +./attr_print: send attr name bar-name value bar-value +./attr_print: send attr number = 4711 +./attr_print: send attr string = whoopee +./attr_scan: unknown_stream: wanted attribute: number +./attr_scan: input attribute name: number +./attr_scan: input attribute value: 4711 +./attr_scan: unknown_stream: wanted attribute: string +./attr_scan: input attribute name: string +./attr_scan: input attribute value: whoopee +./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan: input attribute name: foo-name +./attr_scan: input attribute value: foo-value +./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan: input attribute name: bar-name +./attr_scan: input attribute value: bar-value +./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan: input attribute name: (end) +./attr_scan: unknown_stream: wanted attribute: number +./attr_scan: input attribute name: number +./attr_scan: input attribute value: 4711 +./attr_scan: unknown_stream: wanted attribute: string +./attr_scan: input attribute name: string +./attr_scan: input attribute value: whoopee +./attr_scan: unknown_stream: wanted attribute: (list terminator) +./attr_scan: input attribute name: (end) +number 4711 +string whoopee +(hash) foo-name foo-value +(hash) bar-name bar-value +number 4711 +string whoopee +(hash) foo-name foo-value +(hash) bar-name bar-value diff --git a/src/util/attr_scan0.c b/src/util/attr_scan0.c new file mode 100644 index 0000000..13aa125 --- /dev/null +++ b/src/util/attr_scan0.c @@ -0,0 +1,596 @@ +/*++ +/* NAME +/* attr_scan0 3 +/* SUMMARY +/* recover attributes from byte stream +/* SYNOPSIS +/* #include +/* +/* int attr_scan0(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM *fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vscan0(fp, flags, ap) +/* VSTREAM *fp; +/* int flags; +/* va_list ap; +/* +/* int attr_scan_more0(fp) +/* VSTREAM *fp; +/* DESCRIPTION +/* attr_scan0() takes zero or more (name, value) request attributes +/* and recovers the attribute values from the byte stream that was +/* possibly generated by attr_print0(). +/* +/* attr_vscan0() provides an alternative interface that is convenient +/* for calling from within a variadic function. +/* +/* attr_scan_more0() returns 0 when a terminator is found (and +/* consumes that terminator), returns 1 when more input is +/* expected (without consuming input), and returns -1 otherwise +/* (error). +/* +/* The input stream is formatted as follows, where (item)* stands +/* for zero or more instances of the specified item, and where +/* (item1 | item2) stands for choice: +/* +/* .in +5 +/* attr-list :== (simple-attr | multi-attr)* null +/* .br +/* multi-attr :== "{" null simple-attr* "}" null +/* .br +/* simple-attr :== attr-name null attr-value null +/* .br +/* attr-name :== any string not containing null +/* .br +/* attr-value :== any string not containing null +/* .br +/* null :== the ASCII null character +/* .in +/* +/* All attribute names and attribute values are sent as null terminated +/* strings. Each string must be no longer than 4*var_line_limit +/* characters including the terminator. +/* These formatting rules favor implementations in C. +/* +/* Normally, attributes must be received in the sequence as specified with +/* the attr_scan0() argument list. The input stream may contain additional +/* attributes at any point in the input stream, including additional +/* instances of requested attributes. +/* +/* Additional input attributes or input attribute instances are silently +/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified +/* (see below). This allows for some flexibility in the evolution of +/* protocols while still providing the option of being strict where +/* this is desirable. +/* +/* Arguments: +/* .IP fp +/* Stream to recover the input attributes from. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MISSING +/* Log a warning when the input attribute list terminates before all +/* requested attributes are recovered. It is always an error when the +/* input stream ends without the newline attribute list terminator. +/* .IP ATTR_FLAG_EXTRA +/* Log a warning and stop attribute recovery when the input stream +/* contains an attribute that was not requested. This includes the +/* case of additional instances of a requested attribute. +/* .IP ATTR_FLAG_MORE +/* After recovering the requested attributes, leave the input stream +/* in a state that is usable for more attr_scan0() operations from the +/* same input attribute list. +/* By default, attr_scan0() skips forward past the input attribute list +/* terminator. +/* .IP ATTR_FLAG_PRINTABLE +/* Santize received string values with printable(_, '?'). +/* .IP ATTR_FLAG_STRICT +/* For convenience, this value combines both ATTR_FLAG_MISSING and +/* ATTR_FLAG_EXTRA. +/* .IP ATTR_FLAG_NONE +/* For convenience, this value requests none of the above. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "RECV_ATTR_INT(const char *name, int *ptr)" +/* This argument is followed by an attribute name and an integer pointer. +/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)" +/* This argument is followed by an attribute name and a long pointer. +/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)" +/* The name and value must match what the client sends. +/* This attribute does not increment the result value. +/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)" +/* This argument is followed by a function pointer and a generic data +/* pointer. The caller-specified function returns < 0 in case of +/* error. +/* .IP "RECV_ATTR_HASH(HTABLE *table)" +/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)" +/* Receive a sequence of attribute names and string values. +/* There can be no more than 1024 attributes in a hash table. +/* .sp +/* The attribute string values are stored in the hash table under +/* keys equal to the attribute name (obtained from the input stream). +/* Values from the input stream are added to the hash table. Existing +/* hash table entries are not replaced. +/* .sp +/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests +/* format their payload as a multi-attr sequence (see syntax +/* above). When the receiver's input does not start with a +/* multi-attr delimiter (i.e. the sender did not request +/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will +/* store all attribute names and values up to the attribute +/* list terminator. In terms of code, this means that the +/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed +/* by ATTR_TYPE_END. +/* .IP ATTR_TYPE_END +/* This argument terminates the requested attribute list. +/* .RE +/* BUGS +/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary +/* names from possibly untrusted sources. +/* This is unsafe, unless the resulting table is queried only with +/* known to be good attribute names. +/* DIAGNOSTICS +/* attr_scan0() and attr_vscan0() return -1 when malformed input is +/* detected (string too long, incomplete line, missing end marker). +/* Otherwise, the result value is the number of attributes that were +/* successfully recovered from the input stream (a hash table counts +/* as the number of entries stored into the table). +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_print0(3) send attributes over byte stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_scan0_string - pull a string from the input stream */ + +static int attr_scan0_string(VSTREAM *fp, VSTRING *plain_buf, const char *context) +{ + int ch; + + if ((ch = vstring_get_null(plain_buf, fp)) == VSTREAM_EOF) { + msg_warn("%s on %s while reading %s", + vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", + VSTREAM_PATH(fp), context); + return (-1); + } + if (ch != 0) { + msg_warn("unexpected end-of-input from %s while reading %s", + VSTREAM_PATH(fp), context); + return (-1); + } + if (msg_verbose) + msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); + return (ch); +} + +/* attr_scan0_data - pull a data blob from the input stream */ + +static int attr_scan0_data(VSTREAM *fp, VSTRING *str_buf, + const char *context) +{ + static VSTRING *base64_buf = 0; + int ch; + + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + if ((ch = attr_scan0_string(fp, base64_buf, context)) < 0) + return (-1); + if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) { + msg_warn("malformed base64 data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(base64_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan0_number - pull a number from the input stream */ + +static int attr_scan0_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan0_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan0_long_number - pull a number from the input stream */ + +static int attr_scan0_long_number(VSTREAM *fp, unsigned long *ptr, + VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan0_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_vscan0 - receive attribute list from stream */ + +int attr_vscan0(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_scan0"; + static VSTRING *str_buf = 0; + static VSTRING *name_buf = 0; + int wanted_type = -1; + char *wanted_name; + unsigned int *number; + unsigned long *long_number; + VSTRING *string; + HTABLE *hash_table; + int ch; + int conversions; + ATTR_SCAN_CUSTOM_FN scan_fn; + void *scan_arg; + const char *expect_val; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * EOF check. + */ + if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF) + return (0); + vstream_ungetc(fp, ch); + + /* + * Initialize. + */ + if (str_buf == 0) { + str_buf = vstring_alloc(10); + name_buf = vstring_alloc(10); + } + + /* + * Iterate over all (type, name, value) triples. + */ + for (conversions = 0; /* void */ ; conversions++) { + + /* + * Determine the next attribute type and attribute name on the + * caller's wish list. + * + * If we're reading into a hash table, we already know that the + * attribute value is string-valued, and we get the attribute name + * from the input stream instead. This is secure only when the + * resulting table is queried with known to be good attribute names. + */ + if (wanted_type != ATTR_TYPE_HASH + && wanted_type != ATTR_TYPE_CLOSE) { + wanted_type = va_arg(ap, int); + if (wanted_type == ATTR_TYPE_END) { + if ((flags & ATTR_FLAG_MORE) != 0) + return (conversions); + wanted_name = "(list terminator)"; + } else if (wanted_type == ATTR_TYPE_HASH) { + wanted_name = "(any attribute name or list terminator)"; + hash_table = va_arg(ap, HTABLE *); + } else if (wanted_type != ATTR_TYPE_FUNC) { + wanted_name = va_arg(ap, char *); + } + } + + /* + * Locate the next attribute of interest in the input stream. + */ + while (wanted_type != ATTR_TYPE_FUNC) { + + /* + * Get the name of the next attribute. Hitting EOF is always bad. + * Hitting the end-of-input early is OK if the caller is prepared + * to deal with missing inputs. + */ + if (msg_verbose) + msg_info("%s: wanted attribute: %s", + VSTREAM_PATH(fp), wanted_name); + if ((ch = attr_scan0_string(fp, name_buf, + "input attribute name")) == VSTREAM_EOF) + return (-1); + if (LEN(name_buf) == 0) { + if (wanted_type == ATTR_TYPE_END + || wanted_type == ATTR_TYPE_HASH) + return (conversions); + if ((flags & ATTR_FLAG_MISSING) != 0) + msg_warn("missing attribute %s in input from %s", + wanted_name, VSTREAM_PATH(fp)); + return (conversions); + } + + /* + * See if the caller asks for this attribute. + */ + if (wanted_type == ATTR_TYPE_HASH + && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) { + wanted_type = ATTR_TYPE_CLOSE; + wanted_name = "(any attribute name or '}')"; + /* Advance in the input stream. */ + continue; + } else if (wanted_type == ATTR_TYPE_CLOSE + && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) { + /* Advance in the argument list. */ + wanted_type = -1; + break; + } + if (wanted_type == ATTR_TYPE_HASH + || wanted_type == ATTR_TYPE_CLOSE + || (wanted_type != ATTR_TYPE_END + && strcmp(wanted_name, STR(name_buf)) == 0)) + break; + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("unexpected attribute %s from %s (expecting: %s)", + STR(name_buf), VSTREAM_PATH(fp), wanted_name); + return (conversions); + } + + /* + * Skip over this attribute. The caller does not ask for it. + */ + (void) attr_scan0_string(fp, str_buf, "input attribute value"); + } + + /* + * Do the requested conversion. + */ + switch (wanted_type) { + case ATTR_TYPE_INT: + number = va_arg(ap, unsigned int *); + if ((ch = attr_scan0_number(fp, number, str_buf, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_LONG: + long_number = va_arg(ap, unsigned long *); + if ((ch = attr_scan0_long_number(fp, long_number, str_buf, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_STR: + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan0_string(fp, string, + "input attribute value")) < 0) + return (-1); + if (flags & ATTR_FLAG_PRINTABLE) + (void) printable(STR(string), '?'); + break; + case ATTR_TYPE_DATA: + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan0_data(fp, string, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_FUNC: + scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN); + scan_arg = va_arg(ap, void *); + if (scan_fn(attr_scan0, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0) + return (-1); + break; + case ATTR_TYPE_STREQ: + expect_val = va_arg(ap, const char *); + if ((ch = attr_scan0_string(fp, str_buf, + "input attribute value")) < 0) + return (-1); + if (strcmp(expect_val, STR(str_buf)) != 0) { + msg_warn("unexpected %s %s from %s (expected: %s)", + STR(name_buf), STR(str_buf), VSTREAM_PATH(fp), + expect_val); + return (-1); + } + conversions -= 1; + break; + case ATTR_TYPE_HASH: + case ATTR_TYPE_CLOSE: + if ((ch = attr_scan0_string(fp, str_buf, + "input attribute value")) < 0) + return (-1); + if (flags & ATTR_FLAG_PRINTABLE) { + (void) printable(STR(name_buf), '?'); + (void) printable(STR(str_buf), '?'); + } + if (htable_locate(hash_table, STR(name_buf)) != 0) { + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("duplicate attribute %s in input from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (conversions); + } + } else if (hash_table->used >= ATTR_HASH_LIMIT) { + msg_warn("attribute count exceeds limit %d in input from %s", + ATTR_HASH_LIMIT, VSTREAM_PATH(fp)); + return (conversions); + } else { + htable_enter(hash_table, STR(name_buf), + mystrdup(STR(str_buf))); + } + break; + case -1: + conversions -= 1; + break; + default: + msg_panic("%s: unknown type code: %d", myname, wanted_type); + } + } +} + +/* attr_scan0 - read attribute list from stream */ + +int attr_scan0(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vscan0(fp, flags, ap); + va_end(ap); + return (ret); +} + +/* attr_scan_more0 - look ahead for more */ + +int attr_scan_more0(VSTREAM *fp) +{ + int ch; + + switch (ch = VSTREAM_GETC(fp)) { + case 0: + if (msg_verbose) + msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp)); + return (0); + case VSTREAM_EOF: + if (msg_verbose) + msg_info("%s: EOF", VSTREAM_PATH(fp)); + return (-1); + default: + if (msg_verbose) + msg_info("%s: non-terminator '%c' (lookahead)", + VSTREAM_PATH(fp), ch); + (void) vstream_ungetc(fp, ch); + return (1); + } +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan0 test + * program. + */ +#include + +int var_line_limit = 2048; + +int main(int unused_argc, char **used_argv) +{ + VSTRING *data_val = vstring_alloc(1); + VSTRING *str_val = vstring_alloc(1); + HTABLE *table = htable_create(1); + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + int int_val; + long long_val; + long long_val2; + int ret; + + msg_verbose = 1; + msg_vstream_init(used_argv[0], VSTREAM_ERR); + if ((ret = attr_scan0(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + RECV_ATTR_HASH(table), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2), + ATTR_TYPE_END)) > 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(str_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan0(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + ATTR_TYPE_END)) == 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan0(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + ATTR_TYPE_END)) != 0) + vstream_printf("return: %d\n", ret); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + vstring_free(data_val); + vstring_free(str_val); + htable_free(table, myfree); + + return (0); +} + +#endif diff --git a/src/util/attr_scan0.ref b/src/util/attr_scan0.ref new file mode 100644 index 0000000..9055d79 --- /dev/null +++ b/src/util/attr_scan0.ref @@ -0,0 +1,79 @@ +./attr_print0: send attr protocol = test +./attr_print0: send attr number = 4711 +./attr_print0: send attr long_number = 1234 +./attr_print0: send attr string = whoopee +./attr_print0: send attr data = [data 7 bytes] +./attr_print0: send attr name bar-name value bar-value +./attr_print0: send attr name foo-name value foo-value +./attr_print0: send attr long_number = 4321 +./attr_print0: send attr protocol = test +./attr_print0: send attr number = 4711 +./attr_print0: send attr long_number = 1234 +./attr_print0: send attr string = whoopee +./attr_print0: send attr data = [data 7 bytes] +./attr_print0: send attr protocol = not-test +./attr_scan0: unknown_stream: wanted attribute: protocol +./attr_scan0: input attribute name: protocol +./attr_scan0: input attribute value: test +./attr_scan0: unknown_stream: wanted attribute: number +./attr_scan0: input attribute name: number +./attr_scan0: input attribute value: 4711 +./attr_scan0: unknown_stream: wanted attribute: long_number +./attr_scan0: input attribute name: long_number +./attr_scan0: input attribute value: 1234 +./attr_scan0: unknown_stream: wanted attribute: string +./attr_scan0: input attribute name: string +./attr_scan0: input attribute value: whoopee +./attr_scan0: unknown_stream: wanted attribute: data +./attr_scan0: input attribute name: data +./attr_scan0: input attribute value: d2hvb3BlZQ== +./attr_scan0: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan0: input attribute name: { +./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan0: input attribute name: bar-name +./attr_scan0: input attribute value: bar-value +./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan0: input attribute name: foo-name +./attr_scan0: input attribute value: foo-value +./attr_scan0: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan0: input attribute name: } +./attr_scan0: unknown_stream: wanted attribute: long_number +./attr_scan0: input attribute name: long_number +./attr_scan0: input attribute value: 4321 +./attr_scan0: unknown_stream: wanted attribute: (list terminator) +./attr_scan0: input attribute name: (end) +./attr_scan0: unknown_stream: wanted attribute: protocol +./attr_scan0: input attribute name: protocol +./attr_scan0: input attribute value: test +./attr_scan0: unknown_stream: wanted attribute: number +./attr_scan0: input attribute name: number +./attr_scan0: input attribute value: 4711 +./attr_scan0: unknown_stream: wanted attribute: long_number +./attr_scan0: input attribute name: long_number +./attr_scan0: input attribute value: 1234 +./attr_scan0: unknown_stream: wanted attribute: string +./attr_scan0: input attribute name: string +./attr_scan0: input attribute value: whoopee +./attr_scan0: unknown_stream: wanted attribute: data +./attr_scan0: input attribute name: data +./attr_scan0: input attribute value: d2hvb3BlZQ== +./attr_scan0: unknown_stream: wanted attribute: (list terminator) +./attr_scan0: input attribute name: (end) +./attr_scan0: unknown_stream: wanted attribute: protocol +./attr_scan0: input attribute name: protocol +./attr_scan0: input attribute value: not-test +./attr_scan0: warning: unexpected protocol not-test from unknown_stream (expected: test) +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +long_number 4321 +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +return: -1 diff --git a/src/util/attr_scan64.c b/src/util/attr_scan64.c new file mode 100644 index 0000000..0d9b114 --- /dev/null +++ b/src/util/attr_scan64.c @@ -0,0 +1,665 @@ +/*++ +/* NAME +/* attr_scan64 3 +/* SUMMARY +/* recover attributes from byte stream +/* SYNOPSIS +/* #include +/* +/* int attr_scan64(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM *fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vscan64(fp, flags, ap) +/* VSTREAM *fp; +/* int flags; +/* va_list ap; +/* +/* int attr_scan_more64(fp) +/* VSTREAM *fp; +/* DESCRIPTION +/* attr_scan64() takes zero or more (name, value) request attributes +/* and recovers the attribute values from the byte stream that was +/* possibly generated by attr_print64(). +/* +/* attr_vscan64() provides an alternative interface that is convenient +/* for calling from within a variadic function. +/* +/* attr_scan_more64() returns 0 when a terminator is found +/* (and consumes that terminator), returns 1 when more input +/* is expected (without consuming input), and returns -1 +/* otherwise (error). +/* +/* The input stream is formatted as follows, where (item)* stands +/* for zero or more instances of the specified item, and where +/* (item1 | item2) stands for choice: +/* +/* .in +5 +/* attr-list :== (simple-attr | multi-attr)* newline +/* .br +/* multi-attr :== "{" newline simple-attr* "}" newline +/* .br +/* simple-attr :== attr-name colon attr-value newline +/* .br +/* attr-name :== any base64 encoded string +/* .br +/* attr-value :== any base64 encoded string +/* .br +/* colon :== the ASCII colon character +/* .br +/* newline :== the ASCII newline character +/* .in +/* +/* All attribute names and attribute values are sent as base64-encoded +/* strings. Each base64 encoding must be no longer than 4*var_line_limit +/* characters. The formatting rules aim to make implementations in PERL +/* and other languages easy. +/* +/* Normally, attributes must be received in the sequence as specified with +/* the attr_scan64() argument list. The input stream may contain additional +/* attributes at any point in the input stream, including additional +/* instances of requested attributes. +/* +/* Additional input attributes or input attribute instances are silently +/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified +/* (see below). This allows for some flexibility in the evolution of +/* protocols while still providing the option of being strict where +/* this is desirable. +/* +/* Arguments: +/* .IP fp +/* Stream to recover the input attributes from. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MISSING +/* Log a warning when the input attribute list terminates before all +/* requested attributes are recovered. It is always an error when the +/* input stream ends without the newline attribute list terminator. +/* .IP ATTR_FLAG_EXTRA +/* Log a warning and stop attribute recovery when the input stream +/* contains an attribute that was not requested. This includes the +/* case of additional instances of a requested attribute. +/* .IP ATTR_FLAG_MORE +/* After recovering the requested attributes, leave the input stream +/* in a state that is usable for more attr_scan64() operations from the +/* same input attribute list. +/* By default, attr_scan64() skips forward past the input attribute list +/* terminator. +/* .IP ATTR_FLAG_PRINTABLE +/* Santize received string values with printable(_, '?'). +/* .IP ATTR_FLAG_STRICT +/* For convenience, this value combines both ATTR_FLAG_MISSING and +/* ATTR_FLAG_EXTRA. +/* .IP ATTR_FLAG_NONE +/* For convenience, this value requests none of the above. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "RECV_ATTR_INT(const char *name, int *ptr)" +/* This argument is followed by an attribute name and an integer pointer. +/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)" +/* This argument is followed by an attribute name and a long pointer. +/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)" +/* The name and value must match what the client sends. +/* This attribute does not increment the result value. +/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)" +/* This argument is followed by a function pointer and a generic data +/* pointer. The caller-specified function returns < 0 in case of +/* error. +/* .IP "RECV_ATTR_HASH(HTABLE *table)" +/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)" +/* Receive a sequence of attribute names and string values. +/* There can be no more than 1024 attributes in a hash table. +/* .sp +/* The attribute string values are stored in the hash table under +/* keys equal to the attribute name (obtained from the input stream). +/* Values from the input stream are added to the hash table. Existing +/* hash table entries are not replaced. +/* .sp +/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests +/* format their payload as a multi-attr sequence (see syntax +/* above). When the receiver's input does not start with a +/* multi-attr delimiter (i.e. the sender did not request +/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will +/* store all attribute names and values up to the attribute +/* list terminator. In terms of code, this means that the +/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed +/* by ATTR_TYPE_END. +/* .IP ATTR_TYPE_END +/* This argument terminates the requested attribute list. +/* .RE +/* BUGS +/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary +/* names from possibly untrusted sources. +/* This is unsafe, unless the resulting table is queried only with +/* known to be good attribute names. +/* DIAGNOSTICS +/* attr_scan64() and attr_vscan64() return -1 when malformed input is +/* detected (string too long, incomplete line, missing end marker). +/* Otherwise, the result value is the number of attributes that were +/* successfully recovered from the input stream (a hash table counts +/* as the number of entries stored into the table). +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_print64(3) send attributes over byte stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_scan64_string - pull a string from the input stream */ + +static int attr_scan64_string(VSTREAM *fp, VSTRING *plain_buf, const char *context) +{ + static VSTRING *base64_buf = 0; + +#if 0 + extern int var_line_limit; /* XXX */ + int limit = var_line_limit * 4; + +#endif + int ch; + + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + + VSTRING_RESET(base64_buf); + while ((ch = VSTREAM_GETC(fp)) != ':' && ch != '\n') { + if (ch == VSTREAM_EOF) { + msg_warn("%s on %s while reading %s", + vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", + VSTREAM_PATH(fp), context); + return (-1); + } + VSTRING_ADDCH(base64_buf, ch); +#if 0 + if (LEN(base64_buf) > limit) { + msg_warn("string length > %d characters from %s while reading %s", + limit, VSTREAM_PATH(fp), context); + return (-1); + } +#endif + } + VSTRING_TERMINATE(base64_buf); + if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) { + msg_warn("malformed base64 data from %s: %.100s", + VSTREAM_PATH(fp), STR(base64_buf)); + return (-1); + } + if (msg_verbose) + msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); + return (ch); +} + +/* attr_scan64_number - pull a number from the input stream */ + +static int attr_scan64_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan64_long_number - pull a number from the input stream */ + +static int attr_scan64_long_number(VSTREAM *fp, unsigned long *ptr, + VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_vscan64 - receive attribute list from stream */ + +int attr_vscan64(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_scan64"; + static VSTRING *str_buf = 0; + static VSTRING *name_buf = 0; + int wanted_type = -1; + char *wanted_name; + unsigned int *number; + unsigned long *long_number; + VSTRING *string; + HTABLE *hash_table; + int ch; + int conversions; + ATTR_SCAN_CUSTOM_FN scan_fn; + void *scan_arg; + const char *expect_val; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * EOF check. + */ + if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF) + return (0); + vstream_ungetc(fp, ch); + + /* + * Initialize. + */ + if (str_buf == 0) { + str_buf = vstring_alloc(10); + name_buf = vstring_alloc(10); + } + + /* + * Iterate over all (type, name, value) triples. + */ + for (conversions = 0; /* void */ ; conversions++) { + + /* + * Determine the next attribute type and attribute name on the + * caller's wish list. + * + * If we're reading into a hash table, we already know that the + * attribute value is string-valued, and we get the attribute name + * from the input stream instead. This is secure only when the + * resulting table is queried with known to be good attribute names. + */ + if (wanted_type != ATTR_TYPE_HASH + && wanted_type != ATTR_TYPE_CLOSE) { + wanted_type = va_arg(ap, int); + if (wanted_type == ATTR_TYPE_END) { + if ((flags & ATTR_FLAG_MORE) != 0) + return (conversions); + wanted_name = "(list terminator)"; + } else if (wanted_type == ATTR_TYPE_HASH) { + wanted_name = "(any attribute name or list terminator)"; + hash_table = va_arg(ap, HTABLE *); + } else if (wanted_type != ATTR_TYPE_FUNC) { + wanted_name = va_arg(ap, char *); + } + } + + /* + * Locate the next attribute of interest in the input stream. + */ + while (wanted_type != ATTR_TYPE_FUNC) { + + /* + * Get the name of the next attribute. Hitting EOF is always bad. + * Hitting the end-of-input early is OK if the caller is prepared + * to deal with missing inputs. + */ + if (msg_verbose) + msg_info("%s: wanted attribute: %s", + VSTREAM_PATH(fp), wanted_name); + if ((ch = attr_scan64_string(fp, name_buf, + "input attribute name")) == VSTREAM_EOF) + return (-1); + if (ch == '\n' && LEN(name_buf) == 0) { + if (wanted_type == ATTR_TYPE_END + || wanted_type == ATTR_TYPE_HASH) + return (conversions); + if ((flags & ATTR_FLAG_MISSING) != 0) + msg_warn("missing attribute %s in input from %s", + wanted_name, VSTREAM_PATH(fp)); + return (conversions); + } + + /* + * See if the caller asks for this attribute. + */ + if (wanted_type == ATTR_TYPE_HASH + && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) { + wanted_type = ATTR_TYPE_CLOSE; + wanted_name = "(any attribute name or '}')"; + /* Advance in the input stream. */ + continue; + } else if (wanted_type == ATTR_TYPE_CLOSE + && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) { + /* Advance in the argument list. */ + wanted_type = -1; + break; + } + if (wanted_type == ATTR_TYPE_HASH + || wanted_type == ATTR_TYPE_CLOSE + || (wanted_type != ATTR_TYPE_END + && strcmp(wanted_name, STR(name_buf)) == 0)) + break; + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("unexpected attribute %s from %s (expecting: %s)", + STR(name_buf), VSTREAM_PATH(fp), wanted_name); + return (conversions); + } + + /* + * Skip over this attribute. The caller does not ask for it. + */ + while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) + /* void */ ; + } + + /* + * Do the requested conversion. If the target attribute is a + * non-array type, disallow sending a multi-valued attribute, and + * disallow sending no value. If the target attribute is an array + * type, allow the sender to send a zero-element array (i.e. no value + * at all). XXX Need to impose a bound on the number of array + * elements. + */ + switch (wanted_type) { + case ATTR_TYPE_INT: + if (ch != ':') { + msg_warn("missing value for number attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + number = va_arg(ap, unsigned int *); + if ((ch = attr_scan64_number(fp, number, str_buf, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + break; + case ATTR_TYPE_LONG: + if (ch != ':') { + msg_warn("missing value for number attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + long_number = va_arg(ap, unsigned long *); + if ((ch = attr_scan64_long_number(fp, long_number, str_buf, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + break; + case ATTR_TYPE_STR: + if (ch != ':') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan64_string(fp, string, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if (flags & ATTR_FLAG_PRINTABLE) + (void) printable(STR(string), '?'); + break; + case ATTR_TYPE_DATA: + if (ch != ':') { + msg_warn("missing value for data attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan64_string(fp, string, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + break; + case ATTR_TYPE_FUNC: + scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN); + scan_arg = va_arg(ap, void *); + if (scan_fn(attr_scan64, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0) + return (-1); + break; + case ATTR_TYPE_STREQ: + if (ch != ':') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + expect_val = va_arg(ap, const char *); + if ((ch = attr_scan64_string(fp, str_buf, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if (strcmp(expect_val, STR(str_buf)) != 0) { + msg_warn("unexpected %s %s from %s (expected: %s)", + STR(name_buf), STR(str_buf), VSTREAM_PATH(fp), + expect_val); + return (-1); + } + conversions -= 1; + break; + case ATTR_TYPE_HASH: + case ATTR_TYPE_CLOSE: + if (ch != ':') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if ((ch = attr_scan64_string(fp, str_buf, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if (flags & ATTR_FLAG_PRINTABLE) { + (void) printable(STR(name_buf), '?'); + (void) printable(STR(str_buf), '?'); + } + if (htable_locate(hash_table, STR(name_buf)) != 0) { + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("duplicate attribute %s in input from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (conversions); + } + } else if (hash_table->used >= ATTR_HASH_LIMIT) { + msg_warn("attribute count exceeds limit %d in input from %s", + ATTR_HASH_LIMIT, VSTREAM_PATH(fp)); + return (conversions); + } else { + htable_enter(hash_table, STR(name_buf), + mystrdup(STR(str_buf))); + } + break; + case -1: + conversions -= 1; + break; + default: + msg_panic("%s: unknown type code: %d", myname, wanted_type); + } + } +} + +/* attr_scan64 - read attribute list from stream */ + +int attr_scan64(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vscan64(fp, flags, ap); + va_end(ap); + return (ret); +} + +/* attr_scan_more64 - look ahead for more */ + +int attr_scan_more64(VSTREAM *fp) +{ + int ch; + + switch (ch = VSTREAM_GETC(fp)) { + case '\n': + if (msg_verbose) + msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp)); + return (0); + case VSTREAM_EOF: + if (msg_verbose) + msg_info("%s: EOF", VSTREAM_PATH(fp)); + return (-1); + default: + if (msg_verbose) + msg_info("%s: non-terminator '%c' (lookahead)", + VSTREAM_PATH(fp), ch); + (void) vstream_ungetc(fp, ch); + return (1); + } +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan64 test + * program. + */ +#include + +int var_line_limit = 2048; + +int main(int unused_argc, char **used_argv) +{ + VSTRING *data_val = vstring_alloc(1); + VSTRING *str_val = vstring_alloc(1); + HTABLE *table = htable_create(1); + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + int int_val; + long long_val; + long long_val2; + int ret; + + msg_verbose = 1; + msg_vstream_init(used_argv[0], VSTREAM_ERR); + if ((ret = attr_scan64(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + RECV_ATTR_HASH(table), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2), + ATTR_TYPE_END)) > 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan64(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + ATTR_TYPE_END)) == 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan64(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + ATTR_TYPE_END)) != 0) + vstream_printf("return: %d\n", ret); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + vstring_free(data_val); + vstring_free(str_val); + htable_free(table, myfree); + + return (0); +} + +#endif diff --git a/src/util/attr_scan64.ref b/src/util/attr_scan64.ref new file mode 100644 index 0000000..ccf27f1 --- /dev/null +++ b/src/util/attr_scan64.ref @@ -0,0 +1,79 @@ +./attr_print64: send attr protocol = test +./attr_print64: send attr number = 4711 +./attr_print64: send attr long_number = 1234 +./attr_print64: send attr string = whoopee +./attr_print64: send attr data = [data 7 bytes] +./attr_print64: send attr name bar-name value bar-value +./attr_print64: send attr name foo-name value foo-value +./attr_print64: send attr long_number = 4321 +./attr_print64: send attr protocol = test +./attr_print64: send attr number = 4711 +./attr_print64: send attr long_number = 1234 +./attr_print64: send attr string = whoopee +./attr_print64: send attr data = [data 7 bytes] +./attr_print64: send attr protocol = not-test +./attr_scan64: unknown_stream: wanted attribute: protocol +./attr_scan64: input attribute name: protocol +./attr_scan64: input attribute value: test +./attr_scan64: unknown_stream: wanted attribute: number +./attr_scan64: input attribute name: number +./attr_scan64: input attribute value: 4711 +./attr_scan64: unknown_stream: wanted attribute: long_number +./attr_scan64: input attribute name: long_number +./attr_scan64: input attribute value: 1234 +./attr_scan64: unknown_stream: wanted attribute: string +./attr_scan64: input attribute name: string +./attr_scan64: input attribute value: whoopee +./attr_scan64: unknown_stream: wanted attribute: data +./attr_scan64: input attribute name: data +./attr_scan64: input attribute value: whoopee +./attr_scan64: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan64: input attribute name: { +./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan64: input attribute name: bar-name +./attr_scan64: input attribute value: bar-value +./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan64: input attribute name: foo-name +./attr_scan64: input attribute value: foo-value +./attr_scan64: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan64: input attribute name: } +./attr_scan64: unknown_stream: wanted attribute: long_number +./attr_scan64: input attribute name: long_number +./attr_scan64: input attribute value: 4321 +./attr_scan64: unknown_stream: wanted attribute: (list terminator) +./attr_scan64: input attribute name: (end) +./attr_scan64: unknown_stream: wanted attribute: protocol +./attr_scan64: input attribute name: protocol +./attr_scan64: input attribute value: test +./attr_scan64: unknown_stream: wanted attribute: number +./attr_scan64: input attribute name: number +./attr_scan64: input attribute value: 4711 +./attr_scan64: unknown_stream: wanted attribute: long_number +./attr_scan64: input attribute name: long_number +./attr_scan64: input attribute value: 1234 +./attr_scan64: unknown_stream: wanted attribute: string +./attr_scan64: input attribute name: string +./attr_scan64: input attribute value: whoopee +./attr_scan64: unknown_stream: wanted attribute: data +./attr_scan64: input attribute name: data +./attr_scan64: input attribute value: whoopee +./attr_scan64: unknown_stream: wanted attribute: (list terminator) +./attr_scan64: input attribute name: (end) +./attr_scan64: unknown_stream: wanted attribute: protocol +./attr_scan64: input attribute name: protocol +./attr_scan64: input attribute value: not-test +./attr_scan64: warning: unexpected protocol not-test from unknown_stream (expected: test) +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +long_number 4321 +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +return: -1 diff --git a/src/util/attr_scan_plain.c b/src/util/attr_scan_plain.c new file mode 100644 index 0000000..d7e2f66 --- /dev/null +++ b/src/util/attr_scan_plain.c @@ -0,0 +1,643 @@ +/*++ +/* NAME +/* attr_scan_plain 3 +/* SUMMARY +/* recover attributes from byte stream +/* SYNOPSIS +/* #include +/* +/* int attr_scan_plain(fp, flags, type, name, ..., ATTR_TYPE_END) +/* VSTREAM *fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vscan_plain(fp, flags, ap) +/* VSTREAM *fp; +/* int flags; +/* va_list ap; +/* +/* int attr_scan_more_plain(fp) +/* VSTREAM *fp; +/* DESCRIPTION +/* attr_scan_plain() takes zero or more (name, value) request attributes +/* and recovers the attribute values from the byte stream that was +/* possibly generated by attr_print_plain(). +/* +/* attr_vscan_plain() provides an alternative interface that is convenient +/* for calling from within a variadic function. +/* +/* attr_scan_more_plain() returns 0 when a terminator is found +/* (and consumes that terminator), returns 1 when more input +/* is expected (without consuming input), and returns -1 +/* otherwise (error). +/* +/* The input stream is formatted as follows, where (item)* stands +/* for zero or more instances of the specified item, and where +/* (item1 | item2) stands for choice: +/* +/* .in +5 +/* attr-list :== (simple-attr | multi-attr)* newline +/* .br +/* multi-attr :== "{" newline simple-attr* "}" newline +/* .br +/* simple-attr :== attr-name "=" attr-value newline +/* .br +/* attr-name :== any string without null or "=" or newline. +/* .br +/* attr-value :== any string without null or newline. +/* .br +/* newline :== the ASCII newline character +/* .in +/* +/* All attribute names and attribute values are sent as plain +/* strings. Each string must be no longer than 4*var_line_limit +/* characters. The formatting rules aim to make implementations in PERL +/* and other languages easy. +/* +/* Normally, attributes must be received in the sequence as specified +/* with the attr_scan_plain() argument list. The input stream may +/* contain additional attributes at any point in the input stream, +/* including additional instances of requested attributes. +/* +/* Additional input attributes or input attribute instances are silently +/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified +/* (see below). This allows for some flexibility in the evolution of +/* protocols while still providing the option of being strict where +/* this is desirable. +/* +/* Arguments: +/* .IP fp +/* Stream to recover the input attributes from. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MISSING +/* Log a warning when the input attribute list terminates before all +/* requested attributes are recovered. It is always an error when the +/* input stream ends without the newline attribute list terminator. +/* .IP ATTR_FLAG_EXTRA +/* Log a warning and stop attribute recovery when the input stream +/* contains an attribute that was not requested. This includes the +/* case of additional instances of a requested attribute. +/* .IP ATTR_FLAG_MORE +/* After recovering the requested attributes, leave the input stream +/* in a state that is usable for more attr_scan_plain() operations +/* from the same input attribute list. +/* By default, attr_scan_plain() skips forward past the input attribute +/* list terminator. +/* .IP ATTR_FLAG_PRINTABLE +/* Santize received string values with printable(_, '?'). +/* .IP ATTR_FLAG_STRICT +/* For convenience, this value combines both ATTR_FLAG_MISSING and +/* ATTR_FLAG_EXTRA. +/* .IP ATTR_FLAG_NONE +/* For convenience, this value requests none of the above. +/* .RE +/* .IP List of attributes followed by terminator: +/* .RS +/* .IP "RECV_ATTR_INT(const char *name, int *ptr)" +/* This argument is followed by an attribute name and an integer pointer. +/* .IP "RECV_ATTR_LONG(const char *name, long *ptr)" +/* This argument is followed by an attribute name and a long pointer. +/* .IP "RECV_ATTR_STR(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_STREQ(const char *name, const char *value)" +/* The name and value must match what the client sends. +/* This attribute does not increment the result value. +/* .IP "RECV_ATTR_DATA(const char *name, VSTRING *vp)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "RECV_ATTR_FUNC(ATTR_SCAN_CUSTOM_FN, void *data)" +/* This argument is followed by a function pointer and a generic data +/* pointer. The caller-specified function returns < 0 in case of +/* error. +/* .IP "RECV_ATTR_HASH(HTABLE *table)" +/* .IP "RECV_ATTR_NAMEVAL(NVTABLE *table)" +/* Receive a sequence of attribute names and string values. +/* There can be no more than 1024 attributes in a hash table. +/* .sp +/* The attribute string values are stored in the hash table under +/* keys equal to the attribute name (obtained from the input stream). +/* Values from the input stream are added to the hash table. Existing +/* hash table entries are not replaced. +/* .sp +/* Note: the SEND_ATTR_HASH or SEND_ATTR_NAMEVAL requests +/* format their payload as a multi-attr sequence (see syntax +/* above). When the receiver's input does not start with a +/* multi-attr delimiter (i.e. the sender did not request +/* SEND_ATTR_HASH or SEND_ATTR_NAMEVAL), the receiver will +/* store all attribute names and values up to the attribute +/* list terminator. In terms of code, this means that the +/* RECV_ATTR_HASH or RECV_ATTR_NAMEVAL request must be followed +/* by ATTR_TYPE_END. +/* .IP ATTR_TYPE_END +/* This argument terminates the requested attribute list. +/* .RE +/* BUGS +/* RECV_ATTR_HASH (RECV_ATTR_NAMEVAL) accepts attributes with arbitrary +/* names from possibly untrusted sources. +/* This is unsafe, unless the resulting table is queried only with +/* known to be good attribute names. +/* DIAGNOSTICS +/* attr_scan_plain() and attr_vscan_plain() return -1 when malformed input +/* is detected (string too long, incomplete line, missing end marker). +/* Otherwise, the result value is the number of attributes that were +/* successfully recovered from the input stream (a hash table counts +/* as the number of entries stored into the table). +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_print_plain(3) send attributes over byte stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_scan_plain_string - pull a string from the input stream */ + +static int attr_scan_plain_string(VSTREAM *fp, VSTRING *plain_buf, + int terminator, const char *context) +{ +#if 0 + extern int var_line_limit; /* XXX */ + int limit = var_line_limit * 4; + +#endif + int ch; + + VSTRING_RESET(plain_buf); + while ((ch = VSTREAM_GETC(fp)) != '\n' + && (terminator == 0 || ch != terminator)) { + if (ch == VSTREAM_EOF) { + msg_warn("%s on %s while reading %s", + vstream_ftimeout(fp) ? "timeout" : "premature end-of-input", + VSTREAM_PATH(fp), context); + return (-1); + } + VSTRING_ADDCH(plain_buf, ch); +#if 0 + if (LEN(plain_buf) > limit) { + msg_warn("string length > %d characters from %s while reading %s", + limit, VSTREAM_PATH(fp), context); + return (-1); + } +#endif + } + VSTRING_TERMINATE(plain_buf); + + if (msg_verbose) + msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); + return (ch); +} + +/* attr_scan_plain_data - pull a data blob from the input stream */ + +static int attr_scan_plain_data(VSTREAM *fp, VSTRING *str_buf, + int terminator, + const char *context) +{ + static VSTRING *base64_buf = 0; + int ch; + + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + if ((ch = attr_scan_plain_string(fp, base64_buf, terminator, context)) < 0) + return (-1); + if (base64_decode(str_buf, STR(base64_buf), LEN(base64_buf)) == 0) { + msg_warn("malformed base64 data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(base64_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan_plain_number - pull a number from the input stream */ + +static int attr_scan_plain_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, + int terminator, const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan_plain_long_number - pull a number from the input stream */ + +static int attr_scan_plain_long_number(VSTREAM *fp, unsigned long *ptr, + VSTRING *str_buf, + int terminator, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan_plain_string(fp, str_buf, terminator, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_vscan_plain - receive attribute list from stream */ + +int attr_vscan_plain(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_scan_plain"; + static VSTRING *str_buf = 0; + static VSTRING *name_buf = 0; + int wanted_type = -1; + char *wanted_name; + unsigned int *number; + unsigned long *long_number; + VSTRING *string; + HTABLE *hash_table; + int ch; + int conversions; + ATTR_SCAN_CUSTOM_FN scan_fn; + void *scan_arg; + const char *expect_val; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * EOF check. + */ + if ((ch = VSTREAM_GETC(fp)) == VSTREAM_EOF) + return (0); + vstream_ungetc(fp, ch); + + /* + * Initialize. + */ + if (str_buf == 0) { + str_buf = vstring_alloc(10); + name_buf = vstring_alloc(10); + } + + /* + * Iterate over all (type, name, value) triples. + */ + for (conversions = 0; /* void */ ; conversions++) { + + /* + * Determine the next attribute type and attribute name on the + * caller's wish list. + * + * If we're reading into a hash table, we already know that the + * attribute value is string-valued, and we get the attribute name + * from the input stream instead. This is secure only when the + * resulting table is queried with known to be good attribute names. + */ + if (wanted_type != ATTR_TYPE_HASH + && wanted_type != ATTR_TYPE_CLOSE) { + wanted_type = va_arg(ap, int); + if (wanted_type == ATTR_TYPE_END) { + if ((flags & ATTR_FLAG_MORE) != 0) + return (conversions); + wanted_name = "(list terminator)"; + } else if (wanted_type == ATTR_TYPE_HASH) { + wanted_name = "(any attribute name or list terminator)"; + hash_table = va_arg(ap, HTABLE *); + } else if (wanted_type != ATTR_TYPE_FUNC) { + wanted_name = va_arg(ap, char *); + } + } + + /* + * Locate the next attribute of interest in the input stream. + */ + while (wanted_type != ATTR_TYPE_FUNC) { + + /* + * Get the name of the next attribute. Hitting EOF is always bad. + * Hitting the end-of-input early is OK if the caller is prepared + * to deal with missing inputs. + */ + if (msg_verbose) + msg_info("%s: wanted attribute: %s", + VSTREAM_PATH(fp), wanted_name); + if ((ch = attr_scan_plain_string(fp, name_buf, '=', + "input attribute name")) == VSTREAM_EOF) + return (-1); + if (ch == '\n' && LEN(name_buf) == 0) { + if (wanted_type == ATTR_TYPE_END + || wanted_type == ATTR_TYPE_HASH) + return (conversions); + if ((flags & ATTR_FLAG_MISSING) != 0) + msg_warn("missing attribute %s in input from %s", + wanted_name, VSTREAM_PATH(fp)); + return (conversions); + } + + /* + * See if the caller asks for this attribute. + */ + if (wanted_type == ATTR_TYPE_HASH + && ch == '\n' && strcmp(ATTR_NAME_OPEN, STR(name_buf)) == 0) { + wanted_type = ATTR_TYPE_CLOSE; + wanted_name = "(any attribute name or '}')"; + /* Advance in the input stream. */ + continue; + } else if (wanted_type == ATTR_TYPE_CLOSE + && ch == '\n' && strcmp(ATTR_NAME_CLOSE, STR(name_buf)) == 0) { + /* Advance in the argument list. */ + wanted_type = -1; + break; + } + if (wanted_type == ATTR_TYPE_HASH + || wanted_type == ATTR_TYPE_CLOSE + || (wanted_type != ATTR_TYPE_END + && strcmp(wanted_name, STR(name_buf)) == 0)) + break; + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("unexpected attribute %s from %s (expecting: %s)", + STR(name_buf), VSTREAM_PATH(fp), wanted_name); + return (conversions); + } + + /* + * Skip over this attribute. The caller does not ask for it. + */ + while (ch != '\n' && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) + /* void */ ; + } + + /* + * Do the requested conversion. + */ + switch (wanted_type) { + case ATTR_TYPE_INT: + if (ch != '=') { + msg_warn("missing value for number attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + number = va_arg(ap, unsigned int *); + if ((ch = attr_scan_plain_number(fp, number, str_buf, 0, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_LONG: + if (ch != '=') { + msg_warn("missing value for number attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + long_number = va_arg(ap, unsigned long *); + if ((ch = attr_scan_plain_long_number(fp, long_number, str_buf, + 0, "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_STR: + if (ch != '=') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan_plain_string(fp, string, 0, + "input attribute value")) < 0) + return (-1); + if (flags & ATTR_FLAG_PRINTABLE) + (void) printable(STR(string), '?'); + break; + case ATTR_TYPE_DATA: + if (ch != '=') { + msg_warn("missing value for data attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan_plain_data(fp, string, 0, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_FUNC: + scan_fn = va_arg(ap, ATTR_SCAN_CUSTOM_FN); + scan_arg = va_arg(ap, void *); + if (scan_fn(attr_scan_plain, fp, flags | ATTR_FLAG_MORE, scan_arg) < 0) + return (-1); + break; + case ATTR_TYPE_STREQ: + if (ch != '=') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + expect_val = va_arg(ap, const char *); + if ((ch = attr_scan_plain_string(fp, str_buf, 0, + "input attribute value")) < 0) + return (-1); + if (strcmp(expect_val, STR(str_buf)) != 0) { + msg_warn("unexpected %s %s from %s (expected: %s)", + STR(name_buf), STR(str_buf), VSTREAM_PATH(fp), + expect_val); + return (-1); + } + conversions -= 1; + break; + case ATTR_TYPE_HASH: + case ATTR_TYPE_CLOSE: + if (ch != '=') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if ((ch = attr_scan_plain_string(fp, str_buf, 0, + "input attribute value")) < 0) + return (-1); + if (flags & ATTR_FLAG_PRINTABLE) { + (void) printable(STR(name_buf), '?'); + (void) printable(STR(str_buf), '?'); + } + if (htable_locate(hash_table, STR(name_buf)) != 0) { + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("duplicate attribute %s in input from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (conversions); + } + } else if (hash_table->used >= ATTR_HASH_LIMIT) { + msg_warn("attribute count exceeds limit %d in input from %s", + ATTR_HASH_LIMIT, VSTREAM_PATH(fp)); + return (conversions); + } else { + htable_enter(hash_table, STR(name_buf), + mystrdup(STR(str_buf))); + } + break; + case -1: + conversions -= 1; + break; + default: + msg_panic("%s: unknown type code: %d", myname, wanted_type); + } + } +} + +/* attr_scan_plain - read attribute list from stream */ + +int attr_scan_plain(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vscan_plain(fp, flags, ap); + va_end(ap); + return (ret); +} + +/* attr_scan_more_plain - look ahead for more */ + +int attr_scan_more_plain(VSTREAM *fp) +{ + int ch; + + switch (ch = VSTREAM_GETC(fp)) { + case '\n': + if (msg_verbose) + msg_info("%s: terminator (consumed)", VSTREAM_PATH(fp)); + return (0); + case VSTREAM_EOF: + if (msg_verbose) + msg_info("%s: EOF", VSTREAM_PATH(fp)); + return (-1); + default: + if (msg_verbose) + msg_info("%s: non-terminator '%c' (lookahead)", + VSTREAM_PATH(fp), ch); + (void) vstream_ungetc(fp, ch); + return (1); + } +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan_plain test + * program. + */ +#include + +int var_line_limit = 2048; + +int main(int unused_argc, char **used_argv) +{ + VSTRING *data_val = vstring_alloc(1); + VSTRING *str_val = vstring_alloc(1); + HTABLE *table = htable_create(1); + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + int int_val; + long long_val; + long long_val2; + int ret; + + msg_verbose = 1; + msg_vstream_init(used_argv[0], VSTREAM_ERR); + if ((ret = attr_scan_plain(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + RECV_ATTR_HASH(table), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val2), + ATTR_TYPE_END)) > 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val2); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan_plain(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + RECV_ATTR_INT(ATTR_NAME_INT, &int_val), + RECV_ATTR_LONG(ATTR_NAME_LONG, &long_val), + RECV_ATTR_STR(ATTR_NAME_STR, str_val), + RECV_ATTR_DATA(ATTR_NAME_DATA, data_val), + ATTR_TYPE_END)) == 4) { + vstream_printf("%s %d\n", ATTR_NAME_INT, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + vstream_printf("%s %s\n", ATTR_NAME_DATA, STR(data_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, (char *) ht[0]->value); + myfree((void *) ht_info_list); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan_plain(VSTREAM_IN, + ATTR_FLAG_STRICT, + RECV_ATTR_STREQ("protocol", "test"), + ATTR_TYPE_END)) != 0) + vstream_printf("return: %d\n", ret); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + vstring_free(data_val); + vstring_free(str_val); + htable_free(table, myfree); + + return (0); +} + +#endif diff --git a/src/util/attr_scan_plain.ref b/src/util/attr_scan_plain.ref new file mode 100644 index 0000000..1c0f358 --- /dev/null +++ b/src/util/attr_scan_plain.ref @@ -0,0 +1,79 @@ +./attr_print_plain: send attr protocol = test +./attr_print_plain: send attr number = 4711 +./attr_print_plain: send attr long_number = 1234 +./attr_print_plain: send attr string = whoopee +./attr_print_plain: send attr data = [data 7 bytes] +./attr_print_plain: send attr name bar-name value bar-value +./attr_print_plain: send attr name foo-name value foo-value +./attr_print_plain: send attr long_number = 4321 +./attr_print_plain: send attr protocol = test +./attr_print_plain: send attr number = 4711 +./attr_print_plain: send attr long_number = 1234 +./attr_print_plain: send attr string = whoopee +./attr_print_plain: send attr data = [data 7 bytes] +./attr_print_plain: send attr protocol = not-test +./attr_scan_plain: unknown_stream: wanted attribute: protocol +./attr_scan_plain: input attribute name: protocol +./attr_scan_plain: input attribute value: test +./attr_scan_plain: unknown_stream: wanted attribute: number +./attr_scan_plain: input attribute name: number +./attr_scan_plain: input attribute value: 4711 +./attr_scan_plain: unknown_stream: wanted attribute: long_number +./attr_scan_plain: input attribute name: long_number +./attr_scan_plain: input attribute value: 1234 +./attr_scan_plain: unknown_stream: wanted attribute: string +./attr_scan_plain: input attribute name: string +./attr_scan_plain: input attribute value: whoopee +./attr_scan_plain: unknown_stream: wanted attribute: data +./attr_scan_plain: input attribute name: data +./attr_scan_plain: input attribute value: d2hvb3BlZQ== +./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan_plain: input attribute name: { +./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan_plain: input attribute name: bar-name +./attr_scan_plain: input attribute value: bar-value +./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan_plain: input attribute name: foo-name +./attr_scan_plain: input attribute value: foo-value +./attr_scan_plain: unknown_stream: wanted attribute: (any attribute name or '}') +./attr_scan_plain: input attribute name: } +./attr_scan_plain: unknown_stream: wanted attribute: long_number +./attr_scan_plain: input attribute name: long_number +./attr_scan_plain: input attribute value: 4321 +./attr_scan_plain: unknown_stream: wanted attribute: (list terminator) +./attr_scan_plain: input attribute name: (end) +./attr_scan_plain: unknown_stream: wanted attribute: protocol +./attr_scan_plain: input attribute name: protocol +./attr_scan_plain: input attribute value: test +./attr_scan_plain: unknown_stream: wanted attribute: number +./attr_scan_plain: input attribute name: number +./attr_scan_plain: input attribute value: 4711 +./attr_scan_plain: unknown_stream: wanted attribute: long_number +./attr_scan_plain: input attribute name: long_number +./attr_scan_plain: input attribute value: 1234 +./attr_scan_plain: unknown_stream: wanted attribute: string +./attr_scan_plain: input attribute name: string +./attr_scan_plain: input attribute value: whoopee +./attr_scan_plain: unknown_stream: wanted attribute: data +./attr_scan_plain: input attribute name: data +./attr_scan_plain: input attribute value: d2hvb3BlZQ== +./attr_scan_plain: unknown_stream: wanted attribute: (list terminator) +./attr_scan_plain: input attribute name: (end) +./attr_scan_plain: unknown_stream: wanted attribute: protocol +./attr_scan_plain: input attribute name: protocol +./attr_scan_plain: input attribute value: not-test +./attr_scan_plain: warning: unexpected protocol not-test from unknown_stream (expected: test) +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +long_number 4321 +number 4711 +long_number 1234 +string whoopee +data whoopee +(hash) bar-name bar-value +(hash) foo-name foo-value +return: -1 diff --git a/src/util/auto_clnt.c b/src/util/auto_clnt.c new file mode 100644 index 0000000..cdbbe22 --- /dev/null +++ b/src/util/auto_clnt.c @@ -0,0 +1,372 @@ +/*++ +/* NAME +/* auto_clnt 3 +/* SUMMARY +/* client endpoint maintenance +/* SYNOPSIS +/* #include +/* +/* typedef void (*AUTO_CLNT_HANDSHAKE_FN)(VSTREAM *); +/* +/* AUTO_CLNT *auto_clnt_create(service, timeout, max_idle, max_ttl) +/* const char *service; +/* int timeout; +/* int max_idle; +/* int max_ttl; +/* +/* VSTREAM *auto_clnt_access(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* +/* void auto_clnt_recover(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* +/* const char *auto_clnt_name(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* +/* void auto_clnt_free(auto_clnt) +/* AUTO_CLNT *auto_clnt; +/* +/* void auto_clnt_control(auto_clnt, name, value, ... AUTO_CLNT_CTL_END) +/* AUTO_CLNT *auto_clnt; +/* int name; +/* DESCRIPTION +/* This module maintains IPC client endpoints that automatically +/* disconnect after a being idle for a configurable amount of time, +/* that disconnect after a configurable time to live, +/* and that transparently handle most server-initiated disconnects. +/* +/* This module tries each operation only a limited number of +/* times and then reports an error. This is unlike the +/* clnt_stream(3) module which will retry forever, so that +/* the application never experiences an error. +/* +/* auto_clnt_create() instantiates a client endpoint. +/* +/* auto_clnt_access() returns an open stream to the service specified +/* to auto_clnt_create(). The stream instance may change between calls. +/* The result is a null pointer in case of failure. +/* +/* auto_clnt_recover() recovers from a server-initiated disconnect +/* that happened in the middle of an I/O operation. +/* +/* auto_clnt_name() returns the name of the specified client endpoint. +/* +/* auto_clnt_free() destroys of the specified client endpoint. +/* +/* auto_clnt_control() allows the user to fine tune the behavior of +/* the specified client. The arguments are a list of (name, value) +/* terminated with AUTO_CLNT_CTL_END. +/* The following lists the names and the types of the corresponding +/* value arguments. +/* .IP "AUTO_CLNT_CTL_HANDSHAKE(VSTREAM *)" +/* A pointer to function that will be called at the start of a +/* new connection, and that returns 0 in case of success. +/* .PP +/* Arguments: +/* .IP service +/* The service argument specifies "transport:servername" where +/* transport is currently limited to one of the following: +/* .RS +/* .IP inet +/* servername has the form "inet:host:port". +/* .IP local +/* servername has the form "local:private/servicename" or +/* "local:public/servicename". This is the preferred way to +/* specify Postfix daemons that are configured as "unix" in +/* master.cf. +/* .IP unix +/* servername has the form "unix:private/servicename" or +/* "unix:public/servicename". This does not work on Solaris, +/* where Postfix uses STREAMS instead of UNIX-domain sockets. +/* .RE +/* .IP timeout +/* The time limit for sending, receiving, or for connecting +/* to a server. Specify a value <=0 to disable the time limit. +/* .IP max_idle +/* Idle time after which the client disconnects. Specify 0 to +/* disable the limit. +/* .IP max_ttl +/* Upper bound on the time that a connection is allowed to persist. +/* Specify 0 to disable the limit. +/* .IP open_action +/* Application call-back routine that opens a stream or returns a +/* null pointer upon failure. In case of success, the call-back routine +/* is expected to set the stream pathname to the server endpoint name. +/* .IP context +/* Application context that is passed to the open_action routine. +/* .IP handshake +/* A null pointer, or a pointer to function that will be called +/* at the start of a new connection and that returns 0 in case +/* of success. +/* DIAGNOSTICS +/* Warnings: communication failure. Fatal error: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * AUTO_CLNT is an opaque structure. None of the access methods can easily + * be implemented as a macro, and access is not performance critical anyway. + */ +struct AUTO_CLNT { + VSTREAM *vstream; /* buffered I/O */ + char *endpoint; /* host:port or pathname */ + int timeout; /* I/O time limit */ + int max_idle; /* time before client disconnect */ + int max_ttl; /* time before client disconnect */ + AUTO_CLNT_HANDSHAKE_FN handshake; /* new connection only */ + int (*connect) (const char *, int, int); /* unix, local, inet */ +}; + +static void auto_clnt_close(AUTO_CLNT *); + +/* auto_clnt_event - server-initiated disconnect or client-side max_idle */ + +static void auto_clnt_event(int unused_event, void *context) +{ + AUTO_CLNT *auto_clnt = (AUTO_CLNT *) context; + + /* + * Sanity check. This routine causes the stream to be closed, so it + * cannot be called when the stream is already closed. + */ + if (auto_clnt->vstream == 0) + msg_panic("auto_clnt_event: stream is closed"); + + auto_clnt_close(auto_clnt); +} + +/* auto_clnt_ttl_event - client-side expiration */ + +static void auto_clnt_ttl_event(int event, void *context) +{ + + /* + * XXX This function is needed only because event_request_timer() cannot + * distinguish between requests that specify the same call-back routine + * and call-back context. The fix is obvious: specify a request ID along + * with the call-back routine, but there is too much code that would have + * to be changed. + * + * XXX Should we be concerned that an overly aggressive optimizer will + * eliminate this function and replace calls to auto_clnt_ttl_event() by + * direct calls to auto_clnt_event()? It should not, because there exists + * code that takes the address of both functions. + */ + auto_clnt_event(event, context); +} + +/* auto_clnt_open - connect to service */ + +static void auto_clnt_open(AUTO_CLNT *auto_clnt) +{ + const char *myname = "auto_clnt_open"; + int fd; + + /* + * Sanity check. + */ + if (auto_clnt->vstream) + msg_panic("auto_clnt_open: stream is open"); + + /* + * Schedule a read event so that we can clean up when the remote side + * disconnects, and schedule a timer event so that we can cleanup an idle + * connection. Note that both events are handled by the same routine. + * + * Finally, schedule an event to force disconnection even when the + * connection is not idle. This is to prevent one client from clinging on + * to a server forever. + */ + fd = auto_clnt->connect(auto_clnt->endpoint, BLOCKING, auto_clnt->timeout); + if (fd < 0) { + msg_warn("connect to %s: %m", auto_clnt->endpoint); + } else { + if (msg_verbose) + msg_info("%s: connected to %s", myname, auto_clnt->endpoint); + auto_clnt->vstream = vstream_fdopen(fd, O_RDWR); + vstream_control(auto_clnt->vstream, + CA_VSTREAM_CTL_PATH(auto_clnt->endpoint), + CA_VSTREAM_CTL_TIMEOUT(auto_clnt->timeout), + CA_VSTREAM_CTL_END); + } + + if (auto_clnt->vstream != 0) { + close_on_exec(vstream_fileno(auto_clnt->vstream), CLOSE_ON_EXEC); + event_enable_read(vstream_fileno(auto_clnt->vstream), auto_clnt_event, + (void *) auto_clnt); + if (auto_clnt->max_idle > 0) + event_request_timer(auto_clnt_event, (void *) auto_clnt, + auto_clnt->max_idle); + if (auto_clnt->max_ttl > 0) + event_request_timer(auto_clnt_ttl_event, (void *) auto_clnt, + auto_clnt->max_ttl); + } +} + +/* auto_clnt_close - disconnect from service */ + +static void auto_clnt_close(AUTO_CLNT *auto_clnt) +{ + const char *myname = "auto_clnt_close"; + + /* + * Sanity check. + */ + if (auto_clnt->vstream == 0) + msg_panic("%s: stream is closed", myname); + + /* + * Be sure to disable read and timer events. + */ + if (msg_verbose) + msg_info("%s: disconnect %s stream", + myname, VSTREAM_PATH(auto_clnt->vstream)); + event_disable_readwrite(vstream_fileno(auto_clnt->vstream)); + event_cancel_timer(auto_clnt_event, (void *) auto_clnt); + event_cancel_timer(auto_clnt_ttl_event, (void *) auto_clnt); + (void) vstream_fclose(auto_clnt->vstream); + auto_clnt->vstream = 0; +} + +/* auto_clnt_recover - recover from server-initiated disconnect */ + +void auto_clnt_recover(AUTO_CLNT *auto_clnt) +{ + + /* + * Clean up. Don't re-connect until the caller needs it. + */ + if (auto_clnt->vstream) + auto_clnt_close(auto_clnt); +} + +/* auto_clnt_access - access a client stream */ + +VSTREAM *auto_clnt_access(AUTO_CLNT *auto_clnt) +{ + AUTO_CLNT_HANDSHAKE_FN handshake; + + /* + * Open a stream or restart the idle timer. + * + * Important! Do not restart the TTL timer! + */ + if (auto_clnt->vstream == 0) { + auto_clnt_open(auto_clnt); + handshake = (auto_clnt->vstream ? auto_clnt->handshake : 0); + } else { + if (auto_clnt->max_idle > 0) + event_request_timer(auto_clnt_event, (void *) auto_clnt, + auto_clnt->max_idle); + handshake = 0; + } + if (handshake != 0 && handshake(auto_clnt->vstream) != 0) + return (0); + return (auto_clnt->vstream); +} + +/* auto_clnt_create - create client stream object */ + +AUTO_CLNT *auto_clnt_create(const char *service, int timeout, + int max_idle, int max_ttl) +{ + const char *myname = "auto_clnt_create"; + char *transport = mystrdup(service); + char *endpoint; + AUTO_CLNT *auto_clnt; + + /* + * Don't open the stream until the caller needs it. + */ + if ((endpoint = split_at(transport, ':')) == 0 + || *endpoint == 0 || *transport == 0) + msg_fatal("need service transport:endpoint instead of \"%s\"", service); + if (msg_verbose) + msg_info("%s: transport=%s endpoint=%s", myname, transport, endpoint); + auto_clnt = (AUTO_CLNT *) mymalloc(sizeof(*auto_clnt)); + auto_clnt->vstream = 0; + auto_clnt->endpoint = mystrdup(endpoint); + auto_clnt->timeout = timeout; + auto_clnt->max_idle = max_idle; + auto_clnt->max_ttl = max_ttl; + auto_clnt->handshake = 0; + if (strcmp(transport, "inet") == 0) { + auto_clnt->connect = inet_connect; + } else if (strcmp(transport, "local") == 0) { + auto_clnt->connect = LOCAL_CONNECT; + } else if (strcmp(transport, "unix") == 0) { + auto_clnt->connect = unix_connect; + } else { + msg_fatal("invalid transport name: %s in service: %s", + transport, service); + } + myfree(transport); + return (auto_clnt); +} + +/* auto_clnt_name - return client stream name */ + +const char *auto_clnt_name(AUTO_CLNT *auto_clnt) +{ + return (auto_clnt->endpoint); +} + +/* auto_clnt_free - destroy client stream instance */ + +void auto_clnt_free(AUTO_CLNT *auto_clnt) +{ + if (auto_clnt->vstream) + auto_clnt_close(auto_clnt); + myfree(auto_clnt->endpoint); + myfree((void *) auto_clnt); +} + +/* auto_clnt_control - fine control */ + +void auto_clnt_control(AUTO_CLNT *client, int name,...) +{ + const char *myname = "auto_clnt_control"; + va_list ap; + + for (va_start(ap, name); name != AUTO_CLNT_CTL_END; name = va_arg(ap, int)) { + switch (name) { + case AUTO_CLNT_CTL_HANDSHAKE: + client->handshake = va_arg(ap, AUTO_CLNT_HANDSHAKE_FN); + break; + default: + msg_panic("%s: bad name %d", myname, name); + } + } + va_end(ap); +} diff --git a/src/util/auto_clnt.h b/src/util/auto_clnt.h new file mode 100644 index 0000000..61c6680 --- /dev/null +++ b/src/util/auto_clnt.h @@ -0,0 +1,51 @@ +#ifndef _AUTO_CLNT_H_INCLUDED_ +#define _AUTO_CLNT_H_INCLUDED_ + +/*++ +/* NAME +/* auto_clnt 3h +/* SUMMARY +/* client endpoint maintenance +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +typedef struct AUTO_CLNT AUTO_CLNT; +typedef int (*AUTO_CLNT_HANDSHAKE_FN) (VSTREAM *); + +extern AUTO_CLNT *auto_clnt_create(const char *, int, int, int); +extern VSTREAM *auto_clnt_access(AUTO_CLNT *); +extern void auto_clnt_recover(AUTO_CLNT *); +extern const char *auto_clnt_name(AUTO_CLNT *); +extern void auto_clnt_free(AUTO_CLNT *); +extern void auto_clnt_control(AUTO_CLNT *, int,...); + +#define AUTO_CLNT_CTL_END 0 +#define AUTO_CLNT_CTL_HANDSHAKE 1 /* handshake before first request */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/balpar.c b/src/util/balpar.c new file mode 100644 index 0000000..6ff97eb --- /dev/null +++ b/src/util/balpar.c @@ -0,0 +1,56 @@ +/*++ +/* NAME +/* balpar 3 +/* SUMMARY +/* determine length of string in parentheses +/* SYNOPSIS +/* #include +/* +/* size_t balpar(string, parens) +/* const char *string; +/* const char *parens; +/* DESCRIPTION +/* balpar() determines the length of a string enclosed in +/* the specified parentheses, zero in case of error. +/* SEE ALSO +/* A balpar() routine appears in Brian W. Kernighan, P.J. Plauger: +/* "Software Tools", Addison-Wesley 1976. This function is different. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include + +/* balpar - return length of {text} */ + +size_t balpar(const char *string, const char *parens) +{ + const char *cp; + int level; + int ch; + + if (*string != parens[0]) + return (0); + for (level = 1, cp = string + 1; (ch = *cp) != 0; cp++) { + if (ch == parens[1]) { + if (--level == 0) + return (cp - string + 1); + } else if (ch == parens[0]) { + level++; + } + } + return (0); +} diff --git a/src/util/base32_code.c b/src/util/base32_code.c new file mode 100644 index 0000000..31566ea --- /dev/null +++ b/src/util/base32_code.c @@ -0,0 +1,266 @@ +/*++ +/* NAME +/* base32_code 3 +/* SUMMARY +/* encode/decode data, base 32 style +/* SYNOPSIS +/* #include +/* +/* VSTRING *base32_encode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* +/* VSTRING *base32_decode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* DESCRIPTION +/* base32_encode() takes a block of len bytes and encodes it as one +/* null-terminated string. The result value is the result argument. +/* +/* base32_decode() performs the opposite transformation. The result +/* value is the result argument. The result is null terminated, whether +/* or not that makes sense. +/* DIAGNOSTICS +/* base32_decode() returns a null pointer when the input contains +/* characters not in the base 32 alphabet. +/* SEE ALSO +/* RFC 4648; padding is strictly enforced +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include + +#ifndef UCHAR_MAX +#define UCHAR_MAX 0xff +#endif + +/* Utility library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +static unsigned char to_b32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + +#define UNSIG_CHAR_PTR(x) ((unsigned char *)(x)) + +/* base32_encode - raw data to encoded */ + +VSTRING *base32_encode(VSTRING *result, const char *in, ssize_t len) +{ + const unsigned char *cp; + ssize_t count; + static int pad_count[] = {0, 6, 4, 3, 1}; + + /* + * Encode 5 -> 8. + */ + VSTRING_RESET(result); + for (cp = UNSIG_CHAR_PTR(in), count = len; count > 0; count -= 5, cp += 5) { + VSTRING_ADDCH(result, to_b32[cp[0] >> 3]); + if (count < 2) { + VSTRING_ADDCH(result, to_b32[(cp[0] & 0x7) << 2]); + break; + } + VSTRING_ADDCH(result, to_b32[(cp[0] & 0x7) << 2 | cp[1] >> 6]); + VSTRING_ADDCH(result, to_b32[(cp[1] & 0x3f) >> 1]); + if (count < 3) { + VSTRING_ADDCH(result, to_b32[(cp[1] & 0x1) << 4]); + break; + } + VSTRING_ADDCH(result, to_b32[(cp[1] & 0x1) << 4 | cp[2] >> 4]); + if (count < 4) { + VSTRING_ADDCH(result, to_b32[(cp[2] & 0xf) << 1]); + break; + } + VSTRING_ADDCH(result, to_b32[(cp[2] & 0xf) << 1 | cp[3] >> 7]); + VSTRING_ADDCH(result, to_b32[(cp[3] & 0x7f) >> 2]); + if (count < 5) { + VSTRING_ADDCH(result, to_b32[(cp[3] & 0x3) << 3]); + break; + } + VSTRING_ADDCH(result, to_b32[(cp[3] & 0x3) << 3 | cp[4] >> 5]); + VSTRING_ADDCH(result, to_b32[cp[4] & 0x1f]); + } + if (count > 0) + vstring_strncat(result, "======", pad_count[count]); + VSTRING_TERMINATE(result); + return (result); +} + +/* base32_decode - encoded data to raw */ + +VSTRING *base32_decode(VSTRING *result, const char *in, ssize_t len) +{ + static unsigned char *un_b32 = 0; + const unsigned char *cp; + ssize_t count; + unsigned int ch0; + unsigned int ch1; + unsigned int ch2; + unsigned int ch3; + unsigned int ch4; + unsigned int ch5; + unsigned int ch6; + unsigned int ch7; + +#define CHARS_PER_BYTE (UCHAR_MAX + 1) +#define INVALID 0xff +#if 1 +#define ENFORCE_LENGTH(x) (x) +#define ENFORCE_PADDING(x) (x) +#define ENFORCE_NULL_BITS(x) (x) +#else +#define ENFORCE_LENGTH(x) (1) +#define ENFORCE_PADDING(x) (1) +#define ENFORCE_NULL_BITS(x) (1) +#endif + + /* + * Sanity check. + */ + if (ENFORCE_LENGTH(len % 8)) + return (0); + + /* + * Once: initialize the decoding lookup table on the fly. + */ + if (un_b32 == 0) { + un_b32 = (unsigned char *) mymalloc(CHARS_PER_BYTE); + memset(un_b32, INVALID, CHARS_PER_BYTE); + for (cp = to_b32; cp < to_b32 + sizeof(to_b32) - 1; cp++) + un_b32[*cp] = cp - to_b32; + } + + /* + * Decode 8 -> 5. + */ + VSTRING_RESET(result); + for (cp = UNSIG_CHAR_PTR(in), count = 0; count < len; count += 8) { + if ((ch0 = un_b32[*cp++]) == INVALID + || (ch1 = un_b32[*cp++]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch0 << 3 | ch1 >> 2); + if ((ch2 = *cp++) == '=' + && ENFORCE_PADDING(strcmp((char *) cp, "=====") == 0) + && ENFORCE_NULL_BITS((ch1 & 0x3) == 0)) + break; + if ((ch2 = un_b32[ch2]) == INVALID) + return (0); + if ((ch3 = un_b32[*cp++]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch1 << 6 | ch2 << 1 | ch3 >> 4); + if ((ch4 = *cp++) == '=' + && ENFORCE_PADDING(strcmp((char *) cp, "===") == 0) + && ENFORCE_NULL_BITS((ch3 & 0xf) == 0)) + break; + if ((ch4 = un_b32[ch4]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch3 << 4 | ch4 >> 1); + if ((ch5 = *cp++) == '=' + && ENFORCE_PADDING(strcmp((char *) cp, "==") == 0) + && ENFORCE_NULL_BITS((ch4 & 0x1) == 0)) + break; + if ((ch5 = un_b32[ch5]) == INVALID) + return (0); + if ((ch6 = un_b32[*cp++]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch4 << 7 | ch5 << 2 | ch6 >> 3); + if ((ch7 = *cp++) == '=' + && ENFORCE_NULL_BITS((ch6 & 0x7) == 0)) + break; + if ((ch7 = un_b32[ch7]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch6 << 5 | ch7); + } + VSTRING_TERMINATE(result); + return (result); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: convert to base 32 and back. + */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *b1 = vstring_alloc(1); + VSTRING *b2 = vstring_alloc(1); + VSTRING *test = vstring_alloc(1); + int i, j; + + /* + * Test all byte values (except null) on all byte positions. + */ + for (j = 0; j < 256; j++) + for (i = 1; i < 256; i++) + VSTRING_ADDCH(test, i); + VSTRING_TERMINATE(test); + +#define DECODE(b,x,l) { \ + if (base32_decode((b),(x),(l)) == 0) \ + msg_panic("bad base32: %s", (x)); \ + } +#define VERIFY(b,t,l) { \ + if (memcmp((b), (t), (l)) != 0) \ + msg_panic("bad test: %s", (b)); \ + } + + /* + * Test all padding variants. + */ + for (i = 1; i <= 8; i++) { + base32_encode(b1, STR(test), LEN(test)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), STR(test), LEN(test)); + + base32_encode(b1, STR(test), LEN(test)); + base32_encode(b2, STR(b1), LEN(b1)); + base32_encode(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), STR(test), LEN(test)); + + base32_encode(b1, STR(test), LEN(test)); + base32_encode(b2, STR(b1), LEN(b1)); + base32_encode(b1, STR(b2), LEN(b2)); + base32_encode(b2, STR(b1), LEN(b1)); + base32_encode(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), STR(test), LEN(test)); + vstring_truncate(test, LEN(test) - 1); + } + vstring_free(test); + vstring_free(b1); + vstring_free(b2); + return (0); +} + +#endif diff --git a/src/util/base32_code.h b/src/util/base32_code.h new file mode 100644 index 0000000..aa258fe --- /dev/null +++ b/src/util/base32_code.h @@ -0,0 +1,41 @@ +#ifndef _BASE32_CODE_H_INCLUDED_ +#define _BASE32_CODE_H_INCLUDED_ + +/*++ +/* NAME +/* base32_code 3h +/* SUMMARY +/* encode/decode data, base 32 style +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern VSTRING *base32_encode(VSTRING *, const char *, ssize_t); +extern VSTRING *WARN_UNUSED_RESULT base32_decode(VSTRING *, const char *, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/base64_code.c b/src/util/base64_code.c new file mode 100644 index 0000000..e607218 --- /dev/null +++ b/src/util/base64_code.c @@ -0,0 +1,228 @@ +/*++ +/* NAME +/* base64_code 3 +/* SUMMARY +/* encode/decode data, base 64 style +/* SYNOPSIS +/* #include +/* +/* VSTRING *base64_encode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* +/* VSTRING *base64_decode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* +/* VSTRING *base64_encode_opt(result, in, len, flags) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* int flags; +/* +/* VSTRING *base64_decode_opt(result, in, len, flags) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* int flags; +/* DESCRIPTION +/* base64_encode() takes a block of len bytes and encodes it as one +/* null-terminated string. The result value is the result argument. +/* +/* base64_decode() performs the opposite transformation. The result +/* value is the result argument. The result is null terminated, whether +/* or not that makes sense. +/* +/* base64_encode_opt() and base64_decode_opt() provide extended +/* interfaces. In both cases the flags arguments is the bit-wise +/* OR of zero or more the following: +/* .IP BASE64_FLAG_APPEND +/* Append the result, instead of overwriting the result buffer. +/* .PP +/* For convenience, BASE64_FLAG_NONE specifies none of the above. +/* DIAGNOSTICS +/* base64_decode () returns a null pointer when the input contains +/* characters not in the base 64 alphabet. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include + +#ifndef UCHAR_MAX +#define UCHAR_MAX 0xff +#endif + +/* Utility library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +static unsigned char to_b64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define UNSIG_CHAR_PTR(x) ((unsigned char *)(x)) + +/* base64_encode - raw data to encoded */ + +#undef base64_encode + +extern VSTRING *base64_encode(VSTRING *, const char *, ssize_t); + +VSTRING *base64_encode(VSTRING *result, const char *in, ssize_t len) +{ + return (base64_encode_opt(result, in, len, BASE64_FLAG_NONE)); +} + +VSTRING *base64_encode_opt(VSTRING *result, const char *in, ssize_t len, + int flags) +{ + const unsigned char *cp; + ssize_t count; + + /* + * Encode 3 -> 4. + */ + if ((flags & BASE64_FLAG_APPEND) == 0) + VSTRING_RESET(result); + for (cp = UNSIG_CHAR_PTR(in), count = len; count > 0; count -= 3, cp += 3) { + VSTRING_ADDCH(result, to_b64[cp[0] >> 2]); + if (count > 1) { + VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4 | cp[1] >> 4]); + if (count > 2) { + VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2 | cp[2] >> 6]); + VSTRING_ADDCH(result, to_b64[cp[2] & 0x3f]); + } else { + VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2]); + VSTRING_ADDCH(result, '='); + break; + } + } else { + VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4]); + VSTRING_ADDCH(result, '='); + VSTRING_ADDCH(result, '='); + break; + } + } + VSTRING_TERMINATE(result); + return (result); +} + +/* base64_decode - encoded data to raw */ + +#undef base64_decode + +extern VSTRING *base64_decode(VSTRING *, const char *, ssize_t); + +VSTRING *base64_decode(VSTRING *result, const char *in, ssize_t len) +{ + return (base64_decode_opt(result, in, len, BASE64_FLAG_NONE)); +} + +VSTRING *base64_decode_opt(VSTRING *result, const char *in, ssize_t len, + int flags) +{ + static unsigned char *un_b64 = 0; + const unsigned char *cp; + ssize_t count; + unsigned int ch0; + unsigned int ch1; + unsigned int ch2; + unsigned int ch3; + +#define CHARS_PER_BYTE (UCHAR_MAX + 1) +#define INVALID 0xff + + /* + * Sanity check. + */ + if (len % 4) + return (0); + + /* + * Once: initialize the decoding lookup table on the fly. + */ + if (un_b64 == 0) { + un_b64 = (unsigned char *) mymalloc(CHARS_PER_BYTE); + memset(un_b64, INVALID, CHARS_PER_BYTE); + for (cp = to_b64; cp < to_b64 + sizeof(to_b64) - 1; cp++) + un_b64[*cp] = cp - to_b64; + } + + /* + * Decode 4 -> 3. + */ + if ((flags & BASE64_FLAG_APPEND) == 0) + VSTRING_RESET(result); + for (cp = UNSIG_CHAR_PTR(in), count = 0; count < len; count += 4) { + if ((ch0 = un_b64[*cp++]) == INVALID + || (ch1 = un_b64[*cp++]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch0 << 2 | ch1 >> 4); + if ((ch2 = *cp++) == '=') + break; + if ((ch2 = un_b64[ch2]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch1 << 4 | ch2 >> 2); + if ((ch3 = *cp++) == '=') + break; + if ((ch3 = un_b64[ch3]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch2 << 6 | ch3); + } + VSTRING_TERMINATE(result); + return (result); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: convert to base 64 and back. + */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *b1 = vstring_alloc(1); + VSTRING *b2 = vstring_alloc(1); + char test[256]; + int n; + + for (n = 0; n < sizeof(test); n++) + test[n] = n; + base64_encode(b1, test, sizeof(test)); + if (base64_decode(b2, STR(b1), LEN(b1)) == 0) + msg_panic("bad base64: %s", STR(b1)); + if (LEN(b2) != sizeof(test)) + msg_panic("bad decode length: %ld != %ld", + (long) LEN(b2), (long) sizeof(test)); + for (n = 0; n < sizeof(test); n++) + if (STR(b2)[n] != test[n]) + msg_panic("bad decode value %d != %d", + (unsigned char) STR(b2)[n], (unsigned char) test[n]); + vstring_free(b1); + vstring_free(b2); + return (0); +} + +#endif diff --git a/src/util/base64_code.h b/src/util/base64_code.h new file mode 100644 index 0000000..fefdf4e --- /dev/null +++ b/src/util/base64_code.h @@ -0,0 +1,49 @@ +#ifndef _BASE64_CODE_H_INCLUDED_ +#define _BASE64_CODE_H_INCLUDED_ + +/*++ +/* NAME +/* base64_code 3h +/* SUMMARY +/* encode/decode data, base 64 style +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern VSTRING *base64_encode_opt(VSTRING *, const char *, ssize_t, int); +extern VSTRING *WARN_UNUSED_RESULT base64_decode_opt(VSTRING *, const char *, ssize_t, int); + +#define BASE64_FLAG_NONE 0 +#define BASE64_FLAG_APPEND (1<<0) + +#define base64_encode(bp, cp, ln) \ + base64_encode_opt((bp), (cp), (ln), BASE64_FLAG_NONE) +#define base64_decode(bp, cp, ln) \ + base64_decode_opt((bp), (cp), (ln), BASE64_FLAG_NONE) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/basename.c b/src/util/basename.c new file mode 100644 index 0000000..429e6cf --- /dev/null +++ b/src/util/basename.c @@ -0,0 +1,49 @@ +/*++ +/* NAME +/* basename 3 +/* SUMMARY +/* extract file basename +/* SYNOPSIS +/* #include +/* +/* char *basename(path) +/* const char *path; +/* DESCRIPTION +/* The \fBbasename\fR routine skips over the last '/' in +/* \fIpath\fR and returns a pointer to the result. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +#ifndef HAVE_BASENAME + +/* Utility library. */ + +#include "stringops.h" + +/* basename - skip directory prefix */ + +char *basename(const char *path) +{ + char *result; + + if ((result = strrchr(path, '/')) == 0) + result = (char *) path; + else + result += 1; + return (result); +} + +#endif diff --git a/src/util/binhash.c b/src/util/binhash.c new file mode 100644 index 0000000..fd9bc87 --- /dev/null +++ b/src/util/binhash.c @@ -0,0 +1,447 @@ +/*++ +/* NAME +/* binhash 3 +/* SUMMARY +/* hash table manager +/* SYNOPSIS +/* #include +/* +/* typedef struct { +/* .in +4 +/* void *key; +/* ssize_t key_len; +/* void *value; +/* /* private fields... */ +/* .in -4 +/* } BINHASH_INFO; +/* +/* BINHASH *binhash_create(size) +/* ssize_t size; +/* +/* BINHASH_INFO *binhash_enter(table, key, key_len, value) +/* BINHASH *table; +/* const void *key; +/* ssize_t key_len; +/* void *value; +/* +/* char *binhash_find(table, key, key_len) +/* BINHASH *table; +/* const void *key; +/* ssize_t key_len; +/* +/* BINHASH_INFO *binhash_locate(table, key, key_len) +/* BINHASH *table; +/* const void *key; +/* ssize_t key_len; +/* +/* void binhash_delete(table, key, key_len, free_fn) +/* BINHASH *table; +/* const void *key; +/* ssize_t key_len; +/* void (*free_fn)(void *); +/* +/* void binhash_free(table, free_fn) +/* BINHASH *table; +/* void (*free_fn)(void *); +/* +/* void binhash_walk(table, action, ptr) +/* BINHASH *table; +/* void (*action)(BINHASH_INFO *info, void *ptr); +/* void *ptr; +/* +/* BINHASH_INFO **binhash_list(table) +/* BINHASH *table; +/* +/* BINHASH_INFO *binhash_sequence(table, how) +/* BINHASH *table; +/* int how; +/* DESCRIPTION +/* This module maintains one or more hash tables. Each table entry +/* consists of a unique binary-valued lookup key and a generic +/* character-pointer value. +/* The tables are automatically resized when they fill up. When the +/* values to be remembered are not character pointers, proper casts +/* should be used or the code will not be portable. +/* +/* binhash_create() creates a table of the specified size and returns a +/* pointer to the result. The lookup keys are saved with mymemdup(). +/* +/* binhash_enter() stores a (key, value) pair into the specified table +/* and returns a pointer to the resulting entry. The code does not +/* check if an entry with that key already exists: use binhash_locate() +/* for updating an existing entry. The key is copied; the value is not. +/* +/* binhash_find() returns the value that was stored under the given key, +/* or a null pointer if it was not found. In order to distinguish +/* a null value from a non-existent value, use binhash_locate(). +/* +/* binhash_locate() returns a pointer to the entry that was stored +/* for the given key, or a null pointer if it was not found. +/* +/* binhash_delete() removes one entry that was stored under the given key. +/* If the free_fn argument is not a null pointer, the corresponding +/* function is called with as argument the value that was stored under +/* the key. +/* +/* binhash_free() destroys a hash table, including contents. If the free_fn +/* argument is not a null pointer, the corresponding function is called +/* for each table entry, with as argument the value that was stored +/* with the entry. +/* +/* binhash_walk() invokes the action function for each table entry, with +/* a pointer to the entry as its argument. The ptr argument is passed +/* on to the action function. +/* +/* binhash_list() returns a null-terminated list of pointers to +/* all elements in the named table. The list should be passed to +/* myfree(). +/* +/* binhash_sequence() returns the first or next element +/* depending on the value of the "how" argument. Specify +/* BINHASH_SEQ_FIRST to start a new sequence, BINHASH_SEQ_NEXT +/* to continue, and BINHASH_SEQ_STOP to terminate a sequence +/* early. The caller must not delete an element before it is +/* visited. +/* RESTRICTIONS +/* A callback function should not modify the hash table that is +/* specified to its caller. +/* DIAGNOSTICS +/* The following conditions are reported and cause the program to +/* terminate immediately: memory allocation failure; an attempt +/* to delete a non-existent entry. +/* SEE ALSO +/* mymalloc(3) memory management wrapper +/* hash_fnv(3) Fowler/Noll/Vo hash function +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* C library */ + +#include +#include + +/* Local stuff */ + +#include "mymalloc.h" +#include "msg.h" +#include "binhash.h" + +/* binhash_hash - hash a string */ + +#ifndef NO_HASH_FNV +#include "hash_fnv.h" + +#define binhash_hash(key, len, size) (hash_fnv((key), (len)) % (size)) + +#else + +static size_t binhash_hash(const void *key, ssize_t len, size_t size) +{ + size_t h = 0; + size_t g; + + /* + * From the "Dragon" book by Aho, Sethi and Ullman. + */ + + while (len-- > 0) { + h = (h << 4U) + *(unsigned const char *) key++; + if ((g = (h & 0xf0000000)) != 0) { + h ^= (g >> 24U); + h ^= g; + } + } + return (h % size); +} + +#endif + +/* binhash_link - insert element into table */ + +#define binhash_link(table, elm) { \ + BINHASH_INFO **_h = table->data + binhash_hash(elm->key, elm->key_len, table->size);\ + elm->prev = 0; \ + if ((elm->next = *_h) != 0) \ + (*_h)->prev = elm; \ + *_h = elm; \ + table->used++; \ +} + +/* binhash_size - allocate and initialize hash table */ + +static void binhash_size(BINHASH *table, size_t size) +{ + BINHASH_INFO **h; + + size |= 1; + + table->data = h = (BINHASH_INFO **) mymalloc(size * sizeof(BINHASH_INFO *)); + table->size = size; + table->used = 0; + + while (size-- > 0) + *h++ = 0; +} + +/* binhash_create - create initial hash table */ + +BINHASH *binhash_create(ssize_t size) +{ + BINHASH *table; + + table = (BINHASH *) mymalloc(sizeof(BINHASH)); + binhash_size(table, size < 13 ? 13 : size); + table->seq_bucket = table->seq_element = 0; + return (table); +} + +/* binhash_grow - extend existing table */ + +static void binhash_grow(BINHASH *table) +{ + BINHASH_INFO *ht; + BINHASH_INFO *next; + ssize_t old_size = table->size; + BINHASH_INFO **h = table->data; + BINHASH_INFO **old_entries = h; + + binhash_size(table, 2 * old_size); + + while (old_size-- > 0) { + for (ht = *h++; ht; ht = next) { + next = ht->next; + binhash_link(table, ht); + } + } + myfree((void *) old_entries); +} + +/* binhash_enter - enter (key, value) pair */ + +BINHASH_INFO *binhash_enter(BINHASH *table, const void *key, ssize_t key_len, void *value) +{ + BINHASH_INFO *ht; + + if (table->used >= table->size) + binhash_grow(table); + ht = (BINHASH_INFO *) mymalloc(sizeof(BINHASH_INFO)); + ht->key = mymemdup(key, key_len); + ht->key_len = key_len; + ht->value = value; + binhash_link(table, ht); + return (ht); +} + +/* binhash_find - lookup value */ + +void *binhash_find(BINHASH *table, const void *key, ssize_t key_len) +{ + BINHASH_INFO *ht; + +#define KEY_EQ(x,y,l) (((unsigned char *) x)[0] == ((unsigned char *) y)[0] && memcmp(x,y,l) == 0) + + if (table != 0) + for (ht = table->data[binhash_hash(key, key_len, table->size)]; ht; ht = ht->next) + if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len)) + return (ht->value); + return (0); +} + +/* binhash_locate - lookup entry */ + +BINHASH_INFO *binhash_locate(BINHASH *table, const void *key, ssize_t key_len) +{ + BINHASH_INFO *ht; + + if (table != 0) + for (ht = table->data[binhash_hash(key, key_len, table->size)]; ht; ht = ht->next) + if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len)) + return (ht); + return (0); +} + +/* binhash_delete - delete one entry */ + +void binhash_delete(BINHASH *table, const void *key, ssize_t key_len, void (*free_fn) (void *)) +{ + if (table != 0) { + BINHASH_INFO *ht; + BINHASH_INFO **h = table->data + binhash_hash(key, key_len, table->size); + + for (ht = *h; ht; ht = ht->next) { + if (key_len == ht->key_len && KEY_EQ(key, ht->key, key_len)) { + if (ht->next) + ht->next->prev = ht->prev; + if (ht->prev) + ht->prev->next = ht->next; + else + *h = ht->next; + table->used--; + myfree(ht->key); + if (free_fn) + (*free_fn) (ht->value); + myfree((void *) ht); + return; + } + } + msg_panic("binhash_delete: unknown_key: \"%s\"", (char *) key); + } +} + +/* binhash_free - destroy hash table */ + +void binhash_free(BINHASH *table, void (*free_fn) (void *)) +{ + if (table != 0) { + ssize_t i = table->size; + BINHASH_INFO *ht; + BINHASH_INFO *next; + BINHASH_INFO **h = table->data; + + while (i-- > 0) { + for (ht = *h++; ht; ht = next) { + next = ht->next; + myfree(ht->key); + if (free_fn) + (*free_fn) (ht->value); + myfree((void *) ht); + } + } + myfree((void *) table->data); + table->data = 0; + if (table->seq_bucket) + myfree((void *) table->seq_bucket); + table->seq_bucket = 0; + myfree((void *) table); + } +} + +/* binhash_walk - iterate over hash table */ + +void binhash_walk(BINHASH *table, void (*action) (BINHASH_INFO *, void *), + void *ptr) { + if (table != 0) { + ssize_t i = table->size; + BINHASH_INFO **h = table->data; + BINHASH_INFO *ht; + + while (i-- > 0) + for (ht = *h++; ht; ht = ht->next) + (*action) (ht, ptr); + } +} + +/* binhash_list - list all table members */ + +BINHASH_INFO **binhash_list(table) +BINHASH *table; +{ + BINHASH_INFO **list; + BINHASH_INFO *member; + ssize_t count = 0; + ssize_t i; + + if (table != 0) { + list = (BINHASH_INFO **) mymalloc(sizeof(*list) * (table->used + 1)); + for (i = 0; i < table->size; i++) + for (member = table->data[i]; member != 0; member = member->next) + list[count++] = member; + } else { + list = (BINHASH_INFO **) mymalloc(sizeof(*list)); + } + list[count] = 0; + return (list); +} + +/* binhash_sequence - dict(3) compatibility iterator */ + +BINHASH_INFO *binhash_sequence(BINHASH *table, int how) +{ + if (table == 0) + return (0); + + switch (how) { + case BINHASH_SEQ_FIRST: /* start new sequence */ + if (table->seq_bucket) + myfree((void *) table->seq_bucket); + table->seq_bucket = binhash_list(table); + table->seq_element = table->seq_bucket; + return (*(table->seq_element)++); + case BINHASH_SEQ_NEXT: /* next element */ + if (table->seq_element && *table->seq_element) + return (*(table->seq_element)++); + /* FALLTHROUGH */ + default: /* terminate sequence */ + if (table->seq_bucket) { + myfree((void *) table->seq_bucket); + table->seq_bucket = table->seq_element = 0; + } + return (0); + } +} + +#ifdef TEST +#include +#include + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *buf = vstring_alloc(10); + ssize_t count = 0; + BINHASH *hash; + BINHASH_INFO **ht_info; + BINHASH_INFO **ht; + BINHASH_INFO *info; + ssize_t i; + ssize_t r; + int op; + + /* + * Load a large number of strings including terminator, and delete them + * in a random order. + */ + hash = binhash_create(10); + while (vstring_get(buf, VSTREAM_IN) != VSTREAM_EOF) + binhash_enter(hash, vstring_str(buf), VSTRING_LEN(buf) + 1, + CAST_INT_TO_VOID_PTR(count++)); + if (count != hash->used) + msg_panic("%ld entries stored, but %lu entries exist", + (long) count, (unsigned long) hash->used); + for (i = 0, op = BINHASH_SEQ_FIRST; (info = binhash_sequence(hash, op)) != 0; + i++, op = BINHASH_SEQ_NEXT) + if (memchr(info->key, 0, info->key_len) == 0) + msg_panic("no null byte in lookup key"); + if (i != hash->used) + msg_panic("%ld entries found, but %lu entries exist", + (long) i, (unsigned long) hash->used); + ht_info = binhash_list(hash); + for (i = 0; i < hash->used; i++) { + r = myrand() % hash->used; + info = ht_info[i]; + ht_info[i] = ht_info[r]; + ht_info[r] = info; + } + for (ht = ht_info; *ht; ht++) + binhash_delete(hash, ht[0]->key, ht[0]->key_len, (void (*) (void *)) 0); + if (hash->used > 0) + msg_panic("%ld entries not deleted", (long) hash->used); + myfree((void *) ht_info); + binhash_free(hash, (void (*) (void *)) 0); + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/binhash.h b/src/util/binhash.h new file mode 100644 index 0000000..3b4e1a4 --- /dev/null +++ b/src/util/binhash.h @@ -0,0 +1,65 @@ +#ifndef _BINHASH_H_INCLUDED_ +#define _BINHASH_H_INCLUDED_ + +/*++ +/* NAME +/* binhash 3h +/* SUMMARY +/* hash table manager +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* Structure of one hash table entry. */ + +typedef struct BINHASH_INFO { + void *key; /* lookup key */ + ssize_t key_len; /* key length */ + void *value; /* associated value */ + struct BINHASH_INFO *next; /* colliding entry */ + struct BINHASH_INFO *prev; /* colliding entry */ +} BINHASH_INFO; + + /* Structure of one hash table. */ + +typedef struct BINHASH { + ssize_t size; /* length of entries array */ + ssize_t used; /* number of entries in table */ + BINHASH_INFO **data; /* entries array, auto-resized */ + BINHASH_INFO **seq_bucket; /* current sequence hash bucket */ + BINHASH_INFO **seq_element; /* current sequence element */ +} BINHASH; + +extern BINHASH *binhash_create(ssize_t); +extern BINHASH_INFO *binhash_enter(BINHASH *, const void *, ssize_t, void *); +extern BINHASH_INFO *binhash_locate(BINHASH *, const void *, ssize_t); +extern void *binhash_find(BINHASH *, const void *, ssize_t); +extern void binhash_delete(BINHASH *, const void *, ssize_t, void (*) (void *)); +extern void binhash_free(BINHASH *, void (*) (void *)); +extern void binhash_walk(BINHASH *, void (*) (BINHASH_INFO *, void *), void *); +extern BINHASH_INFO **binhash_list(BINHASH *); +extern BINHASH_INFO *binhash_sequence(BINHASH *, int); + +#define BINHASH_SEQ_FIRST 0 +#define BINHASH_SEQ_NEXT 1 +#define BINHASH_SEQ_STOP (-1) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* CREATION DATE +/* Thu Feb 20 16:54:29 EST 1997 +/* LAST MODIFICATION +/* %E% %U% +/* VERSION/RELEASE +/* %I% +/*--*/ + +#endif diff --git a/src/util/byte_mask.c b/src/util/byte_mask.c new file mode 100644 index 0000000..e818476 --- /dev/null +++ b/src/util/byte_mask.c @@ -0,0 +1,306 @@ +/*++ +/* NAME +/* byte_mask 3 +/* SUMMARY +/* map byte sequence to bit mask +/* SYNOPSIS +/* #include +/* +/* typedef struct { +/* .in +4 +/* int byte_val; +/* int mask; +/* .in -4 +/* } BYTE_MASK; +/* +/* int byte_mask( +/* const char *context, +/* const BYTE_MASK *table, +/* const char *bytes); +/* +/* const char *str_byte_mask( +/* const char *context, +/* const BYTE_MASK *table, +/* int mask); +/* +/* int byte_mask_opt( +/* const char *context; +/* const BYTE_MASK *table, +/* const char *bytes, +/* int flags); +/* +/* const char *str_byte_mask_opt( +/* VSTRING *buf, +/* const char *context, +/* const BYTE_MASK *table, +/* int mask, +/* int flags); +/* DESCRIPTION +/* byte_mask() takes a null-terminated \fItable\fR with (byte +/* value, single-bit mask) pairs and computes the bit-wise OR +/* of the masks that correspond to the byte values pointed to +/* by the \fIbytes\fR argument. +/* +/* str_byte_mask() translates a bit mask into its equivalent +/* bytes. The result is written to a static buffer that is +/* overwritten upon each call. +/* +/* byte_mask_opt() and str_byte_mask_opt() are extended versions +/* with additional fine control. +/* +/* Arguments: +/* .IP buf +/* Null pointer or pointer to buffer storage. +/* .IP context +/* What kind of byte values and bit masks are being manipulated, +/* reported in error messages. Typically, this would be the +/* name of a user-configurable parameter or command-line +/* attribute. +/* .IP table +/* Table with (byte value, single-bit mask) pairs. +/* .IP bytes +/* A null-terminated string that is to be converted into a bit +/* mask. +/* .IP mask +/* A bit mask that is to be converted into null-terminated +/* string. +/* .IP flags +/* Bit-wise OR of one or more of the following. Where features +/* would have conflicting results (e.g., FATAL versus IGNORE), +/* the feature that takes precedence is described first. +/* +/* When converting from string to mask, at least one of the +/* following must be specified: BYTE_MASK_FATAL, BYTE_MASK_RETURN, +/* BYTE_MASK_WARN or BYTE_MASK_IGNORE. +/* +/* When converting from mask to string, at least one of the +/* following must be specified: BYTE_MASK_FATAL, BYTE_MASK_RETURN, +/* BYTE_MASK_WARN or BYTE_MASK_IGNORE. +/* .RS +/* .IP BYTE_MASK_FATAL +/* Require that all values in \fIbytes\fR exist in \fItable\fR, +/* and require that all bits listed in \fImask\fR exist in +/* \fItable\fR. Terminate with a fatal run-time error if this +/* condition is not met. This feature is enabled by default +/* when calling byte_mask() or str_name_mask(). +/* .IP BYTE_MASK_RETURN +/* Require that all values in \fIbytes\fR exist in \fItable\fR, +/* and require that all bits listed in \fImask\fR exist in +/* \fItable\fR. Log a warning, and return 0 (byte_mask()) or +/* a null pointer (str_byte_mask()) if this condition is not +/* met. This feature is not enabled by default when calling +/* byte_mask() or str_name_mask(). +/* .IP BYTE_MASK_WARN +/* Require that all values in \fIbytes\fR exist in \fItable\fR, +/* and require that all bits listed in \fImask\fR exist in +/* \fItable\fR. Log a warning if this condition is not met, +/* continue processing, and return all valid bits or bytes. +/* This feature is not enabled by default when calling byte_mask() +/* or str_byte_mask(). +/* .IP BYTE_MASK_IGNORE +/* Silently ignore values in \fIbytes\fR that don't exist in +/* \fItable\fR, and silently ignore bits listed in \fImask\fR +/* that don't exist in \fItable\fR. This feature is not enabled +/* by default when calling byte_mask() or str_byte_mask(). +/* .IP BYTE_MASK_ANY_CASE +/* Enable case-insensitive matching. This feature is not +/* enabled by default when calling byte_mask(); it has no +/* effect with str_byte_mask(). +/* .RE +/* The value BYTE_MASK_NONE explicitly requests no features, +/* and BYTE_MASK_DEFAULT enables the default options. +/* DIAGNOSTICS +/* Fatal: the \fIbytes\fR argument specifies a name not found in +/* \fItable\fR, or the \fImask\fR specifies a bit not found in +/* \fItable\fR. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* This code is a clone of Postfix name_mask(3). +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +#define STR(x) vstring_str(x) + +/* byte_mask_opt - compute mask corresponding to byte string */ + +int byte_mask_opt(const char *context, const BYTE_MASK *table, + const char *bytes, int flags) +{ + const char myname[] = "byte_mask"; + const char *bp; + int result = 0; + const BYTE_MASK *np; + + if ((flags & BYTE_MASK_REQUIRED) == 0) + msg_panic("%s: missing BYTE_MASK_FATAL/RETURN/WARN/IGNORE flag", + myname); + + /* + * Iterate over bytes string, and look up each byte in the table. If the + * byte is found, merge its mask with the result. + */ + for (bp = bytes; *bp; bp++) { + int byte_val = *(const unsigned char *) bp; + + for (np = table; /* void */ ; np++) { + if (np->byte_val == 0) { + if (flags & BYTE_MASK_FATAL) { + msg_fatal("unknown %s value \"%c\" in \"%s\"", + context, byte_val, bytes); + } else if (flags & BYTE_MASK_RETURN) { + msg_warn("unknown %s value \"%c\" in \"%s\"", + context, byte_val, bytes); + return (0); + } else if (flags & BYTE_MASK_WARN) { + msg_warn("unknown %s value \"%c\" in \"%s\"", + context, byte_val, bytes); + } + break; + } + if ((flags & BYTE_MASK_ANY_CASE) ? + (TOLOWER(byte_val) == TOLOWER(np->byte_val)) : + (byte_val == np->byte_val)) { + if (msg_verbose) + msg_info("%s: %c", myname, byte_val); + result |= np->mask; + break; + } + } + } + return (result); +} + +/* str_byte_mask_opt - mask to string */ + +const char *str_byte_mask_opt(VSTRING *buf, const char *context, + const BYTE_MASK *table, + int mask, int flags) +{ + const char myname[] = "byte_mask"; + const BYTE_MASK *np; + static VSTRING *my_buf = 0; + + if ((flags & STR_BYTE_MASK_REQUIRED) == 0) + msg_panic("%s: missing BYTE_MASK_FATAL/RETURN/WARN/IGNORE flag", + myname); + + if (buf == 0) { + if (my_buf == 0) + my_buf = vstring_alloc(1); + buf = my_buf; + } + VSTRING_RESET(buf); + + for (np = table; mask != 0; np++) { + if (np->byte_val == 0) { + if (flags & BYTE_MASK_FATAL) { + msg_fatal("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + } else if (flags & BYTE_MASK_RETURN) { + msg_warn("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + return (0); + } else if (flags & BYTE_MASK_WARN) { + msg_warn("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + } + break; + } + if (mask & np->mask) { + mask &= ~np->mask; + vstring_sprintf_append(buf, "%c", np->byte_val); + } + } + VSTRING_TERMINATE(buf); + + return (STR(buf)); +} + +#ifdef TEST + + /* + * Stand-alone test program. + */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + static const BYTE_MASK demo_table[] = { + '0', 1 << 0, + '1', 1 << 1, + '2', 1 << 2, + '3', 1 << 3, + 0, 0, + }; + static const NAME_MASK feature_table[] = { + "DEFAULT", BYTE_MASK_DEFAULT, + "FATAL", BYTE_MASK_FATAL, + "ANY_CASE", BYTE_MASK_ANY_CASE, + "RETURN", BYTE_MASK_RETURN, + "WARN", BYTE_MASK_WARN, + "IGNORE", BYTE_MASK_IGNORE, + 0, + }; + int in_feature_mask; + int out_feature_mask; + int demo_mask; + const char *demo_str; + VSTRING *out_buf = vstring_alloc(1); + VSTRING *in_buf = vstring_alloc(1); + + if (argc != 3) + msg_fatal("usage: %s in-feature-mask out-feature-mask", argv[0]); + in_feature_mask = name_mask(argv[1], feature_table, argv[1]); + out_feature_mask = name_mask(argv[2], feature_table, argv[2]); + while (vstring_get_nonl(in_buf, VSTREAM_IN) != VSTREAM_EOF) { + demo_mask = byte_mask_opt("name", demo_table, + STR(in_buf), in_feature_mask); + demo_str = str_byte_mask_opt(out_buf, "mask", demo_table, + demo_mask, out_feature_mask); + vstream_printf("%s -> 0x%x -> %s\n", + STR(in_buf), demo_mask, + demo_str ? demo_str : "(null)"); + demo_mask <<=1; + demo_str = str_byte_mask_opt(out_buf, "mask", demo_table, + demo_mask, out_feature_mask); + vstream_printf("0x%x -> %s\n", + demo_mask, demo_str ? demo_str : "(null)"); + vstream_fflush(VSTREAM_OUT); + } + vstring_free(in_buf); + vstring_free(out_buf); + exit(0); +} + +#endif diff --git a/src/util/byte_mask.h b/src/util/byte_mask.h new file mode 100644 index 0000000..6cdea1d --- /dev/null +++ b/src/util/byte_mask.h @@ -0,0 +1,64 @@ +#ifndef _BYTE_MASK_H_INCLUDED_ +#define _BYTE_MASK_H_INCLUDED_ + +/*++ +/* NAME +/* byte_mask 3h +/* SUMMARY +/* map names to bit mask +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +typedef struct { + int byte_val; + int mask; +} BYTE_MASK; + +#define BYTE_MASK_FATAL (1<<0) +#define BYTE_MASK_ANY_CASE (1<<1) +#define BYTE_MASK_RETURN (1<<2) +#define BYTE_MASK_WARN (1<<6) +#define BYTE_MASK_IGNORE (1<<7) + +#define BYTE_MASK_REQUIRED \ + (BYTE_MASK_FATAL | BYTE_MASK_RETURN | BYTE_MASK_WARN | BYTE_MASK_IGNORE) +#define STR_BYTE_MASK_REQUIRED (BYTE_MASK_REQUIRED) + +#define BYTE_MASK_NONE 0 +#define BYTE_MASK_DEFAULT (BYTE_MASK_FATAL) + +#define byte_mask(tag, table, str) \ + byte_mask_opt((tag), (table), (str), BYTE_MASK_DEFAULT) +#define str_byte_mask(tag, table, mask) \ + str_byte_mask_opt(((VSTRING *) 0), (tag), (table), (mask), BYTE_MASK_DEFAULT) + +extern int byte_mask_opt(const char *, const BYTE_MASK *, const char *, int); +extern const char *str_byte_mask_opt(VSTRING *, const char *, const BYTE_MASK *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/byte_mask.in b/src/util/byte_mask.in new file mode 100644 index 0000000..6d89c5a --- /dev/null +++ b/src/util/byte_mask.in @@ -0,0 +1,7 @@ +0123 +0 +1 +2 +3 +1234 +abcd diff --git a/src/util/byte_mask.ref0 b/src/util/byte_mask.ref0 new file mode 100644 index 0000000..a6ab11b --- /dev/null +++ b/src/util/byte_mask.ref0 @@ -0,0 +1,14 @@ +0123 -> 0xf -> 0123 +0x1e -> 123 +0 -> 0x1 -> 0 +0x2 -> 1 +1 -> 0x2 -> 1 +0x4 -> 2 +2 -> 0x4 -> 2 +0x8 -> 3 +3 -> 0x8 -> 3 +0x10 -> +1234 -> 0xe -> 123 +0x1c -> 23 +abcd -> 0x0 -> +0x0 -> diff --git a/src/util/byte_mask.ref1 b/src/util/byte_mask.ref1 new file mode 100644 index 0000000..92e3f4f --- /dev/null +++ b/src/util/byte_mask.ref1 @@ -0,0 +1,22 @@ +unknown: warning: byte_mask: unknown mask bit in mask: 0x10 +0123 -> 0xf -> 0123 +0x1e -> 123 +0 -> 0x1 -> 0 +0x2 -> 1 +1 -> 0x2 -> 1 +0x4 -> 2 +2 -> 0x4 -> 2 +0x8 -> 3 +unknown: warning: byte_mask: unknown mask bit in mask: 0x10 +3 -> 0x8 -> 3 +0x10 -> +unknown: warning: unknown name value "4" in "1234" +unknown: warning: byte_mask: unknown mask bit in mask: 0x10 +1234 -> 0xe -> 123 +0x1c -> 23 +unknown: warning: unknown name value "a" in "abcd" +unknown: warning: unknown name value "b" in "abcd" +unknown: warning: unknown name value "c" in "abcd" +unknown: warning: unknown name value "d" in "abcd" +abcd -> 0x0 -> +0x0 -> diff --git a/src/util/byte_mask.ref2 b/src/util/byte_mask.ref2 new file mode 100644 index 0000000..631ec53 --- /dev/null +++ b/src/util/byte_mask.ref2 @@ -0,0 +1,10 @@ +unknown: fatal: byte_mask: unknown mask bit in mask: 0x10 +0 -> 0x1 -> 0 +0x2 -> 1 +1 -> 0x2 -> 1 +0x4 -> 2 +2 -> 0x4 -> 2 +0x8 -> 3 +unknown: fatal: byte_mask: unknown mask bit in mask: 0x10 +unknown: fatal: unknown name value "4" in "1234" +unknown: fatal: unknown name value "a" in "abcd" diff --git a/src/util/cache.in b/src/util/cache.in new file mode 100644 index 0000000..89974bd --- /dev/null +++ b/src/util/cache.in @@ -0,0 +1,26 @@ +a +1 +b +2 +c +3 +d +4 +e +5 +f +6 +f +e +d +c +b +a +1 +b +c +d +e +f +6 +f diff --git a/src/util/casefold.c b/src/util/casefold.c new file mode 100644 index 0000000..d3ebd4b --- /dev/null +++ b/src/util/casefold.c @@ -0,0 +1,359 @@ +/*++ +/* NAME +/* casefold 3 +/* SUMMARY +/* casefold text for caseless comparison +/* SYNOPSIS +/* #include +/* +/* char *casefold( +/* VSTRING *dst, +/* const char *src) +/* +/* char *casefold_append( +/* VSTRING *dst, +/* const char *src) +/* +/* char *casefold_len( +/* VSTRING *dst, +/* const char *src, +/* ssize_t src_len) +/* AUXILIARY FUNCTIONS +/* char *casefoldx( +/* int flags, +/* VSTRING *dst, +/* const char *src, +/* ssize_t src_len) +/* DESCRIPTION +/* casefold() converts text to a form that is suitable for +/* caseless comparison, rather than presentation to humans. +/* +/* When compiled without EAI support or util_utf8_enable is +/* zero, casefold() implements ASCII case folding, leaving +/* non-ASCII byte values unchanged. +/* +/* When compiled with EAI support and util_utf8_enable is +/* non-zero, casefold() implements UTF-8 case folding using +/* the en_US locale, as recommended when the conversion result +/* is not meant to be presented to humans. +/* +/* casefold_len() implements casefold() with a source length +/* argument. +/* +/* casefold_append() implements casefold() without overwriting +/* the result. +/* +/* casefoldx() implements a more complex API that implements +/* all of the above and more. +/* +/* Arguments: +/* .IP src +/* Null-terminated input string. +/* .IP dest +/* Output buffer, null-terminated. Specify a null pointer to +/* use an internal buffer that is overwritten upon each call. +/* .IP src_len +/* The string length, -1 to determine the length dynamically. +/* .IP flags +/* Bitwise OR of zero or more of the following: +/* .RS +/* .IP CASEF_FLAG_UTF8 +/* Enable UTF-8 support. This flag has no effect when compiled +/* without EAI support. +/* .IP CASEF_FLAG_APPEND +/* Append the result to the buffer, instead of overwriting it. +/* DIAGNOSTICS +/* All errors are fatal. There appear to be no input-dependent +/* errors. +/* +/* With the ICU 4.8 library, there is no casefold error for +/* UTF-8 code points U+0000..U+10FFFF (including surrogate +/* range), not even when running inside an empty chroot jail. +/* Nor does malformed UTF-8 trigger errors; non-UTF-8 bytes +/* are copied verbatim. Based on ICU 4.8 source-code review +/* and experimentation(!) we conclude that UTF-8 casefolding +/* has no data-dependent error cases, and that it is safe to +/* treat all casefolding errors as fatal runtime errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#ifndef NO_EAI +#include +#include +#include +#endif + +/* Utility library. */ + +#include +#include + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* casefoldx - casefold an UTF-8 string */ + +char *casefoldx(int flags, VSTRING *dest, const char *src, ssize_t len) +{ + size_t old_len; + +#ifdef NO_EAI + + /* + * ASCII mode only. + */ + if (len < 0) + len = strlen(src); + if ((flags & CASEF_FLAG_APPEND) == 0) + VSTRING_RESET(dest); + old_len = VSTRING_LEN(dest); + vstring_strncat(dest, src, len); + lowercase(STR(dest) + old_len); + return (STR(dest)); +#else + + /* + * Unicode mode. + */ + const char myname[] = "casefold"; + static VSTRING *fold_buf = 0; + static UCaseMap *csm = 0; + UErrorCode error; + ssize_t space_needed; + int n; + + /* + * Handle special cases. + */ + if (len < 0) + len = strlen(src); + if (dest == 0) + dest = (fold_buf != 0 ? fold_buf : (fold_buf = vstring_alloc(100))); + if ((flags & CASEF_FLAG_APPEND) == 0) + VSTRING_RESET(dest); + old_len = VSTRING_LEN(dest); + + /* + * All-ASCII input, or ASCII mode only. + */ + if ((flags & CASEF_FLAG_UTF8) == 0 || allascii(src)) { + vstring_strncat(dest, src, len); + lowercase(STR(dest) + old_len); + return (STR(dest)); + } + + /* + * ICU 4.8 ucasemap_utf8FoldCase() does not complain about UTF-8 syntax + * errors. XXX Based on source-code review we conclude that non-UTF-8 + * bytes are copied verbatim, and experiments confirm this. Given that + * this behavior is intentional, we assume that it will stay that way. + */ +#if 0 + if (valid_utf8_string(src, len) == 0) { + if (err) + *err = "malformed UTF-8 or invalid codepoint"; + return (0); + } +#endif + + /* + * One-time initialization. With ICU 4.8 this works while chrooted. + */ + if (csm == 0) { + error = U_ZERO_ERROR; + csm = ucasemap_open("en_US", U_FOLD_CASE_DEFAULT, &error); + if (U_SUCCESS(error) == 0) + msg_fatal("ucasemap_open error: %s", u_errorName(error)); + } + + /* + * Fold the input, adjusting the buffer size if needed. Safety: don't + * loop forever. + * + * Note: the requested amount of space for casemapped output (as reported + * with space_needed below) does not include storage for the null + * terminator. The terminator is written only when the output buffer is + * large enough. This is why we overallocate space when the output does + * not fit. But if the output fits exactly, then the output will be + * unterminated, and we have to terminate the output ourselves. + */ + for (n = 0; n < 3; n++) { + error = U_ZERO_ERROR; + space_needed = ucasemap_utf8FoldCase(csm, STR(dest) + old_len, + vstring_avail(dest), src, len, &error); + if (U_SUCCESS(error)) { + vstring_set_payload_size(dest, old_len + space_needed); + if (vstring_avail(dest) == 0) /* exact fit, no terminator */ + VSTRING_TERMINATE(dest); /* add terminator */ + break; + } else if (error == U_BUFFER_OVERFLOW_ERROR) { + VSTRING_SPACE(dest, space_needed + 1); /* for terminator */ + } else { + msg_fatal("%s: conversion error for \"%s\": %s", + myname, src, u_errorName(error)); + } + } + return (STR(dest)); +#endif /* NO_EAI */ +} + +#ifdef TEST + +static void encode_utf8(VSTRING *buffer, int codepoint) +{ + const char myname[] = "encode_utf8"; + + VSTRING_RESET(buffer); + if (codepoint < 0x80) { + VSTRING_ADDCH(buffer, codepoint); + } else if (codepoint < 0x800) { + VSTRING_ADDCH(buffer, 0xc0 | (codepoint >> 6)); + VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f)); + } else if (codepoint < 0x10000) { + VSTRING_ADDCH(buffer, 0xe0 | (codepoint >> 12)); + VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 6) & 0x3f)); + VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f)); + } else if (codepoint <= 0x10FFFF) { + VSTRING_ADDCH(buffer, 0xf0 | (codepoint >> 18)); + VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 12) & 0x3f)); + VSTRING_ADDCH(buffer, 0x80 | ((codepoint >> 6) & 0x3f)); + VSTRING_ADDCH(buffer, 0x80 | (codepoint & 0x3f)); + } else { + msg_panic("%s: out-of-range codepoint U+%X", myname, codepoint); + } + VSTRING_TERMINATE(buffer); +} + +#include +#include +#include + +#include +#include +#include + +int main(int argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(1); + VSTRING *dest = vstring_alloc(1); + char *bp; + char *conv_res; + char *cmd; + int codepoint, first, last; + VSTREAM *fp; + + if (setlocale(LC_ALL, "C") == 0) + msg_fatal("setlocale(LC_ALL, C) failed: %m"); + + msg_vstream_init(argv[0], VSTREAM_ERR); + + util_utf8_enable = 1; + + VSTRING_SPACE(buffer, 256); /* chroot/file pathname */ + + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + bp = STR(buffer); + vstream_printf("> %s\n", bp); + cmd = mystrtok(&bp, CHARS_SPACE); + if (cmd == 0 || *cmd == '#') + continue; + while (ISSPACE(*bp)) + bp++; + + /* + * Null-terminated string. + */ + if (strcmp(cmd, "fold") == 0) { + conv_res = casefold(dest, bp); + vstream_printf("\"%s\" ->fold \"%s\"\n", bp, conv_res); + } + + /* + * Codepoint range. + */ + else if (strcmp(cmd, "range") == 0 + && sscanf(bp, "%i %i", &first, &last) == 2 + && first <= last) { + for (codepoint = first; codepoint <= last; codepoint++) { + if (codepoint >= 0xD800 && codepoint <= 0xDFFF) { + vstream_printf("skipping surrogate range\n"); + codepoint = 0xDFFF; + } else { + encode_utf8(buffer, codepoint); + if (msg_verbose) + vstream_printf("U+%X -> %s\n", codepoint, STR(buffer)); + if (valid_utf8_string(STR(buffer), LEN(buffer)) == 0) + msg_fatal("bad utf-8 encoding for U+%X\n", codepoint); + casefold(dest, STR(buffer)); + } + } + vstream_printf("range completed: 0x%x..0x%x\n", first, last); + } + + /* + * Chroot directory. + */ + else if (strcmp(cmd, "chroot") == 0 + && sscanf(bp, "%255s", STR(buffer)) == 1) { + if (geteuid() == 0) { + if (chdir(STR(buffer)) < 0) + msg_fatal("chdir(%s): %m", STR(buffer)); + if (chroot(STR(buffer)) < 0) + msg_fatal("chroot(%s): %m", STR(buffer)); + vstream_printf("chroot %s completed\n", STR(buffer)); + } + } + + /* + * File. + */ + else if (strcmp(cmd, "file") == 0 + && sscanf(bp, "%255s", STR(buffer)) == 1) { + if ((fp = vstream_fopen(STR(buffer), O_RDONLY, 0)) == 0) + msg_fatal("open(%s): %m", STR(buffer)); + while (vstring_fgets_nonl(buffer, fp)) + vstream_printf("%s\n", casefold(dest, STR(buffer))); + vstream_fclose(fp); + } + + /* + * Verbose. + */ + else if (strcmp(cmd, "verbose") == 0 + && sscanf(bp, "%i", &msg_verbose) == 1) { + /* void */ ; + } + + /* + * Usage + */ + else { + vstream_printf("Usage: %s chroot | file | fold | range | verbose \n", + argv[0]); + } + vstream_fflush(VSTREAM_OUT); + } + vstring_free(buffer); + vstring_free(dest); + exit(0); +} + +#endif /* TEST */ diff --git a/src/util/casefold_test.in b/src/util/casefold_test.in new file mode 100644 index 0000000..c1a530e --- /dev/null +++ b/src/util/casefold_test.in @@ -0,0 +1,24 @@ +# Ignored when not running as root. +chroot /tmp +# Casefold U+0000 .. U+10FFFF excluding surrogates. +range 0x0 0xD7FF +range 0xD800 0xD800 +range 0xDFFF 0xDFFF +range 0xE000 0x10FFFF +# Demonstrate that range is not a noop. +verbose 1 +range 0xE000 0xE007 +verbose 0 +# Upper-case greek -> lower-case greek. +fold Δημοσθένους.example.com +# Exact-fit null termination test. +fold Δημοσθένους.exxample.com +# Upper-case ASCII -> lower-case ASCII. +fold HeLlO.ExAmPlE.CoM +# Folding does not change aliases for '.'. +fold x。example.com +fold x.example.com +fold x。example.com +# Bad UTF-8 +fold YYY€€€ +fold €€€XXX diff --git a/src/util/casefold_test.ref b/src/util/casefold_test.ref new file mode 100644 index 0000000..639fe06 --- /dev/null +++ b/src/util/casefold_test.ref @@ -0,0 +1,47 @@ +> # Ignored when not running as root. +> chroot /tmp +> # Casefold U+0000 .. U+10FFFF excluding surrogates. +> range 0x0 0xD7FF +range completed: 0x0..0xd7ff +> range 0xD800 0xD800 +skipping surrogate range +range completed: 0xd800..0xd800 +> range 0xDFFF 0xDFFF +skipping surrogate range +range completed: 0xdfff..0xdfff +> range 0xE000 0x10FFFF +range completed: 0xe000..0x10ffff +> # Demonstrate that range is not a noop. +> verbose 1 +> range 0xE000 0xE007 +U+E000 ->  +U+E001 -> î€ +U+E002 ->  +U+E003 ->  +U+E004 ->  +U+E005 ->  +U+E006 ->  +U+E007 ->  +range completed: 0xe000..0xe007 +> verbose 0 +> # Upper-case greek -> lower-case greek. +> fold Δημοσθένους.example.com +"Δημοσθένους.example.com" ->fold "δημοσθένουσ.example.com" +> # Exact-fit null termination test. +> fold Δημοσθένους.exxample.com +"Δημοσθένους.exxample.com" ->fold "δημοσθένουσ.exxample.com" +> # Upper-case ASCII -> lower-case ASCII. +> fold HeLlO.ExAmPlE.CoM +"HeLlO.ExAmPlE.CoM" ->fold "hello.example.com" +> # Folding does not change aliases for '.'. +> fold x。example.com +"x。example.com" ->fold "x。example.com" +> fold x.example.com +"x.example.com" ->fold "x.example.com" +> fold x。example.com +"x。example.com" ->fold "x。example.com" +> # Bad UTF-8 +> fold YYY€€€ +"YYY€€€" ->fold "yyy€€€" +> fold €€€XXX +"€€€XXX" ->fold "€€€xxx" diff --git a/src/util/check_arg.h b/src/util/check_arg.h new file mode 100644 index 0000000..09f0932 --- /dev/null +++ b/src/util/check_arg.h @@ -0,0 +1,157 @@ +#ifndef _CHECK_ARG_INCLUDED_ +#define _CHECK_ARG_INCLUDED_ + +/*++ +/* NAME +/* check_arg 3h +/* SUMMARY +/* type checking/narrowing/widening for unprototyped arguments +/* SYNOPSIS +/* #include +/* +/* /* Example checking infrastructure for int, int *, const int *. */ +/* CHECK_VAL_HELPER_DCL(tag, int); +/* CHECK_PTR_HELPER_DCL(tag, int); +/* CHECK_CPTR_HELPER_DCL(tag, int); +/* +/* /* Example variables with type int, int *, const int *. */ +/* int int_val; +/* int *int_ptr; +/* const int *int_cptr; +/* +/* /* Example variadic function with type-flag arguments. */ +/* func(FLAG_INT_VAL, CHECK_VAL(tag, int, int_val), +/* FLAG_INT_PTR, CHECK_PTR(tag, int, int_ptr), +/* FLAG_INT_CPTR, CHECK_CPTR(tag, int, int_cptr) +/* FLAG_END); +/* DESCRIPTION +/* This module implements wrappers for unprototyped function +/* arguments, to enable the same type checking, type narrowing, +/* and type widening as for prototyped function arguments. The +/* wrappers may also be useful in other contexts. +/* +/* Typically, these wrappers are hidden away in a per-module +/* header file that is read by the consumers of that module. +/* To protect consumers against name collisions between wrappers +/* in different header files, wrappers should be called with +/* a distinct per-module tag value. The tag syntax is that +/* of a C identifier. +/* +/* Use CHECK_VAL(tag, type, argument) for arguments with a +/* basic type: int, long, etc., and types defined with "typedef" +/* where indirection is built into the type itself (for example, +/* the result of "typedef int *foo" or function pointer +/* typedefs). +/* +/* Use CHECK_PTR(tag, type, argument) for non-const pointer +/* arguments, CHECK_CPTR(tag, type, argument) for const pointer +/* arguments, and CHECK_PPTR(tag, type, argument) for pointer- +/* to-pointer arguments. +/* +/* Use CHECK_*_HELPER_DCL(tag, type) to provide the +/* checking infrastructure for all CHECK_*(tag, type, ...) +/* instances with the same *, tag and type. Depending on +/* the compilation environment, the infrastructure consists +/* of an inline function definition or a dummy assignment +/* target declaration. +/* +/* The compiler should report the following problems: +/* .IP \(bu +/* Const pointer argument where a non-const pointer is expected. +/* .IP \(bu +/* Pointer argument where a non-pointer is expected and +/* vice-versa. +/* .IP \(bu +/* Pointer/pointer type mismatches except void/non-void pointers. +/* Just like function prototypes, all CHECK_*PTR() wrappers +/* cast their result to the desired type. +/* .IP \(bu +/* Non-constant non-pointer argument where a pointer is expected. +/*. PP +/* Just like function prototypes, the CHECK_*PTR() wrappers +/* handle "bare" numerical constants by casting their argument +/* to the desired pointer type. +/* +/* Just like function prototypes, the CHECK_VAL() wrapper +/* cannot force the caller to specify a particular non-pointer +/* type and casts its argument value to the desired type which +/* may wider or narrower than the argument value. +/* IMPLEMENTATION + + /* + * Choose between an implementation based on inline functions (standardized + * with C99) or conditional assignment (portable to older compilers, with + * some caveats as discussed below). + */ +#ifndef NO_INLINE + + /* + * Parameter checks expand into inline helper function calls. + */ +#define CHECK_VAL(tag, type, v) check_val_##tag##type(v) +#define CHECK_PTR(tag, type, v) check_ptr_##tag##type(v) +#define CHECK_CPTR(tag, type, v) check_cptr_##tag##type(v) +#define CHECK_PPTR(tag, type, v) check_pptr_##tag##type(v) + + /* + * Macros to instantiate the inline helper functions. + */ +#define CHECK_VAL_HELPER_DCL(tag, type) \ + static inline type check_val_##tag##type(type v) { return v; } +#define CHECK_PTR_HELPER_DCL(tag, type) \ + static inline type *check_ptr_##tag##type(type *v) { return v; } +#define CHECK_CPTR_HELPER_DCL(tag, type) \ + static inline const type *check_cptr_##tag##type(const type *v) \ + { return v; } +#define CHECK_PPTR_HELPER_DCL(tag, type) \ + static inline type **check_pptr_##tag##type(type **v) { return v; } + +#else /* NO_INLINE */ + + /* + * Parameter checks expand into unreachable conditional assignments. + * Inspired by OpenSSL's verified pointer check, our implementation also + * detects const/non-const pointer conflicts, and it also supports + * non-pointer expressions. + */ +#define CHECK_VAL(tag, type, v) ((type) (1 ? (v) : (CHECK_VAL_DUMMY(type) = (v)))) +#define CHECK_PTR(tag, type, v) ((type *) (1 ? (v) : (CHECK_PTR_DUMMY(type) = (v)))) +#define CHECK_CPTR(tag, type, v) \ + ((const type *) (1 ? (v) : (CHECK_CPTR_DUMMY(type) = (v)))) +#define CHECK_PPTR(tag, type, v) ((type **) (1 ? (v) : (CHECK_PPTR_DUMMY(type) = (v)))) + + /* + * These macros instantiate assignment target declarations. Since the + * assignment is made in unreachable code, the compiler "should" not emit + * any references to those assignment targets. We use the "extern" class so + * that gcc will not complain about unused variables. Using "extern" breaks + * when a compiler does emit references to unreachable assignment targets. + * Hopefully, those cases will be rare. + */ +#define CHECK_VAL_HELPER_DCL(tag, type) extern type CHECK_VAL_DUMMY(type) +#define CHECK_PTR_HELPER_DCL(tag, type) extern type *CHECK_PTR_DUMMY(type) +#define CHECK_CPTR_HELPER_DCL(tag, type) extern const type *CHECK_CPTR_DUMMY(type) +#define CHECK_PPTR_HELPER_DCL(tag, type) extern type **CHECK_PPTR_DUMMY(type) + + /* + * The actual dummy assignment target names. + */ +#define CHECK_VAL_DUMMY(type) check_val_dummy_##type +#define CHECK_PTR_DUMMY(type) check_ptr_dummy_##type +#define CHECK_CPTR_DUMMY(type) check_cptr_dummy_##type +#define CHECK_PPTR_DUMMY(type) check_pptr_dummy_##type + +#endif /* NO_INLINE */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/chroot_uid.c b/src/util/chroot_uid.c new file mode 100644 index 0000000..4a7660f --- /dev/null +++ b/src/util/chroot_uid.c @@ -0,0 +1,88 @@ +/*++ +/* NAME +/* chroot_uid 3 +/* SUMMARY +/* limit possible damage a process can do +/* SYNOPSIS +/* #include +/* +/* void chroot_uid(root_dir, user_name) +/* const char *root_dir; +/* const char *user_name; +/* DESCRIPTION +/* \fBchroot_uid\fR changes the process root to \fIroot_dir\fR and +/* changes process privileges to those of \fIuser_name\fR. +/* DIAGNOSTICS +/* System call errors are reported via the msg(3) interface. +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "chroot_uid.h" + +/* chroot_uid - restrict the damage that this program can do */ + +void chroot_uid(const char *root_dir, const char *user_name) +{ + struct passwd *pwd; + uid_t uid; + gid_t gid; + + /* + * Look up the uid/gid before entering the jail, and save them so they + * can't be clobbered. Set up the primary and secondary groups. + */ + if (user_name != 0) { + if ((pwd = getpwnam(user_name)) == 0) + msg_fatal("unknown user: %s", user_name); + uid = pwd->pw_uid; + gid = pwd->pw_gid; + if (setgid(gid) < 0) + msg_fatal("setgid(%ld): %m", (long) gid); + if (initgroups(user_name, gid) < 0) + msg_fatal("initgroups: %m"); + } + + /* + * Enter the jail. + */ + if (root_dir) { + if (chroot(root_dir)) + msg_fatal("chroot(%s): %m", root_dir); + if (chdir("/")) + msg_fatal("chdir(/): %m"); + } + + /* + * Drop the user privileges. + */ + if (user_name != 0) + if (setuid(uid) < 0) + msg_fatal("setuid(%ld): %m", (long) uid); + + /* + * Give the desperate developer a clue of what is happening. + */ + if (msg_verbose > 1) + msg_info("chroot %s user %s", + root_dir ? root_dir : "(none)", + user_name ? user_name : "(none)"); +} diff --git a/src/util/chroot_uid.h b/src/util/chroot_uid.h new file mode 100644 index 0000000..f2a8399 --- /dev/null +++ b/src/util/chroot_uid.h @@ -0,0 +1,29 @@ +#ifndef _CHROOT_UID_H_INCLUDED_ +#define _CHROOT_UID_H_INCLUDED_ + +/*++ +/* NAME +/* chroot_uid 3h +/* SUMMARY +/* limit possible damage a process can do +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern void chroot_uid(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/cidr_match.c b/src/util/cidr_match.c new file mode 100644 index 0000000..0ae7c56 --- /dev/null +++ b/src/util/cidr_match.c @@ -0,0 +1,316 @@ +/*++ +/* NAME +/* cidr_match 3 +/* SUMMARY +/* CIDR-style pattern matching +/* SYNOPSIS +/* #include +/* +/* VSTRING *cidr_match_parse(info, pattern, match, why) +/* CIDR_MATCH *info; +/* char *pattern; +/* VSTRING *why; +/* +/* int cidr_match_execute(info, address) +/* CIDR_MATCH *info; +/* const char *address; +/* AUXILIARY FUNCTIONS +/* VSTRING *cidr_match_parse_if(info, pattern, match, why) +/* CIDR_MATCH *info; +/* char *pattern; +/* VSTRING *why; +/* +/* void cidr_match_endif(info) +/* CIDR_MATCH *info; +/* DESCRIPTION +/* This module parses address or address/length patterns and +/* provides simple address matching. The implementation is +/* such that parsing and execution can be done without dynamic +/* memory allocation. The purpose is to minimize overhead when +/* called by functions that parse and execute on the fly, such +/* as match_hostaddr(). +/* +/* cidr_match_parse() parses an address or address/mask +/* expression and stores the result into the info argument. +/* A non-zero (or zero) match argument requests a positive (or +/* negative) match. The symbolic constants CIDR_MATCH_TRUE and +/* CIDR_MATCH_FALSE may help to improve code readability. +/* The result is non-zero in case of problems: either the +/* value of the why argument, or a newly allocated VSTRING +/* (the caller should give the latter to vstring_free()). +/* The pattern argument is destroyed. +/* +/* cidr_match_parse_if() parses the address that follows an IF +/* token, and stores the result into the info argument. +/* The arguments are the same as for cidr_match_parse(). +/* +/* cidr_match_endif() handles the occurrence of an ENDIF token, +/* and updates the info argument. +/* +/* cidr_match_execute() matches the specified address against +/* a list of parsed expressions, and returns the matching +/* expression's data structure. +/* SEE ALSO +/* dict_cidr(3) CIDR-style lookup table +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * This is how we figure out the address family, address bit count and + * address byte count for a CIDR_MATCH entry. + */ +#ifdef HAS_IPV6 +#define CIDR_MATCH_ADDR_FAMILY(a) (strchr((a), ':') ? AF_INET6 : AF_INET) +#define CIDR_MATCH_ADDR_BIT_COUNT(f) \ + ((f) == AF_INET6 ? MAI_V6ADDR_BITS : \ + (f) == AF_INET ? MAI_V4ADDR_BITS : \ + (msg_panic("%s: bad address family %d", myname, (f)), 0)) +#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \ + ((f) == AF_INET6 ? MAI_V6ADDR_BYTES : \ + (f) == AF_INET ? MAI_V4ADDR_BYTES : \ + (msg_panic("%s: bad address family %d", myname, (f)), 0)) +#else +#define CIDR_MATCH_ADDR_FAMILY(a) (AF_INET) +#define CIDR_MATCH_ADDR_BIT_COUNT(f) \ + ((f) == AF_INET ? MAI_V4ADDR_BITS : \ + (msg_panic("%s: bad address family %d", myname, (f)), 0)) +#define CIDR_MATCH_ADDR_BYTE_COUNT(f) \ + ((f) == AF_INET ? MAI_V4ADDR_BYTES : \ + (msg_panic("%s: bad address family %d", myname, (f)), 0)) +#endif + +/* cidr_match_entry - match one entry */ + +static inline int cidr_match_entry(CIDR_MATCH *entry, + unsigned char *addr_bytes) +{ + unsigned char *mp; + unsigned char *np; + unsigned char *ap; + + /* Unoptimized case: netmask with some or all bits zero. */ + if (entry->mask_shift < entry->addr_bit_count) { + for (np = entry->net_bytes, mp = entry->mask_bytes, + ap = addr_bytes; /* void */ ; np++, mp++, ap++) { + if (ap >= addr_bytes + entry->addr_byte_count) + return (entry->match); + if ((*ap & *mp) != *np) + break; + } + } + /* Optimized case: all 1 netmask (i.e. no netmask specified). */ + else { + for (np = entry->net_bytes, + ap = addr_bytes; /* void */ ; np++, ap++) { + if (ap >= addr_bytes + entry->addr_byte_count) + return (entry->match); + if (*ap != *np) + break; + } + } + return (!entry->match); +} + +/* cidr_match_execute - match address against compiled CIDR pattern list */ + +CIDR_MATCH *cidr_match_execute(CIDR_MATCH *list, const char *addr) +{ + unsigned char addr_bytes[CIDR_MATCH_ABYTES]; + unsigned addr_family; + CIDR_MATCH *entry; + + addr_family = CIDR_MATCH_ADDR_FAMILY(addr); + if (inet_pton(addr_family, addr, addr_bytes) != 1) + return (0); + + for (entry = list; entry; entry = entry->next) { + + switch (entry->op) { + + case CIDR_MATCH_OP_MATCH: + if (entry->addr_family == addr_family) + if (cidr_match_entry(entry, addr_bytes)) + return (entry); + break; + + case CIDR_MATCH_OP_IF: + if (entry->addr_family == addr_family) + if (cidr_match_entry(entry, addr_bytes)) + continue; + /* An IF without matching ENDIF has no end-of block entry. */ + if ((entry = entry->block_end) == 0) + return (0); + /* FALLTHROUGH */ + + case CIDR_MATCH_OP_ENDIF: + continue; + } + } + return (0); +} + +/* cidr_match_parse - parse CIDR pattern */ + +VSTRING *cidr_match_parse(CIDR_MATCH *ip, char *pattern, int match, + VSTRING *why) +{ + const char *myname = "cidr_match_parse"; + char *mask_search; + char *mask; + MAI_HOSTADDR_STR hostaddr; + unsigned char *np; + unsigned char *mp; + + /* + * Strip [] from [addr/len] or [addr]/len, destroying the pattern. CIDR + * maps don't need [] to eliminate syntax ambiguity, but matchlists need + * it. While stripping [], figure out where we should start looking for + * /mask information. + */ + if (*pattern == '[') { + pattern++; + if ((mask_search = split_at(pattern, ']')) == 0) { + vstring_sprintf(why ? why : (why = vstring_alloc(20)), + "missing ']' character after \"[%s\"", pattern); + return (why); + } else if (*mask_search != '/') { + if (*mask_search != 0) { + vstring_sprintf(why ? why : (why = vstring_alloc(20)), + "garbage after \"[%s]\"", pattern); + return (why); + } + mask_search = pattern; + } + } else + mask_search = pattern; + + /* + * Parse the pattern into network and mask, destroying the pattern. + */ + if ((mask = split_at(mask_search, '/')) != 0) { + const char *parse_error; + + ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern); + ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family); + ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family); + if (!alldig(mask)) { + parse_error = "bad mask value"; + } else if ((ip->mask_shift = atoi(mask)) > ip->addr_bit_count) { + parse_error = "bad mask length"; + } else if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) { + parse_error = "bad network value"; + } else { + parse_error = 0; + } + if (parse_error != 0) { + vstring_sprintf(why ? why : (why = vstring_alloc(20)), + "%s in \"%s/%s\"", parse_error, pattern, mask); + return (why); + } + if (ip->mask_shift > 0) { + /* Allow for bytes > 8. */ + memset(ip->mask_bytes, ~0U, ip->addr_byte_count); + mask_addr(ip->mask_bytes, ip->addr_byte_count, ip->mask_shift); + } else + memset(ip->mask_bytes, 0, ip->addr_byte_count); + + /* + * Sanity check: all host address bits must be zero. + */ + for (np = ip->net_bytes, mp = ip->mask_bytes; + np < ip->net_bytes + ip->addr_byte_count; np++, mp++) { + if (*np & ~(*mp)) { + mask_addr(ip->net_bytes, ip->addr_byte_count, ip->mask_shift); + if (inet_ntop(ip->addr_family, ip->net_bytes, hostaddr.buf, + sizeof(hostaddr.buf)) == 0) + msg_fatal("inet_ntop: %m"); + vstring_sprintf(why ? why : (why = vstring_alloc(20)), + "non-null host address bits in \"%s/%s\", " + "perhaps you should use \"%s/%d\" instead", + pattern, mask, hostaddr.buf, ip->mask_shift); + return (why); + } + } + } + + /* + * No /mask specified. Treat a bare network address as /allbits. + */ + else { + ip->addr_family = CIDR_MATCH_ADDR_FAMILY(pattern); + ip->addr_bit_count = CIDR_MATCH_ADDR_BIT_COUNT(ip->addr_family); + ip->addr_byte_count = CIDR_MATCH_ADDR_BYTE_COUNT(ip->addr_family); + if (inet_pton(ip->addr_family, pattern, ip->net_bytes) != 1) { + vstring_sprintf(why ? why : (why = vstring_alloc(20)), + "bad address pattern: \"%s\"", pattern); + return (why); + } + ip->mask_shift = ip->addr_bit_count; + /* Allow for bytes > 8. */ + memset(ip->mask_bytes, ~0U, ip->addr_byte_count); + } + + /* + * Wrap up the result. + */ + ip->op = CIDR_MATCH_OP_MATCH; + ip->match = match; + ip->next = 0; + ip->block_end = 0; + + return (0); +} + +/* cidr_match_parse_if - parse CIDR pattern after IF */ + +VSTRING *cidr_match_parse_if(CIDR_MATCH *ip, char *pattern, int match, + VSTRING *why) +{ + VSTRING *ret; + + if ((ret = cidr_match_parse(ip, pattern, match, why)) == 0) + ip->op = CIDR_MATCH_OP_IF; + return (ret); +} + +/* cidr_match_endif - handle ENDIF pattern */ + +void cidr_match_endif(CIDR_MATCH *ip) +{ + memset(ip, 0, sizeof(*ip)); + ip->op = CIDR_MATCH_OP_ENDIF; + ip->next = 0; /* maybe not all bits 0 */ + ip->block_end = 0; +} diff --git a/src/util/cidr_match.h b/src/util/cidr_match.h new file mode 100644 index 0000000..22f16a0 --- /dev/null +++ b/src/util/cidr_match.h @@ -0,0 +1,82 @@ +#ifndef _CIDR_MATCH_H_INCLUDED_ +#define _CIDR_MATCH_H_INCLUDED_ + +/*++ +/* NAME +/* dict_cidr 3h +/* SUMMARY +/* CIDR-style pattern matching +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include /* CHAR_BIT */ + + /* + * Utility library. + */ +#include /* MAI_V6ADDR_BYTES etc. */ +#include + + /* + * External interface. + * + * Address length is protocol dependent. Find out how large our address byte + * strings should be. + */ +#ifdef HAS_IPV6 +# define CIDR_MATCH_ABYTES MAI_V6ADDR_BYTES +#else +# define CIDR_MATCH_ABYTES MAI_V4ADDR_BYTES +#endif + + /* + * Each parsed CIDR pattern can be member of a linked list. + */ +typedef struct CIDR_MATCH { + int op; /* operation, match or control flow */ + int match; /* positive or negative match */ + unsigned char net_bytes[CIDR_MATCH_ABYTES]; /* network portion */ + unsigned char mask_bytes[CIDR_MATCH_ABYTES]; /* network mask */ + unsigned char addr_family; /* AF_XXX */ + unsigned char addr_byte_count; /* typically, 4 or 16 */ + unsigned char addr_bit_count; /* optimization */ + unsigned char mask_shift; /* optimization */ + struct CIDR_MATCH *next; /* next entry */ + struct CIDR_MATCH *block_end; /* block terminator */ +} CIDR_MATCH; + +#define CIDR_MATCH_OP_MATCH 1 /* Match this pattern */ +#define CIDR_MATCH_OP_IF 2 /* Increase if/endif nesting on match */ +#define CIDR_MATCH_OP_ENDIF 3 /* Decrease if/endif nesting on match */ + +#define CIDR_MATCH_TRUE 1 /* Request positive match */ +#define CIDR_MATCH_FALSE 0 /* Request negative match */ + +extern VSTRING *cidr_match_parse(CIDR_MATCH *, char *, int, VSTRING *); +extern VSTRING *cidr_match_parse_if(CIDR_MATCH *, char *, int, VSTRING *); +extern void cidr_match_endif(CIDR_MATCH *); + +extern CIDR_MATCH *cidr_match_execute(CIDR_MATCH *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/clean_env.c b/src/util/clean_env.c new file mode 100644 index 0000000..5ae5528 --- /dev/null +++ b/src/util/clean_env.c @@ -0,0 +1,146 @@ +/*++ +/* NAME +/* clean_env 3 +/* SUMMARY +/* clean up the environment +/* SYNOPSIS +/* #include +/* +/* void clean_env(preserve_list) +/* const char **preserve_list; +/* +/* void update_env(preserve_list) +/* const char **preserve_list; +/* DESCRIPTION +/* clean_env() reduces the process environment to the bare minimum. +/* The function takes a null-terminated list of arguments. +/* Each argument specifies the name of an environment variable +/* that should be preserved, or specifies a name=value that should +/* be entered into the new environment. +/* +/* update_env() applies name=value settings, but otherwise does not +/* change the process environment. +/* DIAGNOSTICS +/* Fatal error: out of memory. +/* SEE ALSO +/* safe_getenv(3), guarded getenv() +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* clean_env - clean up the environment */ + +void clean_env(char **preserve_list) +{ + extern char **environ; + ARGV *save_list; + char *value; + char **cpp; + char *copy; + char *key; + char *val; + const char *err; + + /* + * Preserve or specify selected environment variables. + */ + save_list = argv_alloc(10); + for (cpp = preserve_list; *cpp; cpp++) { + if (strchr(*cpp, '=') != 0) { + copy = mystrdup(*cpp); + err = split_nameval(copy, &key, &val); + if (err != 0) + msg_fatal("clean_env: %s in: %s", err, *cpp); + argv_add(save_list, key, val, (char *) 0); + myfree(copy); + } else if ((value = safe_getenv(*cpp)) != 0) { + argv_add(save_list, *cpp, value, (char *) 0); + } + } + + /* + * Truncate the process environment, if available. On some systems + * (Ultrix!), environ can be a null pointer. + */ + if (environ) + environ[0] = 0; + + /* + * Restore preserved environment variables. + */ + for (cpp = save_list->argv; *cpp; cpp += 2) + if (setenv(cpp[0], cpp[1], 1)) + msg_fatal("setenv(%s, %s): %m", cpp[0], cpp[1]); + + /* + * Cleanup. + */ + argv_free(save_list); +} + +/* update_env - apply name=value settings only */ + +void update_env(char **preserve_list) +{ + char **cpp; + ARGV *save_list; + char *copy; + char *key; + char *val; + const char *err; + + /* + * Extract name=value settings. + */ + save_list = argv_alloc(10); + for (cpp = preserve_list; *cpp; cpp++) { + if (strchr(*cpp, '=') != 0) { + copy = mystrdup(*cpp); + err = split_nameval(copy, &key, &val); + if (err != 0) + msg_fatal("update_env: %s in: %s", err, *cpp); + argv_add(save_list, key, val, (char *) 0); + myfree(copy); + } + } + + /* + * Apply name=value settings. + */ + for (cpp = save_list->argv; *cpp; cpp += 2) + if (setenv(cpp[0], cpp[1], 1)) + msg_fatal("setenv(%s, %s): %m", cpp[0], cpp[1]); + + /* + * Cleanup. + */ + argv_free(save_list); +} diff --git a/src/util/clean_env.h b/src/util/clean_env.h new file mode 100644 index 0000000..5c89723 --- /dev/null +++ b/src/util/clean_env.h @@ -0,0 +1,36 @@ +#ifndef _CLEAN_ENV_H_INCLUDED_ +#define _CLEAN_ENV_H_INCLUDED_ + +/*++ +/* NAME +/* clean_env 3h +/* SUMMARY +/* clean up the environment +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void clean_env(char **); +extern void update_env(char **); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/close_on_exec.c b/src/util/close_on_exec.c new file mode 100644 index 0000000..efa3415 --- /dev/null +++ b/src/util/close_on_exec.c @@ -0,0 +1,60 @@ +/*++ +/* NAME +/* close_on_exec 3 +/* SUMMARY +/* set/clear close-on-exec flag +/* SYNOPSIS +/* #include +/* +/* int close_on_exec(int fd, int on) +/* DESCRIPTION +/* the \fIclose_on_exec\fR() function manipulates the close-on-exec +/* flag for the specified open file, and returns the old setting. +/* +/* Arguments: +/* .IP fd +/* A file descriptor. +/* .IP on +/* Use CLOSE_ON_EXEC or PASS_ON_EXEC. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include +#include + +/* Utility library. */ + +#include "msg.h" + +/* Application-specific. */ + +#include "iostuff.h" + +#define PATTERN FD_CLOEXEC + +/* close_on_exec - set/clear close-on-exec flag */ + +int close_on_exec(fd, on) +int fd; +int on; +{ + int flags; + + if ((flags = fcntl(fd, F_GETFD, 0)) < 0) + msg_fatal("fcntl: get flags: %m"); + if (fcntl(fd, F_SETFD, on ? flags | PATTERN : flags & ~PATTERN) < 0) + msg_fatal("fcntl: set close-on-exec flag %s: %m", on ? "on" : "off"); + return ((flags & PATTERN) != 0); +} diff --git a/src/util/compat_va_copy.h b/src/util/compat_va_copy.h new file mode 100644 index 0000000..6a2042b --- /dev/null +++ b/src/util/compat_va_copy.h @@ -0,0 +1,44 @@ +#ifndef _COMPAT_VA_COPY_H_INCLUDED_ +#define _COMPAT_VA_COPY_H_INCLUDED_ + +/*++ +/* NAME +/* compat_va_copy 3h +/* SUMMARY +/* compatibility +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * C99 defines va_start and va_copy as macros, so we can probe the + * compilation environment with #ifdef etc. Some environments define + * __va_copy so we probe for that, too. + */ +#if !defined(va_start) +#error "include first" +#endif + +#if !defined(VA_COPY) +#if defined(va_copy) +#define VA_COPY(dest, src) va_copy(dest, src) +#elif defined(__va_copy) +#define VA_COPY(dest, src) __va_copy(dest, src) +#else +#define VA_COPY(dest, src) (dest) = (src) +#endif +#endif /* VA_COPY */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/concatenate.c b/src/util/concatenate.c new file mode 100644 index 0000000..7b6a3eb --- /dev/null +++ b/src/util/concatenate.c @@ -0,0 +1,73 @@ +/*++ +/* NAME +/* concatenate 3 +/* SUMMARY +/* concatenate strings +/* SYNOPSIS +/* #include +/* +/* char *concatenate(str, ...) +/* const char *str; +/* DESCRIPTION +/* The \fBconcatenate\fR routine concatenates a null-terminated +/* list of pointers to null-terminated character strings. +/* The result is dynamically allocated and should be passed to myfree() +/* when no longer needed. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include /* 44BSD stdarg.h uses abort() */ +#include +#include + +/* Utility library. */ + +#include "mymalloc.h" +#include "stringops.h" +#include "compat_va_copy.h" + +/* concatenate - concatenate null-terminated list of strings */ + +char *concatenate(const char *arg0,...) +{ + char *result; + va_list ap; + va_list ap2; + ssize_t len; + char *arg; + + /* + * Initialize argument lists. + */ + va_start(ap, arg0); + VA_COPY(ap2, ap); + + /* + * Compute the length of the resulting string. + */ + len = strlen(arg0); + while ((arg = va_arg(ap, char *)) != 0) + len += strlen(arg); + va_end(ap); + + /* + * Build the resulting string. Don't care about wasting a CPU cycle. + */ + result = mymalloc(len + 1); + strcpy(result, arg0); + while ((arg = va_arg(ap2, char *)) != 0) + strcat(result, arg); + va_end(ap2); + return (result); +} diff --git a/src/util/connect.h b/src/util/connect.h new file mode 100644 index 0000000..1e8de11 --- /dev/null +++ b/src/util/connect.h @@ -0,0 +1,43 @@ +#ifndef _CONNECT_H_INCLUDED_ +#define _CONNECT_H_INCLUDED_ + +/*++ +/* NAME +/* connect 3h +/* SUMMARY +/* client interface file +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * Client external interface. + */ +extern int unix_connect(const char *, int, int); +extern int inet_connect(const char *, int, int); +extern int stream_connect(const char *, int, int); +extern int unix_dgram_connect(const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/ctable.c b/src/util/ctable.c new file mode 100644 index 0000000..4993671 --- /dev/null +++ b/src/util/ctable.c @@ -0,0 +1,321 @@ +/*++ +/* NAME +/* ctable 3 +/* SUMMARY +/* cache manager +/* SYNOPSIS +/* #include +/* +/* CTABLE *ctable_create(limit, create, delete, context) +/* ssize_t limit; +/* void *(*create)(const char *key, void *context); +/* void (*delete)(void *value, void *context); +/* void *context; +/* +/* const void *ctable_locate(cache, key) +/* CTABLE *cache; +/* const char *key; +/* +/* const void *ctable_refresh(cache, key) +/* CTABLE *cache; +/* const char *key; +/* +/* const void *ctable_newcontext(cache, context) +/* CTABLE *cache; +/* void *context; +/* +/* void ctable_free(cache) +/* CTABLE *cache; +/* +/* void ctable_walk(cache, action) +/* CTABLE *cache; +/* void (*action)(const char *key, const void *value); +/* DESCRIPTION +/* This module maintains multiple caches. Cache items are purged +/* automatically when the number of items exceeds a configurable +/* limit. Caches never shrink. Each cache entry consists of a +/* string-valued lookup key and a generic data pointer value. +/* +/* ctable_create() creates a cache with the specified size limit, and +/* returns a pointer to the result. The create and delete arguments +/* specify pointers to call-back functions that create a value, given +/* a key, and delete a given value, respectively. The context argument +/* is passed on to the call-back routines. +/* The create() and delete() functions must not modify the cache. +/* +/* ctable_locate() looks up or generates the value that corresponds to +/* the specified key, and returns that value. +/* +/* ctable_refresh() flushes the value (if any) associated with +/* the specified key, and returns the same result as ctable_locate(). +/* +/* ctable_newcontext() updates the context that is passed on +/* to call-back routines. +/* +/* ctable_free() destroys the specified cache, including its contents. +/* +/* ctable_walk() iterates over all elements in the cache, and invokes +/* the action function for each cache element with the corresponding +/* key and value as arguments. This function is useful mainly for +/* cache performance debugging. +/* Note: the action() function must not modify the cache. +/* DIAGNOSTICS +/* Fatal errors: out of memory. Panic: interface violation. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + + /* + * Cache entries are kept in most-recently used order. We use a hash table + * to quickly locate cache entries. + */ +#define CTABLE_ENTRY struct ctable_entry + +struct ctable_entry { + RING ring; /* MRU linkage */ + const char *key; /* lookup key */ + void *value; /* corresponding value */ +}; + +#define RING_TO_CTABLE_ENTRY(ring_ptr) \ + RING_TO_APPL(ring_ptr, CTABLE_ENTRY, ring) +#define RING_PTR_OF(x) (&((x)->ring)) + +struct ctable { + HTABLE *table; /* table with key, ctable_entry pairs */ + size_t limit; /* max nr of entries */ + size_t used; /* current nr of entries */ + CTABLE_CREATE_FN create; /* constructor */ + CTABLE_DELETE_FN delete; /* destructor */ + RING ring; /* MRU linkage */ + void *context; /* application context */ +}; + +#define CTABLE_MIN_SIZE 5 + +/* ctable_create - create empty cache */ + +CTABLE *ctable_create(ssize_t limit, CTABLE_CREATE_FN create, + CTABLE_DELETE_FN delete, void *context) +{ + CTABLE *cache = (CTABLE *) mymalloc(sizeof(CTABLE)); + const char *myname = "ctable_create"; + + if (limit < 1) + msg_panic("%s: bad cache limit: %ld", myname, (long) limit); + + cache->table = htable_create(limit); + cache->limit = (limit < CTABLE_MIN_SIZE ? CTABLE_MIN_SIZE : limit); + cache->used = 0; + cache->create = create; + cache->delete = delete; + ring_init(RING_PTR_OF(cache)); + cache->context = context; + return (cache); +} + +/* ctable_locate - look up or create cache item */ + +const void *ctable_locate(CTABLE *cache, const char *key) +{ + const char *myname = "ctable_locate"; + CTABLE_ENTRY *entry; + + /* + * If the entry is not in the cache, make sure there is room for a new + * entry and install it at the front of the MRU chain. Otherwise, move + * the entry to the front of the MRU chain if it is not already there. + * All this means that the cache never shrinks. + */ + if ((entry = (CTABLE_ENTRY *) htable_find(cache->table, key)) == 0) { + if (cache->used >= cache->limit) { + entry = RING_TO_CTABLE_ENTRY(ring_pred(RING_PTR_OF(cache))); + if (msg_verbose) + msg_info("%s: purge entry key %s", myname, entry->key); + ring_detach(RING_PTR_OF(entry)); + cache->delete(entry->value, cache->context); + htable_delete(cache->table, entry->key, (void (*) (void *)) 0); + } else { + entry = (CTABLE_ENTRY *) mymalloc(sizeof(CTABLE_ENTRY)); + cache->used++; + } + entry->value = cache->create(key, cache->context); + entry->key = htable_enter(cache->table, key, (void *) entry)->key; + ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry)); + if (msg_verbose) + msg_info("%s: install entry key %s", myname, entry->key); + } else if (entry == RING_TO_CTABLE_ENTRY(ring_succ(RING_PTR_OF(cache)))) { + if (msg_verbose) + msg_info("%s: leave existing entry key %s", myname, entry->key); + } else { + ring_detach(RING_PTR_OF(entry)); + ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry)); + if (msg_verbose) + msg_info("%s: move existing entry key %s", myname, entry->key); + } + return (entry->value); +} + +/* ctable_refresh - page-in fresh data for given key */ + +const void *ctable_refresh(CTABLE *cache, const char *key) +{ + const char *myname = "ctable_refresh"; + CTABLE_ENTRY *entry; + + /* Materialize entry if missing. */ + if ((entry = (CTABLE_ENTRY *) htable_find(cache->table, key)) == 0) + return ctable_locate(cache, key); + + /* Otherwise, refresh its content. */ + cache->delete(entry->value, cache->context); + entry->value = cache->create(key, cache->context); + + /* Update its MRU linkage. */ + if (entry != RING_TO_CTABLE_ENTRY(ring_succ(RING_PTR_OF(cache)))) { + ring_detach(RING_PTR_OF(entry)); + ring_append(RING_PTR_OF(cache), RING_PTR_OF(entry)); + } + if (msg_verbose) + msg_info("%s: refresh entry key %s", myname, entry->key); + return (entry->value); +} + +/* ctable_newcontext - update call-back context */ + +void ctable_newcontext(CTABLE *cache, void *context) +{ + cache->context = context; +} + +static CTABLE *ctable_free_cache; + +/* ctable_free_callback - callback function */ + +static void ctable_free_callback(void *ptr) +{ + CTABLE_ENTRY *entry = (CTABLE_ENTRY *) ptr; + + ctable_free_cache->delete(entry->value, ctable_free_cache->context); + myfree((void *) entry); +} + +/* ctable_free - destroy cache and contents */ + +void ctable_free(CTABLE *cache) +{ + CTABLE *saved_cache = ctable_free_cache; + + /* + * XXX the hash table does not pass application context so we have to + * store it in a global variable. + */ + ctable_free_cache = cache; + htable_free(cache->table, ctable_free_callback); + myfree((void *) cache); + ctable_free_cache = saved_cache; +} + +/* ctable_walk - iterate over all cache entries */ + +void ctable_walk(CTABLE *cache, void (*action) (const char *, const void *)) +{ + RING *entry = RING_PTR_OF(cache); + + /* Walking down the MRU chain is less work than using ht_walk(). */ + + while ((entry = ring_succ(entry)) != RING_PTR_OF(cache)) + action((RING_TO_CTABLE_ENTRY(entry)->key), + (RING_TO_CTABLE_ENTRY(entry)->value)); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. Read keys from stdin, ask for values not + * in cache. + */ +#include +#include +#include +#include +#include + +#define STR(x) vstring_str(x) + +static void *ask(const char *key, void *context) +{ + VSTRING *data_buf = (VSTRING *) context; + + vstream_printf("ask: %s = ", key); + vstream_fflush(VSTREAM_OUT); + if (vstring_get_nonl(data_buf, VSTREAM_IN) == VSTREAM_EOF) + vstream_longjmp(VSTREAM_IN, 1); + if (!isatty(0)) { + vstream_printf("%s\n", STR(data_buf)); + vstream_fflush(VSTREAM_OUT); + } + return (mystrdup(STR(data_buf))); +} + +static void drop(void *data, void *unused_context) +{ + myfree(data); +} + +int main(int unused_argc, char **argv) +{ + VSTRING *key_buf; + VSTRING *data_buf; + CTABLE *cache; + const char *value; + + msg_vstream_init(argv[0], VSTREAM_ERR); + key_buf = vstring_alloc(100); + data_buf = vstring_alloc(100); + cache = ctable_create(1, ask, drop, (void *) data_buf); + msg_verbose = 1; + vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_EXCEPT, CA_VSTREAM_CTL_END); + + if (vstream_setjmp(VSTREAM_IN) == 0) { + for (;;) { + vstream_printf("key = "); + vstream_fflush(VSTREAM_OUT); + if (vstring_get_nonl(key_buf, VSTREAM_IN) == VSTREAM_EOF) + vstream_longjmp(VSTREAM_IN, 1); + if (!isatty(0)) { + vstream_printf("%s\n", STR(key_buf)); + vstream_fflush(VSTREAM_OUT); + } + value = ctable_locate(cache, STR(key_buf)); + vstream_printf("result: %s\n", value); + } + } + ctable_free(cache); + vstring_free(key_buf); + vstring_free(data_buf); + return (0); +} + +#endif diff --git a/src/util/ctable.h b/src/util/ctable.h new file mode 100644 index 0000000..ea16914 --- /dev/null +++ b/src/util/ctable.h @@ -0,0 +1,41 @@ +#ifndef _CTABLE_H_INCLUDED_ +#define _CTABLE_H_INCLUDED_ + +/*++ +/* NAME +/* ctable 5 +/* SUMMARY +/* cache manager +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Interface of the cache manager. The structure of a cache is not visible + * to the caller. + */ + +#define CTABLE struct ctable +typedef void *(*CTABLE_CREATE_FN) (const char *, void *); +typedef void (*CTABLE_DELETE_FN) (void *, void *); + +extern CTABLE *ctable_create(ssize_t, CTABLE_CREATE_FN, CTABLE_DELETE_FN, void *); +extern void ctable_free(CTABLE *); +extern void ctable_walk(CTABLE *, void (*) (const char *, const void *)); +extern const void *ctable_locate(CTABLE *, const char *); +extern const void *ctable_refresh(CTABLE *, const char *); +extern void ctable_newcontext(CTABLE *, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/ctable.in b/src/util/ctable.in new file mode 100644 index 0000000..78763cf --- /dev/null +++ b/src/util/ctable.in @@ -0,0 +1,39 @@ +a +1 +b +2 +c +3 +d +4 +e +5 +f +6 +f +a +1 +b +2 +c +3 +d +4 +e +5 +f +6 +f +e +d +c +b +a +1 +b +c +d +e +f +6 +f diff --git a/src/util/ctable.ref b/src/util/ctable.ref new file mode 100644 index 0000000..34e8a95 --- /dev/null +++ b/src/util/ctable.ref @@ -0,0 +1,99 @@ +key = a +ask: a = 1 +./ctable: ctable_locate: install entry key a +result: 1 +key = b +ask: b = 2 +./ctable: ctable_locate: install entry key b +result: 2 +key = c +ask: c = 3 +./ctable: ctable_locate: install entry key c +result: 3 +key = d +ask: d = 4 +./ctable: ctable_locate: install entry key d +result: 4 +key = e +ask: e = 5 +./ctable: ctable_locate: install entry key e +result: 5 +key = f +./ctable: ctable_locate: purge entry key a +ask: f = 6 +./ctable: ctable_locate: install entry key f +result: 6 +key = f +./ctable: ctable_locate: leave existing entry key f +result: 6 +key = a +./ctable: ctable_locate: purge entry key b +ask: a = 1 +./ctable: ctable_locate: install entry key a +result: 1 +key = b +./ctable: ctable_locate: purge entry key c +ask: b = 2 +./ctable: ctable_locate: install entry key b +result: 2 +key = c +./ctable: ctable_locate: purge entry key d +ask: c = 3 +./ctable: ctable_locate: install entry key c +result: 3 +key = d +./ctable: ctable_locate: purge entry key e +ask: d = 4 +./ctable: ctable_locate: install entry key d +result: 4 +key = e +./ctable: ctable_locate: purge entry key f +ask: e = 5 +./ctable: ctable_locate: install entry key e +result: 5 +key = f +./ctable: ctable_locate: purge entry key a +ask: f = 6 +./ctable: ctable_locate: install entry key f +result: 6 +key = f +./ctable: ctable_locate: leave existing entry key f +result: 6 +key = e +./ctable: ctable_locate: move existing entry key e +result: 5 +key = d +./ctable: ctable_locate: move existing entry key d +result: 4 +key = c +./ctable: ctable_locate: move existing entry key c +result: 3 +key = b +./ctable: ctable_locate: move existing entry key b +result: 2 +key = a +./ctable: ctable_locate: purge entry key f +ask: a = 1 +./ctable: ctable_locate: install entry key a +result: 1 +key = b +./ctable: ctable_locate: move existing entry key b +result: 2 +key = c +./ctable: ctable_locate: move existing entry key c +result: 3 +key = d +./ctable: ctable_locate: move existing entry key d +result: 4 +key = e +./ctable: ctable_locate: move existing entry key e +result: 5 +key = f +./ctable: ctable_locate: purge entry key a +ask: f = 6 +./ctable: ctable_locate: install entry key f +result: 6 +key = f +./ctable: ctable_locate: leave existing entry key f +result: 6 +key = \ No newline at end of file diff --git a/src/util/dict.c b/src/util/dict.c new file mode 100644 index 0000000..5d53860 --- /dev/null +++ b/src/util/dict.c @@ -0,0 +1,664 @@ +/*++ +/* NAME +/* dict 3 +/* SUMMARY +/* dictionary manager +/* SYNOPSIS +/* #include +/* +/* void dict_register(dict_name, dict_info) +/* const char *dict_name; +/* DICT *dict_info; +/* +/* DICT *dict_handle(dict_name) +/* const char *dict_name; +/* +/* void dict_unregister(dict_name) +/* const char *dict_name; +/* +/* int dict_update(dict_name, member, value) +/* const char *dict_name; +/* const char *member; +/* const char *value; +/* +/* const char *dict_lookup(dict_name, member) +/* const char *dict_name; +/* const char *member; +/* +/* int dict_delete(dict_name, member) +/* const char *dict_name; +/* const char *member; +/* +/* int dict_sequence(dict_name, func, member, value) +/* const char *dict_name; +/* int func; +/* const char **member; +/* const char **value; +/* +/* const char *dict_eval(dict_name, string, int recursive) +/* const char *dict_name; +/* const char *string; +/* int recursive; +/* +/* int dict_walk(action, context) +/* void (*action)(dict_name, dict_handle, context) +/* void *context; +/* +/* int dict_error(dict_name) +/* const char *dict_name; +/* +/* const char *dict_changed_name() +/* +/* void DICT_OWNER_AGGREGATE_INIT(aggregate) +/* DICT_OWNER aggregate; +/* +/* void DICT_OWNER_AGGREGATE_UPDATE(aggregate, source) +/* DICT_OWNER aggregate; +/* DICT_OWNER source; +/* AUXILIARY FUNCTIONS +/* int dict_load_file_xt(dict_name, path) +/* const char *dict_name; +/* const char *path; +/* +/* void dict_load_fp(dict_name, fp) +/* const char *dict_name; +/* VSTREAM *fp; +/* +/* const char *dict_flags_str(dict_flags) +/* int dict_flags; +/* +/* int dict_flags_mask(names) +/* const char *names; +/* DESCRIPTION +/* This module maintains a collection of name-value dictionaries. +/* Each dictionary has its own name and has its own methods to read +/* or update members. Examples of dictionaries that can be accessed +/* in this manner are the global UNIX-style process environment, +/* hash tables, NIS maps, DBM files, and so on. Dictionary values +/* are not limited to strings but can be arbitrary objects as long +/* as they can be represented by character pointers. +/* FEATURES +/* .fi +/* .ad +/* Notable features of this module are: +/* .IP "macro expansion (string-valued dictionaries only)" +/* Macros of the form $\fIname\fR can be expanded to the current +/* value of \fIname\fR. The forms $(\fIname\fR) and ${\fIname\fR} are +/* also supported. +/* .IP "unknown names" +/* An update request for an unknown dictionary name will trigger +/* the instantiation of an in-memory dictionary with that name. +/* A lookup request (including delete and sequence) for an +/* unknown dictionary will result in a "not found" and "no +/* error" result. +/* .PP +/* dict_register() adds a new dictionary, including access methods, +/* to the list of known dictionaries, or increments the reference +/* count for an existing (name, dictionary) pair. Otherwise, it is +/* an error to pass an existing name (this would cause a memory leak). +/* +/* dict_handle() returns the generic dictionary handle of the +/* named dictionary, or a null pointer when the named dictionary +/* is not found. +/* +/* dict_unregister() decrements the reference count of the named +/* dictionary. When the reference count reaches zero, dict_unregister() +/* breaks the (name, dictionary) association and executes the +/* dictionary's optional \fIremove\fR method. +/* +/* dict_update() updates the value of the named dictionary member. +/* The dictionary member and the named dictionary are instantiated +/* on the fly. The result value is zero (DICT_STAT_SUCCESS) +/* when the update was made. +/* +/* dict_lookup() returns the value of the named member (i.e. without +/* expanding macros in the member value). The \fIdict_name\fR argument +/* specifies the dictionary to search. The result is a null pointer +/* when no value is found, otherwise the result is owned by the +/* underlying dictionary method. Make a copy if the result is to be +/* modified, or if the result is to survive multiple dict_lookup() calls. +/* +/* dict_delete() removes the named member from the named dictionary. +/* The result value is zero (DICT_STAT_SUCCESS) when the member +/* was found. +/* +/* dict_sequence() steps through the named dictionary and returns +/* keys and values in some implementation-defined order. The func +/* argument is DICT_SEQ_FUN_FIRST to set the cursor to the first +/* entry or DICT_SEQ_FUN_NEXT to select the next entry. The result +/* is owned by the underlying dictionary method. Make a copy if the +/* result is to be modified, or if the result is to survive multiple +/* dict_sequence() calls. The result value is zero (DICT_STAT_SUCCESS) +/* when a member was found. +/* +/* dict_eval() expands macro references in the specified string. +/* The result is owned by the dictionary manager. Make a copy if the +/* result is to survive multiple dict_eval() calls. When the +/* \fIrecursive\fR argument is non-zero, macro references in macro +/* lookup results are expanded recursively. +/* +/* dict_walk() iterates over all registered dictionaries in some +/* arbitrary order, and invokes the specified action routine with +/* as arguments: +/* .IP "const char *dict_name" +/* Dictionary name. +/* .IP "DICT *dict_handle" +/* Generic dictionary handle. +/* .IP "char *context" +/* Application context from the caller. +/* .PP +/* dict_changed_name() returns non-zero when any dictionary needs to +/* be re-opened because it has changed or because it was unlinked. +/* A non-zero result is the name of a changed dictionary. +/* +/* dict_load_file_xt() reads name-value entries from the named file. +/* Lines that begin with whitespace are concatenated to the preceding +/* line (the newline is deleted). +/* Each entry is stored in the dictionary named by \fIdict_name\fR. +/* The result is zero if the file could not be opened. +/* +/* dict_load_fp() reads name-value entries from an open stream. +/* It has the same semantics as the dict_load_file_xt() function. +/* +/* dict_flags_str() returns a printable representation of the +/* specified dictionary flags. The result is overwritten upon +/* each call. +/* +/* dict_flags_mask() returns the bitmask for the specified +/* comma/space-separated dictionary flag names. +/* TRUST AND PROVENANCE +/* .ad +/* .fi +/* Each dictionary has an owner attribute that contains (status, +/* uid) information about the owner of a dictionary. The +/* status is one of the following: +/* .IP DICT_OWNER_TRUSTED +/* The dictionary is owned by a trusted user. The uid is zero, +/* and specifies a UNIX user ID. +/* .IP DICT_OWNER_UNTRUSTED +/* The dictionary is owned by an untrusted user. The uid is +/* non-zero, and specifies a UNIX user ID. +/* .IP DICT_OWNER_UNKNOWN +/* The dictionary is owned by an unspecified user. For example, +/* the origin is unauthenticated, or different parts of a +/* dictionary aggregate (see below) are owned by different +/* untrusted users. The uid is non-zero and does not specify +/* a UNIX user ID. +/* .PP +/* Note that dictionary ownership does not necessarily imply +/* ownership of lookup results. For example, a PCRE table may +/* be owned by the trusted root user, but the result of $number +/* expansion can contain data from an arbitrary remote SMTP +/* client. See dict_open(3) for how to disallow $number +/* expansions with security-sensitive operations. +/* +/* Two macros are available to help determine the provenance +/* and trustworthiness of a dictionary aggregate. The macros +/* are unsafe because they may evaluate arguments more than +/* once. +/* +/* DICT_OWNER_AGGREGATE_INIT() initialize aggregate owner +/* attributes to the highest trust level. +/* +/* DICT_OWNER_AGGREGATE_UPDATE() updates the aggregate owner +/* attributes with the attributes of the specified source, and +/* reduces the aggregate trust level as appropriate. +/* SEE ALSO +/* htable(3) +/* BUGS +/* DIAGNOSTICS +/* Fatal errors: out of memory, malformed macro name. +/* +/* The lookup routine returns non-null when the request is +/* satisfied. The update, delete and sequence routines return +/* zero (DICT_STAT_SUCCESS) when the request is satisfied. +/* The dict_error() function returns non-zero only when the +/* last operation was not satisfied due to a dictionary access +/* error. The result can have the following values: +/* .IP DICT_ERR_NONE(zero) +/* There was no dictionary access error. For example, the +/* request was satisfied, the requested information did not +/* exist in the dictionary, or the information already existed +/* when it should not exist (collision). +/* .IP DICT_ERR_RETRY(<0) +/* The dictionary was temporarily unavailable. This can happen +/* with network-based services. +/* .IP DICT_ERR_CONFIG(<0) +/* The dictionary was unavailable due to a configuration error. +/* .PP +/* Generally, a program is expected to test the function result +/* value for "success" first. If the operation was not successful, +/* a program is expected to test for a non-zero dict->error +/* status to distinguish between a data notfound/collision +/* condition or a dictionary access error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "htable.h" +#include "mymalloc.h" +#include "vstream.h" +#include "vstring.h" +#include "readlline.h" +#include "mac_expand.h" +#include "stringops.h" +#include "iostuff.h" +#include "name_mask.h" +#include "dict.h" +#include "dict_ht.h" +#include "warn_stat.h" +#include "line_number.h" + +static HTABLE *dict_table; + + /* + * Each (name, dictionary) instance has a reference count. The count is part + * of the name, not the dictionary. The same dictionary may be registered + * under multiple names. The structure below keeps track of instances and + * reference counts. + */ +typedef struct { + DICT *dict; + int refcount; +} DICT_NODE; + +#define dict_node(dict) \ + (dict_table ? (DICT_NODE *) htable_find(dict_table, dict) : 0) + +/* Find a dictionary handle by name for lookup purposes. */ + +#define DICT_FIND_FOR_LOOKUP(dict, dict_name) do { \ + DICT_NODE *node; \ + if ((node = dict_node(dict_name)) != 0) \ + dict = node->dict; \ + else \ + dict = 0; \ +} while (0) + +/* Find a dictionary handle by name for update purposes. */ + +#define DICT_FIND_FOR_UPDATE(dict, dict_name) do { \ + DICT_NODE *node; \ + if ((node = dict_node(dict_name)) == 0) { \ + dict = dict_ht_open(dict_name, O_CREAT | O_RDWR, 0); \ + dict_register(dict_name, dict); \ + } else \ + dict = node->dict; \ +} while (0) + +#define STR(x) vstring_str(x) + +/* dict_register - make association with dictionary */ + +void dict_register(const char *dict_name, DICT *dict_info) +{ + const char *myname = "dict_register"; + DICT_NODE *node; + + if (dict_table == 0) + dict_table = htable_create(0); + if ((node = dict_node(dict_name)) == 0) { + node = (DICT_NODE *) mymalloc(sizeof(*node)); + node->dict = dict_info; + node->refcount = 0; + htable_enter(dict_table, dict_name, (void *) node); + } else if (dict_info != node->dict) + msg_fatal("%s: dictionary name exists: %s", myname, dict_name); + node->refcount++; + if (msg_verbose > 1) + msg_info("%s: %s %d", myname, dict_name, node->refcount); +} + +/* dict_handle - locate generic dictionary handle */ + +DICT *dict_handle(const char *dict_name) +{ + DICT_NODE *node; + + return ((node = dict_node(dict_name)) != 0 ? node->dict : 0); +} + +/* dict_node_free - dict_unregister() callback */ + +static void dict_node_free(void *ptr) +{ + DICT_NODE *node = (DICT_NODE *) ptr; + DICT *dict = node->dict; + + if (dict->close) + dict->close(dict); + myfree((void *) node); +} + +/* dict_unregister - break association with named dictionary */ + +void dict_unregister(const char *dict_name) +{ + const char *myname = "dict_unregister"; + DICT_NODE *node; + + if ((node = dict_node(dict_name)) == 0) + msg_panic("non-existing dictionary: %s", dict_name); + if (msg_verbose > 1) + msg_info("%s: %s %d", myname, dict_name, node->refcount); + if (--(node->refcount) == 0) + htable_delete(dict_table, dict_name, dict_node_free); +} + +/* dict_update - replace or add dictionary entry */ + +int dict_update(const char *dict_name, const char *member, const char *value) +{ + const char *myname = "dict_update"; + DICT *dict; + + DICT_FIND_FOR_UPDATE(dict, dict_name); + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, value); + return (dict->update(dict, member, value)); +} + +/* dict_lookup - look up dictionary entry */ + +const char *dict_lookup(const char *dict_name, const char *member) +{ + const char *myname = "dict_lookup"; + DICT *dict; + const char *ret; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (dict != 0) { + ret = dict->lookup(dict, member); + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, ret ? ret : + dict->error ? "(error)" : "(notfound)"); + return (ret); + } else { + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, "(notfound)"); + return (0); + } +} + +/* dict_delete - delete dictionary entry */ + +int dict_delete(const char *dict_name, const char *member) +{ + const char *myname = "dict_delete"; + DICT *dict; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (msg_verbose > 1) + msg_info("%s: delete %s", myname, member); + return (dict ? dict->delete(dict, member) : DICT_STAT_FAIL); +} + +/* dict_sequence - traverse dictionary */ + +int dict_sequence(const char *dict_name, const int func, + const char **member, const char **value) +{ + const char *myname = "dict_sequence"; + DICT *dict; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (msg_verbose > 1) + msg_info("%s: sequence func %d", myname, func); + return (dict ? dict->sequence(dict, func, member, value) : DICT_STAT_FAIL); +} + +/* dict_error - return last error */ + +int dict_error(const char *dict_name) +{ + DICT *dict; + + DICT_FIND_FOR_LOOKUP(dict, dict_name); + return (dict ? dict->error : DICT_ERR_NONE); +} + +/* dict_load_file_xt - read entries from text file */ + +int dict_load_file_xt(const char *dict_name, const char *path) +{ + VSTREAM *fp; + struct stat st; + time_t before; + time_t after; + + /* + * Read the file again if it is hot. This may result in reading a partial + * parameter name when a file changes in the middle of a read. + */ + for (before = time((time_t *) 0); /* see below */ ; before = after) { + if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) + return (0); + dict_load_fp(dict_name, fp); + if (fstat(vstream_fileno(fp), &st) < 0) + msg_fatal("fstat %s: %m", path); + if (vstream_ferror(fp) || vstream_fclose(fp)) + msg_fatal("read %s: %m", path); + after = time((time_t *) 0); + if (st.st_mtime < before - 1 || st.st_mtime > after) + break; + if (msg_verbose > 1) + msg_info("pausing to let %s cool down", path); + doze(300000); + } + return (1); +} + +/* dict_load_fp - read entries from open stream */ + +void dict_load_fp(const char *dict_name, VSTREAM *fp) +{ + const char *myname = "dict_load_fp"; + VSTRING *buf; + char *member; + char *val; + const char *old; + int last_line; + int lineno; + const char *err; + struct stat st; + DICT *dict; + + /* + * Instantiate the dictionary even if the file is empty. + */ + DICT_FIND_FOR_UPDATE(dict, dict_name); + buf = vstring_alloc(100); + last_line = 0; + + if (fstat(vstream_fileno(fp), &st) < 0) + msg_fatal("fstat %s: %m", VSTREAM_PATH(fp)); + while (readllines(buf, fp, &last_line, &lineno)) { + if ((err = split_nameval(STR(buf), &member, &val)) != 0) + msg_fatal("%s, line %d: %s: \"%s\"", + VSTREAM_PATH(fp), + lineno, + err, STR(buf)); + if (msg_verbose > 1) + msg_info("%s: %s = %s", myname, member, val); + if ((old = dict->lookup(dict, member)) != 0 + && strcmp(old, val) != 0) + msg_warn("%s, line %d: overriding earlier entry: %s=%s", + VSTREAM_PATH(fp), lineno, member, old); + if (dict->update(dict, member, val) != 0) + msg_fatal("%s, line %d: unable to update %s:%s", + VSTREAM_PATH(fp), lineno, dict->type, dict->name); + } + vstring_free(buf); + dict->owner.uid = st.st_uid; + dict->owner.status = (st.st_uid != 0); +} + +/* dict_eval_lookup - macro parser call-back routine */ + +static const char *dict_eval_lookup(const char *key, int unused_type, + void *context) +{ + char *dict_name = (char *) context; + const char *pp = 0; + DICT *dict; + + /* + * XXX how would one recover? + */ + DICT_FIND_FOR_LOOKUP(dict, dict_name); + if (dict != 0 + && (pp = dict->lookup(dict, key)) == 0 && dict->error != 0) + msg_fatal("dictionary %s: lookup %s: operation failed", dict_name, key); + return (pp); +} + +/* dict_eval - expand embedded dictionary references */ + +const char *dict_eval(const char *dict_name, const char *value, int recursive) +{ + const char *myname = "dict_eval"; + static VSTRING *buf; + int status; + + /* + * Initialize. + */ + if (buf == 0) + buf = vstring_alloc(10); + + /* + * Expand macros, possibly recursively. + */ +#define DONT_FILTER (char *) 0 + + status = mac_expand(buf, value, + recursive ? MAC_EXP_FLAG_RECURSE : MAC_EXP_FLAG_NONE, + DONT_FILTER, dict_eval_lookup, (void *) dict_name); + if (status & MAC_PARSE_ERROR) + msg_fatal("dictionary %s: macro processing error", dict_name); + if (msg_verbose > 1) { + if (strcmp(value, STR(buf)) != 0) + msg_info("%s: expand %s -> %s", myname, value, STR(buf)); + else + msg_info("%s: const %s", myname, value); + } + return (STR(buf)); +} + +/* dict_walk - iterate over all dictionaries in arbitrary order */ + +void dict_walk(DICT_WALK_ACTION action, void *ptr) +{ + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + HTABLE_INFO *h; + + ht_info_list = htable_list(dict_table); + for (ht = ht_info_list; (h = *ht) != 0; ht++) + action(h->key, (DICT *) h->value, ptr); + myfree((void *) ht_info_list); +} + +/* dict_changed_name - see if any dictionary has changed */ + +const char *dict_changed_name(void) +{ + const char *myname = "dict_changed_name"; + struct stat st; + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + HTABLE_INFO *h; + const char *status; + DICT *dict; + + ht_info_list = htable_list(dict_table); + for (status = 0, ht = ht_info_list; status == 0 && (h = *ht) != 0; ht++) { + dict = ((DICT_NODE *) h->value)->dict; + if (dict->stat_fd < 0) /* not file-based */ + continue; + if (dict->mtime == 0) /* not bloody likely */ + msg_warn("%s: table %s: null time stamp", myname, h->key); + if (fstat(dict->stat_fd, &st) < 0) + msg_fatal("%s: fstat: %m", myname); + if (((dict->flags & DICT_FLAG_MULTI_WRITER) == 0 + && st.st_mtime != dict->mtime) + || st.st_nlink == 0) + status = h->key; + } + myfree((void *) ht_info_list); + return (status); +} + +/* dict_changed - backwards compatibility */ + +int dict_changed(void) +{ + return (dict_changed_name() != 0); +} + + /* + * Mapping between flag names and flag values. + */ +static const NAME_MASK dict_mask[] = { + "warn_dup", DICT_FLAG_DUP_WARN, /* if file, warn about dups */ + "ignore_dup", DICT_FLAG_DUP_IGNORE, /* if file, ignore dups */ + "try0null", DICT_FLAG_TRY0NULL, /* do not append 0 to key/value */ + "try1null", DICT_FLAG_TRY1NULL, /* append 0 to key/value */ + "fixed", DICT_FLAG_FIXED, /* fixed key map */ + "pattern", DICT_FLAG_PATTERN, /* keys are patterns */ + "lock", DICT_FLAG_LOCK, /* lock before access */ + "replace", DICT_FLAG_DUP_REPLACE, /* if file, replace dups */ + "sync_update", DICT_FLAG_SYNC_UPDATE, /* if file, sync updates */ + "debug", DICT_FLAG_DEBUG, /* log access */ + "no_regsub", DICT_FLAG_NO_REGSUB, /* disallow regexp substitution */ + "no_proxy", DICT_FLAG_NO_PROXY, /* disallow proxy mapping */ + "no_unauth", DICT_FLAG_NO_UNAUTH, /* disallow unauthenticated data */ + "fold_fix", DICT_FLAG_FOLD_FIX, /* case-fold with fixed-case key map */ + "fold_mul", DICT_FLAG_FOLD_MUL, /* case-fold with multi-case key map */ + "open_lock", DICT_FLAG_OPEN_LOCK, /* permanent lock upon open */ + "bulk_update", DICT_FLAG_BULK_UPDATE, /* bulk update if supported */ + "multi_writer", DICT_FLAG_MULTI_WRITER, /* multi-writer safe */ + "utf8_request", DICT_FLAG_UTF8_REQUEST, /* request UTF-8 activation */ + "utf8_active", DICT_FLAG_UTF8_ACTIVE, /* UTF-8 is activated */ + "src_rhs_is_file", DICT_FLAG_SRC_RHS_IS_FILE, /* value from file */ + 0, +}; + +/* dict_flags_str - convert bitmask to symbolic flag names */ + +const char *dict_flags_str(int dict_flags) +{ + static VSTRING *buf = 0; + + if (buf == 0) + buf = vstring_alloc(1); + + return (str_name_mask_opt(buf, "dictionary flags", dict_mask, dict_flags, + NAME_MASK_NUMBER | NAME_MASK_PIPE)); +} + +/* dict_flags_mask - convert symbolic flag names to bitmask */ + +int dict_flags_mask(const char *names) +{ + return (name_mask("dictionary flags", dict_mask, names)); +} diff --git a/src/util/dict.h b/src/util/dict.h new file mode 100644 index 0000000..4f0cab8 --- /dev/null +++ b/src/util/dict.h @@ -0,0 +1,340 @@ +#ifndef _DICT_H_INCLUDED_ +#define _DICT_H_INCLUDED_ + +/*++ +/* NAME +/* dict 3h +/* SUMMARY +/* dictionary manager +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include +#include + +#ifdef NO_SIGSETJMP +#define DICT_JMP_BUF jmp_buf +#else +#define DICT_JMP_BUF sigjmp_buf +#endif + + /* + * Utility library. + */ +#include +#include +#include +#include + + /* + * Provenance information. + */ +typedef struct DICT_OWNER { + int status; /* see below */ + uid_t uid; /* use only if status == UNTRUSTED */ +} DICT_OWNER; + + /* + * Note that trust levels are not in numerical order. + */ +#define DICT_OWNER_UNKNOWN (-1) /* ex: unauthenticated tcp, proxy */ +#define DICT_OWNER_TRUSTED (!1) /* ex: root-owned config file */ +#define DICT_OWNER_UNTRUSTED (!0) /* ex: non-root config file */ + + /* + * When combining tables with different provenance, we initialize to the + * highest trust level, and remember the lowest trust level that we find + * during aggregation. If we combine tables that are owned by different + * untrusted users, the resulting provenance is "unknown". + */ +#define DICT_OWNER_AGGREGATE_INIT(dst) { \ + (dst).status = DICT_OWNER_TRUSTED; \ + (dst).uid = 0; \ + } while (0) + + /* + * The following is derived from the 3x3 transition matrix. + */ +#define DICT_OWNER_AGGREGATE_UPDATE(dst, src) do { \ + if ((dst).status == DICT_OWNER_TRUSTED \ + || (src).status == DICT_OWNER_UNKNOWN) { \ + (dst) = (src); \ + } else if ((dst).status == (src).status \ + && (dst).uid != (src).uid) { \ + (dst).status = DICT_OWNER_UNKNOWN; \ + (dst).uid = ~0; \ + } \ + } while (0) + + /* + * Generic dictionary interface - in reality, a dictionary extends this + * structure with private members to maintain internal state. + */ +typedef struct DICT { + char *type; /* for diagnostics */ + char *name; /* for diagnostics */ + int flags; /* see below */ + const char *(*lookup) (struct DICT *, const char *); + int (*update) (struct DICT *, const char *, const char *); + int (*delete) (struct DICT *, const char *); + int (*sequence) (struct DICT *, int, const char **, const char **); + int (*lock) (struct DICT *, int); + void (*close) (struct DICT *); + int lock_type; /* for read/write lock */ + int lock_fd; /* for read/write lock */ + int stat_fd; /* change detection */ + time_t mtime; /* mod time at open */ + VSTRING *fold_buf; /* key folding buffer */ + DICT_OWNER owner; /* provenance */ + int error; /* last operation only */ + DICT_JMP_BUF *jbuf; /* exception handling */ + struct DICT_UTF8_BACKUP *utf8_backup; /* see below */ + struct VSTRING *file_buf; /* dict_file_to_buf() */ + struct VSTRING *file_b64; /* dict_file_to_b64() */ +} DICT; + +extern DICT *dict_alloc(const char *, const char *, ssize_t); +extern void dict_free(DICT *); + +extern DICT *dict_debug(DICT *); + +#define DICT_DEBUG(d) ((d)->flags & DICT_FLAG_DEBUG ? dict_debug(d) : (d)) + + /* + * See dict_open.c embedded manpage for flag definitions. + */ +#define DICT_FLAG_NONE (0) +#define DICT_FLAG_DUP_WARN (1<<0) /* warn about dups if not supported */ +#define DICT_FLAG_DUP_IGNORE (1<<1) /* ignore dups if not supported */ +#define DICT_FLAG_TRY0NULL (1<<2) /* do not append 0 to key/value */ +#define DICT_FLAG_TRY1NULL (1<<3) /* append 0 to key/value */ +#define DICT_FLAG_FIXED (1<<4) /* fixed key map */ +#define DICT_FLAG_PATTERN (1<<5) /* keys are patterns */ +#define DICT_FLAG_LOCK (1<<6) /* use temp lock before access */ +#define DICT_FLAG_DUP_REPLACE (1<<7) /* replace dups if supported */ +#define DICT_FLAG_SYNC_UPDATE (1<<8) /* sync updates if supported */ +#define DICT_FLAG_DEBUG (1<<9) /* log access */ +/*#define DICT_FLAG_FOLD_KEY (1<<10) /* lowercase the lookup key */ +#define DICT_FLAG_NO_REGSUB (1<<11) /* disallow regexp substitution */ +#define DICT_FLAG_NO_PROXY (1<<12) /* disallow proxy mapping */ +#define DICT_FLAG_NO_UNAUTH (1<<13) /* disallow unauthenticated data */ +#define DICT_FLAG_FOLD_FIX (1<<14) /* case-fold key with fixed-case map */ +#define DICT_FLAG_FOLD_MUL (1<<15) /* case-fold key with multi-case map */ +#define DICT_FLAG_FOLD_ANY (DICT_FLAG_FOLD_FIX | DICT_FLAG_FOLD_MUL) +#define DICT_FLAG_OPEN_LOCK (1<<16) /* perm lock if not multi-writer safe */ +#define DICT_FLAG_BULK_UPDATE (1<<17) /* optimize for bulk updates */ +#define DICT_FLAG_MULTI_WRITER (1<<18) /* multi-writer safe map */ +#define DICT_FLAG_UTF8_REQUEST (1<<19) /* activate UTF-8 if possible */ +#define DICT_FLAG_UTF8_ACTIVE (1<<20) /* UTF-8 proxy layer is present */ +#define DICT_FLAG_SRC_RHS_IS_FILE \ + (1<<21) /* Map source RHS is a file */ + +#define DICT_FLAG_UTF8_MASK (DICT_FLAG_UTF8_REQUEST) + + /* IMPORTANT: Update the dict_mask[] table when the above changes */ + + /* + * The subsets of flags that control how a map is used. These are relevant + * mainly for proxymap support. Note: some categories overlap. + * + * DICT_FLAG_IMPL_MASK - flags that are set by the map implementation itself. + * + * DICT_FLAG_PARANOID - requestor flags that forbid the use of insecure map + * types for security-sensitive operations. These flags are checked by the + * map implementation itself upon open, lookup etc. requests. + * + * DICT_FLAG_RQST_MASK - all requestor flags, including paranoid flags, that + * the requestor may change between open, lookup etc. requests. These + * specify requestor properties, not map properties. + * + * DICT_FLAG_INST_MASK - none of the above flags. The requestor may not change + * these flags between open, lookup, etc. requests (although a map may make + * changes to its copy of some of these flags). The proxymap server opens + * only one map instance for all client requests with the same values of + * these flags, and the proxymap client uses its own saved copy of these + * flags. DICT_FLAG_SRC_RHS_IS_FILE is an example of such a flag. + */ +#define DICT_FLAG_PARANOID \ + (DICT_FLAG_NO_REGSUB | DICT_FLAG_NO_PROXY | DICT_FLAG_NO_UNAUTH) +#define DICT_FLAG_IMPL_MASK (DICT_FLAG_FIXED | DICT_FLAG_PATTERN | \ + DICT_FLAG_MULTI_WRITER) +#define DICT_FLAG_RQST_MASK (DICT_FLAG_FOLD_ANY | DICT_FLAG_LOCK | \ + DICT_FLAG_DUP_REPLACE | DICT_FLAG_DUP_WARN | \ + DICT_FLAG_DUP_IGNORE | DICT_FLAG_SYNC_UPDATE | \ + DICT_FLAG_PARANOID | DICT_FLAG_UTF8_MASK) +#define DICT_FLAG_INST_MASK ~(DICT_FLAG_IMPL_MASK | DICT_FLAG_RQST_MASK) + + /* + * Feature tests. + */ +#define DICT_NEED_UTF8_ACTIVATION(enable, flags) \ + ((enable) && ((flags) & DICT_FLAG_UTF8_MASK)) + + /* + * dict->error values. Errors must be negative; smtpd_check depends on this. + */ +#define DICT_ERR_NONE 0 /* no error */ +#define DICT_ERR_RETRY (-1) /* soft error */ +#define DICT_ERR_CONFIG (-2) /* configuration error */ + + /* + * Result values for exposed functions except lookup. FAIL/ERROR are + * suggested values, not for use in comparisons for equality. + */ +#define DICT_STAT_FAIL 1 /* any value > 0: notfound, conflict */ +#define DICT_STAT_SUCCESS 0 /* request satisfied */ +#define DICT_STAT_ERROR (-1) /* any value < 0: database error */ + + /* + * Set an error code and return a result value. + */ +#define DICT_ERR_VAL_RETURN(dict, err, val) do { \ + (dict)->error = (err); \ + return (val); \ + } while (0) + + /* + * Sequence function types. + */ +#define DICT_SEQ_FUN_FIRST 0 /* set cursor to first record */ +#define DICT_SEQ_FUN_NEXT 1 /* set cursor to next record */ + + /* + * Interface for dictionary types. + */ +extern ARGV *dict_mapnames(void); +typedef void (*DICT_MAPNAMES_EXTEND_FN) (ARGV *); +extern DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN); + + + /* + * High-level interface, with logical dictionary names. + */ +extern void dict_register(const char *, DICT *); +extern DICT *dict_handle(const char *); +extern void dict_unregister(const char *); +extern int dict_update(const char *, const char *, const char *); +extern const char *dict_lookup(const char *, const char *); +extern int dict_delete(const char *, const char *); +extern int dict_sequence(const char *, const int, const char **, const char **); +extern int dict_load_file_xt(const char *, const char *); +extern void dict_load_fp(const char *, VSTREAM *); +extern const char *dict_eval(const char *, const char *, int); +extern int dict_error(const char *); + + /* + * Low-level interface, with physical dictionary handles. + */ +typedef DICT *(*DICT_OPEN_FN) (const char *, int, int); +typedef DICT_OPEN_FN (*DICT_OPEN_EXTEND_FN) (const char *); +extern DICT *dict_open(const char *, int, int); +extern DICT *dict_open3(const char *, const char *, int, int); +extern void dict_open_register(const char *, DICT_OPEN_FN); +extern DICT_OPEN_EXTEND_FN dict_open_extend(DICT_OPEN_EXTEND_FN); + +#define dict_get(dp, key) ((const char *) (dp)->lookup((dp), (key))) +#define dict_put(dp, key, val) (dp)->update((dp), (key), (val)) +#define dict_del(dp, key) (dp)->delete((dp), (key)) +#define dict_seq(dp, f, key, val) (dp)->sequence((dp), (f), (key), (val)) +#define dict_close(dp) (dp)->close(dp) +typedef void (*DICT_WALK_ACTION) (const char *, DICT *, void *); +extern void dict_walk(DICT_WALK_ACTION, void *); +extern int dict_changed(void); +extern const char *dict_changed_name(void); +extern const char *dict_flags_str(int); +extern int dict_flags_mask(const char *); +extern void dict_type_override(DICT *, const char *); + + /* + * Check and convert UTF-8 keys and values. + */ +typedef struct DICT_UTF8_BACKUP { + const char *(*lookup) (struct DICT *, const char *); + int (*update) (struct DICT *, const char *, const char *); + int (*delete) (struct DICT *, const char *); +} DICT_UTF8_BACKUP; + +extern DICT *dict_utf8_activate(DICT *); + + /* + * Driver for interactive or scripted tests. + */ +void dict_test(int, char **); + + /* + * Behind-the-scenes support to continue execution with reduced + * functionality. + */ +extern int dict_allow_surrogate; +extern DICT *PRINTFLIKE(5, 6) dict_surrogate(const char *, const char *, int, int, const char *,...); + + /* + * This name is reserved for matchlist error handling. + */ +#define DICT_TYPE_NOFILE "non-existent" +#define DICT_TYPE_NOUTF8 "non-UTF-8" + + /* + * Duplicated from vstream(3). This should probably be abstracted out. + * + * Exception handling. We use pointer to jmp_buf to avoid a lot of unused + * baggage for streams that don't need this functionality. + * + * XXX sigsetjmp()/siglongjmp() save and restore the signal mask which can + * avoid surprises in code that manipulates signals, but unfortunately some + * systems have bugs in their implementation. + */ +#ifdef NO_SIGSETJMP +#define dict_setjmp(dict) setjmp((dict)->jbuf[0]) +#define dict_longjmp(dict, val) longjmp((dict)->jbuf[0], (val)) +#else +#define dict_setjmp(dict) sigsetjmp((dict)->jbuf[0], 1) +#define dict_longjmp(dict, val) siglongjmp((dict)->jbuf[0], (val)) +#endif +#define dict_isjmp(dict) ((dict)->jbuf != 0) + + /* + * Temporary API. If exception handling proves to be useful, + * dict_jmp_alloc() should be integrated into dict_alloc(). + */ +extern void dict_jmp_alloc(DICT *); + + /* + * dict_file(3). + */ +extern struct VSTRING *dict_file_to_buf(DICT *, const char *); +extern struct VSTRING *dict_file_to_b64(DICT *, const char *); +extern struct VSTRING *dict_file_from_b64(DICT *, const char *); +extern char *dict_file_get_error(DICT *); +extern void dict_file_purge_buffers(DICT *); +extern const char *dict_file_lookup(DICT *dict, const char *); + + /* + * dict_stream(3) + */ +extern VSTREAM *dict_stream_open(const char *dict_type, const char *mapname, + int open_flags, int dict_flags, struct stat * st, VSTRING **why); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/dict_alloc.c b/src/util/dict_alloc.c new file mode 100644 index 0000000..3285a38 --- /dev/null +++ b/src/util/dict_alloc.c @@ -0,0 +1,196 @@ +/*++ +/* NAME +/* dict_alloc 3 +/* SUMMARY +/* dictionary memory manager +/* SYNOPSIS +/* #include +/* +/* DICT *dict_alloc(dict_type, dict_name, size) +/* const char *dict_type; +/* const char *dict_name; +/* ssize_t size; +/* +/* void dict_free(dict) +/* DICT *ptr; +/* +/* void dict_jmp_alloc(dict) +/* DICT *ptr; +/* DESCRIPTION +/* dict_alloc() allocates memory for a dictionary structure of +/* \fIsize\fR bytes, initializes all generic dictionary +/* properties to default settings, +/* and installs default methods that do not support any operation. +/* The caller is supposed to override the default methods with +/* ones that it supports. +/* The purpose of the default methods is to trap an attempt to +/* invoke an unsupported method. +/* +/* One exception is the default lock function. When the +/* dictionary provides a file handle for locking, the default +/* lock function returns the result from myflock with the +/* locking method specified in the lock_type member, otherwise +/* it returns 0. Presently, the lock function is used only to +/* implement the DICT_FLAG_OPEN_LOCK feature (lock the database +/* exclusively after it is opened) for databases that are not +/* multi-writer safe. +/* +/* dict_free() releases memory and cleans up after dict_alloc(). +/* It is up to the caller to dispose of any memory that was allocated +/* by the caller. +/* +/* dict_jmp_alloc() implements preliminary support for exception +/* handling. This will eventually be built into dict_alloc(). +/* +/* Arguments: +/* .IP dict_type +/* The official name for this type of dictionary, as used by +/* dict_open(3) etc. This is stored under the \fBtype\fR +/* member. +/* .IP dict_name +/* Dictionary name. This is stored as the \fBname\fR member. +/* .IP size +/* The size in bytes of the dictionary subclass structure instance. +/* SEE ALSO +/* dict(3) +/* DIAGNOSTICS +/* Fatal errors: the process invokes a default method. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "myflock.h" +#include "dict.h" + +/* dict_default_lookup - trap unimplemented operation */ + +static const char *dict_default_lookup(DICT *dict, const char *unused_key) +{ + msg_fatal("table %s:%s: lookup operation is not supported", + dict->type, dict->name); +} + +/* dict_default_update - trap unimplemented operation */ + +static int dict_default_update(DICT *dict, const char *unused_key, + const char *unused_value) +{ + msg_fatal("table %s:%s: update operation is not supported", + dict->type, dict->name); +} + +/* dict_default_delete - trap unimplemented operation */ + +static int dict_default_delete(DICT *dict, const char *unused_key) +{ + msg_fatal("table %s:%s: delete operation is not supported", + dict->type, dict->name); +} + +/* dict_default_sequence - trap unimplemented operation */ + +static int dict_default_sequence(DICT *dict, int unused_function, + const char **unused_key, const char **unused_value) +{ + msg_fatal("table %s:%s: sequence operation is not supported", + dict->type, dict->name); +} + +/* dict_default_lock - default lock handler */ + +static int dict_default_lock(DICT *dict, int operation) +{ + if (dict->lock_fd >= 0) { + return (myflock(dict->lock_fd, dict->lock_type, operation)); + } else { + return (0); + } +} + +/* dict_default_close - trap unimplemented operation */ + +static void dict_default_close(DICT *dict) +{ + msg_fatal("table %s:%s: close operation is not supported", + dict->type, dict->name); +} + +/* dict_alloc - allocate dictionary object, initialize super-class */ + +DICT *dict_alloc(const char *dict_type, const char *dict_name, ssize_t size) +{ + DICT *dict = (DICT *) mymalloc(size); + + dict->type = mystrdup(dict_type); + dict->name = mystrdup(dict_name); + dict->flags = DICT_FLAG_FIXED; + dict->lookup = dict_default_lookup; + dict->update = dict_default_update; + dict->delete = dict_default_delete; + dict->sequence = dict_default_sequence; + dict->close = dict_default_close; + dict->lock = dict_default_lock; + dict->lock_type = INTERNAL_LOCK; + dict->lock_fd = -1; + dict->stat_fd = -1; + dict->mtime = 0; + dict->fold_buf = 0; + dict->owner.status = DICT_OWNER_UNKNOWN; + dict->owner.uid = INT_MAX; + dict->error = DICT_ERR_NONE; + dict->jbuf = 0; + dict->utf8_backup = 0; + dict->file_buf = 0; + dict->file_b64 = 0; + return dict; +} + +/* dict_free - super-class destructor */ + +void dict_free(DICT *dict) +{ + myfree(dict->type); + myfree(dict->name); + if (dict->jbuf) + myfree((void *) dict->jbuf); + if (dict->utf8_backup) + myfree((void *) dict->utf8_backup); + if (dict->file_buf) + vstring_free(dict->file_buf); + if (dict->file_b64) + vstring_free(dict->file_b64); + myfree((void *) dict); +} + + /* + * TODO: add a dict_flags() argument to dict_alloc() and handle jump buffer + * allocation there. + */ + +/* dict_jmp_alloc - enable exception handling */ + +void dict_jmp_alloc(DICT *dict) +{ + if (dict->jbuf == 0) + dict->jbuf = (DICT_JMP_BUF *) mymalloc(sizeof(DICT_JMP_BUF)); +} diff --git a/src/util/dict_cache.c b/src/util/dict_cache.c new file mode 100644 index 0000000..d8e874d --- /dev/null +++ b/src/util/dict_cache.c @@ -0,0 +1,1121 @@ +/*++ +/* NAME +/* dict_cache 3 +/* SUMMARY +/* External cache manager +/* SYNOPSIS +/* #include +/* +/* DICT_CACHE *dict_cache_open(dbname, open_flags, dict_flags) +/* const char *dbname; +/* int open_flags; +/* int dict_flags; +/* +/* void dict_cache_close(cache) +/* DICT_CACHE *cache; +/* +/* const char *dict_cache_lookup(cache, cache_key) +/* DICT_CACHE *cache; +/* const char *cache_key; +/* +/* int dict_cache_update(cache, cache_key, cache_val) +/* DICT_CACHE *cache; +/* const char *cache_key; +/* const char *cache_val; +/* +/* int dict_cache_delete(cache, cache_key) +/* DICT_CACHE *cache; +/* const char *cache_key; +/* +/* int dict_cache_sequence(cache, first_next, cache_key, cache_val) +/* DICT_CACHE *cache; +/* int first_next; +/* const char **cache_key; +/* const char **cache_val; +/* AUXILIARY FUNCTIONS +/* void dict_cache_control(cache, name, value, ...) +/* DICT_CACHE *cache; +/* int name; +/* +/* typedef int (*DICT_CACHE_VALIDATOR_FN) (const char *cache_key, +/* const char *cache_val, void *context); +/* +/* const char *dict_cache_name(cache) +/* DICT_CACHE *cache; +/* DESCRIPTION +/* This module maintains external cache files with support +/* for expiration. The underlying table must implement the +/* "lookup", "update", "delete" and "sequence" operations. +/* +/* Although this API is similar to the one documented in +/* dict_open(3), there are subtle differences in the interaction +/* between the iterators that access all cache elements, and +/* other operations that access individual cache elements. +/* +/* In particular, when a "sequence" or "cleanup" operation is +/* in progress the cache intercepts requests to delete the +/* "current" entry, as this would cause some databases to +/* mis-behave. Instead, the cache implements a "delete behind" +/* strategy, and deletes such an entry after the "sequence" +/* or "cleanup" operation moves on to the next cache element. +/* The "delete behind" strategy also affects the cache lookup +/* and update operations as detailed below. +/* +/* dict_cache_open() is a wrapper around the dict_open() +/* function. It opens the specified cache and returns a handle +/* that must be used for subsequent access. This function does +/* not return in case of error. +/* +/* dict_cache_close() closes the specified cache and releases +/* memory that was allocated by dict_cache_open(), and terminates +/* any thread that was started with dict_cache_control(). +/* +/* dict_cache_lookup() looks up the specified cache entry. +/* The result value is a null pointer when the cache entry was +/* not found, or when the entry is scheduled for "delete +/* behind". +/* +/* dict_cache_update() updates the specified cache entry. If +/* the entry is scheduled for "delete behind", the delete +/* operation is canceled (because of this, the cache must be +/* opened with DICT_FLAG_DUP_REPLACE). This function does not +/* return in case of error. +/* +/* dict_cache_delete() removes the specified cache entry. If +/* this is the "current" entry of a "sequence" operation, the +/* entry is scheduled for "delete behind". The result value +/* is zero when the entry was found. +/* +/* dict_cache_sequence() iterates over the specified cache and +/* returns each entry in an implementation-defined order. The +/* result value is zero when a cache entry was found. +/* +/* Important: programs must not use both dict_cache_sequence() +/* and the built-in cache cleanup feature. +/* +/* dict_cache_control() provides control over the built-in +/* cache cleanup feature and logging. The arguments are a list +/* of macros with zero or more arguments, terminated with +/* CA_DICT_CACHE_CTL_END which has none. The following lists +/* the macros and corresponding argument types. +/* .IP "CA_DICT_CACHE_CTL_FLAGS(int flags)" +/* The arguments to this command are the bit-wise OR of zero +/* or more of the following: +/* .RS +/* .IP CA_DICT_CACHE_CTL_FLAG_VERBOSE +/* Enable verbose logging of cache activity. +/* .IP CA_DICT_CACHE_CTL_FLAG_EXP_SUMMARY +/* Log cache statistics after each cache cleanup run. +/* .RE +/* .IP "CA_DICT_CACHE_CTL_INTERVAL(int interval)" +/* The interval between cache cleanup runs. Specify a null +/* validator or interval to stop cache cleanup. +/* .IP "CA_DICT_CACHE_CTL_VALIDATOR(DICT_CACHE_VALIDATOR_FN validator)" +/* An application call-back routine that returns non-zero when +/* a cache entry should be kept. The call-back function should +/* not make changes to the cache. Specify a null validator or +/* interval to stop cache cleanup. +/* .IP "CA_DICT_CACHE_CTL_CONTEXT(void *context)" +/* Application context that is passed to the validator function. +/* .RE +/* .PP +/* dict_cache_name() returns the name of the specified cache. +/* +/* Arguments: +/* .IP "dbname, open_flags, dict_flags" +/* These are passed unchanged to dict_open(). The cache must +/* be opened with DICT_FLAG_DUP_REPLACE. +/* .IP cache +/* Cache handle created with dict_cache_open(). +/* .IP cache_key +/* Cache lookup key. +/* .IP cache_val +/* Information that is stored under a cache lookup key. +/* .IP first_next +/* One of DICT_SEQ_FUN_FIRST (first cache element) or +/* DICT_SEQ_FUN_NEXT (next cache element). +/* .sp +/* Note: there is no "stop" request. To ensure that the "delete +/* behind" strategy does not interfere with database access, +/* allow dict_cache_sequence() to run to completion. +/* .IP table +/* A bare dictionary handle. +/* DIAGNOSTICS +/* When a request is satisfied, the lookup routine returns +/* non-null, and the update, delete and sequence routines +/* return zero. The cache->error value is zero when a request +/* could not be satisfied because an item did not exist (delete, +/* sequence) or if it could not be updated. The cache->error +/* value is non-zero only when a request could not be satisfied, +/* and the cause was a database error. +/* +/* Cache access errors are logged with a warning message. To +/* avoid spamming the log, each type of operation logs no more +/* than one cache access error per second, per cache. Specify +/* the DICT_CACHE_FLAG_VERBOSE flag (see above) to log all +/* warnings. +/* BUGS +/* There should be a way to suspend automatic program suicide +/* until a cache cleanup run is completed. Some entries may +/* never be removed when the process max_idle time is less +/* than the time needed to make a full pass over the cache. +/* +/* The delete-behind strategy assumes that all updates are +/* made by a single process. Otherwise, delete-behind may +/* remove an entry that was updated after it was scheduled for +/* deletion. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* A predecessor of this code was written first for the Postfix +/* tlsmgr(8) daemon. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * XXX Deleting entries while enumerating a map can he tricky. Some map + * types have a concept of cursor and support a "delete the current element" + * operation. Some map types without cursors don't behave well when the + * current first/next entry is deleted (example: with Berkeley DB < 2, the + * "next" operation produces garbage). To avoid trouble, we delete an entry + * after advancing the current first/next position beyond it; we use the + * same strategy with application requests to delete the current entry. + */ + + /* + * Opaque data structure. Use dict_cache_name() to access the name of the + * underlying database. + */ +struct DICT_CACHE { + char *name; /* full name including proxy: */ + int cache_flags; /* see below */ + int user_flags; /* logging */ + DICT *db; /* database handle */ + int error; /* last operation only */ + + /* Delete-behind support. */ + char *saved_curr_key; /* "current" cache lookup key */ + char *saved_curr_val; /* "current" cache lookup result */ + + /* Cleanup support. */ + int exp_interval; /* time between cleanup runs */ + DICT_CACHE_VALIDATOR_FN exp_validator; /* expiration call-back */ + void *exp_context; /* call-back context */ + int retained; /* entries retained in cleanup run */ + int dropped; /* entries removed in cleanup run */ + + /* Rate-limited logging support. */ + int log_delay; + time_t upd_log_stamp; /* last update warning */ + time_t get_log_stamp; /* last lookup warning */ + time_t del_log_stamp; /* last delete warning */ + time_t seq_log_stamp; /* last sequence warning */ +}; + +#define DC_FLAG_DEL_SAVED_CURRENT_KEY (1<<0) /* delete-behind is scheduled */ + + /* + * Don't log cache access errors more than once per second. + */ +#define DC_DEF_LOG_DELAY 1 + + /* + * Macros to make obscure code more readable. + */ +#define DC_SCHEDULE_FOR_DELETE_BEHIND(cp) \ + ((cp)->cache_flags |= DC_FLAG_DEL_SAVED_CURRENT_KEY) + +#define DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key) \ + ((cp)->saved_curr_key && strcmp((cp)->saved_curr_key, (cache_key)) == 0) + +#define DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) \ + (/* NOT: (cp)->saved_curr_key && */ \ + ((cp)->cache_flags & DC_FLAG_DEL_SAVED_CURRENT_KEY) != 0) + +#define DC_CANCEL_DELETE_BEHIND(cp) \ + ((cp)->cache_flags &= ~DC_FLAG_DEL_SAVED_CURRENT_KEY) + + /* + * Special key to store the time of the last cache cleanup run completion. + */ +#define DC_LAST_CACHE_CLEANUP_COMPLETED "_LAST_CACHE_CLEANUP_COMPLETED_" + +/* dict_cache_lookup - load entry from cache */ + +const char *dict_cache_lookup(DICT_CACHE *cp, const char *cache_key) +{ + const char *myname = "dict_cache_lookup"; + const char *cache_val; + DICT *db = cp->db; + + /* + * Search for the cache entry. Don't return an entry that is scheduled + * for delete-behind. + */ + if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) + && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) { + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s (pretend not found - scheduled for deletion)", + myname, cache_key); + DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, (char *) 0); + } else { + cache_val = dict_get(db, cache_key); + if (cache_val == 0 && db->error != 0) + msg_rate_delay(&cp->get_log_stamp, cp->log_delay, msg_warn, + "%s: cache lookup for '%s' failed due to error", + cp->name, cache_key); + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s value=%s", myname, cache_key, + cache_val ? cache_val : db->error ? + "error" : "(not found)"); + DICT_ERR_VAL_RETURN(cp, db->error, cache_val); + } +} + +/* dict_cache_update - save entry to cache */ + +int dict_cache_update(DICT_CACHE *cp, const char *cache_key, + const char *cache_val) +{ + const char *myname = "dict_cache_update"; + DICT *db = cp->db; + int put_res; + + /* + * Store the cache entry and cancel the delete-behind operation. + */ + if (DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp) + && DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) { + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: cancel delete-behind for key=%s", myname, cache_key); + DC_CANCEL_DELETE_BEHIND(cp); + } + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s value=%s", myname, cache_key, cache_val); + put_res = dict_put(db, cache_key, cache_val); + if (put_res != 0) + msg_rate_delay(&cp->upd_log_stamp, cp->log_delay, msg_warn, + "%s: could not update entry for %s", cp->name, cache_key); + DICT_ERR_VAL_RETURN(cp, db->error, put_res); +} + +/* dict_cache_delete - delete entry from cache */ + +int dict_cache_delete(DICT_CACHE *cp, const char *cache_key) +{ + const char *myname = "dict_cache_delete"; + int del_res; + DICT *db = cp->db; + + /* + * Delete the entry, unless we would delete the current first/next entry. + * In that case, schedule the "current" entry for delete-behind to avoid + * mis-behavior by some databases. + */ + if (DC_MATCH_SAVED_CURRENT_KEY(cp, cache_key)) { + DC_SCHEDULE_FOR_DELETE_BEHIND(cp); + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s (current entry - schedule for delete-behind)", + myname, cache_key); + DICT_ERR_VAL_RETURN(cp, DICT_ERR_NONE, DICT_STAT_SUCCESS); + } else { + del_res = dict_del(db, cache_key); + if (del_res != 0) + msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn, + "%s: could not delete entry for %s", cp->name, cache_key); + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s (%s)", myname, cache_key, + del_res == 0 ? "found" : + db->error ? "error" : "not found"); + DICT_ERR_VAL_RETURN(cp, db->error, del_res); + } +} + +/* dict_cache_sequence - look up the first/next cache entry */ + +int dict_cache_sequence(DICT_CACHE *cp, int first_next, + const char **cache_key, + const char **cache_val) +{ + const char *myname = "dict_cache_sequence"; + int seq_res; + const char *raw_cache_key; + const char *raw_cache_val; + char *previous_curr_key; + char *previous_curr_val; + DICT *db = cp->db; + + /* + * Find the first or next database entry. Hide the record with the cache + * cleanup completion time stamp. + */ + seq_res = dict_seq(db, first_next, &raw_cache_key, &raw_cache_val); + if (seq_res == 0 + && strcmp(raw_cache_key, DC_LAST_CACHE_CLEANUP_COMPLETED) == 0) + seq_res = + dict_seq(db, DICT_SEQ_FUN_NEXT, &raw_cache_key, &raw_cache_val); + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: key=%s value=%s", myname, + seq_res == 0 ? raw_cache_key : db->error ? + "(error)" : "(not found)", + seq_res == 0 ? raw_cache_val : db->error ? + "(error)" : "(not found)"); + if (db->error) + msg_rate_delay(&cp->seq_log_stamp, cp->log_delay, msg_warn, + "%s: sequence error", cp->name); + + /* + * Save the current cache_key and cache_val before they are clobbered by + * our own delete operation below. This also prevents surprises when the + * application accesses the database after this function returns. + * + * We also use the saved cache_key to protect the current entry against + * application delete requests. + */ + previous_curr_key = cp->saved_curr_key; + previous_curr_val = cp->saved_curr_val; + if (seq_res == 0) { + cp->saved_curr_key = mystrdup(raw_cache_key); + cp->saved_curr_val = mystrdup(raw_cache_val); + } else { + cp->saved_curr_key = 0; + cp->saved_curr_val = 0; + } + + /* + * Delete behind. + */ + if (db->error == 0 && DC_IS_SCHEDULED_FOR_DELETE_BEHIND(cp)) { + DC_CANCEL_DELETE_BEHIND(cp); + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: delete-behind key=%s value=%s", + myname, previous_curr_key, previous_curr_val); + if (dict_del(db, previous_curr_key) != 0) + msg_rate_delay(&cp->del_log_stamp, cp->log_delay, msg_warn, + "%s: could not delete entry for %s", + cp->name, previous_curr_key); + } + + /* + * Clean up previous iteration key and value. + */ + if (previous_curr_key) + myfree(previous_curr_key); + if (previous_curr_val) + myfree(previous_curr_val); + + /* + * Return the result. + */ + *cache_key = (cp)->saved_curr_key; + *cache_val = (cp)->saved_curr_val; + DICT_ERR_VAL_RETURN(cp, db->error, seq_res); +} + +/* dict_cache_delete_behind_reset - reset "delete behind" state */ + +static void dict_cache_delete_behind_reset(DICT_CACHE *cp) +{ +#define FREE_AND_WIPE(s) do { if (s) { myfree(s); (s) = 0; } } while (0) + + DC_CANCEL_DELETE_BEHIND(cp); + FREE_AND_WIPE(cp->saved_curr_key); + FREE_AND_WIPE(cp->saved_curr_val); +} + +/* dict_cache_clean_stat_log_reset - log and reset cache cleanup statistics */ + +static void dict_cache_clean_stat_log_reset(DICT_CACHE *cp, + const char *full_partial) +{ + if (cp->user_flags & DICT_CACHE_FLAG_STATISTICS) + msg_info("cache %s %s cleanup: retained=%d dropped=%d entries", + cp->name, full_partial, cp->retained, cp->dropped); + cp->retained = cp->dropped = 0; +} + +/* dict_cache_clean_event - examine one cache entry */ + +static void dict_cache_clean_event(int unused_event, void *cache_context) +{ + const char *myname = "dict_cache_clean_event"; + DICT_CACHE *cp = (DICT_CACHE *) cache_context; + const char *cache_key; + const char *cache_val; + int next_interval; + VSTRING *stamp_buf; + int first_next; + + /* + * We interleave cache cleanup with other processing, so that the + * application's service remains available, with perhaps increased + * latency. + */ + + /* + * Start a new cache cleanup run. + */ + if (cp->saved_curr_key == 0) { + cp->retained = cp->dropped = 0; + first_next = DICT_SEQ_FUN_FIRST; + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: start %s cache cleanup", myname, cp->name); + } + + /* + * Continue a cache cleanup run in progress. + */ + else { + first_next = DICT_SEQ_FUN_NEXT; + } + + /* + * Examine one cache entry. + */ + if (dict_cache_sequence(cp, first_next, &cache_key, &cache_val) == 0) { + if (cp->exp_validator(cache_key, cache_val, cp->exp_context) == 0) { + DC_SCHEDULE_FOR_DELETE_BEHIND(cp); + cp->dropped++; + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: drop %s cache entry for %s", + myname, cp->name, cache_key); + } else { + cp->retained++; + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: keep %s cache entry for %s", + myname, cp->name, cache_key); + } + next_interval = 0; + } + + /* + * Cache cleanup completed. Report vital statistics. + */ + else if (cp->error != 0) { + msg_warn("%s: cache cleanup scan terminated due to error", cp->name); + dict_cache_clean_stat_log_reset(cp, "partial"); + next_interval = cp->exp_interval; + } else { + if (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) + msg_info("%s: done %s cache cleanup scan", myname, cp->name); + dict_cache_clean_stat_log_reset(cp, "full"); + stamp_buf = vstring_alloc(100); + vstring_sprintf(stamp_buf, "%ld", (long) event_time()); + dict_put(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED, + vstring_str(stamp_buf)); + vstring_free(stamp_buf); + next_interval = cp->exp_interval; + } + event_request_timer(dict_cache_clean_event, cache_context, next_interval); +} + +/* dict_cache_control - schedule or stop the cache cleanup thread */ + +void dict_cache_control(DICT_CACHE *cp,...) +{ + const char *myname = "dict_cache_control"; + const char *last_done; + time_t next_interval; + int cache_cleanup_is_active = (cp->exp_validator && cp->exp_interval); + va_list ap; + int name; + + /* + * Update the control settings. + */ + va_start(ap, cp); + while ((name = va_arg(ap, int)) > 0) { + switch (name) { + case DICT_CACHE_CTL_END: + break; + case DICT_CACHE_CTL_FLAGS: + cp->user_flags = va_arg(ap, int); + cp->log_delay = (cp->user_flags & DICT_CACHE_FLAG_VERBOSE) ? + 0 : DC_DEF_LOG_DELAY; + break; + case DICT_CACHE_CTL_INTERVAL: + cp->exp_interval = va_arg(ap, int); + if (cp->exp_interval < 0) + msg_panic("%s: bad %s cache cleanup interval %d", + myname, cp->name, cp->exp_interval); + break; + case DICT_CACHE_CTL_VALIDATOR: + cp->exp_validator = va_arg(ap, DICT_CACHE_VALIDATOR_FN); + break; + case DICT_CACHE_CTL_CONTEXT: + cp->exp_context = va_arg(ap, void *); + break; + default: + msg_panic("%s: bad command: %d", myname, name); + } + } + va_end(ap); + + /* + * Schedule the cache cleanup thread. + */ + if (cp->exp_interval && cp->exp_validator) { + + /* + * Sanity checks. + */ + if (cache_cleanup_is_active) + msg_panic("%s: %s cache cleanup is already scheduled", + myname, cp->name); + + /* + * The next start time depends on the last completion time. + */ +#define NEXT_START(last, delta) ((delta) + (unsigned long) atol(last)) +#define NOW (time((time_t *) 0)) /* NOT: event_time() */ + + if ((last_done = dict_get(cp->db, DC_LAST_CACHE_CLEANUP_COMPLETED)) == 0 + || (next_interval = (NEXT_START(last_done, cp->exp_interval) - NOW)) < 0) + next_interval = 0; + if (next_interval > cp->exp_interval) + next_interval = cp->exp_interval; + if ((cp->user_flags & DICT_CACHE_FLAG_VERBOSE) && next_interval > 0) + msg_info("%s cache cleanup will start after %ds", + cp->name, (int) next_interval); + event_request_timer(dict_cache_clean_event, (void *) cp, + (int) next_interval); + } + + /* + * Cancel the cache cleanup thread. + */ + else if (cache_cleanup_is_active) { + if (cp->retained || cp->dropped) + dict_cache_clean_stat_log_reset(cp, "partial"); + dict_cache_delete_behind_reset(cp); + event_cancel_timer(dict_cache_clean_event, (void *) cp); + } +} + +/* dict_cache_open - open cache file */ + +DICT_CACHE *dict_cache_open(const char *dbname, int open_flags, int dict_flags) +{ + DICT_CACHE *cp; + DICT *dict; + + /* + * Open the database as requested. Don't attempt to second-guess the + * application. + */ + dict = dict_open(dbname, open_flags, dict_flags); + + /* + * Create the DICT_CACHE object. + */ + cp = (DICT_CACHE *) mymalloc(sizeof(*cp)); + cp->name = mystrdup(dbname); + cp->cache_flags = 0; + cp->user_flags = 0; + cp->db = dict; + cp->saved_curr_key = 0; + cp->saved_curr_val = 0; + cp->exp_interval = 0; + cp->exp_validator = 0; + cp->exp_context = 0; + cp->retained = 0; + cp->dropped = 0; + cp->log_delay = DC_DEF_LOG_DELAY; + cp->upd_log_stamp = cp->get_log_stamp = + cp->del_log_stamp = cp->seq_log_stamp = 0; + + return (cp); +} + +/* dict_cache_close - close cache file */ + +void dict_cache_close(DICT_CACHE *cp) +{ + + /* + * Destroy the DICT_CACHE object. + */ + dict_cache_control(cp, DICT_CACHE_CTL_INTERVAL, 0, DICT_CACHE_CTL_END); + myfree(cp->name); + dict_close(cp->db); + if (cp->saved_curr_key) + myfree(cp->saved_curr_key); + if (cp->saved_curr_val) + myfree(cp->saved_curr_val); + myfree((void *) cp); +} + +/* dict_cache_name - get the cache name */ + +const char *dict_cache_name(DICT_CACHE *cp) +{ + + /* + * This is used for verbose logging or warning messages, so the cost of + * call is only made where needed (well sort off - code that does not + * execute still presents overhead for the processor pipeline, processor + * cache, etc). + */ + return (cp->name); +} + + /* + * Test driver with support for interleaved access. First, enter a number of + * requests to look up, update or delete a sequence of cache entries, then + * interleave those sequences with the "run" command. + */ +#ifdef TEST +#include +#include +#include +#include + +#define DELIMS " " +#define USAGE "\n\tTo manage settings:" \ + "\n\tverbose (verbosity level)" \ + "\n\telapsed (0=don't show elapsed time)" \ + "\n\tlmdb_map_size (initial LMDB size limit)" \ + "\n\tcache : (switch to named database)" \ + "\n\tstatus (show map size, cache, pending requests)" \ + "\n\n\tTo manage pending requests:" \ + "\n\treset (discard pending requests)" \ + "\n\trun (execute pending requests in interleaved order)" \ + "\n\n\tTo add a pending request:" \ + "\n\tquery (negative to reverse order)" \ + "\n\tupdate (negative to reverse order)" \ + "\n\tdelete (negative to reverse order)" \ + "\n\tpurge " \ + "\n\tcount " + + /* + * For realism, open the cache with the same flags as postscreen(8) and + * verify(8). + */ +#define DICT_CACHE_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE | \ + DICT_FLAG_OPEN_LOCK) + + /* + * Storage for one request to access a sequence of cache entries. + */ +typedef struct DICT_CACHE_SREQ { + int flags; /* per-request: reverse, purge */ + char *cmd; /* command for status report */ + void (*action) (struct DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *); + char *suffix; /* key suffix */ + int done; /* progress indicator */ + int todo; /* number of entries to process */ + int first_next; /* first/next */ +} DICT_CACHE_SREQ; + +#define DICT_CACHE_SREQ_FLAG_PURGE (1<<1) /* purge instead of count */ +#define DICT_CACHE_SREQ_FLAG_REVERSE (1<<2) /* reverse instead of forward */ + +#define DICT_CACHE_SREQ_LIMIT 10 + + /* + * All test requests combined. + */ +typedef struct DICT_CACHE_TEST { + int flags; /* exclusion flags */ + int size; /* allocated slots */ + int used; /* used slots */ + DICT_CACHE_SREQ job_list[1]; /* actually, a bunch */ +} DICT_CACHE_TEST; + +#define DICT_CACHE_TEST_FLAG_ITER (1<<0) /* count or purge */ + +#define STR(x) vstring_str(x) + +int show_elapsed = 1; /* show elapsed time */ + +#ifdef HAS_LMDB +extern size_t dict_lmdb_map_size; /* LMDB-specific */ + +#endif + +/* usage - command-line usage message */ + +static NORETURN usage(const char *progname) +{ + msg_fatal("usage: %s (no argument)", progname); +} + +/* make_tagged_key - make tagged search key */ + +static void make_tagged_key(VSTRING *bp, DICT_CACHE_SREQ *cp) +{ + if (cp->done < 0) + msg_panic("make_tagged_key: bad done count: %d", cp->done); + if (cp->todo < 1) + msg_panic("make_tagged_key: bad todo count: %d", cp->todo); + vstring_sprintf(bp, "%d-%s", + (cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ? + cp->todo - cp->done - 1 : cp->done, cp->suffix); +} + +/* create_requests - create request list */ + +static DICT_CACHE_TEST *create_requests(int count) +{ + DICT_CACHE_TEST *tp; + DICT_CACHE_SREQ *cp; + + tp = (DICT_CACHE_TEST *) mymalloc(sizeof(DICT_CACHE_TEST) + + (count - 1) *sizeof(DICT_CACHE_SREQ)); + tp->flags = 0; + tp->size = count; + tp->used = 0; + for (cp = tp->job_list; cp < tp->job_list + count; cp++) { + cp->flags = 0; + cp->cmd = 0; + cp->action = 0; + cp->suffix = 0; + cp->todo = 0; + cp->first_next = DICT_SEQ_FUN_FIRST; + } + return (tp); +} + +/* reset_requests - reset request list */ + +static void reset_requests(DICT_CACHE_TEST *tp) +{ + DICT_CACHE_SREQ *cp; + + tp->flags = 0; + tp->used = 0; + for (cp = tp->job_list; cp < tp->job_list + tp->size; cp++) { + cp->flags = 0; + if (cp->cmd) { + myfree(cp->cmd); + cp->cmd = 0; + } + cp->action = 0; + if (cp->suffix) { + myfree(cp->suffix); + cp->suffix = 0; + } + cp->todo = 0; + cp->first_next = DICT_SEQ_FUN_FIRST; + } +} + +/* free_requests - destroy request list */ + +static void free_requests(DICT_CACHE_TEST *tp) +{ + reset_requests(tp); + myfree((void *) tp); +} + +/* run_requests - execute pending requests in interleaved order */ + +static void run_requests(DICT_CACHE_TEST *tp, DICT_CACHE *dp, VSTRING *bp) +{ + DICT_CACHE_SREQ *cp; + int todo; + struct timeval start; + struct timeval finish; + struct timeval elapsed; + + if (dp == 0) { + msg_warn("no cache"); + return; + } + GETTIMEOFDAY(&start); + do { + todo = 0; + for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++) { + if (cp->done < cp->todo) { + todo = 1; + cp->action(cp, dp, bp); + } + } + } while (todo); + GETTIMEOFDAY(&finish); + timersub(&finish, &start, &elapsed); + if (show_elapsed) + vstream_printf("Elapsed: %g\n", + elapsed.tv_sec + elapsed.tv_usec / 1000000.0); + + reset_requests(tp); +} + +/* show_status - show settings and pending requests */ + +static void show_status(DICT_CACHE_TEST *tp, DICT_CACHE *dp) +{ + DICT_CACHE_SREQ *cp; + +#ifdef HAS_LMDB + vstream_printf("lmdb_map_size\t%ld\n", (long) dict_lmdb_map_size); +#endif + vstream_printf("cache\t%s\n", dp ? dp->name : "(none)"); + + if (tp->used == 0) + vstream_printf("No pending requests\n"); + else + vstream_printf("%s\t%s\t%s\t%s\t%s\t%s\n", + "cmd", "dir", "suffix", "count", "done", "first/next"); + + for (cp = tp->job_list; cp < tp->job_list + tp->used; cp++) + if (cp->todo > 0) + vstream_printf("%s\t%s\t%s\t%d\t%d\t%d\n", + cp->cmd, + (cp->flags & DICT_CACHE_SREQ_FLAG_REVERSE) ? + "reverse" : "forward", + cp->suffix ? cp->suffix : "(null)", cp->todo, + cp->done, cp->first_next); +} + +/* query_action - lookup cache entry */ + +static void query_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp) +{ + const char *lookup; + + make_tagged_key(bp, cp); + if ((lookup = dict_cache_lookup(dp, STR(bp))) == 0) { + if (dp->error) + msg_warn("query_action: query failed: %s: %m", STR(bp)); + else + msg_warn("query_action: query failed: %s", STR(bp)); + } else if (strcmp(STR(bp), lookup) != 0) { + msg_warn("lookup result \"%s\" differs from key \"%s\"", + lookup, STR(bp)); + } + cp->done += 1; +} + +/* update_action - update cache entry */ + +static void update_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp) +{ + make_tagged_key(bp, cp); + if (dict_cache_update(dp, STR(bp), STR(bp)) != 0) { + if (dp->error) + msg_warn("update_action: update failed: %s: %m", STR(bp)); + else + msg_warn("update_action: update failed: %s", STR(bp)); + } + cp->done += 1; +} + +/* delete_action - delete cache entry */ + +static void delete_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp) +{ + make_tagged_key(bp, cp); + if (dict_cache_delete(dp, STR(bp)) != 0) { + if (dp->error) + msg_warn("delete_action: delete failed: %s: %m", STR(bp)); + else + msg_warn("delete_action: delete failed: %s", STR(bp)); + } + cp->done += 1; +} + +/* iter_action - iterate over cache and act on entries with given suffix */ + +static void iter_action(DICT_CACHE_SREQ *cp, DICT_CACHE *dp, VSTRING *bp) +{ + const char *cache_key; + const char *cache_val; + const char *what; + const char *suffix; + + if (dict_cache_sequence(dp, cp->first_next, &cache_key, &cache_val) == 0) { + if (strcmp(cache_key, cache_val) != 0) + msg_warn("value \"%s\" differs from key \"%s\"", + cache_val, cache_key); + suffix = cache_key + strspn(cache_key, "0123456789"); + if (suffix[0] == '-' && strcmp(suffix + 1, cp->suffix) == 0) { + cp->done += 1; + cp->todo = cp->done + 1; /* XXX */ + if ((cp->flags & DICT_CACHE_SREQ_FLAG_PURGE) + && dict_cache_delete(dp, cache_key) != 0) { + if (dp->error) + msg_warn("purge_action: delete failed: %s: %m", STR(bp)); + else + msg_warn("purge_action: delete failed: %s", STR(bp)); + } + } + cp->first_next = DICT_SEQ_FUN_NEXT; + } else { + what = (cp->flags & DICT_CACHE_SREQ_FLAG_PURGE) ? "purge" : "count"; + if (dp->error) + msg_warn("%s error after %d: %m", what, cp->done); + else + vstream_printf("suffix=%s %s=%d\n", cp->suffix, what, cp->done); + cp->todo = 0; + } +} + + /* + * Table-driven support. + */ +typedef struct DICT_CACHE_SREQ_INFO { + const char *name; + int argc; + void (*action) (DICT_CACHE_SREQ *, DICT_CACHE *, VSTRING *); + int test_flags; + int req_flags; +} DICT_CACHE_SREQ_INFO; + +static DICT_CACHE_SREQ_INFO req_info[] = { + {"query", 3, query_action}, + {"update", 3, update_action}, + {"delete", 3, delete_action}, + {"count", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER}, + {"purge", 2, iter_action, DICT_CACHE_TEST_FLAG_ITER, DICT_CACHE_SREQ_FLAG_PURGE}, + 0, +}; + +/* add_request - add a request to the list */ + +static void add_request(DICT_CACHE_TEST *tp, ARGV *argv) +{ + DICT_CACHE_SREQ_INFO *rp; + DICT_CACHE_SREQ *cp; + int req_flags; + int count; + char *cmd = argv->argv[0]; + char *suffix = (argv->argc > 1 ? argv->argv[1] : 0); + char *todo = (argv->argc > 2 ? argv->argv[2] : "1"); /* XXX */ + + if (tp->used >= tp->size) { + msg_warn("%s: request list is full", cmd); + return; + } + for (rp = req_info; /* See below */ ; rp++) { + if (rp->name == 0) { + vstream_printf("usage: %s\n", USAGE); + return; + } + if (strcmp(rp->name, argv->argv[0]) == 0 + && rp->argc == argv->argc) + break; + } + req_flags = rp->req_flags; + if (todo[0] == '-') { + req_flags |= DICT_CACHE_SREQ_FLAG_REVERSE; + todo += 1; + } + if (!alldig(todo) || (count = atoi(todo)) == 0) { + msg_warn("%s: bad count: %s", cmd, todo); + return; + } + if (tp->flags & rp->test_flags) { + msg_warn("%s: command conflicts with other command", cmd); + return; + } + tp->flags |= rp->test_flags; + cp = tp->job_list + tp->used; + cp->cmd = mystrdup(cmd); + cp->action = rp->action; + if (suffix) + cp->suffix = mystrdup(suffix); + cp->done = 0; + cp->flags = req_flags; + cp->todo = count; + tp->used += 1; +} + +/* main - main program */ + +int main(int argc, char **argv) +{ + DICT_CACHE_TEST *test_job; + VSTRING *inbuf = vstring_alloc(100); + char *bufp; + ARGV *args; + DICT_CACHE *cache = 0; + int stdin_is_tty; + + msg_vstream_init(argv[0], VSTREAM_ERR); + if (argc != 1) + usage(argv[0]); + + + test_job = create_requests(DICT_CACHE_SREQ_LIMIT); + + stdin_is_tty = isatty(0); + + for (;;) { + if (stdin_is_tty) { + vstream_printf("> "); + vstream_fflush(VSTREAM_OUT); + } + if (vstring_fgets_nonl(inbuf, VSTREAM_IN) == 0) + break; + bufp = vstring_str(inbuf); + if (!stdin_is_tty) { + vstream_printf("> %s\n", bufp); + vstream_fflush(VSTREAM_OUT); + } + if (*bufp == '#') + continue; + args = argv_split(bufp, DELIMS); + if (argc == 0) { + vstream_printf("usage: %s\n", USAGE); + vstream_fflush(VSTREAM_OUT); + continue; + } + if (strcmp(args->argv[0], "verbose") == 0 && args->argc == 2) { + msg_verbose = atoi(args->argv[1]); + } else if (strcmp(args->argv[0], "elapsed") == 0 && args->argc == 2) { + show_elapsed = atoi(args->argv[1]); +#ifdef HAS_LMDB + } else if (strcmp(args->argv[0], "lmdb_map_size") == 0 && args->argc == 2) { + dict_lmdb_map_size = atol(args->argv[1]); +#endif + } else if (strcmp(args->argv[0], "cache") == 0 && args->argc == 2) { + if (cache) + dict_cache_close(cache); + cache = dict_cache_open(args->argv[1], O_CREAT | O_RDWR, + DICT_CACHE_OPEN_FLAGS); + } else if (strcmp(args->argv[0], "reset") == 0 && args->argc == 1) { + reset_requests(test_job); + } else if (strcmp(args->argv[0], "run") == 0 && args->argc == 1) { + run_requests(test_job, cache, inbuf); + } else if (strcmp(args->argv[0], "status") == 0 && args->argc == 1) { + show_status(test_job, cache); + } else { + add_request(test_job, args); + } + vstream_fflush(VSTREAM_OUT); + argv_free(args); + } + + vstring_free(inbuf); + free_requests(test_job); + if (cache) + dict_cache_close(cache); + return (0); +} + +#endif diff --git a/src/util/dict_cache.h b/src/util/dict_cache.h new file mode 100644 index 0000000..2d403b5 --- /dev/null +++ b/src/util/dict_cache.h @@ -0,0 +1,67 @@ +#ifndef _DICT_CACHE_H_INCLUDED_ +#define _DICT_CACHE_H_INCLUDED_ + +/*++ +/* NAME +/* dict_cache 3h +/* SUMMARY +/* External cache manager +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * External interface. + */ +typedef struct DICT_CACHE DICT_CACHE; +typedef int (*DICT_CACHE_VALIDATOR_FN) (const char *, const char *, void *); + +extern DICT_CACHE *dict_cache_open(const char *, int, int); +extern void dict_cache_close(DICT_CACHE *); +extern const char *dict_cache_lookup(DICT_CACHE *, const char *); +extern int dict_cache_update(DICT_CACHE *, const char *, const char *); +extern int dict_cache_delete(DICT_CACHE *, const char *); +extern int dict_cache_sequence(DICT_CACHE *, int, const char **, const char **); +extern void dict_cache_control(DICT_CACHE *,...); +extern const char *dict_cache_name(DICT_CACHE *); + +#define DICT_CACHE_FLAG_VERBOSE (1<<0) /* verbose operation */ +#define DICT_CACHE_FLAG_STATISTICS (1<<1) /* log cache statistics */ + +/* Legacy API: type-unchecked argument, internal use. */ +#define DICT_CACHE_CTL_END 0 /* list terminator */ +#define DICT_CACHE_CTL_FLAGS 1 /* see above */ +#define DICT_CACHE_CTL_INTERVAL 2 /* cleanup interval */ +#define DICT_CACHE_CTL_VALIDATOR 3 /* call-back validator */ +#define DICT_CACHE_CTL_CONTEXT 4 /* call-back context */ + +/* Safer API: type-checked arguments, external use. */ +#define CA_DICT_CACHE_CTL_END DICT_CACHE_CTL_END +#define CA_DICT_CACHE_CTL_FLAGS(v) DICT_CACHE_CTL_FLAGS, CHECK_VAL(DICT_CACHE, int, (v)) +#define CA_DICT_CACHE_CTL_INTERVAL(v) DICT_CACHE_CTL_INTERVAL, CHECK_VAL(DICT_CACHE, int, (v)) +#define CA_DICT_CACHE_CTL_VALIDATOR(v) DICT_CACHE_CTL_VALIDATOR, CHECK_VAL(DICT_CACHE, DICT_CACHE_VALIDATOR_FN, (v)) +#define CA_DICT_CACHE_CTL_CONTEXT(v) DICT_CACHE_CTL_CONTEXT, CHECK_PTR(DICT_CACHE, void, (v)) + +CHECK_VAL_HELPER_DCL(DICT_CACHE, int); +CHECK_VAL_HELPER_DCL(DICT_CACHE, DICT_CACHE_VALIDATOR_FN); +CHECK_PTR_HELPER_DCL(DICT_CACHE, void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_cdb.c b/src/util/dict_cdb.c new file mode 100644 index 0000000..a9133cc --- /dev/null +++ b/src/util/dict_cdb.c @@ -0,0 +1,446 @@ +/*++ +/* NAME +/* dict_cdb 3 +/* SUMMARY +/* dictionary manager interface to CDB files +/* SYNOPSIS +/* #include +/* +/* DICT *dict_cdb_open(path, open_flags, dict_flags) +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* +/* DESCRIPTION +/* dict_cdb_open() opens the specified CDB database. The result is +/* a pointer to a structure that can be used to access the dictionary +/* using the generic methods documented in dict_open(3). +/* +/* Arguments: +/* .IP path +/* The database pathname, not including the ".cdb" suffix. +/* .IP open_flags +/* Flags passed to open(). Specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC. +/* .IP dict_flags +/* Flags used by the dictionary interface. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* DIAGNOSTICS +/* Fatal errors: cannot open file, write error, out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Michael Tokarev based on dict_db.c by +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include "sys_defs.h" + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "vstring.h" +#include "stringops.h" +#include "iostuff.h" +#include "myflock.h" +#include "stringops.h" +#include "dict.h" +#include "dict_cdb.h" +#include "warn_stat.h" + +#ifdef HAS_CDB + +#include +#ifndef TINYCDB_VERSION +#include +#endif +#ifndef cdb_fileno +#define cdb_fileno(c) ((c)->fd) +#endif + +#ifndef CDB_SUFFIX +#define CDB_SUFFIX ".cdb" +#endif +#ifndef CDB_TMP_SUFFIX +#define CDB_TMP_SUFFIX CDB_SUFFIX ".tmp" +#endif + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + struct cdb cdb; /* cdb structure */ +} DICT_CDBQ; /* query interface */ + +typedef struct { + DICT dict; /* generic members */ + struct cdb_make cdbm; /* cdb_make structure */ + char *cdb_path; /* cdb pathname (.cdb) */ + char *tmp_path; /* temporary pathname (.tmp) */ +} DICT_CDBM; /* rebuild interface */ + +/* dict_cdbq_lookup - find database entry, query mode */ + +static const char *dict_cdbq_lookup(DICT *dict, const char *name) +{ + DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict; + unsigned vlen; + int status = 0; + static char *buf; + static unsigned len; + const char *result = 0; + + dict->error = 0; + + /* CDB is constant, so do not try to acquire a lock. */ + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * See if this CDB file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + status = cdb_find(&dict_cdbq->cdb, name, strlen(name) + 1); + if (status > 0) + dict->flags &= ~DICT_FLAG_TRY0NULL; + } + + /* + * See if this CDB file was written with no null byte appended to key and + * value. + */ + if (status == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + status = cdb_find(&dict_cdbq->cdb, name, strlen(name)); + if (status > 0) + dict->flags &= ~DICT_FLAG_TRY1NULL; + } + if (status < 0) + msg_fatal("error reading %s: %m", dict->name); + + if (status) { + vlen = cdb_datalen(&dict_cdbq->cdb); + if (len < vlen) { + if (buf == 0) + buf = mymalloc(vlen + 1); + else + buf = myrealloc(buf, vlen + 1); + len = vlen; + } + if (cdb_read(&dict_cdbq->cdb, buf, vlen, + cdb_datapos(&dict_cdbq->cdb)) < 0) + msg_fatal("error reading %s: %m", dict->name); + buf[vlen] = '\0'; + result = buf; + } + /* No locking so not release the lock. */ + + return (result); +} + +/* dict_cdbq_close - close data base, query mode */ + +static void dict_cdbq_close(DICT *dict) +{ + DICT_CDBQ *dict_cdbq = (DICT_CDBQ *) dict; + + cdb_free(&dict_cdbq->cdb); + close(dict->stat_fd); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_cdbq_open - open data base, query mode */ + +static DICT *dict_cdbq_open(const char *path, int dict_flags) +{ + DICT_CDBQ *dict_cdbq; + struct stat st; + char *cdb_path; + int fd; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_CDBQ_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + myfree(cdb_path); \ + return (__d); \ + } while (0) + + cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0); + + if ((fd = open(cdb_path, O_RDONLY)) < 0) + DICT_CDBQ_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path, + O_RDONLY, dict_flags, + "open database %s: %m", cdb_path)); + + dict_cdbq = (DICT_CDBQ *) dict_alloc(DICT_TYPE_CDB, + cdb_path, sizeof(*dict_cdbq)); +#if defined(TINYCDB_VERSION) + if (cdb_init(&(dict_cdbq->cdb), fd) != 0) + msg_fatal("dict_cdbq_open: unable to init %s: %m", cdb_path); +#else + cdb_init(&(dict_cdbq->cdb), fd); +#endif + dict_cdbq->dict.lookup = dict_cdbq_lookup; + dict_cdbq->dict.close = dict_cdbq_close; + dict_cdbq->dict.stat_fd = fd; + if (fstat(fd, &st) < 0) + msg_fatal("dict_dbq_open: fstat: %m"); + dict_cdbq->dict.mtime = st.st_mtime; + dict_cdbq->dict.owner.uid = st.st_uid; + dict_cdbq->dict.owner.status = (st.st_uid != 0); + close_on_exec(fd, CLOSE_ON_EXEC); + + /* + * Warn if the source file is newer than the indexed file, except when + * the source file changed only seconds ago. + */ + if (stat(path, &st) == 0 + && st.st_mtime > dict_cdbq->dict.mtime + && st.st_mtime < time((time_t *) 0) - 100) + msg_warn("database %s is older than source file %s", cdb_path, path); + + /* + * If undecided about appending a null byte to key and value, choose to + * try both in query mode. + */ + if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + dict_flags |= DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL; + dict_cdbq->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_cdbq->dict.fold_buf = vstring_alloc(10); + + DICT_CDBQ_OPEN_RETURN(DICT_DEBUG (&dict_cdbq->dict)); +} + +/* dict_cdbm_update - add database entry, create mode */ + +static int dict_cdbm_update(DICT *dict, const char *name, const char *value) +{ + DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict; + unsigned ksize, vsize; + int r; + + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + ksize = strlen(name); + vsize = strlen(value); + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + ksize++; + vsize++; + } + + /* + * Do the add operation. No locking is done. + */ +#ifdef TINYCDB_VERSION +#ifndef CDB_PUT_ADD +#error please upgrate tinycdb to at least 0.5 version +#endif + if (dict->flags & DICT_FLAG_DUP_IGNORE) + r = CDB_PUT_ADD; + else if (dict->flags & DICT_FLAG_DUP_REPLACE) + r = CDB_PUT_REPLACE; + else + r = CDB_PUT_INSERT; + r = cdb_make_put(&dict_cdbm->cdbm, name, ksize, value, vsize, r); + if (r < 0) + msg_fatal("error writing %s: %m", dict_cdbm->tmp_path); + else if (r > 0) { + if (dict->flags & (DICT_FLAG_DUP_IGNORE | DICT_FLAG_DUP_REPLACE)) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s: duplicate entry: \"%s\"", + dict_cdbm->dict.name, name); + else + msg_fatal("%s: duplicate entry: \"%s\"", + dict_cdbm->dict.name, name); + } + return (r); +#else + if (cdb_make_add(&dict_cdbm->cdbm, name, ksize, value, vsize) < 0) + msg_fatal("error writing %s: %m", dict_cdbm->tmp_path); + return (0); +#endif +} + +/* dict_cdbm_close - close data base and rename file.tmp to file.cdb */ + +static void dict_cdbm_close(DICT *dict) +{ + DICT_CDBM *dict_cdbm = (DICT_CDBM *) dict; + int fd = cdb_fileno(&dict_cdbm->cdbm); + + /* + * Note: if FCNTL locking is used, closing any file descriptor on a + * locked file cancels all locks that the process may have on that file. + * CDB is FCNTL locking safe, because it uses the same file descriptor + * for database I/O and locking. + */ + if (cdb_make_finish(&dict_cdbm->cdbm) < 0) + msg_fatal("finish database %s: %m", dict_cdbm->tmp_path); + if (rename(dict_cdbm->tmp_path, dict_cdbm->cdb_path) < 0) + msg_fatal("rename database from %s to %s: %m", + dict_cdbm->tmp_path, dict_cdbm->cdb_path); + if (close(fd) < 0) /* releases a lock */ + msg_fatal("close database %s: %m", dict_cdbm->cdb_path); + myfree(dict_cdbm->cdb_path); + myfree(dict_cdbm->tmp_path); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_cdbm_open - create database as file.tmp */ + +static DICT *dict_cdbm_open(const char *path, int dict_flags) +{ + DICT_CDBM *dict_cdbm; + char *cdb_path; + char *tmp_path; + int fd; + struct stat st0, st1; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_CDBM_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (cdb_path) \ + myfree(cdb_path); \ + if (tmp_path) \ + myfree(tmp_path); \ + return (__d); \ + } while (0) + + cdb_path = concatenate(path, CDB_SUFFIX, (char *) 0); + tmp_path = concatenate(path, CDB_TMP_SUFFIX, (char *) 0); + + /* + * Repeat until we have opened *and* locked *existing* file. Since the + * new (tmp) file will be renamed to be .cdb file, locking here is + * somewhat funny to work around possible race conditions. Note that we + * can't open a file with O_TRUNC as we can't know if another process + * isn't creating it at the same time. + */ + for (;;) { + if ((fd = open(tmp_path, O_RDWR | O_CREAT, 0644)) < 0) + DICT_CDBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_CDB, path, + O_RDWR, dict_flags, + "open database %s: %m", + tmp_path)); + if (fstat(fd, &st0) < 0) + msg_fatal("fstat(%s): %m", tmp_path); + + /* + * Get an exclusive lock - we're going to change the database so we + * can't have any spectators. + */ + if (myflock(fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("lock %s: %m", tmp_path); + + if (stat(tmp_path, &st1) < 0) + msg_fatal("stat(%s): %m", tmp_path); + + /* + * Compare file's state before and after lock: should be the same, + * and nlinks should be >0, or else we opened non-existing file... + */ + if (st0.st_ino == st1.st_ino && st0.st_dev == st1.st_dev + && st0.st_rdev == st1.st_rdev && st0.st_nlink == st1.st_nlink + && st0.st_nlink > 0) + break; /* successfully opened */ + + close(fd); + + } + +#ifndef NO_FTRUNCATE + if (st0.st_size) + ftruncate(fd, 0); +#endif + + dict_cdbm = (DICT_CDBM *) dict_alloc(DICT_TYPE_CDB, path, + sizeof(*dict_cdbm)); + if (cdb_make_start(&dict_cdbm->cdbm, fd) < 0) + msg_fatal("initialize database %s: %m", tmp_path); + dict_cdbm->dict.close = dict_cdbm_close; + dict_cdbm->dict.update = dict_cdbm_update; + dict_cdbm->cdb_path = cdb_path; + dict_cdbm->tmp_path = tmp_path; + cdb_path = tmp_path = 0; /* DICT_CDBM_OPEN_RETURN() */ + dict_cdbm->dict.owner.uid = st1.st_uid; + dict_cdbm->dict.owner.status = (st1.st_uid != 0); + close_on_exec(fd, CLOSE_ON_EXEC); + + /* + * If undecided about appending a null byte to key and value, choose a + * default to not append a null byte when creating a cdb. + */ + if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + dict_flags |= DICT_FLAG_TRY0NULL; + else if ((dict_flags & DICT_FLAG_TRY1NULL) + && (dict_flags & DICT_FLAG_TRY0NULL)) + dict_flags &= ~DICT_FLAG_TRY0NULL; + dict_cdbm->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_cdbm->dict.fold_buf = vstring_alloc(10); + + DICT_CDBM_OPEN_RETURN(DICT_DEBUG (&dict_cdbm->dict)); +} + +/* dict_cdb_open - open data base for query mode or create mode */ + +DICT *dict_cdb_open(const char *path, int open_flags, int dict_flags) +{ + switch (open_flags & (O_RDONLY | O_RDWR | O_WRONLY | O_CREAT | O_TRUNC)) { + case O_RDONLY: /* query mode */ + return dict_cdbq_open(path, dict_flags); + case O_WRONLY | O_CREAT | O_TRUNC: /* create mode */ + case O_RDWR | O_CREAT | O_TRUNC: /* sloppiness */ + return dict_cdbm_open(path, dict_flags); + default: + msg_fatal("dict_cdb_open: inappropriate open flags for cdb database" + " - specify O_RDONLY or O_WRONLY|O_CREAT|O_TRUNC"); + } +} + +#endif /* HAS_CDB */ diff --git a/src/util/dict_cdb.h b/src/util/dict_cdb.h new file mode 100644 index 0000000..e2c13d4 --- /dev/null +++ b/src/util/dict_cdb.h @@ -0,0 +1,37 @@ +#ifndef _DICT_CDB_H_INCLUDED_ +#define _DICT_CDB_H_INCLUDED_ + +/*++ +/* NAME +/* dict_cdb 3h +/* SUMMARY +/* dictionary manager interface to CDB files +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_CDB "cdb" + +extern DICT *dict_cdb_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif /* _DICT_CDB_H_INCLUDED_ */ diff --git a/src/util/dict_cidr.c b/src/util/dict_cidr.c new file mode 100644 index 0000000..d29a2ba --- /dev/null +++ b/src/util/dict_cidr.c @@ -0,0 +1,361 @@ +/*++ +/* NAME +/* dict_cidr 3 +/* SUMMARY +/* Dictionary interface for CIDR data +/* SYNOPSIS +/* #include +/* +/* DICT *dict_cidr_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_cidr_open() opens the named file and stores +/* the key/value pairs where the key must be either a +/* "naked" IP address or a netblock in CIDR notation. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* AUTHOR(S) +/* Jozsef Kadlecsik +/* kadlec@blackhole.kfki.hu +/* KFKI Research Institute for Particle and Nuclear Physics +/* POB. 49 +/* 1525 Budapest, Hungary +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * Each rule in a CIDR table is parsed and stored in a linked list. + */ +typedef struct DICT_CIDR_ENTRY { + CIDR_MATCH cidr_info; /* must be first */ + char *value; /* lookup result */ + int lineno; +} DICT_CIDR_ENTRY; + +typedef struct { + DICT dict; /* generic members */ + DICT_CIDR_ENTRY *head; /* first entry */ +} DICT_CIDR; + +/* dict_cidr_lookup - CIDR table lookup */ + +static const char *dict_cidr_lookup(DICT *dict, const char *key) +{ + DICT_CIDR *dict_cidr = (DICT_CIDR *) dict; + DICT_CIDR_ENTRY *entry; + + if (msg_verbose) + msg_info("dict_cidr_lookup: %s: %s", dict->name, key); + + dict->error = 0; + + if ((entry = (DICT_CIDR_ENTRY *) + cidr_match_execute(&(dict_cidr->head->cidr_info), key)) != 0) + return (entry->value); + return (0); +} + +/* dict_cidr_close - close the CIDR table */ + +static void dict_cidr_close(DICT *dict) +{ + DICT_CIDR *dict_cidr = (DICT_CIDR *) dict; + DICT_CIDR_ENTRY *entry; + DICT_CIDR_ENTRY *next; + + for (entry = dict_cidr->head; entry; entry = next) { + next = (DICT_CIDR_ENTRY *) entry->cidr_info.next; + myfree(entry->value); + myfree((void *) entry); + } + dict_free(dict); +} + +/* dict_cidr_parse_rule - parse CIDR table rule into network, mask and value */ + +static DICT_CIDR_ENTRY *dict_cidr_parse_rule(DICT *dict, char *p, int lineno, + int nesting, VSTRING *why) +{ + DICT_CIDR_ENTRY *rule; + char *pattern; + char *value; + CIDR_MATCH cidr_info; + MAI_HOSTADDR_STR hostaddr; + int match = 1; + + /* + * IF must be followed by a pattern. + */ + if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) { + p += 2; + for (;;) { + if (*p == '!') + match = !match; + else if (!ISSPACE(*p)) + break; + p++; + } + if (*p == 0) { + vstring_sprintf(why, "no address pattern"); + return (0); + } + trimblanks(p, 0)[0] = 0; /* Trim trailing blanks */ + if (cidr_match_parse_if(&cidr_info, p, match, why) != 0) + return (0); + value = ""; + } + + /* + * ENDIF must not be followed by other text. + */ + else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) { + p += 5; + while (*p && ISSPACE(*p)) /* Skip whitespace */ + p++; + if (*p != 0) { + vstring_sprintf(why, "garbage after ENDIF"); + return (0); + } + if (nesting == 0) { + vstring_sprintf(why, "ENDIF without IF"); + return (0); + } + cidr_match_endif(&cidr_info); + value = ""; + } + + /* + * An address pattern. + */ + else { + + /* + * Process negation operators. + */ + for (;;) { + if (*p == '!') + match = !match; + else if (!ISSPACE(*p)) + break; + p++; + } + + /* + * Split the rule into key and value. We already eliminated leading + * whitespace, comments, empty lines or lines with whitespace only. + * This means a null key can't happen but we will handle this anyway. + */ + pattern = p; + while (*p && !ISSPACE(*p)) /* Skip over key */ + p++; + if (*p) /* Terminate key */ + *p++ = 0; + while (*p && ISSPACE(*p)) /* Skip whitespace */ + p++; + value = p; + trimblanks(value, 0)[0] = 0; /* Trim trailing blanks */ + if (*pattern == 0) { + vstring_sprintf(why, "no address pattern"); + return (0); + } + + /* + * Parse the pattern, destroying it in the process. + */ + if (cidr_match_parse(&cidr_info, pattern, match, why) != 0) + return (0); + + if (*value == 0) { + vstring_sprintf(why, "no lookup result"); + return (0); + } + } + + /* + * Optionally replace the value file the contents of a file. + */ + if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) { + VSTRING *base64_buf; + char *err; + + if ((base64_buf = dict_file_to_b64(dict, value)) == 0) { + err = dict_file_get_error(dict); + vstring_strcpy(why, err); + myfree(err); + return (0); + } + value = vstring_str(base64_buf); + } + + /* + * Bundle up the result. + */ + rule = (DICT_CIDR_ENTRY *) mymalloc(sizeof(DICT_CIDR_ENTRY)); + rule->cidr_info = cidr_info; + rule->value = mystrdup(value); + rule->lineno = lineno; + + if (msg_verbose) { + if (inet_ntop(cidr_info.addr_family, cidr_info.net_bytes, + hostaddr.buf, sizeof(hostaddr.buf)) == 0) + msg_fatal("inet_ntop: %m"); + msg_info("dict_cidr_open: add %s/%d %s", + hostaddr.buf, cidr_info.mask_shift, rule->value); + } + return (rule); +} + +/* dict_cidr_open - parse CIDR table */ + +DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags) +{ + const char myname[] = "dict_cidr_open"; + DICT_CIDR *dict_cidr; + VSTREAM *map_fp = 0; + struct stat st; + VSTRING *line_buffer = 0; + VSTRING *why = 0; + DICT_CIDR_ENTRY *rule; + DICT_CIDR_ENTRY *last_rule = 0; + int last_line = 0; + int lineno; + int nesting = 0; + DICT_CIDR_ENTRY **rule_stack = 0; + MVECT mvect; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_CIDR_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (map_fp != 0 && vstream_fclose(map_fp)) \ + msg_fatal("cidr map %s: read error: %m", mapname); \ + if (line_buffer != 0) \ + vstring_free(line_buffer); \ + if (why != 0) \ + vstring_free(why); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_CIDR, mapname)); + + /* + * Open the configuration file. + */ + if ((map_fp = dict_stream_open(DICT_TYPE_CIDR, mapname, O_RDONLY, + dict_flags, &st, &why)) == 0) + DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname, + open_flags, dict_flags, + "%s", vstring_str(why))); + line_buffer = vstring_alloc(100); + why = vstring_alloc(100); + + /* + * XXX Eliminate unnecessary queries by setting a flag that says "this + * map matches network addresses only". + */ + dict_cidr = (DICT_CIDR *) dict_alloc(DICT_TYPE_CIDR, mapname, + sizeof(*dict_cidr)); + dict_cidr->dict.lookup = dict_cidr_lookup; + dict_cidr->dict.close = dict_cidr_close; + dict_cidr->dict.flags = dict_flags | DICT_FLAG_PATTERN; + dict_cidr->head = 0; + + dict_cidr->dict.owner.uid = st.st_uid; + dict_cidr->dict.owner.status = (st.st_uid != 0); + + while (readllines(line_buffer, map_fp, &last_line, &lineno)) { + rule = dict_cidr_parse_rule(&dict_cidr->dict, + vstring_str(line_buffer), lineno, + nesting, why); + if (rule == 0) { + msg_warn("cidr map %s, line %d: %s: skipping this rule", + mapname, lineno, vstring_str(why)); + continue; + } + if (rule->cidr_info.op == CIDR_MATCH_OP_IF) { + if (rule_stack == 0) + rule_stack = (DICT_CIDR_ENTRY **) mvect_alloc(&mvect, + sizeof(*rule_stack), nesting + 1, + (MVECT_FN) 0, (MVECT_FN) 0); + else + rule_stack = + (DICT_CIDR_ENTRY **) mvect_realloc(&mvect, nesting + 1); + rule_stack[nesting] = rule; + nesting++; + } else if (rule->cidr_info.op == CIDR_MATCH_OP_ENDIF) { + DICT_CIDR_ENTRY *if_rule; + + if (nesting-- <= 0) + /* Already handled in dict_cidr_parse_rule(). */ + msg_panic("%s: ENDIF without IF", myname); + if_rule = rule_stack[nesting]; + if (if_rule->cidr_info.op != CIDR_MATCH_OP_IF) + msg_panic("%s: unexpected rule stack element type %d", + myname, if_rule->cidr_info.op); + if_rule->cidr_info.block_end = &(rule->cidr_info); + } + if (last_rule == 0) + dict_cidr->head = rule; + else + last_rule->cidr_info.next = &(rule->cidr_info); + last_rule = rule; + } + + while (nesting-- > 0) + msg_warn("cidr map %s, line %d: IF has no matching ENDIF", + mapname, rule_stack[nesting]->lineno); + + if (rule_stack) + (void) mvect_free(&mvect); + + dict_file_purge_buffers(&dict_cidr->dict); + DICT_CIDR_OPEN_RETURN(DICT_DEBUG (&dict_cidr->dict)); +} diff --git a/src/util/dict_cidr.h b/src/util/dict_cidr.h new file mode 100644 index 0000000..308a765 --- /dev/null +++ b/src/util/dict_cidr.h @@ -0,0 +1,43 @@ +#ifndef _DICT_CIDR_H_INCLUDED_ +#define _DICT_CIDR_H_INCLUDED_ + +/*++ +/* NAME +/* dict_cidr 3h +/* SUMMARY +/* Dictionary manager interface to handle cidr data. +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern DICT *dict_cidr_open(const char *, int, int); + +#define DICT_TYPE_CIDR "cidr" + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Jozsef Kadlecsik +/* kadlec@blackhole.kfki.hu +/* KFKI Research Institute for Particle and Nuclear Physics +/* POB. 49 +/* 1525 Budapest 114, Hungary +/*--*/ + +#endif diff --git a/src/util/dict_cidr.in b/src/util/dict_cidr.in new file mode 100644 index 0000000..ee20c17 --- /dev/null +++ b/src/util/dict_cidr.in @@ -0,0 +1,23 @@ +get 172.16.0.0 +get 172.16.0.1 +get 172.16.7.255 +get 172.16.8.1 +get 172.16.17.1 +get 172.17.1.1 +get 172.17.1.2 +get 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 +get 2001:240:5c7:0:2d0:b7ff:febe:ca9f +get 1.1.1.1 +get 1:1:1:1:1:1:1:1 +get 1.2.3.3 +get 1.2.3.4 +get 1.2.3.5 +get 1.2.3.6 +get 1.2.3.7 +get 1.2.3.8 +get ::f3 +get ::f4 +get ::f5 +get ::f6 +get ::f7 +get ::f8 diff --git a/src/util/dict_cidr.map b/src/util/dict_cidr.map new file mode 100644 index 0000000..ac0f9b5 --- /dev/null +++ b/src/util/dict_cidr.map @@ -0,0 +1,50 @@ +172.16.0.0/21 554 match bad netblock 172.16.0.0/21 +172.16.8.0/21 554 match bad netblock 172.16.8.0/21 +172.16.0.0/16 554 match bad netblock 172.16.0.0/16 +172.17.1.1 554 match bad naked address +172.16.1.3/21 whatever +172.16.1.3/33 whatever +172.999.0.0/21 whatever +172.16.1.999 whatever +172.16.1.4 +if 1.2.0.0/16 +if 1.2.3.4/30 +1.2.3.3 1.2.3.3 can't happen +1.2.3.4 1.2.3.4 can happen +1.2.3.5 1.2.3.5 can happen +1.2.3.6 1.2.3.6 can happen +1.2.3.7 1.2.3.7 can happen +1.2.3.8 1.2.3.8 can't happen +endif +endif +if !1.2.3.4/30 +1.2.3.3 1.2.3.3 can happen +1.2.3.8 1.2.3.8 can happen +endif +if ::f4/126 +::f3 ::f3 can't happen +::f4 ::f4 can happen +::f5 ::f5 can happen +::f6 ::f6 can happen +::f7 ::f7 can happen +::f8 ::f8 can't happen +endif +if !::f4/126 +::f3 ::f3 can happen +::f8 ::f8 can happen +endif +2001:240:5c7:0:2d0:b7ff:fe88:2ca7 match 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 +2001:240:5c7::/64 match netblock 2001:240:5c7::/64 +1.0.0.0/0 match 0.0.0.0/0 +! ! 0.0.0.0/0 match 0.0.0.0/0 +1::/0 match ::/0 +::/0 match ::/0 +[1234 can't happen +[1234]junk can't happen +172.16.1.3/3x can't happen +endif +endif +if 1:2::3:4 +if 1:2::3:5 +if ! +! diff --git a/src/util/dict_cidr.ref b/src/util/dict_cidr.ref new file mode 100644 index 0000000..305e3fd --- /dev/null +++ b/src/util/dict_cidr.ref @@ -0,0 +1,63 @@ +./dict_open: warning: cidr map dict_cidr.map, line 5: non-null host address bits in "172.16.1.3/21", perhaps you should use "172.16.0.0/21" instead: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 6: bad mask length in "172.16.1.3/33": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 7: bad network value in "172.999.0.0/21": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 8: bad address pattern: "172.16.1.999": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 9: no lookup result: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 38: non-null host address bits in "1.0.0.0/0", perhaps you should use "0.0.0.0/0" instead: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 40: non-null host address bits in "1::/0", perhaps you should use "::/0" instead: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 42: missing ']' character after "[1234": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 43: garbage after "[1234]": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 44: bad mask value in "172.16.1.3/3x": skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 45: ENDIF without IF: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 46: ENDIF without IF: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 49: no address pattern: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 50: no address pattern: skipping this rule +./dict_open: warning: cidr map dict_cidr.map, line 48: IF has no matching ENDIF +./dict_open: warning: cidr map dict_cidr.map, line 47: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get 172.16.0.0 +172.16.0.0=554 match bad netblock 172.16.0.0/21 +> get 172.16.0.1 +172.16.0.1=554 match bad netblock 172.16.0.0/21 +> get 172.16.7.255 +172.16.7.255=554 match bad netblock 172.16.0.0/21 +> get 172.16.8.1 +172.16.8.1=554 match bad netblock 172.16.8.0/21 +> get 172.16.17.1 +172.16.17.1=554 match bad netblock 172.16.0.0/16 +> get 172.17.1.1 +172.17.1.1=554 match bad naked address +> get 172.17.1.2 +172.17.1.2=match 0.0.0.0/0 +> get 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 +2001:240:5c7:0:2d0:b7ff:fe88:2ca7=match 2001:240:5c7:0:2d0:b7ff:fe88:2ca7 +> get 2001:240:5c7:0:2d0:b7ff:febe:ca9f +2001:240:5c7:0:2d0:b7ff:febe:ca9f=match netblock 2001:240:5c7::/64 +> get 1.1.1.1 +1.1.1.1=match 0.0.0.0/0 +> get 1:1:1:1:1:1:1:1 +1:1:1:1:1:1:1:1=match ::/0 +> get 1.2.3.3 +1.2.3.3=1.2.3.3 can happen +> get 1.2.3.4 +1.2.3.4=1.2.3.4 can happen +> get 1.2.3.5 +1.2.3.5=1.2.3.5 can happen +> get 1.2.3.6 +1.2.3.6=1.2.3.6 can happen +> get 1.2.3.7 +1.2.3.7=1.2.3.7 can happen +> get 1.2.3.8 +1.2.3.8=1.2.3.8 can happen +> get ::f3 +::f3=::f3 can happen +> get ::f4 +::f4=::f4 can happen +> get ::f5 +::f5=::f5 can happen +> get ::f6 +::f6=::f6 can happen +> get ::f7 +::f7=::f7 can happen +> get ::f8 +::f8=::f8 can happen diff --git a/src/util/dict_cidr_file.in b/src/util/dict_cidr_file.in new file mode 100644 index 0000000..531d735 --- /dev/null +++ b/src/util/dict_cidr_file.in @@ -0,0 +1,3 @@ +get 1.1.1.1 +get 2.2.2.2 +get 3.3.3.3 diff --git a/src/util/dict_cidr_file.map b/src/util/dict_cidr_file.map new file mode 100644 index 0000000..e85e8ca --- /dev/null +++ b/src/util/dict_cidr_file.map @@ -0,0 +1,3 @@ +1.1.1.1 dict_cidr_file1 +2.2.2.2 dict_cidr_file2 +3.3.3.3 dict_cidr_file3 diff --git a/src/util/dict_cidr_file.ref b/src/util/dict_cidr_file.ref new file mode 100644 index 0000000..3e9e792 --- /dev/null +++ b/src/util/dict_cidr_file.ref @@ -0,0 +1,8 @@ +./dict_open: warning: cidr map dict_cidr_file.map, line 3: open dict_cidr_file3: No such file or directory: skipping this rule +owner=untrusted (uid=USER) +> get 1.1.1.1 +1.1.1.1=dGhpcy1pcy1maWxlMQo= +> get 2.2.2.2 +2.2.2.2=dGhpcy1pcy1maWxlMgo= +> get 3.3.3.3 +3.3.3.3: not found diff --git a/src/util/dict_db.c b/src/util/dict_db.c new file mode 100644 index 0000000..b2d0c33 --- /dev/null +++ b/src/util/dict_db.c @@ -0,0 +1,880 @@ +/*++ +/* NAME +/* dict_db 3 +/* SUMMARY +/* dictionary manager interface to DB files +/* SYNOPSIS +/* #include +/* +/* extern int dict_db_cache_size; +/* +/* DEFINE_DICT_DB_CACHE_SIZE; +/* +/* DICT *dict_hash_open(path, open_flags, dict_flags) +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* +/* DICT *dict_btree_open(path, open_flags, dict_flags) +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_XXX_open() opens the specified DB database. The result is +/* a pointer to a structure that can be used to access the dictionary +/* using the generic methods documented in dict_open(3). +/* +/* The dict_db_cache_size variable specifies a non-default per-table +/* I/O buffer size. The default buffer size is adequate for reading. +/* For better performance while creating a large table, specify a large +/* buffer size before opening the file. +/* +/* This variable cannot be exported via the dict(3) API and +/* must therefore be defined in the calling program by invoking +/* the DEFINE_DICT_DB_CACHE_SIZE macro at the global level. +/* +/* Arguments: +/* .IP path +/* The database pathname, not including the ".db" suffix. +/* .IP open_flags +/* Flags passed to dbopen(). +/* .IP dict_flags +/* Flags used by the dictionary interface. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* DIAGNOSTICS +/* Fatal errors: cannot open file, write error, out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#include "sys_defs.h" + +#ifdef HAS_DB + +/* System library. */ + +#include +#include +#ifdef PATH_DB_H +#include PATH_DB_H +#else +#include +#endif +#include +#include +#include + +#if defined(_DB_185_H_) && defined(USE_FCNTL_LOCK) +#error "Error: this system must not use the db 1.85 compatibility interface" +#endif + +#ifndef DB_VERSION_MAJOR +#define DB_VERSION_MAJOR 1 +#define DICT_DB_GET(db, key, val, flag) db->get(db, key, val, flag) +#define DICT_DB_PUT(db, key, val, flag) db->put(db, key, val, flag) +#define DICT_DB_DEL(db, key, flag) db->del(db, key, flag) +#define DICT_DB_SYNC(db, flag) db->sync(db, flag) +#define DICT_DB_CLOSE(db) db->close(db) +#define DONT_CLOBBER R_NOOVERWRITE +#endif + +#if DB_VERSION_MAJOR > 1 +#define DICT_DB_GET(db, key, val, flag) sanitize(db->get(db, 0, key, val, flag)) +#define DICT_DB_PUT(db, key, val, flag) sanitize(db->put(db, 0, key, val, flag)) +#define DICT_DB_DEL(db, key, flag) sanitize(db->del(db, 0, key, flag)) +#define DICT_DB_SYNC(db, flag) ((errno = db->sync(db, flag)) ? -1 : 0) +#define DICT_DB_CLOSE(db) ((errno = db->close(db, 0)) ? -1 : 0) +#define DONT_CLOBBER DB_NOOVERWRITE +#endif + +#if (DB_VERSION_MAJOR == 2 && DB_VERSION_MINOR < 6) +#define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs)) +#else +#define DICT_DB_CURSOR(db, curs) (db)->cursor((db), NULL, (curs), 0) +#endif + +#ifndef DB_FCNTL_LOCKING +#define DB_FCNTL_LOCKING 0 +#endif + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "vstring.h" +#include "stringops.h" +#include "iostuff.h" +#include "myflock.h" +#include "dict.h" +#include "dict_db.h" +#include "warn_stat.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + DB *db; /* open db file */ +#if DB_VERSION_MAJOR > 2 + DB_ENV *dbenv; +#endif +#if DB_VERSION_MAJOR > 1 + DBC *cursor; /* dict_db_sequence() */ +#endif + VSTRING *key_buf; /* key result */ + VSTRING *val_buf; /* value result */ +} DICT_DB; + +#define SCOPY(buf, data, size) \ + vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) + +#define DICT_DB_NELM 4096 + +#if DB_VERSION_MAJOR > 1 + +/* sanitize - sanitize db_get/put/del result */ + +static int sanitize(int status) +{ + + /* + * XXX This is unclean but avoids a lot of clutter elsewhere. Categorize + * results into non-fatal errors (i.e., errors that we can deal with), + * success, or fatal error (i.e., all other errors). + */ + switch (status) { + + case DB_NOTFOUND: /* get, del */ + case DB_KEYEXIST: /* put */ + return (1); /* non-fatal */ + + case 0: + return (0); /* success */ + + case DB_KEYEMPTY: /* get, others? */ + status = EINVAL; + /* FALLTHROUGH */ + default: + errno = status; + return (-1); /* fatal */ + } +} + +#endif + +/* dict_db_lookup - find database entry */ + +static const char *dict_db_lookup(DICT *dict, const char *name) +{ + DICT_DB *dict_db = (DICT_DB *) dict; + DB *db = dict_db->db; + DBT db_key; + DBT db_value; + int status; + const char *result = 0; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_db_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + memset(&db_key, 0, sizeof(db_key)); + memset(&db_value, 0, sizeof(db_value)); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); + + /* + * See if this DB file was written with one null byte appended to key and + * value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + db_key.data = (void *) name; + db_key.size = strlen(name) + 1; + if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0) + msg_fatal("error reading %s: %m", dict_db->dict.name); + if (status == 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + result = SCOPY(dict_db->val_buf, db_value.data, db_value.size); + } + } + + /* + * See if this DB file was written with no null byte appended to key and + * value. + */ + if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + db_key.data = (void *) name; + db_key.size = strlen(name); + if ((status = DICT_DB_GET(db, &db_key, &db_value, 0)) < 0) + msg_fatal("error reading %s: %m", dict_db->dict.name); + if (status == 0) { + dict->flags &= ~DICT_FLAG_TRY1NULL; + result = SCOPY(dict_db->val_buf, db_value.data, db_value.size); + } + } + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); + + return (result); +} + +/* dict_db_update - add or update database entry */ + +static int dict_db_update(DICT *dict, const char *name, const char *value) +{ + DICT_DB *dict_db = (DICT_DB *) dict; + DB *db = dict_db->db; + DBT db_key; + DBT db_value; + int status; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_db_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + memset(&db_key, 0, sizeof(db_key)); + memset(&db_value, 0, sizeof(db_value)); + db_key.data = (void *) name; + db_value.data = (void *) value; + db_key.size = strlen(name); + db_value.size = strlen(value); + + /* + * If undecided about appending a null byte to key and value, choose a + * default depending on the platform. + */ + if ((dict->flags & DICT_FLAG_TRY1NULL) + && (dict->flags & DICT_FLAG_TRY0NULL)) { +#ifdef DB_NO_TRAILING_NULL + dict->flags &= ~DICT_FLAG_TRY1NULL; +#else + dict->flags &= ~DICT_FLAG_TRY0NULL; +#endif + } + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + db_key.size++; + db_value.size++; + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); + + /* + * Do the update. + */ + if ((status = DICT_DB_PUT(db, &db_key, &db_value, + (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : DONT_CLOBBER)) < 0) + msg_fatal("error writing %s: %m", dict_db->dict.name); + if (status) { + if (dict->flags & DICT_FLAG_DUP_IGNORE) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s: duplicate entry: \"%s\"", dict_db->dict.name, name); + else + msg_fatal("%s: duplicate entry: \"%s\"", dict_db->dict.name, name); + } + if (dict->flags & DICT_FLAG_SYNC_UPDATE) + if (DICT_DB_SYNC(db, 0) < 0) + msg_fatal("%s: flush dictionary: %m", dict_db->dict.name); + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); + + return (status); +} + +/* delete one entry from the dictionary */ + +static int dict_db_delete(DICT *dict, const char *name) +{ + DICT_DB *dict_db = (DICT_DB *) dict; + DB *db = dict_db->db; + DBT db_key; + int status = 1; + int flags = 0; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_db_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + memset(&db_key, 0, sizeof(db_key)); + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); + + /* + * See if this DB file was written with one null byte appended to key and + * value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + db_key.data = (void *) name; + db_key.size = strlen(name) + 1; + if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0) + msg_fatal("error deleting from %s: %m", dict_db->dict.name); + if (status == 0) + dict->flags &= ~DICT_FLAG_TRY0NULL; + } + + /* + * See if this DB file was written with no null byte appended to key and + * value. + */ + if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + db_key.data = (void *) name; + db_key.size = strlen(name); + if ((status = DICT_DB_DEL(db, &db_key, flags)) < 0) + msg_fatal("error deleting from %s: %m", dict_db->dict.name); + if (status == 0) + dict->flags &= ~DICT_FLAG_TRY1NULL; + } + if (dict->flags & DICT_FLAG_SYNC_UPDATE) + if (DICT_DB_SYNC(db, 0) < 0) + msg_fatal("%s: flush dictionary: %m", dict_db->dict.name); + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); + + return status; +} + +/* dict_db_sequence - traverse the dictionary */ + +static int dict_db_sequence(DICT *dict, int function, + const char **key, const char **value) +{ + const char *myname = "dict_db_sequence"; + DICT_DB *dict_db = (DICT_DB *) dict; + DB *db = dict_db->db; + DBT db_key; + DBT db_value; + int status = 0; + int db_function; + + dict->error = 0; + +#if DB_VERSION_MAJOR > 1 + + /* + * Initialize. + */ + memset(&db_key, 0, sizeof(db_key)); + memset(&db_value, 0, sizeof(db_value)); + + /* + * Determine the function. + */ + switch (function) { + case DICT_SEQ_FUN_FIRST: + if (dict_db->cursor == 0) + DICT_DB_CURSOR(db, &(dict_db->cursor)); + db_function = DB_FIRST; + break; + case DICT_SEQ_FUN_NEXT: + if (dict_db->cursor == 0) + msg_panic("%s: no cursor", myname); + db_function = DB_NEXT; + break; + default: + msg_panic("%s: invalid function %d", myname, function); + } + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); + + /* + * Database lookup. + */ + status = + dict_db->cursor->c_get(dict_db->cursor, &db_key, &db_value, db_function); + if (status != 0 && status != DB_NOTFOUND) + msg_fatal("error [%d] seeking %s: %m", status, dict_db->dict.name); + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); + + if (status == 0) { + + /* + * Copy the result so it is guaranteed null terminated. + */ + *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size); + *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size); + } + return (status); +#else + + /* + * determine the function + */ + switch (function) { + case DICT_SEQ_FUN_FIRST: + db_function = R_FIRST; + break; + case DICT_SEQ_FUN_NEXT: + db_function = R_NEXT; + break; + default: + msg_panic("%s: invalid function %d", myname, function); + } + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_db->dict.name); + + if ((status = db->seq(db, &db_key, &db_value, db_function)) < 0) + msg_fatal("error seeking %s: %m", dict_db->dict.name); + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_db->dict.name); + + if (status == 0) { + + /* + * Copy the result so that it is guaranteed null terminated. + */ + *key = SCOPY(dict_db->key_buf, db_key.data, db_key.size); + *value = SCOPY(dict_db->val_buf, db_value.data, db_value.size); + } + return status; +#endif +} + +/* dict_db_close - close data base */ + +static void dict_db_close(DICT *dict) +{ + DICT_DB *dict_db = (DICT_DB *) dict; + +#if DB_VERSION_MAJOR > 1 + if (dict_db->cursor) + dict_db->cursor->c_close(dict_db->cursor); +#endif + if (DICT_DB_SYNC(dict_db->db, 0) < 0) + msg_fatal("flush database %s: %m", dict_db->dict.name); + + /* + * With some Berkeley DB implementations, close fails with a bogus ENOENT + * error, while it reports no errors with put+sync, no errors with + * del+sync, and no errors with the sync operation just before this + * comment. This happens in programs that never fork and that never share + * the database with other processes. The bogus close error has been + * reported for programs that use the first/next iterator. Instead of + * making Postfix look bad because it reports errors that other programs + * ignore, I'm going to report the bogus error as a non-error. + */ + if (DICT_DB_CLOSE(dict_db->db) < 0) + msg_info("close database %s: %m (possible Berkeley DB bug)", + dict_db->dict.name); +#if DB_VERSION_MAJOR > 2 + dict_db->dbenv->close(dict_db->dbenv, 0); +#endif + if (dict_db->key_buf) + vstring_free(dict_db->key_buf); + if (dict_db->val_buf) + vstring_free(dict_db->val_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +#if DB_VERSION_MAJOR > 2 + +/* dict_db_new_env - workaround for undocumented ./DB_CONFIG read */ + +static DB_ENV *dict_db_new_env(const char *db_path) +{ + VSTRING *db_home_buf; + DB_ENV *dbenv; + u_int32_t cache_size_gbytes; + u_int32_t cache_size_bytes; + int ncache; + + if ((errno = db_env_create(&dbenv, 0)) != 0) + msg_fatal("create DB environment: %m"); +#if DB_VERSION_MAJOR > 4 || (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR >= 7) + if ((errno = dbenv->get_cachesize(dbenv, &cache_size_gbytes, + &cache_size_bytes, &ncache)) != 0) + msg_fatal("get DB cache size: %m"); + if (cache_size_gbytes == 0 && cache_size_bytes < dict_db_cache_size) { + if ((errno = dbenv->set_cache_max(dbenv, cache_size_gbytes, + dict_db_cache_size)) != 0) + msg_fatal("set DB max cache size %d: %m", dict_db_cache_size); + if ((errno = dbenv->set_cachesize(dbenv, cache_size_gbytes, + dict_db_cache_size, ncache)) != 0) + msg_fatal("set DB cache size %d: %m", dict_db_cache_size); + } +#endif + /* XXX db_home is also the default directory for the .db file. */ + db_home_buf = vstring_alloc(100); + if ((errno = dbenv->open(dbenv, sane_dirname(db_home_buf, db_path), + DB_INIT_MPOOL | DB_CREATE | DB_PRIVATE, 0)) != 0) + msg_fatal("open DB environment: %m"); + vstring_free(db_home_buf); + return (dbenv); +} + +#endif + +/* dict_db_open - open data base */ + +static DICT *dict_db_open(const char *class, const char *path, int open_flags, + int type, void *tweak, int dict_flags) +{ + DICT_DB *dict_db; + struct stat st; + DB *db = 0; + char *db_path = 0; + VSTRING *db_base_buf = 0; + int lock_fd = -1; + int dbfd; + +#if DB_VERSION_MAJOR > 1 + int db_flags; + +#endif +#if DB_VERSION_MAJOR > 2 + DB_ENV *dbenv = 0; + +#endif + + /* + * Mismatches between #include file and library are a common cause for + * trouble. + */ +#if DB_VERSION_MAJOR > 1 + int major_version; + int minor_version; + int patch_version; + + (void) db_version(&major_version, &minor_version, &patch_version); + if (major_version != DB_VERSION_MAJOR || minor_version != DB_VERSION_MINOR) + return (dict_surrogate(class, path, open_flags, dict_flags, + "incorrect version of Berkeley DB: " + "compiled against %d.%d.%d, run-time linked against %d.%d.%d", + DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH, + major_version, minor_version, patch_version)); + if (msg_verbose) { + msg_info("Compiled against Berkeley DB: %d.%d.%d\n", + DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH); + msg_info("Run-time linked against Berkeley DB: %d.%d.%d\n", + major_version, minor_version, patch_version); + } +#else + if (msg_verbose) + msg_info("Compiled against Berkeley DB version 1"); +#endif + + db_path = concatenate(path, ".db", (char *) 0); + + /* + * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in + * the time domain) locking while accessing individual database records. + * + * Programs such as postmap/postalias use their own large-grained (in the + * time domain) locks while rewriting the entire file. + * + * XXX DB version 4.1 will not open a zero-length file. This means we must + * open an existing file without O_CREAT|O_TRUNC, and that we must let + * db_open() create a non-existent file for us. + */ +#define LOCK_OPEN_FLAGS(f) ((f) & ~(O_CREAT|O_TRUNC)) +#if DB_VERSION_MAJOR <= 2 +#define FREE_RETURN(e) do { \ + DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \ + if (lock_fd >= 0) (void) close(lock_fd); \ + if (db_base_buf) vstring_free(db_base_buf); \ + if (db_path) myfree(db_path); return (_dict); \ + } while (0) +#else +#define FREE_RETURN(e) do { \ + DICT *_dict = (e); if (db) DICT_DB_CLOSE(db); \ + if (dbenv) dbenv->close(dbenv, 0); \ + if (lock_fd >= 0) (void) close(lock_fd); \ + if (db_base_buf) vstring_free(db_base_buf); \ + if (db_path) myfree(db_path); \ + return (_dict); \ + } while (0) +#endif + + if (dict_flags & DICT_FLAG_LOCK) { + if ((lock_fd = open(db_path, LOCK_OPEN_FLAGS(open_flags), 0644)) < 0) { + if (errno != ENOENT) + FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); + } else { + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("shared-lock database %s for open: %m", db_path); + } + } + + /* + * Use the DB 1.x programming interface. This is the default interface + * with 4.4BSD systems. It is also available via the db_185 compatibility + * interface, but that interface does not have the undocumented feature + * that we need to make file locking safe with POSIX fcntl() locking. + */ +#if DB_VERSION_MAJOR < 2 + if ((db = dbopen(db_path, open_flags, 0644, type, tweak)) == 0) + FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); + dbfd = db->fd(db); +#endif + + /* + * Use the DB 2.x programming interface. Jump a couple extra hoops. + */ +#if DB_VERSION_MAJOR == 2 + db_flags = DB_FCNTL_LOCKING; + if (open_flags == O_RDONLY) + db_flags |= DB_RDONLY; + if (open_flags & O_CREAT) + db_flags |= DB_CREATE; + if (open_flags & O_TRUNC) + db_flags |= DB_TRUNCATE; + if ((errno = db_open(db_path, type, db_flags, 0644, 0, tweak, &db)) != 0) + FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); + if (db == 0) + msg_panic("db_open null result"); + if ((errno = db->fd(db, &dbfd)) != 0) + msg_fatal("get database file descriptor: %m"); +#endif + + /* + * Use the DB 3.x programming interface. Jump even more hoops. + */ +#if DB_VERSION_MAJOR > 2 + db_flags = DB_FCNTL_LOCKING; + if (open_flags == O_RDONLY) + db_flags |= DB_RDONLY; + if (open_flags & O_CREAT) + db_flags |= DB_CREATE; + if (open_flags & O_TRUNC) + db_flags |= DB_TRUNCATE; + if ((errno = db_create(&db, dbenv = dict_db_new_env(db_path), 0)) != 0) + msg_fatal("create DB database: %m"); + if (db == 0) + msg_panic("db_create null result"); + if (type == DB_HASH && db->set_h_nelem(db, DICT_DB_NELM) != 0) + msg_fatal("set DB hash element count %d: %m", DICT_DB_NELM); + db_base_buf = vstring_alloc(100); +#if DB_VERSION_MAJOR == 18 || DB_VERSION_MAJOR == 6 || DB_VERSION_MAJOR == 5 || \ + (DB_VERSION_MAJOR == 4 && DB_VERSION_MINOR > 0) + if ((errno = db->open(db, 0, sane_basename(db_base_buf, db_path), + 0, type, db_flags, 0644)) != 0) + FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); +#elif (DB_VERSION_MAJOR == 3 || DB_VERSION_MAJOR == 4) + if ((errno = db->open(db, sane_basename(db_base_buf, db_path), 0, + type, db_flags, 0644)) != 0) + FREE_RETURN(dict_surrogate(class, path, open_flags, dict_flags, + "open database %s: %m", db_path)); +#else +#error "Unsupported Berkeley DB version" +#endif + vstring_free(db_base_buf); + if ((errno = db->fd(db, &dbfd)) != 0) + msg_fatal("get database file descriptor: %m"); +#endif + if ((dict_flags & DICT_FLAG_LOCK) && lock_fd >= 0) { + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("unlock database %s for open: %m", db_path); + if (close(lock_fd) < 0) + msg_fatal("close database %s: %m", db_path); + lock_fd = -1; + } + dict_db = (DICT_DB *) dict_alloc(class, db_path, sizeof(*dict_db)); + dict_db->dict.lookup = dict_db_lookup; + dict_db->dict.update = dict_db_update; + dict_db->dict.delete = dict_db_delete; + dict_db->dict.sequence = dict_db_sequence; + dict_db->dict.close = dict_db_close; + dict_db->dict.lock_fd = dbfd; + dict_db->dict.stat_fd = dbfd; + if (fstat(dict_db->dict.stat_fd, &st) < 0) + msg_fatal("dict_db_open: fstat: %m"); + dict_db->dict.mtime = st.st_mtime; + dict_db->dict.owner.uid = st.st_uid; + dict_db->dict.owner.status = (st.st_uid != 0); + + /* + * Warn if the source file is newer than the indexed file, except when + * the source file changed only seconds ago. + */ + if ((dict_flags & DICT_FLAG_LOCK) != 0 + && stat(path, &st) == 0 + && st.st_mtime > dict_db->dict.mtime + && st.st_mtime < time((time_t *) 0) - 100) + msg_warn("database %s is older than source file %s", db_path, path); + + close_on_exec(dict_db->dict.lock_fd, CLOSE_ON_EXEC); + close_on_exec(dict_db->dict.stat_fd, CLOSE_ON_EXEC); + dict_db->dict.flags = dict_flags | DICT_FLAG_FIXED; + if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + dict_db->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL); + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_db->dict.fold_buf = vstring_alloc(10); + dict_db->db = db; +#if DB_VERSION_MAJOR > 2 + dict_db->dbenv = dbenv; +#endif +#if DB_VERSION_MAJOR > 1 + dict_db->cursor = 0; +#endif + dict_db->key_buf = 0; + dict_db->val_buf = 0; + + myfree(db_path); + return (DICT_DEBUG (&dict_db->dict)); +} + +/* dict_hash_open - create association with data base */ + +DICT *dict_hash_open(const char *path, int open_flags, int dict_flags) +{ +#if DB_VERSION_MAJOR < 2 + HASHINFO tweak; + + memset((void *) &tweak, 0, sizeof(tweak)); + tweak.nelem = DICT_DB_NELM; + tweak.cachesize = dict_db_cache_size; +#endif +#if DB_VERSION_MAJOR == 2 + DB_INFO tweak; + + memset((void *) &tweak, 0, sizeof(tweak)); + tweak.h_nelem = DICT_DB_NELM; + tweak.db_cachesize = dict_db_cache_size; +#endif +#if DB_VERSION_MAJOR > 2 + void *tweak; + + tweak = 0; +#endif + return (dict_db_open(DICT_TYPE_HASH, path, open_flags, DB_HASH, + (void *) &tweak, dict_flags)); +} + +/* dict_btree_open - create association with data base */ + +DICT *dict_btree_open(const char *path, int open_flags, int dict_flags) +{ +#if DB_VERSION_MAJOR < 2 + BTREEINFO tweak; + + memset((void *) &tweak, 0, sizeof(tweak)); + tweak.cachesize = dict_db_cache_size; +#endif +#if DB_VERSION_MAJOR == 2 + DB_INFO tweak; + + memset((void *) &tweak, 0, sizeof(tweak)); + tweak.db_cachesize = dict_db_cache_size; +#endif +#if DB_VERSION_MAJOR > 2 + void *tweak; + + tweak = 0; +#endif + + return (dict_db_open(DICT_TYPE_BTREE, path, open_flags, DB_BTREE, + (void *) &tweak, dict_flags)); +} + +#endif diff --git a/src/util/dict_db.h b/src/util/dict_db.h new file mode 100644 index 0000000..cc14dc8 --- /dev/null +++ b/src/util/dict_db.h @@ -0,0 +1,55 @@ +#ifndef _DICT_DB_H_INCLUDED_ +#define _DICT_DB_H_INCLUDED_ + +/*++ +/* NAME +/* dict_db 3h +/* SUMMARY +/* dictionary manager interface to DB files +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_HASH "hash" +#define DICT_TYPE_BTREE "btree" + +extern DICT *dict_hash_open(const char *, int, int); +extern DICT *dict_btree_open(const char *, int, int); + + /* + * XXX Should be part of the DICT interface. + * + * You can override the default dict_db_cache_size setting before calling + * dict_hash_open() or dict_btree_open(). This is done in mkmap_db_open() to + * set a larger memory pool for database (re)builds. + */ +extern int dict_db_cache_size; + +#define DEFINE_DICT_DB_CACHE_SIZE int dict_db_cache_size = (128 * 1024) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/dict_dbm.c b/src/util/dict_dbm.c new file mode 100644 index 0000000..e47b7ee --- /dev/null +++ b/src/util/dict_dbm.c @@ -0,0 +1,503 @@ +/*++ +/* NAME +/* dict_dbm 3 +/* SUMMARY +/* dictionary manager interface to DBM files +/* SYNOPSIS +/* #include +/* +/* DICT *dict_dbm_open(path, open_flags, dict_flags) +/* const char *name; +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_dbm_open() opens the named DBM database and makes it available +/* via the generic interface described in dict_open(3). +/* DIAGNOSTICS +/* Fatal errors: cannot open file, file write error, out of memory. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* ndbm(3) data base subroutines +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include "sys_defs.h" + +#ifdef HAS_DBM + +/* System library. */ + +#include +#ifdef PATH_NDBM_H +#include PATH_NDBM_H +#else +#include +#endif +#ifdef R_FIRST +#error "Error: you are including the Berkeley DB version of ndbm.h" +#error "To build with Postfix NDBM support, delete the Berkeley DB ndbm.h file" +#endif +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "htable.h" +#include "iostuff.h" +#include "vstring.h" +#include "myflock.h" +#include "stringops.h" +#include "dict.h" +#include "dict_dbm.h" +#include "warn_stat.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + DBM *dbm; /* open database */ + VSTRING *key_buf; /* key buffer */ + VSTRING *val_buf; /* result buffer */ +} DICT_DBM; + +#define SCOPY(buf, data, size) \ + vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) + +/* dict_dbm_lookup - find database entry */ + +static const char *dict_dbm_lookup(DICT *dict, const char *name) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + datum dbm_value; + const char *result = 0; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_dbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + dict->flags &= ~DICT_FLAG_TRY1NULL; + result = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (result); +} + +/* dict_dbm_update - add or update database entry */ + +static int dict_dbm_update(DICT *dict, const char *name, const char *value) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + datum dbm_value; + int status; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_dbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + dbm_key.dptr = (void *) name; + dbm_value.dptr = (void *) value; + dbm_key.dsize = strlen(name); + dbm_value.dsize = strlen(value); + + /* + * If undecided about appending a null byte to key and value, choose a + * default depending on the platform. + */ + if ((dict->flags & DICT_FLAG_TRY1NULL) + && (dict->flags & DICT_FLAG_TRY0NULL)) { +#ifdef DBM_NO_TRAILING_NULL + dict->flags &= ~DICT_FLAG_TRY1NULL; +#else + dict->flags &= ~DICT_FLAG_TRY0NULL; +#endif + } + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dsize++; + dbm_value.dsize++; + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * Do the update. + */ + if ((status = dbm_store(dict_dbm->dbm, dbm_key, dbm_value, + (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0) + msg_fatal("error writing DBM database %s: %m", dict_dbm->dict.name); + if (status) { + if (dict->flags & DICT_FLAG_DUP_IGNORE) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name); + else + msg_fatal("%s: duplicate entry: \"%s\"", dict_dbm->dict.name, name); + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (status); +} + +/* dict_dbm_delete - delete one entry from the dictionary */ + +static int dict_dbm_delete(DICT *dict, const char *name) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + int status = 1; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_dbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + dbm_clearerr(dict_dbm->dbm); + if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) { + if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */ + msg_fatal("error deleting from %s: %m", dict_dbm->dict.name); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */ + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + dbm_clearerr(dict_dbm->dbm); + if ((status = dbm_delete(dict_dbm->dbm, dbm_key)) < 0) { + if (dbm_error(dict_dbm->dbm) != 0) /* fatal error */ + msg_fatal("error deleting from %s: %m", dict_dbm->dict.name); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */ + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (status); +} + +/* traverse the dictionary */ + +static int dict_dbm_sequence(DICT *dict, int function, + const char **key, const char **value) +{ + const char *myname = "dict_dbm_sequence"; + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + datum dbm_key; + datum dbm_value; + int status; + + dict->error = 0; + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_dbm->dict.name); + + /* + * Determine and execute the seek function. It returns the key. + */ + switch (function) { + case DICT_SEQ_FUN_FIRST: + dbm_key = dbm_firstkey(dict_dbm->dbm); + break; + case DICT_SEQ_FUN_NEXT: + dbm_key = dbm_nextkey(dict_dbm->dbm); + break; + default: + msg_panic("%s: invalid function: %d", myname, function); + } + + if (dbm_key.dptr != 0 && dbm_key.dsize > 0) { + + /* + * Copy the key so that it is guaranteed null terminated. + */ + *key = SCOPY(dict_dbm->key_buf, dbm_key.dptr, dbm_key.dsize); + + /* + * Fetch the corresponding value. + */ + dbm_value = dbm_fetch(dict_dbm->dbm, dbm_key); + + if (dbm_value.dptr != 0 && dbm_value.dsize > 0) { + + /* + * Copy the value so that it is guaranteed null terminated. + */ + *value = SCOPY(dict_dbm->val_buf, dbm_value.dptr, dbm_value.dsize); + status = 0; + } else { + + /* + * Determine if we have hit the last record or an error + * condition. + */ + if (dbm_error(dict_dbm->dbm)) + msg_fatal("error seeking %s: %m", dict_dbm->dict.name); + status = 1; /* no error: eof/not found + * (should not happen!) */ + } + } else { + + /* + * Determine if we have hit the last record or an error condition. + */ + if (dbm_error(dict_dbm->dbm)) + msg_fatal("error seeking %s: %m", dict_dbm->dict.name); + status = 1; /* no error: eof/not found */ + } + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_dbm->dict.name); + + return (status); +} + +/* dict_dbm_close - disassociate from data base */ + +static void dict_dbm_close(DICT *dict) +{ + DICT_DBM *dict_dbm = (DICT_DBM *) dict; + + dbm_close(dict_dbm->dbm); + if (dict_dbm->key_buf) + vstring_free(dict_dbm->key_buf); + if (dict_dbm->val_buf) + vstring_free(dict_dbm->val_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_dbm_open - open DBM data base */ + +DICT *dict_dbm_open(const char *path, int open_flags, int dict_flags) +{ + DICT_DBM *dict_dbm; + struct stat st; + DBM *dbm; + char *dbm_path = 0; + int lock_fd; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_DBM_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (dbm_path != 0) \ + myfree(dbm_path); \ + return (__d); \ + } while (0) + + /* + * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in + * the time domain) locking while accessing individual database records. + * + * Programs such as postmap/postalias use their own large-grained (in the + * time domain) locks while rewriting the entire file. + */ + if (dict_flags & DICT_FLAG_LOCK) { + dbm_path = concatenate(path, ".dir", (char *) 0); + if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) + DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path, + open_flags, dict_flags, + "open database %s: %m", + dbm_path)); + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("shared-lock database %s for open: %m", dbm_path); + } + + /* + * XXX SunOS 5.x has no const in dbm_open() prototype. + */ + if ((dbm = dbm_open((char *) path, open_flags, 0644)) == 0) + DICT_DBM_OPEN_RETURN(dict_surrogate(DICT_TYPE_DBM, path, + open_flags, dict_flags, + "open database %s.{dir,pag}: %m", + path)); + + if (dict_flags & DICT_FLAG_LOCK) { + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("unlock database %s for open: %m", dbm_path); + if (close(lock_fd) < 0) + msg_fatal("close database %s: %m", dbm_path); + } + dict_dbm = (DICT_DBM *) dict_alloc(DICT_TYPE_DBM, path, sizeof(*dict_dbm)); + dict_dbm->dict.lookup = dict_dbm_lookup; + dict_dbm->dict.update = dict_dbm_update; + dict_dbm->dict.delete = dict_dbm_delete; + dict_dbm->dict.sequence = dict_dbm_sequence; + dict_dbm->dict.close = dict_dbm_close; + dict_dbm->dict.lock_fd = dbm_dirfno(dbm); + dict_dbm->dict.stat_fd = dbm_pagfno(dbm); + if (dict_dbm->dict.lock_fd == dict_dbm->dict.stat_fd) + msg_fatal("open database %s: cannot support GDBM", path); + if (fstat(dict_dbm->dict.stat_fd, &st) < 0) + msg_fatal("dict_dbm_open: fstat: %m"); + dict_dbm->dict.mtime = st.st_mtime; + dict_dbm->dict.owner.uid = st.st_uid; + dict_dbm->dict.owner.status = (st.st_uid != 0); + + /* + * Warn if the source file is newer than the indexed file, except when + * the source file changed only seconds ago. + */ + if ((dict_flags & DICT_FLAG_LOCK) != 0 + && stat(path, &st) == 0 + && st.st_mtime > dict_dbm->dict.mtime + && st.st_mtime < time((time_t *) 0) - 100) + msg_warn("database %s is older than source file %s", dbm_path, path); + + close_on_exec(dbm_pagfno(dbm), CLOSE_ON_EXEC); + close_on_exec(dbm_dirfno(dbm), CLOSE_ON_EXEC); + dict_dbm->dict.flags = dict_flags | DICT_FLAG_FIXED; + if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) + dict_dbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_dbm->dict.fold_buf = vstring_alloc(10); + dict_dbm->dbm = dbm; + dict_dbm->key_buf = 0; + dict_dbm->val_buf = 0; + + DICT_DBM_OPEN_RETURN(DICT_DEBUG (&dict_dbm->dict)); +} + +#endif diff --git a/src/util/dict_dbm.h b/src/util/dict_dbm.h new file mode 100644 index 0000000..ecaab62 --- /dev/null +++ b/src/util/dict_dbm.h @@ -0,0 +1,37 @@ +#ifndef _DICT_DBM_H_INCLUDED_ +#define _DICT_DBM_H_INCLUDED_ + +/*++ +/* NAME +/* dict_dbm 3h +/* SUMMARY +/* dictionary manager interface to DBM files +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_DBM "dbm" + +extern DICT *dict_dbm_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_debug.c b/src/util/dict_debug.c new file mode 100644 index 0000000..46634d4 --- /dev/null +++ b/src/util/dict_debug.c @@ -0,0 +1,150 @@ +/*++ +/* NAME +/* dict_debug 3 +/* SUMMARY +/* dictionary manager, logging proxy +/* SYNOPSIS +/* #include +/* +/* DICT *dict_debug(dict_handle) +/* DICT *dict_handle; +/* +/* DICT *DICT_DEBUG(dict_handle) +/* DICT *dict_handle; +/* DESCRIPTION +/* dict_debug() encapsulates the given dictionary object and returns +/* a proxy object that logs all access to the encapsulated object. +/* This is more convenient than having to add logging capability +/* to each individual dictionary access method. +/* +/* DICT_DEBUG() is an unsafe macro that returns the original object if +/* the object's debugging flag is not set, and that otherwise encapsulates +/* the object with dict_debug(). This macro simplifies usage by avoiding +/* clumsy expressions. The macro evaluates its argument multiple times. +/* DIAGNOSTICS +/* Fatal errors: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Application-specific. */ + +typedef struct { + DICT dict; /* the proxy service */ + DICT *real_dict; /* encapsulated object */ +} DICT_DEBUG; + +/* dict_debug_lookup - log lookup operation */ + +static const char *dict_debug_lookup(DICT *dict, const char *key) +{ + DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict; + DICT *real_dict = dict_debug->real_dict; + const char *result; + + real_dict->flags = dict->flags; + result = dict_get(real_dict, key); + dict->flags = real_dict->flags; + msg_info("%s:%s lookup: \"%s\" = \"%s\"", dict->type, dict->name, key, + result ? result : real_dict->error ? "error" : "not_found"); + DICT_ERR_VAL_RETURN(dict, real_dict->error, result); +} + +/* dict_debug_update - log update operation */ + +static int dict_debug_update(DICT *dict, const char *key, const char *value) +{ + DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict; + DICT *real_dict = dict_debug->real_dict; + int result; + + real_dict->flags = dict->flags; + result = dict_put(real_dict, key, value); + dict->flags = real_dict->flags; + msg_info("%s:%s update: \"%s\" = \"%s\": %s", dict->type, dict->name, + key, value, result == 0 ? "success" : real_dict->error ? + "error" : "failed"); + DICT_ERR_VAL_RETURN(dict, real_dict->error, result); +} + +/* dict_debug_delete - log delete operation */ + +static int dict_debug_delete(DICT *dict, const char *key) +{ + DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict; + DICT *real_dict = dict_debug->real_dict; + int result; + + real_dict->flags = dict->flags; + result = dict_del(real_dict, key); + dict->flags = real_dict->flags; + msg_info("%s:%s delete: \"%s\": %s", dict->type, dict->name, key, + result == 0 ? "success" : real_dict->error ? + "error" : "failed"); + DICT_ERR_VAL_RETURN(dict, real_dict->error, result); +} + +/* dict_debug_sequence - log sequence operation */ + +static int dict_debug_sequence(DICT *dict, int function, + const char **key, const char **value) +{ + DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict; + DICT *real_dict = dict_debug->real_dict; + int result; + + real_dict->flags = dict->flags; + result = dict_seq(real_dict, function, key, value); + dict->flags = real_dict->flags; + if (result == 0) + msg_info("%s:%s sequence: \"%s\" = \"%s\"", dict->type, dict->name, + *key, *value); + else + msg_info("%s:%s sequence: found EOF", dict->type, dict->name); + DICT_ERR_VAL_RETURN(dict, real_dict->error, result); +} + +/* dict_debug_close - log operation */ + +static void dict_debug_close(DICT *dict) +{ + DICT_DEBUG *dict_debug = (DICT_DEBUG *) dict; + + dict_close(dict_debug->real_dict); + dict_free(dict); +} + +/* dict_debug - encapsulate dictionary object and install proxies */ + +DICT *dict_debug(DICT *real_dict) +{ + DICT_DEBUG *dict_debug; + + dict_debug = (DICT_DEBUG *) dict_alloc(real_dict->type, + real_dict->name, sizeof(*dict_debug)); + dict_debug->dict.flags = real_dict->flags; /* XXX not synchronized */ + dict_debug->dict.lookup = dict_debug_lookup; + dict_debug->dict.update = dict_debug_update; + dict_debug->dict.delete = dict_debug_delete; + dict_debug->dict.sequence = dict_debug_sequence; + dict_debug->dict.close = dict_debug_close; + dict_debug->real_dict = real_dict; + return (&dict_debug->dict); +} diff --git a/src/util/dict_env.c b/src/util/dict_env.c new file mode 100644 index 0000000..1635b8d --- /dev/null +++ b/src/util/dict_env.c @@ -0,0 +1,112 @@ +/*++ +/* NAME +/* dict_env 3 +/* SUMMARY +/* dictionary manager interface to environment variables +/* SYNOPSIS +/* #include +/* +/* DICT *dict_env_open(name, dummy, dict_flags) +/* const char *name; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_env_open() opens the environment variable array and +/* makes it accessible via the generic operations documented +/* in dict_open(3). The \fIname\fR and \fIdummy\fR arguments +/* are ignored. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* safe_getenv(3) safe getenv() interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include /* sprintf() prototype */ +#include +#include +#include + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "safe.h" +#include "stringops.h" +#include "dict.h" +#include "dict_env.h" + +/* dict_env_update - update environment array */ + +static int dict_env_update(DICT *dict, const char *name, const char *value) +{ + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + if (setenv(name, value, 1)) + msg_fatal("setenv: %m"); + + return (DICT_STAT_SUCCESS); +} + +/* dict_env_lookup - access environment array */ + +static const char *dict_env_lookup(DICT *dict, const char *name) +{ + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + return (safe_getenv(name)); +} + +/* dict_env_close - close environment dictionary */ + +static void dict_env_close(DICT *dict) +{ + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_env_open - make association with environment array */ + +DICT *dict_env_open(const char *name, int unused_flags, int dict_flags) +{ + DICT *dict; + + dict = dict_alloc(DICT_TYPE_ENVIRON, name, sizeof(*dict)); + dict->lookup = dict_env_lookup; + dict->update = dict_env_update; + dict->close = dict_env_close; + dict->flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict->fold_buf = vstring_alloc(10); + dict->owner.status = DICT_OWNER_TRUSTED; + return (DICT_DEBUG (dict)); +} diff --git a/src/util/dict_env.h b/src/util/dict_env.h new file mode 100644 index 0000000..9abfa4f --- /dev/null +++ b/src/util/dict_env.h @@ -0,0 +1,37 @@ +#ifndef _DICT_ENV_H_INCLUDED_ +#define _DICT_ENV_H_INCLUDED_ + +/*++ +/* NAME +/* dict_env 3h +/* SUMMARY +/* dictionary manager interface to environment variables +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_ENVIRON "environ" + +extern DICT *dict_env_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_fail.c b/src/util/dict_fail.c new file mode 100644 index 0000000..c8d9a19 --- /dev/null +++ b/src/util/dict_fail.c @@ -0,0 +1,115 @@ +/*++ +/* NAME +/* dict_fail 3 +/* SUMMARY +/* dictionary manager interface to 'always fail' table +/* SYNOPSIS +/* #include +/* +/* DICT *dict_fail_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_fail_open() implements a dummy dictionary that fails +/* all operations. The name can be used for logging. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + int dict_errno; /* fixed error result */ +} DICT_FAIL; + +/* dict_fail_sequence - fail lookup */ + +static int dict_fail_sequence(DICT *dict, int unused_func, + const char **key, const char **value) +{ + DICT_FAIL *dp = (DICT_FAIL *) dict; + + errno = 0; + DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR); +} + +/* dict_fail_update - fail lookup */ + +static int dict_fail_update(DICT *dict, const char *unused_name, + const char *unused_value) +{ + DICT_FAIL *dp = (DICT_FAIL *) dict; + + errno = 0; + DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR); +} + +/* dict_fail_lookup - fail lookup */ + +static const char *dict_fail_lookup(DICT *dict, const char *unused_name) +{ + DICT_FAIL *dp = (DICT_FAIL *) dict; + + errno = 0; + DICT_ERR_VAL_RETURN(dict, dp->dict_errno, (char *) 0); +} + +/* dict_fail_delete - fail delete */ + +static int dict_fail_delete(DICT *dict, const char *unused_name) +{ + DICT_FAIL *dp = (DICT_FAIL *) dict; + + errno = 0; + DICT_ERR_VAL_RETURN(dict, dp->dict_errno, DICT_STAT_ERROR); +} + +/* dict_fail_close - close fail dictionary */ + +static void dict_fail_close(DICT *dict) +{ + dict_free(dict); +} + +/* dict_fail_open - make association with fail variable */ + +DICT *dict_fail_open(const char *name, int open_flags, int dict_flags) +{ + DICT_FAIL *dp; + + dp = (DICT_FAIL *) dict_alloc(DICT_TYPE_FAIL, name, sizeof(*dp)); + dp->dict.lookup = dict_fail_lookup; + if (open_flags & O_RDWR) { + dp->dict.update = dict_fail_update; + dp->dict.delete = dict_fail_delete; + } + dp->dict.sequence = dict_fail_sequence; + dp->dict.close = dict_fail_close; + dp->dict.flags = dict_flags | DICT_FLAG_PATTERN; + dp->dict_errno = DICT_ERR_RETRY; + dp->dict.owner.status = DICT_OWNER_TRUSTED; + return (DICT_DEBUG (&dp->dict)); +} diff --git a/src/util/dict_fail.h b/src/util/dict_fail.h new file mode 100644 index 0000000..b42a31b --- /dev/null +++ b/src/util/dict_fail.h @@ -0,0 +1,35 @@ +#ifndef _DICT_FAIL_H_INCLUDED_ +#define _DICT_FAIL_H_INCLUDED_ + +/*++ +/* NAME +/* dict_fail 3h +/* SUMMARY +/* dictionary manager interface to 'always fail' table +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_FAIL "fail" + +extern DICT *dict_fail_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* jeffm +/* ghostgun.com +/*--*/ + +#endif diff --git a/src/util/dict_file.c b/src/util/dict_file.c new file mode 100644 index 0000000..c6ea74c --- /dev/null +++ b/src/util/dict_file.c @@ -0,0 +1,231 @@ +/*++ +/* NAME +/* dict_file_to_buf 3 +/* SUMMARY +/* include content from file as blob +/* SYNOPSIS +/* #include +/* +/* VSTRING *dict_file_to_buf( +/* DICT *dict, +/* const char *pathnames) +/* +/* VSTRING *dict_file_to_b64( +/* DICT *dict, +/* const char *pathnames) +/* +/* VSTRING *dict_file_from_b64( +/* DICT *dict, +/* const char *value) +/* +/* char *dict_file_get_error( +/* DICT *dict) +/* +/* void dict_file_purge_buffers( +/* DICT *dict) +/* +/* const char *dict_file_lookup( +/* DICT *dict) +/* DESCRIPTION +/* dict_file_to_buf() reads the content of the specified files, +/* with names separated by CHARS_COMMA_SP, while inserting a +/* gratuitous newline character between files. It returns a +/* pointer to a buffer which is owned by the DICT, or a null +/* pointer in case of error. +/* +/* dict_file_to_b64() invokes dict_file_to_buf() and converts +/* the result to base64. It returns a pointer to a buffer which +/* is owned by the DICT, or a null pointer in case of error. +/* +/* dict_file_from_b64() converts a value from base64. It returns +/* a pointer to a buffer which is owned by the DICT, or a null +/* pointer in case of error. +/* +/* dict_file_purge_buffers() disposes of dict_file-related +/* memory that are associated with this DICT. +/* +/* dict_file_get_error() should be called only after error; +/* it returns a description of the problem. Storage is owned +/* by the caller. +/* +/* dict_file_lookup() wraps the dictionary lookup method and +/* decodes the base64 lookup result. The dictionary must be +/* opened with DICT_FLAG_SRC_RHS_IS_FILE. Sets dict->error to +/* DICT_ERR_CONFIG if the content is invalid. Decoding is not +/* built into the dict->lookup() method, because that would +/* complicate the implementation of map nesting (inline, thash), +/* map composition (pipemap, unionmap), and map proxying. +/* DIAGNOSTICS +/* Panic: interface violation. +/* +/* In case of error the VSTRING result value is a null pointer, and +/* an error description can be retrieved with dict_file_get_error(). +/* The storage is owned by the caller. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* dict_file_to_buf - read files into a buffer */ + +VSTRING *dict_file_to_buf(DICT *dict, const char *pathnames) +{ + struct stat st; + VSTREAM *fp = 0; + ARGV *argv; + char **cpp; + + /* dict_file_to_buf() postcondition: dict->file_buf exists. */ + if (dict->file_buf == 0) + dict->file_buf = vstring_alloc(100); + +#define DICT_FILE_RETURN(retval) do { \ + argv_free(argv); \ + if (fp) vstream_fclose(fp); \ + return (retval); \ + } while (0); + + argv = argv_split(pathnames, CHARS_COMMA_SP); + if (argv->argc == 0) { + vstring_sprintf(dict->file_buf, "empty pathname list: >>%s<<'", + pathnames); + DICT_FILE_RETURN(0); + } + VSTRING_RESET(dict->file_buf); + for (cpp = argv->argv; *cpp; cpp++) { + if ((fp = vstream_fopen(*cpp, O_RDONLY, 0)) == 0 + || fstat(vstream_fileno(fp), &st) < 0) { + vstring_sprintf(dict->file_buf, "open %s: %m", *cpp); + DICT_FILE_RETURN(0); + } + if (st.st_size > SSIZE_T_MAX - LEN(dict->file_buf)) { + vstring_sprintf(dict->file_buf, "file too large: %s", pathnames); + DICT_FILE_RETURN(0); + } + if (vstream_fread_app(fp, dict->file_buf, st.st_size) != st.st_size) { + vstring_sprintf(dict->file_buf, "read %s: %m", *cpp); + DICT_FILE_RETURN(0); + } + (void) vstream_fclose(fp); + fp = 0; + if (cpp[1] != 0) + VSTRING_ADDCH(dict->file_buf, '\n'); + } + VSTRING_TERMINATE(dict->file_buf); + DICT_FILE_RETURN(dict->file_buf); +} + +/* dict_file_to_b64 - read files into a base64-encoded buffer */ + +VSTRING *dict_file_to_b64(DICT *dict, const char *pathnames) +{ + ssize_t helper; + + if (dict_file_to_buf(dict, pathnames) == 0) + return (0); + if (dict->file_b64 == 0) + dict->file_b64 = vstring_alloc(100); + helper = (LEN(dict->file_buf) + 2) / 3; + if (helper > SSIZE_T_MAX / 4) { + vstring_sprintf(dict->file_buf, "file too large: %s", pathnames); + return (0); + } + VSTRING_RESET(dict->file_b64); + VSTRING_SPACE(dict->file_b64, helper * 4); + return (base64_encode(dict->file_b64, STR(dict->file_buf), + LEN(dict->file_buf))); +} + +/* dict_file_from_b64 - convert value from base64 */ + +VSTRING *dict_file_from_b64(DICT *dict, const char *value) +{ + ssize_t helper; + VSTRING *result; + + if (dict->file_buf == 0) + dict->file_buf = vstring_alloc(100); + helper = strlen(value) / 4; + VSTRING_RESET(dict->file_buf); + VSTRING_SPACE(dict->file_buf, helper * 3); + result = base64_decode(dict->file_buf, value, strlen(value)); + if (result == 0) + vstring_sprintf(dict->file_buf, "malformed BASE64 value: %.30s", value); + return (result); +} + +/* dict_file_get_error - return error text */ + +char *dict_file_get_error(DICT *dict) +{ + if (dict->file_buf == 0) + msg_panic("dict_file_get_error: no buffer"); + return (mystrdup(STR(dict->file_buf))); +} + +/* dict_file_purge_buffers - purge file buffers */ + +void dict_file_purge_buffers(DICT *dict) +{ + if (dict->file_buf) { + vstring_free(dict->file_buf); + dict->file_buf = 0; + } + if (dict->file_b64) { + vstring_free(dict->file_b64); + dict->file_b64 = 0; + } +} + +/* dict_file_lookup - look up and decode dictionary entry */ + +const char *dict_file_lookup(DICT *dict, const char *key) +{ + const char myname[] = "dict_file_lookup"; + const char *res; + VSTRING *unb64; + char *err; + + if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) == 0) + msg_panic("%s: dictionary opened without DICT_FLAG_SRC_RHS_IS_FILE", + myname); + if ((res = dict->lookup(dict, key)) == 0) + return (0); + if ((unb64 = dict_file_from_b64(dict, res)) == 0) { + err = dict_file_get_error(dict); + msg_warn("table %s:%s: key %s: %s", dict->type, dict->name, key, err); + myfree(err); + dict->error = DICT_ERR_CONFIG; + return (0); + } + return STR(unb64); +} diff --git a/src/util/dict_ht.c b/src/util/dict_ht.c new file mode 100644 index 0000000..74020d5 --- /dev/null +++ b/src/util/dict_ht.c @@ -0,0 +1,171 @@ +/*++ +/* NAME +/* dict_ht 3 +/* SUMMARY +/* dictionary manager interface to hash tables +/* SYNOPSIS +/* #include +/* +/* DICT *dict_ht_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_ht_open() creates a memory-resident hash table and +/* makes it accessible via the generic dictionary operations +/* documented in dict_open(3). The open_flags argument is +/* ignored. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" + +/* Utility library. */ + +#include "mymalloc.h" +#include "htable.h" +#include "dict.h" +#include "dict_ht.h" +#include "stringops.h" +#include "vstring.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + HTABLE *table; /* hash table */ +} DICT_HT; + +/* dict_ht_delete - delete hash-table entry */ + +static int dict_ht_delete(DICT *dict, const char *name) +{ + DICT_HT *dict_ht = (DICT_HT *) dict; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + if (htable_locate(dict_ht->table, name) == 0) { + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); + } else { + htable_delete(dict_ht->table, name, myfree); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); + } +} + +/* dict_ht_lookup - find hash-table entry */ + +static const char *dict_ht_lookup(DICT *dict, const char *name) +{ + DICT_HT *dict_ht = (DICT_HT *) dict; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, htable_find(dict_ht->table, name)); +} + +/* dict_ht_update - add or update hash-table entry */ + +static int dict_ht_update(DICT *dict, const char *name, const char *value) +{ + DICT_HT *dict_ht = (DICT_HT *) dict; + HTABLE_INFO *ht; + char *saved_value = mystrdup(value); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + if ((ht = htable_locate(dict_ht->table, name)) != 0) { + myfree(ht->value); + } else { + ht = htable_enter(dict_ht->table, name, (void *) 0); + } + ht->value = saved_value; + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); +} + +/* dict_ht_sequence - first/next iterator */ + +static int dict_ht_sequence(DICT *dict, int how, const char **name, + const char **value) +{ + DICT_HT *dict_ht = (DICT_HT *) dict; + HTABLE_INFO *ht; + + ht = htable_sequence(dict_ht->table, + how == DICT_SEQ_FUN_FIRST ? HTABLE_SEQ_FIRST : + how == DICT_SEQ_FUN_NEXT ? HTABLE_SEQ_NEXT : + HTABLE_SEQ_STOP); + if (ht != 0) { + *name = ht->key; + *value = ht->value; + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_SUCCESS); + } else { + *name = 0; + *value = 0; + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, DICT_STAT_FAIL); + } +} + +/* dict_ht_close - disassociate from hash table */ + +static void dict_ht_close(DICT *dict) +{ + DICT_HT *dict_ht = (DICT_HT *) dict; + + htable_free(dict_ht->table, myfree); + if (dict_ht->dict.fold_buf) + vstring_free(dict_ht->dict.fold_buf); + dict_free(dict); +} + +/* dict_ht_open - create association with hash table */ + +DICT *dict_ht_open(const char *name, int unused_open_flags, int dict_flags) +{ + DICT_HT *dict_ht; + + dict_ht = (DICT_HT *) dict_alloc(DICT_TYPE_HT, name, sizeof(*dict_ht)); + dict_ht->dict.lookup = dict_ht_lookup; + dict_ht->dict.update = dict_ht_update; + dict_ht->dict.delete = dict_ht_delete; + dict_ht->dict.sequence = dict_ht_sequence; + dict_ht->dict.close = dict_ht_close; + dict_ht->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_ht->dict.fold_buf = vstring_alloc(10); + dict_ht->table = htable_create(0); + dict_ht->dict.owner.status = DICT_OWNER_TRUSTED; + return (&dict_ht->dict); +} diff --git a/src/util/dict_ht.h b/src/util/dict_ht.h new file mode 100644 index 0000000..7cb31aa --- /dev/null +++ b/src/util/dict_ht.h @@ -0,0 +1,38 @@ +#ifndef _DICT_HT_H_INCLUDED_ +#define _DICT_HT_H_INCLUDED_ + +/*++ +/* NAME +/* dict_ht 3h +/* SUMMARY +/* dictionary manager interface to hash tables +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * External interface. + */ +#define DICT_TYPE_HT "internal" + +extern DICT *dict_ht_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_inline.c b/src/util/dict_inline.c new file mode 100644 index 0000000..72339b2 --- /dev/null +++ b/src/util/dict_inline.c @@ -0,0 +1,150 @@ +/*++ +/* NAME +/* dict_inline 3 +/* SUMMARY +/* dictionary manager interface for inline table +/* SYNOPSIS +/* #include +/* +/* DICT *dict_inline_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_inline_open() opens a read-only, in-memory table. +/* Example: "\fBinline:{\fIkey_1=value_1, ..., key_n=value_n\fR}". +/* The longer form with { key = value } allows values that +/* contain whitespace or comma. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +/* dict_inline_open - open inline table */ + +DICT *dict_inline_open(const char *name, int open_flags, int dict_flags) +{ + DICT *dict; + char *cp, *saved_name = 0; + size_t len; + char *nameval, *vname, *value; + const char *err = 0; + char *free_me = 0; + int count = 0; + + /* + * Clarity first. Let the optimizer worry about redundant code. + */ +#define DICT_INLINE_RETURN(x) do { \ + DICT *__d = (x); \ + if (saved_name != 0) \ + myfree(saved_name); \ + if (free_me != 0) \ + myfree(free_me); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_INLINE, name)); + + /* + * UTF-8 syntax check. + */ + if (DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags) + && allascii(name) == 0 + && valid_utf8_string(name, strlen(name)) == 0) + DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name, + open_flags, dict_flags, + "bad UTF-8 syntax: \"%s:%s\"; " + "need \"%s:{name=value...}\"", + DICT_TYPE_INLINE, name, + DICT_TYPE_INLINE)); + + /* + * Parse the table into its constituent name=value pairs. + */ + if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0 + || *(cp = saved_name = mystrndup(name + 1, len - 2)) == 0) + DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{name=value...}\"", + DICT_TYPE_INLINE, name, + DICT_TYPE_INLINE)); + + /* + * Reuse the "internal" dictionary type. + */ + dict = dict_open3(DICT_TYPE_HT, name, open_flags, dict_flags); + dict_type_override(dict, DICT_TYPE_INLINE); + while ((nameval = mystrtokq(&cp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) { + if (nameval[0] == CHARS_BRACE[0]) + err = free_me = extpar(&nameval, CHARS_BRACE, EXTPAR_FLAG_STRIP); + if (err != 0 || (err = split_qnameval(nameval, &vname, &value)) != 0) + break; + + if ((dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) != 0) { + VSTRING *base64_buf; + + if ((base64_buf = dict_file_to_b64(dict, value)) == 0) { + err = free_me = dict_file_get_error(dict); + break; + } + value = vstring_str(base64_buf); + } + /* No duplicate checks. See comments in dict_thash.c. */ + dict->update(dict, vname, value); + count += 1; + } + if (err != 0 || count == 0) { + dict->close(dict); + DICT_INLINE_RETURN(dict_surrogate(DICT_TYPE_INLINE, name, + open_flags, dict_flags, + "%s: \"%s:%s\"; " + "need \"%s:{name=%s...}\"", + err != 0 ? err : "empty table", + DICT_TYPE_INLINE, name, + DICT_TYPE_INLINE, + (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) ? + "filename" : "value")); + } + dict->owner.status = DICT_OWNER_TRUSTED; + + dict_file_purge_buffers(dict); + DICT_INLINE_RETURN(DICT_DEBUG (dict)); +} diff --git a/src/util/dict_inline.h b/src/util/dict_inline.h new file mode 100644 index 0000000..9879acf --- /dev/null +++ b/src/util/dict_inline.h @@ -0,0 +1,37 @@ +#ifndef _DICT_INLINE_H_INCLUDED_ +#define _DICT_INLINE_H_INCLUDED_ + +/*++ +/* NAME +/* dict_inline 3h +/* SUMMARY +/* dictionary manager interface for inline tables +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_INLINE "inline" + +extern DICT *dict_inline_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_inline.ref b/src/util/dict_inline.ref new file mode 100644 index 0000000..e64e6d0 --- /dev/null +++ b/src/util/dict_inline.ref @@ -0,0 +1,24 @@ +./dict_open: error: empty table: "inline:{ }"; need "inline:{name=value...}" +owner=trusted (uid=2147483647) +./dict_open: error: missing '=' after attribute name: "inline:{ foo = xx }"; need "inline:{name=value...}" +owner=trusted (uid=2147483647) +./dict_open: error: bad syntax: "inline:{ foo=xx }x"; need "inline:{name=value...}" +owner=trusted (uid=2147483647) +./dict_open: error: bad syntax: "inline:{ foo=xx x"; need "inline:{name=value...}" +owner=trusted (uid=2147483647) +./dict_open: error: syntax error after '}' in "{x=y}x": "inline:{ foo=xx {x=y}x}"; need "inline:{name=value...}" +owner=trusted (uid=2147483647) +owner=trusted (uid=2147483647) +> get foo +foo=XX +> get bar +bar=lotsa stuff +> get baz +baz: not found +owner=trusted (uid=2147483647) +> get foo +foo=XX +> get bar +bar=lotsa stuff +> get baz +baz: not found diff --git a/src/util/dict_inline_cidr.ref b/src/util/dict_inline_cidr.ref new file mode 100644 index 0000000..56c00c5 --- /dev/null +++ b/src/util/dict_inline_cidr.ref @@ -0,0 +1,4 @@ +> get 1.2.3.4 +1.2.3.4=got 1.2.3.4 +> get 4.3.2.1 +4.3.2.1: not found diff --git a/src/util/dict_inline_file.ref b/src/util/dict_inline_file.ref new file mode 100644 index 0000000..9d49e95 --- /dev/null +++ b/src/util/dict_inline_file.ref @@ -0,0 +1,12 @@ +./dict_open: error: open xx: No such file or directory: "inline:{ foo=xx, bar=yy }"; need "inline:{name=filename...}" +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: inline:{ foo=xx, bar=yy } is unavailable. open xx: No such file or directory: "inline:{ foo=xx, bar=yy }"; need "inline:{name=filename...}" +foo: error +owner=trusted (uid=2147483647) +> get file1 +file1=dGhpcy1pcy1maWxlMQo= +> get file2 +file2=dGhpcy1pcy1maWxlMgo= +> get file3 +file3: not found diff --git a/src/util/dict_inline_pcre.ref b/src/util/dict_inline_pcre.ref new file mode 100644 index 0000000..0c381f1 --- /dev/null +++ b/src/util/dict_inline_pcre.ref @@ -0,0 +1,4 @@ +> get foo +foo=got foo +> get bar +bar: not found diff --git a/src/util/dict_inline_regexp.ref b/src/util/dict_inline_regexp.ref new file mode 100644 index 0000000..0c381f1 --- /dev/null +++ b/src/util/dict_inline_regexp.ref @@ -0,0 +1,4 @@ +> get foo +foo=got foo +> get bar +bar: not found diff --git a/src/util/dict_lmdb.c b/src/util/dict_lmdb.c new file mode 100644 index 0000000..bed20e0 --- /dev/null +++ b/src/util/dict_lmdb.c @@ -0,0 +1,706 @@ +/*++ +/* NAME +/* dict_lmdb 3 +/* SUMMARY +/* dictionary manager interface to OpenLDAP LMDB files +/* SYNOPSIS +/* #include +/* +/* extern size_t dict_lmdb_map_size; +/* +/* DEFINE_DICT_LMDB_MAP_SIZE; +/* +/* DICT *dict_lmdb_open(path, open_flags, dict_flags) +/* const char *name; +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_lmdb_open() opens the named LMDB database and makes +/* it available via the generic interface described in +/* dict_open(3). +/* +/* The dict_lmdb_map_size variable specifies the initial +/* database memory map size. When a map becomes full its size +/* is doubled, and other programs pick up the size change. +/* +/* This variable cannot be exported via the dict(3) API and +/* must therefore be defined in the calling program by invoking +/* the DEFINE_DICT_LMDB_MAP_SIZE macro at the global level. +/* DIAGNOSTICS +/* Fatal errors: cannot open file, file write error, out of +/* memory. +/* +/* If a jump buffer is specified with dict_setjmp(), then the LMDB +/* client will call dict_longjmp() to return to that execution +/* context after a recoverable error. +/* BUGS +/* The on-the-fly map resize operations require no concurrent +/* activity in the same database by other threads in the same +/* memory address space. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Howard Chu +/* Symas Corporation +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#include + +#ifdef HAS_LMDB + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + SLMDB slmdb; /* sane LMDB API */ + VSTRING *key_buf; /* key buffer */ + VSTRING *val_buf; /* value buffer */ +} DICT_LMDB; + + /* + * The LMDB database filename suffix happens to equal our DICT_TYPE_LMDB + * prefix, but that doesn't mean it is kosher to use DICT_TYPE_LMDB where a + * suffix is needed, so we define an explicit suffix here. + */ +#define DICT_LMDB_SUFFIX "lmdb" + + /* + * Make a safe string copy that is guaranteed to be null-terminated. + */ +#define SCOPY(buf, data, size) \ + vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) + + /* + * Postfix writers recover from a "map full" error by increasing the memory + * map size with a factor DICT_LMDB_SIZE_INCR (up to some limit) and + * retrying the transaction. + * + * Each dict(3) API call is retried no more than a few times. For bulk-mode + * transactions the number of retries is proportional to the size of the + * address space. + * + * We do not expose these details to the Postfix user interface. The purpose of + * Postfix is to solve problems, not punt them to the user. + */ +#define DICT_LMDB_SIZE_INCR 2 /* Increase size by 1 bit on retry */ +#define DICT_LMDB_SIZE_MAX SSIZE_T_MAX + +#define DICT_LMDB_API_RETRY_LIMIT 2 /* Retries per dict(3) API call */ +#define DICT_LMDB_BULK_RETRY_LIMIT \ + ((int) (2 * sizeof(size_t) * CHAR_BIT)) /* Retries per bulk-mode + * transaction */ + +/* #define msg_verbose 1 */ + +/* dict_lmdb_lookup - find database entry */ + +static const char *dict_lmdb_lookup(DICT *dict, const char *name) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict; + MDB_val mdb_key; + MDB_val mdb_value; + const char *result = 0; + int status; + ssize_t klen; + + dict->error = 0; + klen = strlen(name); + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_lmdb_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict->name); + + /* + * See if this LMDB file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + mdb_key.mv_data = (void *) name; + mdb_key.mv_size = klen + 1; + status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value); + if (status == 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data, + mdb_value.mv_size); + } else if (status != MDB_NOTFOUND) { + msg_fatal("error reading %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } + } + + /* + * See if this LMDB file was written with no null byte appended to key + * and value. + */ + if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + mdb_key.mv_data = (void *) name; + mdb_key.mv_size = klen; + status = slmdb_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value); + if (status == 0) { + dict->flags &= ~DICT_FLAG_TRY1NULL; + result = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data, + mdb_value.mv_size); + } else if (status != MDB_NOTFOUND) { + msg_fatal("error reading %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } + } + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict->name); + + return (result); +} + +/* dict_lmdb_update - add or update database entry */ + +static int dict_lmdb_update(DICT *dict, const char *name, const char *value) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict; + MDB_val mdb_key; + MDB_val mdb_value; + int status; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_lmdb_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + mdb_key.mv_data = (void *) name; + mdb_value.mv_data = (void *) value; + mdb_key.mv_size = strlen(name); + mdb_value.mv_size = strlen(value); + + /* + * If undecided about appending a null byte to key and value, choose a + * default depending on the platform. + */ + if ((dict->flags & DICT_FLAG_TRY1NULL) + && (dict->flags & DICT_FLAG_TRY0NULL)) { +#ifdef LMDB_NO_TRAILING_NULL + dict->flags &= ~DICT_FLAG_TRY1NULL; +#else + dict->flags &= ~DICT_FLAG_TRY0NULL; +#endif + } + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + mdb_key.mv_size++; + mdb_value.mv_size++; + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict->name); + + /* + * Do the update. + */ + status = slmdb_put(&dict_lmdb->slmdb, &mdb_key, &mdb_value, + (dict->flags & DICT_FLAG_DUP_REPLACE) ? 0 : MDB_NOOVERWRITE); + if (status != 0) { + if (status == MDB_KEYEXIST) { + if (dict->flags & DICT_FLAG_DUP_IGNORE) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s:%s: duplicate entry: \"%s\"", + dict_lmdb->dict.type, dict_lmdb->dict.name, name); + else + msg_fatal("%s:%s: duplicate entry: \"%s\"", + dict_lmdb->dict.type, dict_lmdb->dict.name, name); + } else { + msg_fatal("error updating %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict->name); + + return (status); +} + +/* dict_lmdb_delete - delete one entry from the dictionary */ + +static int dict_lmdb_delete(DICT *dict, const char *name) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict; + MDB_val mdb_key; + int status = 1; + ssize_t klen; + + dict->error = 0; + klen = strlen(name); + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_lmdb_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict->name); + + /* + * See if this LMDB file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + mdb_key.mv_data = (void *) name; + mdb_key.mv_size = klen + 1; + status = slmdb_del(&dict_lmdb->slmdb, &mdb_key); + if (status != 0) { + if (status == MDB_NOTFOUND) + status = 1; + else + msg_fatal("error deleting from %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } else { + dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */ + } + } + + /* + * See if this LMDB file was written with no null byte appended to key + * and value. + */ + if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + mdb_key.mv_data = (void *) name; + mdb_key.mv_size = klen; + status = slmdb_del(&dict_lmdb->slmdb, &mdb_key); + if (status != 0) { + if (status == MDB_NOTFOUND) + status = 1; + else + msg_fatal("error deleting from %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } else { + dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */ + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict->name); + + return (status); +} + +/* dict_lmdb_sequence - traverse the dictionary */ + +static int dict_lmdb_sequence(DICT *dict, int function, + const char **key, const char **value) +{ + const char *myname = "dict_lmdb_sequence"; + DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict; + MDB_val mdb_key; + MDB_val mdb_value; + MDB_cursor_op op; + int status; + + dict->error = 0; + + /* + * Determine the seek function. + */ + switch (function) { + case DICT_SEQ_FUN_FIRST: + op = MDB_FIRST; + break; + case DICT_SEQ_FUN_NEXT: + op = MDB_NEXT; + break; + default: + msg_panic("%s: invalid function: %d", myname, function); + } + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict->name); + + /* + * Database lookup. + */ + status = slmdb_cursor_get(&dict_lmdb->slmdb, &mdb_key, &mdb_value, op); + + switch (status) { + + /* + * Copy the key and value so they are guaranteed null terminated. + */ + case 0: + *key = SCOPY(dict_lmdb->key_buf, mdb_key.mv_data, mdb_key.mv_size); + if (mdb_value.mv_data != 0 && mdb_value.mv_size > 0) + *value = SCOPY(dict_lmdb->val_buf, mdb_value.mv_data, + mdb_value.mv_size); + else + *value = ""; /* XXX */ + break; + + /* + * End-of-database. + */ + case MDB_NOTFOUND: + status = 1; + /* Not: mdb_cursor_close(). Wrong abstraction level. */ + break; + + /* + * Bust. + */ + default: + msg_fatal("error seeking %s:%s: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, + mdb_strerror(status)); + } + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict->name); + + return (status); +} + +/* dict_lmdb_close - disassociate from data base */ + +static void dict_lmdb_close(DICT *dict) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) dict; + + slmdb_close(&dict_lmdb->slmdb); + if (dict_lmdb->key_buf) + vstring_free(dict_lmdb->key_buf); + if (dict_lmdb->val_buf) + vstring_free(dict_lmdb->val_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_lmdb_longjmp - repeat bulk transaction */ + +static void dict_lmdb_longjmp(void *context, int val) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) context; + + dict_longjmp(&dict_lmdb->dict, val); +} + +/* dict_lmdb_notify - debug logging */ + +static void dict_lmdb_notify(void *context, int error_code,...) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) context; + va_list ap; + + va_start(ap, error_code); + switch (error_code) { + case MDB_SUCCESS: + msg_info("database %s:%s: using size limit %lu during open", + dict_lmdb->dict.type, dict_lmdb->dict.name, + (unsigned long) va_arg(ap, size_t)); + break; + case MDB_MAP_FULL: + msg_info("database %s:%s: using size limit %lu after MDB_MAP_FULL", + dict_lmdb->dict.type, dict_lmdb->dict.name, + (unsigned long) va_arg(ap, size_t)); + break; + case MDB_MAP_RESIZED: + msg_info("database %s:%s: using size limit %lu after MDB_MAP_RESIZED", + dict_lmdb->dict.type, dict_lmdb->dict.name, + (unsigned long) va_arg(ap, size_t)); + break; + case MDB_READERS_FULL: + msg_info("database %s:%s: pausing after MDB_READERS_FULL", + dict_lmdb->dict.type, dict_lmdb->dict.name); + break; + default: + msg_warn("unknown MDB error code: %d", error_code); + break; + } + va_end(ap); +} + +/* dict_lmdb_assert - report LMDB internal assertion failure */ + +static void dict_lmdb_assert(void *context, const char *text) +{ + DICT_LMDB *dict_lmdb = (DICT_LMDB *) context; + + msg_fatal("%s:%s: internal error: %s", + dict_lmdb->dict.type, dict_lmdb->dict.name, text); +} + +/* dict_lmdb_open - open LMDB data base */ + +DICT *dict_lmdb_open(const char *path, int open_flags, int dict_flags) +{ + DICT_LMDB *dict_lmdb; + DICT *dict; + struct stat st; + SLMDB slmdb; + char *mdb_path; + int mdb_flags, slmdb_flags, status; + int db_fd; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_LMDB_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + myfree(mdb_path); \ + return (__d); \ + } while (0) + + mdb_path = concatenate(path, "." DICT_TYPE_LMDB, (char *) 0); + + /* + * Impedance adapters. + */ + mdb_flags = MDB_NOSUBDIR | MDB_NOLOCK; + if (open_flags == O_RDONLY) + mdb_flags |= MDB_RDONLY; + + slmdb_flags = 0; + if (dict_flags & DICT_FLAG_BULK_UPDATE) + slmdb_flags |= SLMDB_FLAG_BULK; + + /* + * Security violation. + * + * By default, LMDB 0.9.9 writes uninitialized heap memory to a + * world-readable database file, as chunks of up to 4096 bytes. This is a + * huge memory disclosure vulnerability: memory content that a program + * does not intend to share ends up in a world-readable file. The content + * of uninitialized heap memory depends on program execution history. + * That history includes code execution in other libraries that are + * linked into the program. + * + * This is a problem whenever the user who writes the database file differs + * from the user who reads the database file. For example, a privileged + * writer and an unprivileged reader. In the case of Postfix, the + * postmap(1) and postalias(1) commands would leak uninitialized heap + * memory, as chunks of up to 4096 bytes, from a root-privileged process + * that writes to a database file, to unprivileged processes that read + * from that database file. + * + * As a workaround the postmap(1) and postalias(1) commands turn on + * MDB_WRITEMAP which disables the use of malloc() in LMDB. However, that + * does not address several disclosures of stack memory. We don't enable + * this workaround for Postfix databases are maintained by Postfix daemon + * processes, because those are accessible only by the postfix user. + * + * LMDB 0.9.10 by default does not write uninitialized heap memory to file + * (specify MDB_NOMEMINIT to revert that change). We use the MDB_WRITEMAP + * workaround for older LMDB versions. + */ +#ifndef MDB_NOMEMINIT + if (dict_flags & DICT_FLAG_BULK_UPDATE) /* XXX Good enough */ + mdb_flags |= MDB_WRITEMAP; +#endif + + /* + * Gracefully handle most database open errors. + */ + if ((status = slmdb_init(&slmdb, dict_lmdb_map_size, DICT_LMDB_SIZE_INCR, + DICT_LMDB_SIZE_MAX)) != 0 + || (status = slmdb_open(&slmdb, mdb_path, open_flags, mdb_flags, + slmdb_flags)) != 0) { + /* This leaks a little memory that would have been used otherwise. */ + dict = dict_surrogate(DICT_TYPE_LMDB, path, open_flags, dict_flags, + "open database %s: %s", mdb_path, mdb_strerror(status)); + DICT_LMDB_OPEN_RETURN(dict); + } + + /* + * XXX Persistent locking belongs in mkmap_lmdb. + * + * We just need to acquire exclusive access momentarily. This establishes + * that no readers are accessing old (obsoleted by copy-on-write) txn + * snapshots, so we are free to reuse all eligible old pages. Downgrade + * the lock right after acquiring it. This is sufficient to keep out + * other writers until we are done. + */ + db_fd = slmdb_fd(&slmdb); + if (dict_flags & DICT_FLAG_BULK_UPDATE) { + if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", mdb_path); + if (myflock(db_fd, MYFLOCK_STYLE_FCNTL, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: unlock dictionary: %m", mdb_path); + } + + /* + * Bundle up. From here on no more assignments to slmdb. + */ + dict_lmdb = (DICT_LMDB *) dict_alloc(DICT_TYPE_LMDB, path, sizeof(*dict_lmdb)); + dict_lmdb->slmdb = slmdb; + dict_lmdb->dict.lookup = dict_lmdb_lookup; + dict_lmdb->dict.update = dict_lmdb_update; + dict_lmdb->dict.delete = dict_lmdb_delete; + dict_lmdb->dict.sequence = dict_lmdb_sequence; + dict_lmdb->dict.close = dict_lmdb_close; + + if (fstat(db_fd, &st) < 0) + msg_fatal("dict_lmdb_open: fstat: %m"); + dict_lmdb->dict.lock_fd = dict_lmdb->dict.stat_fd = db_fd; + dict_lmdb->dict.lock_type = MYFLOCK_STYLE_FCNTL; + dict_lmdb->dict.mtime = st.st_mtime; + dict_lmdb->dict.owner.uid = st.st_uid; + dict_lmdb->dict.owner.status = (st.st_uid != 0); + + dict_lmdb->key_buf = 0; + dict_lmdb->val_buf = 0; + + /* + * Warn if the source file is newer than the indexed file, except when + * the source file changed only seconds ago. + */ + if ((dict_flags & DICT_FLAG_LOCK) != 0 + && stat(path, &st) == 0 + && st.st_mtime > dict_lmdb->dict.mtime + && st.st_mtime < time((time_t *) 0) - 100) + msg_warn("database %s is older than source file %s", mdb_path, path); + +#define DICT_LMDB_IMPL_FLAGS (DICT_FLAG_FIXED | DICT_FLAG_MULTI_WRITER) + + dict_lmdb->dict.flags = dict_flags | DICT_LMDB_IMPL_FLAGS; + if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) + dict_lmdb->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_lmdb->dict.fold_buf = vstring_alloc(10); + + if (dict_flags & DICT_FLAG_BULK_UPDATE) + dict_jmp_alloc(&dict_lmdb->dict); + + /* + * The following requests return an error result only if we have serious + * memory corruption problem. + */ + if (slmdb_control(&dict_lmdb->slmdb, + CA_SLMDB_CTL_API_RETRY_LIMIT(DICT_LMDB_API_RETRY_LIMIT), + CA_SLMDB_CTL_BULK_RETRY_LIMIT(DICT_LMDB_BULK_RETRY_LIMIT), + CA_SLMDB_CTL_LONGJMP_FN(dict_lmdb_longjmp), + CA_SLMDB_CTL_NOTIFY_FN(msg_verbose ? + dict_lmdb_notify : (SLMDB_NOTIFY_FN) 0), + CA_SLMDB_CTL_ASSERT_FN(dict_lmdb_assert), + CA_SLMDB_CTL_CB_CONTEXT((void *) dict_lmdb), + CA_SLMDB_CTL_END) != 0) + msg_panic("dict_lmdb_open: slmdb_control: %m"); + + if (msg_verbose) + dict_lmdb_notify((void *) dict_lmdb, MDB_SUCCESS, + slmdb_curr_limit(&dict_lmdb->slmdb)); + + DICT_LMDB_OPEN_RETURN(DICT_DEBUG (&dict_lmdb->dict)); +} + +#endif diff --git a/src/util/dict_lmdb.h b/src/util/dict_lmdb.h new file mode 100644 index 0000000..ccc165a --- /dev/null +++ b/src/util/dict_lmdb.h @@ -0,0 +1,43 @@ +#ifndef _DICT_LMDB_H_INCLUDED_ +#define _DICT_LMDB_H_INCLUDED_ + +/*++ +/* NAME +/* dict_lmdb 3h +/* SUMMARY +/* dictionary manager interface to OpenLDAP LMDB files +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_LMDB "lmdb" + +extern DICT *dict_lmdb_open(const char *, int, int); + + /* + * XXX Should be part of the DICT interface. + */ +extern size_t dict_lmdb_map_size; + + /* Minimum size without SIGSEGV. */ +#define DEFINE_DICT_LMDB_MAP_SIZE size_t dict_lmdb_map_size = 8192 + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Howard Chu +/* Symas Corporation +/*--*/ + +#endif diff --git a/src/util/dict_ni.c b/src/util/dict_ni.c new file mode 100644 index 0000000..3f62559 --- /dev/null +++ b/src/util/dict_ni.c @@ -0,0 +1,194 @@ +/*++ +/* NAME +/* dict_ni 3 +/* SUMMARY +/* dictionary manager interface to NetInfo +/* SYNOPSIS +/* #include +/* +/* DICT *dict_ni_open(path, dummy, dict_flags) +/* char *path; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_ni_open() `opens' the named NetInfo database. The result is +/* a pointer to a structure that can be used to access the dictionary +/* using the generic methods documented in dict_open(3). +/* DIAGNOSTICS +/* dict_ni_register() returns 0 in case of success, -1 in case +/* of problems. +/* Fatal errors: NetInfo errors, out of memory. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* netinfo(3N) data base subroutines +/* AUTHOR(S) +/* Pieter Schoenmakers +/* Eindhoven University of Technology +/* P.O. Box 513 +/* 5600 MB Eindhoven +/* The Netherlands +/*--*/ + +#include "sys_defs.h" + +#ifdef HAS_NETINFO + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include "dict.h" +#include "dict_ni.h" +#include "msg.h" +#include "mymalloc.h" +#include "stringops.h" + +typedef struct { + DICT dict; /* my super */ + char *path; /* directory path */ +} DICT_NI; + + /* + * We'd like other possibilities, but that is not possible in the current + * dictionary setup... An example of a different setup: use `members' for + * multi-valued lookups (to be compatible with /aliases), and `value' for + * single-valued tables. + */ +#define NETINFO_PROP_KEY "name" +#define NETINFO_PROP_VALUE "members" +#define NETINFO_VALUE_SEP "," + +#define NETINFO_MAX_DOMAIN_DEPTH 100 + +/* Hard worker doing lookups. Returned value is statically allocated and + reused each call. */ +static const char *dict_ni_do_lookup(char *path, char *key_prop, + const char *key_value, char *val_prop) +{ + unsigned int result_cap = 0; + static char *result = 0; + + char *return_val = 0; + ni_namelist values; + int depth = 0; + void *domain; + void *next_domain; + char *query; + ni_status r; + ni_id dir; + + if (msg_verbose) + msg_info("ni_lookup %s %s=%s", path, key_prop, key_value); + + r = ni_open(NULL, ".", &domain); + if (r != NI_OK) { + msg_warn("ni_open `.': %d", r); + return NULL; + } + query = mymalloc(strlen(path) + strlen(key_prop) + 3 + strlen(key_value)); + sprintf(query, "%s/%s=%s", path, key_prop, key_value); + + for (;;) { + + /* + * What does it _mean_ if we find the directory but not the value? + */ + if (ni_pathsearch(domain, &dir, query) == NI_OK + && ni_lookupprop(domain, &dir, val_prop, &values) == NI_OK) + if (values.ni_namelist_len <= 0) + ni_namelist_free(&values); + else { + unsigned int i, l, n; + + for (i = l = 0; i < values.ni_namelist_len; i++) + l += 1 + strlen(values.ni_namelist_val[i]); + if (result_cap < l) { + if (result) + myfree(result); + result_cap = l + 100; + result = mymalloc(result_cap); + } + for (i = l = 0; i < values.ni_namelist_len; i++) { + n = strlen(values.ni_namelist_val[i]); + memcpy(result + l, values.ni_namelist_val[i], n); + l += n; + if (i < values.ni_namelist_len - 1) + result[l++] = ','; + } + result[l] = '\0'; + return_val = result; + break; + } + + if (++depth >= NETINFO_MAX_DOMAIN_DEPTH) { + msg_warn("ni_open: domain depth limit"); + break; + } + r = ni_open(domain, "..", &next_domain); + if (r != NI_OK) { + if (r != NI_FAILED) + msg_warn("ni_open `..': %d", r); + break; + } + ni_free(domain); + domain = next_domain; + } + + ni_free(domain); + myfree(query); + + return return_val; +} + +/* dict_ni_lookup - find table entry */ + +static const char *dict_ni_lookup(DICT *dict, const char *key) +{ + DICT_NI *d = (DICT_NI *) dict; + + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + return dict_ni_do_lookup(d->dict.name, NETINFO_PROP_KEY, + key, NETINFO_PROP_VALUE); +} + +/* dict_ni_close - disassociate from NetInfo map */ + +static void dict_ni_close(DICT *dict) +{ + DICT_NI *d = (DICT_NI *) dict; + + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_ni_open - create association with NetInfo map */ + +DICT *dict_ni_open(const char *path, int unused_flags, int dict_flags) +{ + DICT_NI *d = (void *) dict_alloc(DICT_TYPE_NETINFO, path, sizeof(*d)); + + d->dict.lookup = dict_ni_lookup; + d->dict.close = dict_ni_close; + d->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + d->dict.fold_buf = vstring_alloc(10); + d->dict.owner.status = DICT_OWNER_TRUSTED; + + return (DICT_DEBUG (&d->dict)); +} + +#endif diff --git a/src/util/dict_ni.h b/src/util/dict_ni.h new file mode 100644 index 0000000..652b422 --- /dev/null +++ b/src/util/dict_ni.h @@ -0,0 +1,34 @@ +#ifndef _DICT_NI_H_INCLUDED_ +#define _DICT_NI_H_INCLUDED_ + +/*++ +/* NAME +/* dict_ni 3h +/* SUMMARY +/* dictionary manager interface to NetInfo maps +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_NETINFO "netinfo" + +extern DICT *dict_ni_open(const char *, int, int); + +/* AUTHOR(S) +/* Pieter Schoenmakers +/* Eindhoven University of Technology +/* P.O. Box 513 +/* 5600 MB Eindhoven +/* The Netherlands +/*--*/ + +#endif diff --git a/src/util/dict_nis.c b/src/util/dict_nis.c new file mode 100644 index 0000000..357011f --- /dev/null +++ b/src/util/dict_nis.c @@ -0,0 +1,247 @@ +/*++ +/* NAME +/* dict_nis 3 +/* SUMMARY +/* dictionary manager interface to NIS maps +/* SYNOPSIS +/* #include +/* +/* DICT *dict_nis_open(map, open_flags, dict_flags) +/* const char *map; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_nis_open() makes the specified NIS map accessible via +/* the generic dictionary operations described in dict_open(3). +/* SEE ALSO +/* dict(3) generic dictionary manager +/* DIAGNOSTICS +/* Fatal errors: out of memory, attempt to update NIS map. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +#ifdef HAS_NIS + +#include +#ifndef YPERR_BUSY +#define YPERR_BUSY 16 +#endif +#ifndef YPERR_ACCESS +#define YPERR_ACCESS 15 +#endif + +#endif + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "vstring.h" +#include "stringops.h" +#include "dict.h" +#include "dict_nis.h" + +#ifdef HAS_NIS + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ +} DICT_NIS; + + /* + * Class variables, so that multiple maps can share this info. + */ +static char dict_nis_disabled[1]; +static char *dict_nis_domain; + +/* dict_nis_init - NIS binding */ + +static void dict_nis_init(void) +{ + const char *myname = "dict_nis_init"; + + if (yp_get_default_domain(&dict_nis_domain) != 0 + || dict_nis_domain == 0 || *dict_nis_domain == 0 + || strcasecmp(dict_nis_domain, "(none)") == 0) { + dict_nis_domain = dict_nis_disabled; + msg_warn("%s: NIS domain name not set - NIS lookups disabled", myname); + } + if (msg_verbose) + msg_info("%s: NIS domain %s", myname, dict_nis_domain); +} + +/* dict_nis_strerror - map error number to string */ + +static char *dict_nis_strerror(int err) +{ + + /* + * Grr. There should be a standard function for this. + */ + switch (err) { + case YPERR_BADARGS: + return ("args to function are bad"); + case YPERR_RPC: + return ("RPC failure - domain has been unbound"); + case YPERR_DOMAIN: + return ("can't bind to server on this domain"); + case YPERR_MAP: + return ("no such map in server's domain"); + case YPERR_KEY: + return ("no such key in map"); + case YPERR_YPERR: + return ("internal yp server or client error"); + case YPERR_RESRC: + return ("resource allocation failure"); + case YPERR_NOMORE: + return ("no more records in map database"); + case YPERR_PMAP: + return ("can't communicate with portmapper"); + case YPERR_YPBIND: + return ("can't communicate with ypbind"); + case YPERR_YPSERV: + return ("can't communicate with ypserv"); + case YPERR_NODOM: + return ("local domain name not set"); + case YPERR_BADDB: + return ("yp database is bad"); + case YPERR_VERS: + return ("yp version mismatch"); + case YPERR_ACCESS: + return ("access violation"); + case YPERR_BUSY: + return ("database busy"); + default: + return ("unknown NIS lookup error"); + } +} + +/* dict_nis_lookup - find table entry */ + +static const char *dict_nis_lookup(DICT *dict, const char *key) +{ + DICT_NIS *dict_nis = (DICT_NIS *) dict; + static char *result; + int result_len; + int err; + static VSTRING *buf; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_nis_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + if (dict_nis_domain == dict_nis_disabled) + return (0); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * See if this NIS map was written with one null byte appended to key and + * value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + err = yp_match(dict_nis_domain, dict_nis->dict.name, + (void *) key, strlen(key) + 1, + &result, &result_len); + if (err == 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + return (result); + } + } + + /* + * See if this NIS map was written with no null byte appended to key and + * value. This should never be the case, but better play safe. + */ + if (dict->flags & DICT_FLAG_TRY0NULL) { + err = yp_match(dict_nis_domain, dict_nis->dict.name, + (void *) key, strlen(key), + &result, &result_len); + if (err == 0) { + dict->flags &= ~DICT_FLAG_TRY1NULL; + if (buf == 0) + buf = vstring_alloc(10); + vstring_strncpy(buf, result, result_len); + return (vstring_str(buf)); + } + } + + /* + * When the NIS lookup fails for reasons other than "key not found", keep + * logging warnings, and hope that someone will eventually notice the + * problem and fix it. + */ + if (err != YPERR_KEY) { + msg_warn("lookup %s, NIS domain %s, map %s: %s", + key, dict_nis_domain, dict_nis->dict.name, + dict_nis_strerror(err)); + dict->error = DICT_ERR_RETRY; + } + return (0); +} + +/* dict_nis_close - close NIS map */ + +static void dict_nis_close(DICT *dict) +{ + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_nis_open - open NIS map */ + +DICT *dict_nis_open(const char *map, int open_flags, int dict_flags) +{ + DICT_NIS *dict_nis; + + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_NIS, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_NIS, map)); + + dict_nis = (DICT_NIS *) dict_alloc(DICT_TYPE_NIS, map, sizeof(*dict_nis)); + dict_nis->dict.lookup = dict_nis_lookup; + dict_nis->dict.close = dict_nis_close; + dict_nis->dict.flags = dict_flags | DICT_FLAG_FIXED; + if ((dict_flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + dict_nis->dict.flags |= (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL); + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_nis->dict.fold_buf = vstring_alloc(10); + if (dict_nis_domain == 0) + dict_nis_init(); + dict_nis->dict.owner.status = DICT_OWNER_TRUSTED; + return (DICT_DEBUG (&dict_nis->dict)); +} + +#endif diff --git a/src/util/dict_nis.h b/src/util/dict_nis.h new file mode 100644 index 0000000..97aa7cd --- /dev/null +++ b/src/util/dict_nis.h @@ -0,0 +1,37 @@ +#ifndef _DIST_NIS_H_INCLUDED_ +#define _DIST_NIS_H_INCLUDED_ + +/*++ +/* NAME +/* dict_nis 3h +/* SUMMARY +/* dictionary manager interface to NIS maps +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_NIS "nis" + +extern DICT *dict_nis_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_nisplus.c b/src/util/dict_nisplus.c new file mode 100644 index 0000000..1f5e126 --- /dev/null +++ b/src/util/dict_nisplus.c @@ -0,0 +1,304 @@ +/*++ +/* NAME +/* dict_nisplus 3 +/* SUMMARY +/* dictionary manager interface to NIS+ maps +/* SYNOPSIS +/* #include +/* +/* DICT *dict_nisplus_open(map, open_flags, dict_flags) +/* const char *map; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_nisplus_open() makes the specified NIS+ map accessible via +/* the generic dictionary operations described in dict_open(3). +/* The \fIdummy\fR argument is not used. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* DIAGNOSTICS +/* Fatal errors: +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Geoff Gibbs +/* UK-HGMP-RC +/* Hinxton +/* Cambridge +/* CB10 1SB, UK +/* +/* based on the code for dict_nis.c et al by :- +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#ifdef HAS_NISPLUS +#include /* for nis_list */ +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +#ifdef HAS_NISPLUS + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + char *template; /* parsed query template */ + int column; /* NIS+ field number (start at 1) */ +} DICT_NISPLUS; + + /* + * Begin quote from nis+(1): + * + * The following text represents a context-free grammar that defines the + * set of legal NIS+ names. The terminals in this grammar are the + * characters `.' (dot), `[' (open bracket), `]' (close bracket), `,' + * (comma), `=' (equals) and whitespace. Angle brackets (`<' and `>'), + * which delineate non- terminals, are not part of the grammar. The + * character `|' (vertical bar) is used to separate alternate productions + * and should be read as ``this production OR this production''. + * + * name ::= . | | + * + * simple name ::= . | . + * + * indexed name ::= , + * + * search criterion ::= [ ] + * + * attribute list ::= | , + * + * attribute ::= = + * + * string ::= ISO Latin 1 character set except the character + * '/' (slash). The initial character may not be a terminal character or + * the characters '@' (at), '+' (plus), or (`-') hyphen. + * + * Terminals that appear in strings must be quoted with `"' (double quote). + * The `"' character may be quoted by quoting it with itself `""'. + * + * End quote fron nis+(1). + * + * This NIS client always quotes the entire query string (the value part of + * [attribute=value],file.domain.) so the issue with initial characters + * should not be applicable. One wonders what restrictions are applicable + * when a string is quoted, but the manual doesn't specify what can appear + * between quotes, and we don't want to get burned. + */ + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* dict_nisplus_lookup - find table entry */ + +static const char *dict_nisplus_lookup(DICT *dict, const char *key) +{ + const char *myname = "dict_nisplus_lookup"; + DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict; + static VSTRING *quoted_key; + static VSTRING *query; + static VSTRING *retval; + nis_result *reply; + int count; + const char *cp; + int last_col; + int ch; + + dict->error = 0; + + /* + * Initialize. + */ + if (quoted_key == 0) { + query = vstring_alloc(100); + retval = vstring_alloc(100); + quoted_key = vstring_alloc(100); + } + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Check that the lookup key does not contain characters disallowed by + * nis+(1). + * + * XXX Many client implementations don't seem to care about disallowed + * characters. + */ + VSTRING_RESET(quoted_key); + VSTRING_ADDCH(quoted_key, '"'); + for (cp = key; (ch = *(unsigned const char *) cp) != 0; cp++) { + if ((ISASCII(ch) && !ISPRINT(ch)) || (ch > 126 && ch < 160)) { + msg_warn("map %s:%s: lookup key with non-printing character 0x%x:" + " ignoring this request", + dict->type, dict->name, ch); + return (0); + } else if (ch == '"') { + VSTRING_ADDCH(quoted_key, '"'); + } + VSTRING_ADDCH(quoted_key, ch); + } + VSTRING_ADDCH(quoted_key, '"'); + VSTRING_TERMINATE(quoted_key); + + /* + * Plug the key into the query template, which typically looks something + * like the following: [alias=%s],mail_aliases.org_dir.my.nisplus.domain. + * + * XXX The nis+ documentation defines a length limit for simple names like + * a.b.c., but defines no length limit for (the components of) indexed + * names such as [x=y],a.b.c. Our query length is limited because Postfix + * addresses (in envelopes or in headers) have a finite length. + */ + vstring_sprintf(query, dict_nisplus->template, STR(quoted_key)); + reply = nis_list(STR(query), FOLLOW_LINKS | FOLLOW_PATH, NULL, NULL); + + /* + * When lookup succeeds, the result may be ambiguous, or the requested + * column may not exist. + */ + if (reply->status == NIS_SUCCESS) { + if ((count = NIS_RES_NUMOBJ(reply)) != 1) { + msg_warn("ambiguous match (%d results) for %s in NIS+ map %s:" + " ignoring this request", + count, key, dict_nisplus->dict.name); + nis_freeresult(reply); + return (0); + } else { + last_col = NIS_RES_OBJECT(reply)->zo_data + .objdata_u.en_data.en_cols.en_cols_len - 1; + if (dict_nisplus->column > last_col) + msg_fatal("requested column %d > max column %d in table %s", + dict_nisplus->column, last_col, + dict_nisplus->dict.name); + vstring_strcpy(retval, + NIS_RES_OBJECT(reply)->zo_data.objdata_u + .en_data.en_cols.en_cols_val[dict_nisplus->column] + .ec_value.ec_value_val); + if (msg_verbose) + msg_info("%s: %s, column %d -> %s", myname, STR(query), + dict_nisplus->column, STR(retval)); + nis_freeresult(reply); + return (STR(retval)); + } + } + + /* + * When the NIS+ lookup fails for reasons other than "key not found", + * keep logging warnings, and hope that someone will eventually notice + * the problem and fix it. + */ + else { + if (reply->status != NIS_NOTFOUND + && reply->status != NIS_PARTIAL) { + msg_warn("lookup %s, NIS+ map %s: %s", + key, dict_nisplus->dict.name, + nis_sperrno(reply->status)); + dict->error = DICT_ERR_RETRY; + } else { + if (msg_verbose) + msg_info("%s: not found: query %s", myname, STR(query)); + } + nis_freeresult(reply); + return (0); + } +} + +/* dict_nisplus_close - close NISPLUS map */ + +static void dict_nisplus_close(DICT *dict) +{ + DICT_NISPLUS *dict_nisplus = (DICT_NISPLUS *) dict; + + myfree(dict_nisplus->template); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_nisplus_open - open NISPLUS map */ + +DICT *dict_nisplus_open(const char *map, int open_flags, int dict_flags) +{ + const char *myname = "dict_nisplus_open"; + DICT_NISPLUS *dict_nisplus; + char *col_field; + + /* + * Sanity check. + */ + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_NISPLUS, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_NISPLUS, map)); + + /* + * Initialize. This is a read-only map with fixed strings, not with + * regular expressions. + */ + dict_nisplus = (DICT_NISPLUS *) + dict_alloc(DICT_TYPE_NISPLUS, map, sizeof(*dict_nisplus)); + dict_nisplus->dict.lookup = dict_nisplus_lookup; + dict_nisplus->dict.close = dict_nisplus_close; + dict_nisplus->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_nisplus->dict.fold_buf = vstring_alloc(10); + dict_nisplus->dict.owner.status = DICT_OWNER_TRUSTED; + + /* + * Convert the query template into an indexed name and column number. The + * query template looks like: + * + * [attribute=%s;attribute=value...];simple.name.:column + * + * One instance of %s gets to be replaced by a version of the lookup key; + * other attributes must specify fixed values. The reason for using ';' + * is that the comma character is special in main.cf. When no column + * number is given at the end of the map name, we use a default column. + */ + dict_nisplus->template = mystrdup(map); + translit(dict_nisplus->template, ";", ","); + if ((col_field = strstr(dict_nisplus->template, ".:")) != 0) { + col_field[1] = 0; + col_field += 2; + if (!alldig(col_field) || (dict_nisplus->column = atoi(col_field)) < 1) + msg_fatal("bad column field in NIS+ map name: %s", map); + } else { + dict_nisplus->column = 1; + } + if (msg_verbose) + msg_info("%s: opened NIS+ table %s for column %d", + myname, dict_nisplus->template, dict_nisplus->column); + return (DICT_DEBUG (&dict_nisplus->dict)); +} + +#endif diff --git a/src/util/dict_nisplus.h b/src/util/dict_nisplus.h new file mode 100644 index 0000000..1fd31c9 --- /dev/null +++ b/src/util/dict_nisplus.h @@ -0,0 +1,37 @@ +#ifndef _DICT_NISPLUS_H_INCLUDED_ +#define _DICT_NISPLUS_H_INCLUDED_ + +/*++ +/* NAME +/* dict_nisplus 3h +/* SUMMARY +/* dictionary manager interface to NIS+ maps +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_NISPLUS "nisplus" + +extern DICT *dict_nisplus_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_open.c b/src/util/dict_open.c new file mode 100644 index 0000000..afea391 --- /dev/null +++ b/src/util/dict_open.c @@ -0,0 +1,600 @@ +/*++ +/* NAME +/* dict_open 3 +/* SUMMARY +/* low-level dictionary interface +/* SYNOPSIS +/* #include +/* +/* DICT *dict_open(dict_spec, open_flags, dict_flags) +/* const char *dict_spec; +/* int open_flags; +/* int dict_flags; +/* +/* DICT *dict_open3(dict_type, dict_name, open_flags, dict_flags) +/* const char *dict_type; +/* const char *dict_name; +/* int open_flags; +/* int dict_flags; +/* +/* int dict_put(dict, key, value) +/* DICT *dict; +/* const char *key; +/* const char *value; +/* +/* const char *dict_get(dict, key) +/* DICT *dict; +/* const char *key; +/* +/* int dict_del(dict, key) +/* DICT *dict; +/* const char *key; +/* +/* int dict_seq(dict, func, key, value) +/* DICT *dict; +/* int func; +/* const char **key; +/* const char **value; +/* +/* void dict_close(dict) +/* DICT *dict; +/* +/* typedef DICT *(*DICT_OPEN_FN) (const char *, int, int); +/* +/* dict_open_register(type, open) +/* const char *type; +/* DICT_OPEN_FN open; +/* +/* typedef DICT_OPEN_FN (*DICT_OPEN_EXTEND_FN)(const char *type); +/* +/* DICT_OPEN_EXTEND_FN dict_open_extend(call_back) +/* DICT_OPEN_EXTEND_FN call_back; +/* +/* ARGV *dict_mapnames() +/* +/* typedef ARGV *(*DICT_MAPNAMES_EXTEND_FN)(ARGV *names); +/* +/* DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(call_back) +/* DICT_MAPNAMES_EXTEND_FN call_back; +/* +/* int dict_isjmp(dict) +/* DICT *dict; +/* +/* int dict_setjmp(dict) +/* DICT *dict; +/* +/* int dict_longjmp(dict, val) +/* DICT *dict; +/* int val; +/* +/* void dict_type_override(dict, type) +/* DICT *dict; +/* const char *type; +/* DESCRIPTION +/* This module implements a low-level interface to multiple +/* physical dictionary types. +/* +/* dict_open() takes a type:name pair that specifies a dictionary type +/* and dictionary name, opens the dictionary, and returns a dictionary +/* handle. The \fIopen_flags\fR arguments are as in open(2). The +/* \fIdict_flags\fR are the bit-wise OR of zero or more of the following: +/* .IP DICT_FLAG_DUP_WARN +/* Warn about duplicate keys, if the underlying database does not +/* support duplicate keys. The default is to terminate with a fatal +/* error. +/* .IP DICT_FLAG_DUP_IGNORE +/* Ignore duplicate keys if the underlying database does not +/* support duplicate keys. The default is to terminate with a fatal +/* error. +/* .IP DICT_FLAG_DUP_REPLACE +/* Replace duplicate keys if the underlying database supports such +/* an operation. The default is to terminate with a fatal error. +/* .IP DICT_FLAG_TRY0NULL +/* With maps where this is appropriate, append no null byte to +/* keys and values. +/* When neither DICT_FLAG_TRY0NULL nor DICT_FLAG_TRY1NULL are +/* specified, the software guesses what format to use for reading; +/* and in the absence of definite information, a system-dependent +/* default is chosen for writing. +/* .IP DICT_FLAG_TRY1NULL +/* With maps where this is appropriate, append one null byte to +/* keys and values. +/* When neither DICT_FLAG_TRY0NULL nor DICT_FLAG_TRY1NULL are +/* specified, the software guesses what format to use for reading; +/* and in the absence of definite information, a system-dependent +/* default is chosen for writing. +/* .IP DICT_FLAG_LOCK +/* With maps where this is appropriate, acquire an exclusive lock +/* before writing, and acquire a shared lock before reading. +/* Release the lock when the operation completes. +/* .IP DICT_FLAG_OPEN_LOCK +/* The behavior of this flag depends on whether a database +/* sets the DICT_FLAG_MULTI_WRITER flag to indicate that it +/* is multi-writer safe. +/* +/* With databases that are not multi-writer safe, dict_open() +/* acquires a persistent exclusive lock, or it terminates with +/* a fatal run-time error. +/* +/* With databases that are multi-writer safe, dict_open() +/* downgrades the DICT_FLAG_OPEN_LOCK flag (persistent lock) +/* to DICT_FLAG_LOCK (temporary lock). +/* .IP DICT_FLAG_FOLD_FIX +/* With databases whose lookup fields are fixed-case strings, +/* fold the search string to lower case before accessing the +/* database. This includes hash:, cdb:, dbm:. nis:, ldap:, +/* *sql. WARNING: case folding is supported only for ASCII or +/* valid UTF-8. +/* .IP DICT_FLAG_FOLD_MUL +/* With databases where one lookup field can match both upper +/* and lower case, fold the search key to lower case before +/* accessing the database. This includes regexp: and pcre:. +/* WARNING: case folding is supported only for ASCII or valid +/* UTF-8. +/* .IP DICT_FLAG_FOLD_ANY +/* Short-hand for (DICT_FLAG_FOLD_FIX | DICT_FLAG_FOLD_MUL). +/* .IP DICT_FLAG_SYNC_UPDATE +/* With file-based maps, flush I/O buffers to file after each update. +/* Thus feature is not supported with some file-based dictionaries. +/* .IP DICT_FLAG_NO_REGSUB +/* Disallow regular expression substitution from the lookup string +/* into the lookup result, to block data injection attacks. +/* .IP DICT_FLAG_NO_PROXY +/* Disallow access through the unprivileged \fBproxymap\fR +/* service, to block privilege escalation attacks. +/* .IP DICT_FLAG_NO_UNAUTH +/* Disallow lookup mechanisms that lack any form of authentication, +/* to block privilege escalation attacks (example: tcp_table; +/* even NIS can be secured to some extent by requiring that +/* the server binds to a privileged port). +/* .IP DICT_FLAG_PARANOID +/* A combination of all the paranoia flags: DICT_FLAG_NO_REGSUB, +/* DICT_FLAG_NO_PROXY and DICT_FLAG_NO_UNAUTH. +/* .IP DICT_FLAG_BULK_UPDATE +/* Enable preliminary code for bulk-mode database updates. +/* The caller must create an exception handler with dict_jmp_alloc() +/* and must trap exceptions from the database client with dict_setjmp(). +/* .IP DICT_FLAG_DEBUG +/* Enable additional logging. +/* .IP DICT_FLAG_UTF8_REQUEST +/* With util_utf8_enable != 0, require that lookup/update/delete +/* keys and values are valid UTF-8. Skip a lookup/update/delete +/* request with a non-UTF-8 key, skip an update request with +/* a non-UTF-8 value, and fail a lookup request with a non-UTF-8 +/* value. +/* .IP DICT_FLAG_SRC_RHS_IS_FILE +/* With dictionaries that are created from source text, each +/* value in the source of a dictionary specifies a list of +/* file names separated by comma and/or whitespace. The file +/* contents are concatenated with a newline inserted between +/* files, and the base64-encoded result is stored under the +/* key. +/* .sp +/* NOTE 1: it is up to the application to decode lookup results +/* with dict_file_lookup() or equivalent (this requires that +/* the dictionary is opened with DICT_FLAG_SRC_RHS_IS_FILE). +/* Decoding is not built into the normal dictionary lookup +/* method, because that would complicate dictionary nesting, +/* pipelining, and proxying. +/* .sp +/* NOTE 2: it is up to the application to convert file names +/* into base64-encoded file content before calling the dictionary +/* update method (see dict_file(3) for support). Automatic +/* file content encoding is available only when a dictionary +/* is created from source text. +/* .PP +/* Specify DICT_FLAG_NONE for no special processing. +/* +/* The dictionary types are as follows: +/* .IP environ +/* The process environment array. The \fIdict_name\fR argument is ignored. +/* .IP dbm +/* DBM file. +/* .IP hash +/* Berkeley DB file in hash format. +/* .IP btree +/* Berkeley DB file in btree format. +/* .IP nis +/* NIS map. Only read access is supported. +/* .IP nisplus +/* NIS+ map. Only read access is supported. +/* .IP netinfo +/* NetInfo table. Only read access is supported. +/* .IP ldap +/* LDAP ("light-weight" directory access protocol) database access. +/* .IP pcre +/* PERL-compatible regular expressions. +/* .IP regexp +/* POSIX-compatible regular expressions. +/* .IP texthash +/* Flat text in postmap(1) input format. +/* .PP +/* dict_open3() takes separate arguments for dictionary type and +/* name, but otherwise performs the same functions as dict_open(). +/* +/* The dict_get(), dict_put(), dict_del(), and dict_seq() +/* macros evaluate their first argument multiple times. +/* These names should have been in uppercase. +/* +/* dict_get() retrieves the value stored in the named dictionary +/* under the given key. A null pointer means the value was not found. +/* As with dict_lookup(), the result is owned by the lookup table +/* implementation. Make a copy if the result is to be modified, +/* or if the result is to survive multiple table lookups. +/* +/* dict_put() stores the specified key and value into the named +/* dictionary. A zero (DICT_STAT_SUCCESS) result means the +/* update was made. +/* +/* dict_del() removes a dictionary entry, and returns +/* DICT_STAT_SUCCESS in case of success. +/* +/* dict_seq() iterates over all members in the named dictionary. +/* func is define DICT_SEQ_FUN_FIRST (select first member) or +/* DICT_SEQ_FUN_NEXT (select next member). A zero (DICT_STAT_SUCCESS) +/* result means that an entry was found. +/* +/* dict_close() closes the specified dictionary and cleans up the +/* associated data structures. +/* +/* dict_open_register() adds support for a new dictionary type. +/* +/* dict_open_extend() registers a call-back function that looks +/* up the dictionary open() function for a type that is not +/* registered, or null in case of error. The result value is +/* the last previously-registered call-back or null. +/* +/* dict_mapnames() returns a sorted list with the names of all available +/* dictionary types. +/* +/* dict_mapnames_extend() registers a call-back function that +/* enumerates additional dictionary type names. The result +/* will be sorted by dict_mapnames(). The result value +/* is the last previously-registered call-back or null. +/* +/* dict_setjmp() saves processing context and makes that context +/* available for use with dict_longjmp(). Normally, dict_setjmp() +/* returns zero. A non-zero result means that dict_setjmp() +/* returned through a dict_longjmp() call; the result is the +/* \fIval\fR argument given to dict_longjmp(). dict_isjmp() +/* returns non-zero when dict_setjmp() and dict_longjmp() +/* are enabled for a given dictionary. +/* +/* NB: non-local jumps such as dict_longjmp() are not safe for +/* jumping out of any routine that manipulates DICT data. +/* longjmp() like calls are best avoided in signal handlers. +/* +/* dict_type_override() changes the symbolic dictionary type. +/* This is used by dictionaries whose internals are based on +/* some other dictionary type. +/* DIAGNOSTICS +/* Fatal error: open error, unsupported dictionary type, attempt to +/* update non-writable dictionary. +/* +/* The lookup routine returns non-null when the request is +/* satisfied. The update, delete and sequence routines return +/* zero (DICT_STAT_SUCCESS) when the request is satisfied. +/* The dict->errno value is non-zero only when the last operation +/* was not satisfied due to a dictionary access error. This +/* can have the following values: +/* .IP DICT_ERR_NONE(zero) +/* There was no dictionary access error. For example, the +/* request was satisfied, the requested information did not +/* exist in the dictionary, or the information already existed +/* when it should not exist (collision). +/* .IP DICT_ERR_RETRY(<0) +/* The dictionary was temporarily unavailable. This can happen +/* with network-based services. +/* .IP DICT_ERR_CONFIG(<0) +/* The dictionary was unavailable due to a configuration error. +/* .PP +/* Generally, a program is expected to test the function result +/* value for "success" first. If the operation was not successful, +/* a program is expected to test for a non-zero dict->error +/* status to distinguish between a data notfound/collision +/* condition or a dictionary access error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * lookup table for available map types. + */ +typedef struct { + char *type; + DICT_OPEN_FN open; +} DICT_OPEN_INFO; + +static const DICT_OPEN_INFO dict_open_info[] = { + DICT_TYPE_ENVIRON, dict_env_open, + DICT_TYPE_HT, dict_ht_open, + DICT_TYPE_UNIX, dict_unix_open, + DICT_TYPE_TCP, dict_tcp_open, +#ifdef HAS_DBM + DICT_TYPE_DBM, dict_dbm_open, +#endif +#ifdef HAS_DB + DICT_TYPE_HASH, dict_hash_open, + DICT_TYPE_BTREE, dict_btree_open, +#endif +#ifdef HAS_NIS + DICT_TYPE_NIS, dict_nis_open, +#endif +#ifdef HAS_NISPLUS + DICT_TYPE_NISPLUS, dict_nisplus_open, +#endif +#ifdef HAS_NETINFO + DICT_TYPE_NETINFO, dict_ni_open, +#endif +#ifdef HAS_POSIX_REGEXP + DICT_TYPE_REGEXP, dict_regexp_open, +#endif + DICT_TYPE_STATIC, dict_static_open, + DICT_TYPE_CIDR, dict_cidr_open, + DICT_TYPE_THASH, dict_thash_open, + DICT_TYPE_SOCKMAP, dict_sockmap_open, + DICT_TYPE_FAIL, dict_fail_open, + DICT_TYPE_PIPE, dict_pipe_open, + DICT_TYPE_RANDOM, dict_random_open, + DICT_TYPE_UNION, dict_union_open, + DICT_TYPE_INLINE, dict_inline_open, +#ifndef USE_DYNAMIC_MAPS +#ifdef HAS_PCRE + DICT_TYPE_PCRE, dict_pcre_open, +#endif +#ifdef HAS_CDB + DICT_TYPE_CDB, dict_cdb_open, +#endif +#ifdef HAS_SDBM + DICT_TYPE_SDBM, dict_sdbm_open, +#endif +#ifdef HAS_LMDB + DICT_TYPE_LMDB, dict_lmdb_open, +#endif +#endif /* !USE_DYNAMIC_MAPS */ + 0, +}; + +static HTABLE *dict_open_hash; + + /* + * Extension hooks. + */ +static DICT_OPEN_EXTEND_FN dict_open_extend_hook; +static DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend_hook; + + /* + * Workaround. + */ +DEFINE_DICT_LMDB_MAP_SIZE; +DEFINE_DICT_DB_CACHE_SIZE; + +/* dict_open_init - one-off initialization */ + +static void dict_open_init(void) +{ + const char *myname = "dict_open_init"; + const DICT_OPEN_INFO *dp; + + if (dict_open_hash != 0) + msg_panic("%s: multiple initialization", myname); + dict_open_hash = htable_create(10); + + for (dp = dict_open_info; dp->type; dp++) + htable_enter(dict_open_hash, dp->type, (void *) dp); +} + +/* dict_open - open dictionary */ + +DICT *dict_open(const char *dict_spec, int open_flags, int dict_flags) +{ + char *saved_dict_spec = mystrdup(dict_spec); + char *dict_name; + DICT *dict; + + if ((dict_name = split_at(saved_dict_spec, ':')) == 0) + msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s\"", + dict_spec); + + dict = dict_open3(saved_dict_spec, dict_name, open_flags, dict_flags); + myfree(saved_dict_spec); + return (dict); +} + + +/* dict_open3 - open dictionary */ + +DICT *dict_open3(const char *dict_type, const char *dict_name, + int open_flags, int dict_flags) +{ + const char *myname = "dict_open"; + DICT_OPEN_INFO *dp; + DICT_OPEN_FN open_fn; + DICT *dict; + + if (*dict_type == 0 || *dict_name == 0) + msg_fatal("open dictionary: expecting \"type:name\" form instead of \"%s:%s\"", + dict_type, dict_name); + if (dict_open_hash == 0) + dict_open_init(); + if ((dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type)) == 0) { + if (dict_open_extend_hook != 0 + && (open_fn = dict_open_extend_hook(dict_type)) != 0) { + dict_open_register(dict_type, open_fn); + dp = (DICT_OPEN_INFO *) htable_find(dict_open_hash, dict_type); + } + if (dp == 0) + return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags, + "unsupported dictionary type: %s", dict_type)); + } + if ((dict = dp->open(dict_name, open_flags, dict_flags)) == 0) + return (dict_surrogate(dict_type, dict_name, open_flags, dict_flags, + "cannot open %s:%s: %m", dict_type, dict_name)); + if (msg_verbose) + msg_info("%s: %s:%s", myname, dict_type, dict_name); + /* XXX The choice between wait-for-lock or no-wait is hard-coded. */ + if (dict->flags & DICT_FLAG_OPEN_LOCK) { + if (dict->flags & DICT_FLAG_LOCK) + msg_panic("%s: attempt to open %s:%s with both \"open\" lock and \"access\" lock", + myname, dict_type, dict_name); + /* Multi-writer safe map: downgrade persistent lock to temporary. */ + if (dict->flags & DICT_FLAG_MULTI_WRITER) { + dict->flags &= ~DICT_FLAG_OPEN_LOCK; + dict->flags |= DICT_FLAG_LOCK; + } + /* Multi-writer unsafe map: acquire exclusive lock or bust. */ + else if (dict->lock(dict, MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) < 0) + msg_fatal("%s:%s: unable to get exclusive lock: %m", + dict_type, dict_name); + } + /* Last step: insert proxy for UTF-8 syntax checks and casefolding. */ + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) == 0 + && DICT_NEED_UTF8_ACTIVATION(util_utf8_enable, dict_flags)) + dict = dict_utf8_activate(dict); + return (dict); +} + +/* dict_open_register - register dictionary type */ + +void dict_open_register(const char *type, DICT_OPEN_FN open) +{ + const char *myname = "dict_open_register"; + DICT_OPEN_INFO *dp; + HTABLE_INFO *ht; + + if (dict_open_hash == 0) + dict_open_init(); + if (htable_find(dict_open_hash, type)) + msg_panic("%s: dictionary type exists: %s", myname, type); + dp = (DICT_OPEN_INFO *) mymalloc(sizeof(*dp)); + dp->open = open; + ht = htable_enter(dict_open_hash, type, (void *) dp); + dp->type = ht->key; +} + +/* dict_open_extend - register alternate dictionary search routine */ + +DICT_OPEN_EXTEND_FN dict_open_extend(DICT_OPEN_EXTEND_FN new_cb) +{ + DICT_OPEN_EXTEND_FN old_cb; + + old_cb = dict_open_extend_hook; + dict_open_extend_hook = new_cb; + return (old_cb); +} + +/* dict_sort_alpha_cpp - qsort() callback */ + +static int dict_sort_alpha_cpp(const void *a, const void *b) +{ + return (strcmp(((char **) a)[0], ((char **) b)[0])); +} + +/* dict_mapnames - return an ARGV of available map_names */ + +ARGV *dict_mapnames() +{ + HTABLE_INFO **ht_info; + HTABLE_INFO **ht; + DICT_OPEN_INFO *dp; + ARGV *mapnames; + + if (dict_open_hash == 0) + dict_open_init(); + mapnames = argv_alloc(dict_open_hash->used + 1); + for (ht_info = ht = htable_list(dict_open_hash); *ht; ht++) { + dp = (DICT_OPEN_INFO *) ht[0]->value; + argv_add(mapnames, dp->type, ARGV_END); + } + if (dict_mapnames_extend_hook != 0) + (void) dict_mapnames_extend_hook(mapnames); + qsort((void *) mapnames->argv, mapnames->argc, sizeof(mapnames->argv[0]), + dict_sort_alpha_cpp); + myfree((void *) ht_info); + argv_terminate(mapnames); + return mapnames; +} + +/* dict_mapnames_extend - register alternate dictionary type list routine */ + +DICT_MAPNAMES_EXTEND_FN dict_mapnames_extend(DICT_MAPNAMES_EXTEND_FN new_cb) +{ + DICT_MAPNAMES_EXTEND_FN old_cb; + + old_cb = dict_mapnames_extend_hook; + dict_mapnames_extend_hook = new_cb; + return (old_cb); +} + +/* dict_type_override - disguise a dictionary type */ + +void dict_type_override(DICT *dict, const char *type) +{ + myfree(dict->type); + dict->type = mystrdup(type); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. + */ +int main(int argc, char **argv) +{ + dict_test(argc, argv); + return (0); +} + +#endif diff --git a/src/util/dict_pcre.c b/src/util/dict_pcre.c new file mode 100644 index 0000000..3cceda2 --- /dev/null +++ b/src/util/dict_pcre.c @@ -0,0 +1,1120 @@ +/*++ +/* NAME +/* dict_pcre 3 +/* SUMMARY +/* dictionary manager interface to PCRE regular expression library +/* SYNOPSIS +/* #include +/* +/* DICT *dict_pcre_open(name, dummy, dict_flags) +/* const char *name; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_pcre_open() opens the named file and compiles the contained +/* regular expressions. The result object can be used to match strings +/* against the table. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* AUTHOR(S) +/* Andrew McNamara +/* andrewm@connect.com.au +/* connect.com.au Pty. Ltd. +/* Level 3, 213 Miller St +/* North Sydney, NSW, Australia +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#include "sys_defs.h" + +#ifdef HAS_PCRE + +/* System library. */ + +#include +#include /* sprintf() prototype */ +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +#if HAS_PCRE == 1 +#include +#elif HAS_PCRE == 2 +#define PCRE2_CODE_UNIT_WIDTH 8 +#include +#else +#error "define HAS_PCRE=2 or HAS_PCRE=1" +#endif + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "safe.h" +#include "vstream.h" +#include "vstring.h" +#include "stringops.h" +#include "readlline.h" +#include "dict.h" +#include "dict_pcre.h" +#include "mac_parse.h" +#include "warn_stat.h" +#include "mvect.h" + + /* + * Backwards compatibility. + */ +#if HAS_PCRE == 1 + /* PCRE Legacy JIT supprt. */ +#ifdef PCRE_STUDY_JIT_COMPILE +#define DICT_PCRE_FREE_STUDY(x) pcre_free_study(x) +#else +#define DICT_PCRE_FREE_STUDY(x) pcre_free((char *) (x)) +#endif + + /* PCRE Compiled pattern. */ +#define DICT_PCRE_CODE pcre +#define DICT_PCRE_CODE_FREE(x) myfree((void *) (x)) + + /* Old-style hints versus new-style match_data. */ +#define DICT_PCRE_MATCH_HINT_TYPE pcre_extra * +#define DICT_PCRE_MATCH_HINT_NAME hints +#define DICT_PCRE_MATCH_HINT(x) ((x)->DICT_PCRE_MATCH_HINT_NAME) +#define DICT_PCRE_MATCH_HINT_FREE(x) do { \ + if (DICT_PCRE_MATCH_HINT(x)) \ + DICT_PCRE_FREE_STUDY(DICT_PCRE_MATCH_HINT(x)); \ + } while (0) + + /* PCRE Pattern options. */ +#define DICT_PCRE_CASELESS PCRE_CASELESS +#define DICT_PCRE_MULTILINE PCRE_MULTILINE +#define DICT_PCRE_DOTALL PCRE_DOTALL +#define DICT_PCRE_EXTENDED PCRE_EXTENDED +#define DICT_PCRE_ANCHORED PCRE_ANCHORED +#define DICT_PCRE_DOLLAR_ENDONLY PCRE_DOLLAR_ENDONLY +#define DICT_PCRE_UNGREEDY PCRE_UNGREEDY +#define DICT_PCRE_EXTRA PCRE_EXTRA + + /* PCRE Number of captures in pattern. */ +#ifdef PCRE_INFO_CAPTURECOUNT +#define DICT_PCRE_CAPTURECOUNT_T int +#endif + +#else /* HAS_PCRE */ + + /* PCRE2 Compiled pattern. */ +#define DICT_PCRE_CODE pcre2_code +#define DICT_PCRE_CODE_FREE(x) pcre2_code_free(x) + + /* PCRE2 Old-style hints versus new-style match_data. */ +#define DICT_PCRE_MATCH_HINT_TYPE pcre2_match_data * +#define DICT_PCRE_MATCH_HINT_NAME match_data +#define DICT_PCRE_MATCH_HINT(x) ((x)->DICT_PCRE_MATCH_HINT_NAME) +#define DICT_PCRE_MATCH_HINT_FREE(x) \ + pcre2_match_data_free(DICT_PCRE_MATCH_HINT(x)) + + /* PCRE2 Pattern options. */ +#define DICT_PCRE_CASELESS PCRE2_CASELESS +#define DICT_PCRE_MULTILINE PCRE2_MULTILINE +#define DICT_PCRE_DOTALL PCRE2_DOTALL +#define DICT_PCRE_EXTENDED PCRE2_EXTENDED +#define DICT_PCRE_ANCHORED PCRE2_ANCHORED +#define DICT_PCRE_DOLLAR_ENDONLY PCRE2_DOLLAR_ENDONLY +#define DICT_PCRE_UNGREEDY PCRE2_UNGREEDY +#define DICT_PCRE_EXTRA 0 + + /* PCRE2 Number of captures in pattern. */ +#define DICT_PCRE_CAPTURECOUNT_T uint32_t + +#endif /* HAS_PCRE */ + + /* + * Support for IF/ENDIF based on an idea by Bert Driehuis. + */ +#define DICT_PCRE_OP_MATCH 1 /* Match this regexp */ +#define DICT_PCRE_OP_IF 2 /* Increase if/endif nesting on match */ +#define DICT_PCRE_OP_ENDIF 3 /* Decrease if/endif nesting on match */ + + /* + * Max strings captured by regexp - essentially the max number of (..) + */ +#if HAS_PCRE == 1 +#define PCRE_MAX_CAPTURE 99 +#endif + + /* + * Regular expression before and after compilation. + */ +typedef struct { + char *regexp; /* regular expression */ + int options; /* options */ + int match; /* positive or negative match */ +} DICT_PCRE_REGEXP; + +typedef struct { + DICT_PCRE_CODE *pattern; /* the compiled pattern */ + DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME; +} DICT_PCRE_ENGINE; + + /* + * Compiled generic rule, and subclasses that derive from it. + */ +typedef struct DICT_PCRE_RULE { + int op; /* DICT_PCRE_OP_MATCH/IF/ENDIF */ + int lineno; /* source file line number */ + struct DICT_PCRE_RULE *next; /* next rule in dict */ +} DICT_PCRE_RULE; + +typedef struct { + DICT_PCRE_RULE rule; /* generic part */ + DICT_PCRE_CODE *pattern; /* compiled pattern */ + DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME; + char *replacement; /* replacement string */ + int match; /* positive or negative match */ + size_t max_sub; /* largest $number in replacement */ +} DICT_PCRE_MATCH_RULE; + +typedef struct { + DICT_PCRE_RULE rule; /* generic members */ + DICT_PCRE_CODE *pattern; /* compiled pattern */ + DICT_PCRE_MATCH_HINT_TYPE DICT_PCRE_MATCH_HINT_NAME; + int match; /* positive or negative match */ + struct DICT_PCRE_RULE *endif_rule; /* matching endif rule */ +} DICT_PCRE_IF_RULE; + + /* + * PCRE map. + */ +typedef struct { + DICT dict; /* generic members */ + DICT_PCRE_RULE *head; + VSTRING *expansion_buf; /* lookup result */ +} DICT_PCRE; + +#if HAS_PCRE == 1 +static int dict_pcre_init = 0; /* flag need to init pcre library */ + +#endif + +/* + * Context for $number expansion callback. + */ +typedef struct { + DICT_PCRE *dict_pcre; /* the dictionary handle */ +#if HAS_PCRE == 1 + DICT_PCRE_MATCH_RULE *match_rule; /* the rule we matched */ +#endif + const char *lookup_string; /* string against which we match */ +#if HAS_PCRE == 1 + int offsets[PCRE_MAX_CAPTURE * 3]; /* Cut substrings */ +#else /* HAS_PCRE */ + PCRE2_SIZE *ovector; /* matched string offsets */ +#endif /* HAS_PCRE */ + int matches; /* Count of cuts */ +} DICT_PCRE_EXPAND_CONTEXT; + + /* + * Context for $number pre-scan callback. + */ +typedef struct { + const char *mapname; /* name of regexp map */ + int lineno; /* where in file */ + size_t max_sub; /* Largest $n seen */ + char *literal; /* constant result, $$ -> $ */ +} DICT_PCRE_PRESCAN_CONTEXT; + + /* + * Compatibility. + */ +#ifndef MAC_PARSE_OK +#define MAC_PARSE_OK 0 +#endif + + /* + * Macros to make dense code more accessible. + */ +#define NULL_STARTOFFSET (0) +#define NULL_EXEC_OPTIONS (0) + +/* dict_pcre_expand - replace $number with matched text */ + +static int dict_pcre_expand(int type, VSTRING *buf, void *ptr) +{ + DICT_PCRE_EXPAND_CONTEXT *ctxt = (DICT_PCRE_EXPAND_CONTEXT *) ptr; + DICT_PCRE *dict_pcre = ctxt->dict_pcre; + int n; + +#if HAS_PCRE == 1 + DICT_PCRE_MATCH_RULE *match_rule = ctxt->match_rule; + const char *pp; + int ret; + +#else + PCRE2_SPTR start; + PCRE2_SIZE length; + +#endif + + /* + * Replace $0-${99} with strings cut from matched text. + */ + if (type == MAC_PARSE_VARNAME) { + n = atoi(vstring_str(buf)); +#if HAS_PCRE == 1 + ret = pcre_get_substring(ctxt->lookup_string, ctxt->offsets, + ctxt->matches, n, &pp); + if (ret < 0) { + if (ret == PCRE_ERROR_NOSUBSTRING) + return (MAC_PARSE_UNDEF); + else + msg_fatal("pcre map %s, line %d: pcre_get_substring error: %d", + dict_pcre->dict.name, match_rule->rule.lineno, ret); + } + if (*pp == 0) { + myfree((void *) pp); + return (MAC_PARSE_UNDEF); + } + vstring_strcat(dict_pcre->expansion_buf, pp); + myfree((void *) pp); + return (MAC_PARSE_OK); +#else + start = (unsigned char *) ctxt->lookup_string + ctxt->ovector[2 * n]; + length = ctxt->ovector[2 * n + 1] - ctxt->ovector[2 * n]; + if (length == 0) + return (MAC_PARSE_UNDEF); + vstring_strncat(dict_pcre->expansion_buf, (char *) start, length); + return (MAC_PARSE_OK); +#endif + } + + /* + * Straight text - duplicate with no substitution. + */ + else { + vstring_strcat(dict_pcre->expansion_buf, vstring_str(buf)); + return (MAC_PARSE_OK); + } +} + +#if HAS_PCRE == 2 + +#define DICT_PCRE_GET_ERROR_BUF_LEN 256 + +/* dict_pcre_get_error - convert PCRE2 error number or text */ + +static char *dict_pcre_get_error(VSTRING *buf, int errval) +{ + ssize_t len; + + VSTRING_SPACE(buf, DICT_PCRE_GET_ERROR_BUF_LEN); + if ((len = pcre2_get_error_message(errval, + (unsigned char *) vstring_str(buf), + DICT_PCRE_GET_ERROR_BUF_LEN)) > 0) { + vstring_set_payload_size(buf, len); + } else + vstring_sprintf(buf, "unexpected pcre2 error code %d", errval); + return (vstring_str(buf)); +} + +#endif /* HAS_PCRE == 2 */ + +/* dict_pcre_exec_error - report matching error */ + +static void dict_pcre_exec_error(const char *mapname, int lineno, int errval) +{ +#if HAS_PCRE == 1 + switch (errval) { + case 0: + msg_warn("pcre map %s, line %d: too many (...)", + mapname, lineno); + return; + case PCRE_ERROR_NULL: + case PCRE_ERROR_BADOPTION: + msg_warn("pcre map %s, line %d: bad args to re_exec", + mapname, lineno); + return; + case PCRE_ERROR_BADMAGIC: + case PCRE_ERROR_UNKNOWN_NODE: + msg_warn("pcre map %s, line %d: corrupt compiled regexp", + mapname, lineno); + return; +#ifdef PCRE_ERROR_NOMEMORY + case PCRE_ERROR_NOMEMORY: + msg_warn("pcre map %s, line %d: out of memory", + mapname, lineno); + return; +#endif +#ifdef PCRE_ERROR_MATCHLIMIT + case PCRE_ERROR_MATCHLIMIT: + msg_warn("pcre map %s, line %d: backtracking limit exceeded", + mapname, lineno); + return; +#endif +#ifdef PCRE_ERROR_BADUTF8 + case PCRE_ERROR_BADUTF8: + msg_warn("pcre map %s, line %d: bad UTF-8 sequence in search string", + mapname, lineno); + return; +#endif +#ifdef PCRE_ERROR_BADUTF8_OFFSET + case PCRE_ERROR_BADUTF8_OFFSET: + msg_warn("pcre map %s, line %d: bad UTF-8 start offset in search string", + mapname, lineno); + return; +#endif + default: + msg_warn("pcre map %s, line %d: unknown pcre_exec error: %d", + mapname, lineno, errval); + return; + } +#else /* HAS_PCRE */ + VSTRING *buf = vstring_alloc(DICT_PCRE_GET_ERROR_BUF_LEN); + + msg_warn("pcre map %s, line %d: %s", mapname, lineno, + dict_pcre_get_error(buf, errval)); + vstring_free(buf); +#endif /* HAS_PCRE */ +} + + /* + * Inlined to reduce function call overhead in the time-critical loop. + */ +#if HAS_PCRE == 1 +#define DICT_PCRE_EXEC(ctxt, map, line, pattern, hints, match, str, len) \ + ((ctxt).matches = pcre_exec((pattern), (hints), (str), (len), \ + NULL_STARTOFFSET, NULL_EXEC_OPTIONS, \ + (ctxt).offsets, PCRE_MAX_CAPTURE * 3), \ + (ctxt).matches > 0 ? (match) : \ + (ctxt).matches == PCRE_ERROR_NOMATCH ? !(match) : \ + (dict_pcre_exec_error((map), (line), (ctxt).matches), 0)) +#else +#define DICT_PCRE_EXEC(ctxt, map, line, pattern, match_data, match, str, len) \ + ((ctxt).matches = pcre2_match((pattern), (unsigned char *) (str), (len), \ + NULL_STARTOFFSET, NULL_EXEC_OPTIONS, \ + (match_data), (pcre2_match_context *) 0), \ + (ctxt).matches > 0 ? (match) : \ + (ctxt).matches == PCRE2_ERROR_NOMATCH ? !(match) : \ + (dict_pcre_exec_error((map), (line), (ctxt).matches), 0)) +#endif + +/* dict_pcre_lookup - match string and perform optional substitution */ + +static const char *dict_pcre_lookup(DICT *dict, const char *lookup_string) +{ + DICT_PCRE *dict_pcre = (DICT_PCRE *) dict; + DICT_PCRE_RULE *rule; + DICT_PCRE_IF_RULE *if_rule; + DICT_PCRE_MATCH_RULE *match_rule; + int lookup_len = strlen(lookup_string); + DICT_PCRE_EXPAND_CONTEXT ctxt; + + dict->error = 0; + + if (msg_verbose) + msg_info("dict_pcre_lookup: %s: %s", dict->name, lookup_string); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_MUL) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, lookup_string); + lookup_string = lowercase(vstring_str(dict->fold_buf)); + } + for (rule = dict_pcre->head; rule; rule = rule->next) { + + switch (rule->op) { + + /* + * Search for a matching expression. + */ + case DICT_PCRE_OP_MATCH: + match_rule = (DICT_PCRE_MATCH_RULE *) rule; + if (!DICT_PCRE_EXEC(ctxt, dict->name, rule->lineno, + match_rule->pattern, + DICT_PCRE_MATCH_HINT(match_rule), + match_rule->match, lookup_string, lookup_len)) + continue; + + /* + * Skip $number substitutions when the replacement text contains + * no $number strings, as learned during the compile time + * pre-scan. The pre-scan already replaced $$ by $. + */ + if (match_rule->max_sub == 0) + return match_rule->replacement; + + /* + * We've got a match. Perform substitution on replacement string. + */ + if (dict_pcre->expansion_buf == 0) + dict_pcre->expansion_buf = vstring_alloc(10); + VSTRING_RESET(dict_pcre->expansion_buf); + ctxt.dict_pcre = dict_pcre; +#if HAS_PCRE == 1 + ctxt.match_rule = match_rule; +#else + ctxt.ovector = pcre2_get_ovector_pointer(match_rule->match_data); +#endif + ctxt.lookup_string = lookup_string; + + if (mac_parse(match_rule->replacement, dict_pcre_expand, + (void *) &ctxt) & MAC_PARSE_ERROR) + msg_fatal("pcre map %s, line %d: bad replacement syntax", + dict->name, rule->lineno); + + VSTRING_TERMINATE(dict_pcre->expansion_buf); + return (vstring_str(dict_pcre->expansion_buf)); + + /* + * Conditional. XXX We provide space for matched substring info + * because PCRE uses part of it as workspace for backtracking. + * PCRE will allocate memory if it runs out of backtracking + * storage. + */ + case DICT_PCRE_OP_IF: + if_rule = (DICT_PCRE_IF_RULE *) rule; + if (DICT_PCRE_EXEC(ctxt, dict->name, rule->lineno, + if_rule->pattern, + DICT_PCRE_MATCH_HINT(if_rule), + if_rule->match, lookup_string, lookup_len)) + continue; + /* An IF without matching ENDIF has no "endif" rule. */ + if ((rule = if_rule->endif_rule) == 0) + return (0); + /* FALLTHROUGH */ + + /* + * ENDIF after IF. + */ + case DICT_PCRE_OP_ENDIF: + continue; + + default: + msg_panic("dict_pcre_lookup: impossible operation %d", rule->op); + } + } + return (0); +} + +/* dict_pcre_close - close pcre dictionary */ + +static void dict_pcre_close(DICT *dict) +{ + DICT_PCRE *dict_pcre = (DICT_PCRE *) dict; + DICT_PCRE_RULE *rule; + DICT_PCRE_RULE *next; + DICT_PCRE_MATCH_RULE *match_rule; + DICT_PCRE_IF_RULE *if_rule; + + for (rule = dict_pcre->head; rule; rule = next) { + next = rule->next; + switch (rule->op) { + case DICT_PCRE_OP_MATCH: + match_rule = (DICT_PCRE_MATCH_RULE *) rule; + if (match_rule->pattern) + DICT_PCRE_CODE_FREE(match_rule->pattern); + DICT_PCRE_MATCH_HINT_FREE(match_rule); + if (match_rule->replacement) + myfree((void *) match_rule->replacement); + break; + case DICT_PCRE_OP_IF: + if_rule = (DICT_PCRE_IF_RULE *) rule; + if (if_rule->pattern) + DICT_PCRE_CODE_FREE(if_rule->pattern); + DICT_PCRE_MATCH_HINT_FREE(if_rule); + break; + case DICT_PCRE_OP_ENDIF: + break; + default: + msg_panic("dict_pcre_close: unknown operation %d", rule->op); + } + myfree((void *) rule); + } + if (dict_pcre->expansion_buf) + vstring_free(dict_pcre->expansion_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_pcre_get_pattern - extract pattern from rule */ + +static int dict_pcre_get_pattern(const char *mapname, int lineno, char **bufp, + DICT_PCRE_REGEXP *pattern) +{ + char *p = *bufp; + char re_delimiter; + + /* + * Process negation operators. + */ + pattern->match = 1; + for (;;) { + if (*p == '!') + pattern->match = !pattern->match; + else if (!ISSPACE(*p)) + break; + p++; + } + if (*p == 0) { + msg_warn("pcre map %s, line %d: no regexp: skipping this rule", + mapname, lineno); + return (0); + } + re_delimiter = *p++; + pattern->regexp = p; + + /* + * Search for second delimiter, handling backslash escape. + */ + while (*p) { + if (*p == '\\') { + ++p; + if (*p == 0) + break; + } else if (*p == re_delimiter) + break; + ++p; + } + + if (!*p) { + msg_warn("pcre map %s, line %d: no closing regexp delimiter \"%c\": " + "ignoring this rule", mapname, lineno, re_delimiter); + return (0); + } + *p++ = 0; /* Null term the regexp */ + + /* + * Parse any regexp options. + */ + pattern->options = DICT_PCRE_CASELESS | DICT_PCRE_DOTALL; + while (*p && !ISSPACE(*p)) { + switch (*p) { + case 'i': + pattern->options ^= DICT_PCRE_CASELESS; + break; + case 'm': + pattern->options ^= DICT_PCRE_MULTILINE; + break; + case 's': + pattern->options ^= DICT_PCRE_DOTALL; + break; + case 'x': + pattern->options ^= DICT_PCRE_EXTENDED; + break; + case 'A': + pattern->options ^= DICT_PCRE_ANCHORED; + break; + case 'E': + pattern->options ^= DICT_PCRE_DOLLAR_ENDONLY; + break; + case 'U': + pattern->options ^= DICT_PCRE_UNGREEDY; + break; + case 'X': +#if DICT_PCRE_EXTRA != 0 + pattern->options ^= DICT_PCRE_EXTRA; +#else + msg_warn("pcre map %s, line %d: ignoring obsolete regexp " + "option \"%c\"", mapname, lineno, *p); +#endif + break; + default: + msg_warn("pcre map %s, line %d: unknown regexp option \"%c\": " + "skipping this rule", mapname, lineno, *p); + return (0); + } + ++p; + } + *bufp = p; + return (1); +} + +/* dict_pcre_prescan - sanity check $number instances in replacement text */ + +static int dict_pcre_prescan(int type, VSTRING *buf, void *context) +{ + DICT_PCRE_PRESCAN_CONTEXT *ctxt = (DICT_PCRE_PRESCAN_CONTEXT *) context; + size_t n; + + /* + * Keep a copy of literal text (with $$ already replaced by $) if and + * only if the replacement text contains no $number expression. This way + * we can avoid having to scan the replacement text at lookup time. + */ + if (type == MAC_PARSE_VARNAME) { + if (ctxt->literal) { + myfree(ctxt->literal); + ctxt->literal = 0; + } + if (!alldig(vstring_str(buf))) { + msg_warn("pcre map %s, line %d: non-numeric replacement index \"%s\"", + ctxt->mapname, ctxt->lineno, vstring_str(buf)); + return (MAC_PARSE_ERROR); + } + n = atoi(vstring_str(buf)); + if (n < 1) { + msg_warn("pcre map %s, line %d: out of range replacement index \"%s\"", + ctxt->mapname, ctxt->lineno, vstring_str(buf)); + return (MAC_PARSE_ERROR); + } + if (n > ctxt->max_sub) + ctxt->max_sub = n; + } else if (type == MAC_PARSE_LITERAL && ctxt->max_sub == 0) { + if (ctxt->literal) + msg_panic("pcre map %s, line %d: multiple literals but no $number", + ctxt->mapname, ctxt->lineno); + ctxt->literal = mystrdup(vstring_str(buf)); + } + return (MAC_PARSE_OK); +} + +/* dict_pcre_compile - compile pattern */ + +static int dict_pcre_compile(const char *mapname, int lineno, + DICT_PCRE_REGEXP *pattern, + DICT_PCRE_ENGINE *engine) +{ +#if HAS_PCRE == 1 + const char *error; + int errptr; + + engine->pattern = pcre_compile(pattern->regexp, pattern->options, + &error, &errptr, NULL); + if (engine->pattern == 0) { + msg_warn("pcre map %s, line %d: error in regex at offset %d: %s", + mapname, lineno, errptr, error); + return (0); + } + engine->hints = pcre_study(engine->pattern, 0, &error); + if (error != 0) { + msg_warn("pcre map %s, line %d: error while studying regex: %s", + mapname, lineno, error); + DICT_PCRE_CODE_FREE(engine->pattern); + return (0); + } +#else + int error; + size_t errptr; + + engine->pattern = pcre2_compile((unsigned char *) pattern->regexp, + PCRE2_ZERO_TERMINATED, + pattern->options, &error, &errptr, NULL); + if (engine->pattern == 0) { + VSTRING *buf = vstring_alloc(DICT_PCRE_GET_ERROR_BUF_LEN); + + msg_warn("pcre map %s, line %d: error in regex at offset %lu: %s", + mapname, lineno, (unsigned long) errptr, + dict_pcre_get_error(buf, error)); + vstring_free(buf); + return (0); + } + engine->match_data = pcre2_match_data_create_from_pattern( + engine->pattern, (void *) 0); +#endif + return (1); +} + +/* dict_pcre_rule_alloc - fill in a generic rule structure */ + +static DICT_PCRE_RULE *dict_pcre_rule_alloc(int op, int lineno, size_t size) +{ + DICT_PCRE_RULE *rule; + + rule = (DICT_PCRE_RULE *) mymalloc(size); + rule->op = op; + rule->lineno = lineno; + rule->next = 0; + + return (rule); +} + +/* dict_pcre_parse_rule - parse and compile one rule */ + +static DICT_PCRE_RULE *dict_pcre_parse_rule(DICT *dict, const char *mapname, + int lineno, char *line, + int nesting) +{ + char *p; + +#ifdef DICT_PCRE_CAPTURECOUNT_T + DICT_PCRE_CAPTURECOUNT_T actual_sub; + +#endif +#if 0 + uint32_t namecount; + +#endif + + p = line; + + /* + * An ordinary match rule takes one pattern and replacement text. + */ + if (!ISALNUM(*p)) { + DICT_PCRE_REGEXP regexp; + DICT_PCRE_ENGINE engine; + DICT_PCRE_PRESCAN_CONTEXT prescan_context; + DICT_PCRE_MATCH_RULE *match_rule; + + /* + * Get the pattern string and options. + */ + if (dict_pcre_get_pattern(mapname, lineno, &p, ®exp) == 0) + return (0); + + /* + * Get the replacement text. + */ + while (*p && ISSPACE(*p)) + ++p; + if (!*p) + msg_warn("pcre map %s, line %d: no replacement text: " + "using empty string", mapname, lineno); + + /* + * Sanity check the $number instances in the replacement text. + */ + prescan_context.mapname = mapname; + prescan_context.lineno = lineno; + prescan_context.max_sub = 0; + prescan_context.literal = 0; + + /* + * The optimizer will eliminate code duplication and/or dead code. + */ +#define CREATE_MATCHOP_ERROR_RETURN(rval) do { \ + if (prescan_context.literal) \ + myfree(prescan_context.literal); \ + return (rval); \ + } while (0) + + if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) { + VSTRING *base64_buf; + char *err; + + if ((base64_buf = dict_file_to_b64(dict, p)) == 0) { + err = dict_file_get_error(dict); + msg_warn("pcre map %s, line %d: %s: skipping this rule", + mapname, lineno, err); + myfree(err); + CREATE_MATCHOP_ERROR_RETURN(0); + } + p = vstring_str(base64_buf); + } + if (mac_parse(p, dict_pcre_prescan, (void *) &prescan_context) + & MAC_PARSE_ERROR) { + msg_warn("pcre map %s, line %d: bad replacement syntax: " + "skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + + /* + * Substring replacement not possible with negative regexps. + */ + if (prescan_context.max_sub > 0 && regexp.match == 0) { + msg_warn("pcre map %s, line %d: $number found in negative match " + "replacement text: skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + if (prescan_context.max_sub > 0 && (dict->flags & DICT_FLAG_NO_REGSUB)) { + msg_warn("pcre map %s, line %d: " + "regular expression substitution is not allowed: " + "skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + + /* + * Compile the pattern. + */ + if (dict_pcre_compile(mapname, lineno, ®exp, &engine) == 0) + CREATE_MATCHOP_ERROR_RETURN(0); +#ifdef DICT_PCRE_CAPTURECOUNT_T +#if HAS_PCRE == 1 + if (pcre_fullinfo(engine.pattern, engine.hints, + PCRE_INFO_CAPTURECOUNT, + (void *) &actual_sub) != 0) + msg_panic("pcre map %s, line %d: pcre_fullinfo failed", + mapname, lineno); +#else /* HAS_PCRE */ +#if 0 + if (pcre2_pattern_info( + engine.pattern, PCRE2_INFO_NAMECOUNT, &namecount) != 0) + msg_panic("pcre map %s, line %d: pcre2_pattern_info failed", + mapname, lineno); + if (namecount > 0) { + msg_warn("pcre map %s, line %d: named substrings are not supported", + mapname, lineno); + if (engine.pattern) + DICT_PCRE_CODE_FREE(engine.pattern); + DICT_PCRE_MATCH_HINT_FREE(&engine); + CREATE_MATCHOP_ERROR_RETURN(0); + } +#endif + if (pcre2_pattern_info(engine.pattern, PCRE2_INFO_CAPTURECOUNT, + (void *) &actual_sub) != 0) + msg_panic("pcre map %s, line %d: pcre2_pattern_info failed", + mapname, lineno); +#endif /* HAS_PCRE */ + if (prescan_context.max_sub > actual_sub) { + msg_warn("pcre map %s, line %d: out of range replacement index \"%d\": " + "skipping this rule", mapname, lineno, + (int) prescan_context.max_sub); + if (engine.pattern) + DICT_PCRE_CODE_FREE(engine.pattern); + DICT_PCRE_MATCH_HINT_FREE(&engine); + CREATE_MATCHOP_ERROR_RETURN(0); + } +#endif /* DICT_PCRE_CAPTURECOUNT_T */ + + /* + * Save the result. + */ + match_rule = (DICT_PCRE_MATCH_RULE *) + dict_pcre_rule_alloc(DICT_PCRE_OP_MATCH, lineno, + sizeof(DICT_PCRE_MATCH_RULE)); + match_rule->match = regexp.match; + match_rule->max_sub = prescan_context.max_sub; + if (prescan_context.literal) + match_rule->replacement = prescan_context.literal; + else + match_rule->replacement = mystrdup(p); + match_rule->pattern = engine.pattern; + DICT_PCRE_MATCH_HINT(match_rule) = DICT_PCRE_MATCH_HINT(&engine); + return ((DICT_PCRE_RULE *) match_rule); + } + + /* + * The IF operator takes one pattern but no replacement text. + */ + else if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) { + DICT_PCRE_REGEXP regexp; + DICT_PCRE_ENGINE engine; + DICT_PCRE_IF_RULE *if_rule; + + p += 2; + + /* + * Get the pattern. + */ + while (*p && ISSPACE(*p)) + p++; + if (!dict_pcre_get_pattern(mapname, lineno, &p, ®exp)) + return (0); + + /* + * Warn about out-of-place text. + */ + while (*p && ISSPACE(*p)) + ++p; + if (*p) { + msg_warn("pcre map %s, line %d: ignoring extra text after " + "IF statement: \"%s\"", mapname, lineno, p); + msg_warn("pcre map %s, line %d: do not prepend whitespace" + " to statements between IF and ENDIF", mapname, lineno); + } + + /* + * Compile the pattern. + */ + if (dict_pcre_compile(mapname, lineno, ®exp, &engine) == 0) + return (0); + + /* + * Save the result. + */ + if_rule = (DICT_PCRE_IF_RULE *) + dict_pcre_rule_alloc(DICT_PCRE_OP_IF, lineno, + sizeof(DICT_PCRE_IF_RULE)); + if_rule->match = regexp.match; + if_rule->pattern = engine.pattern; + DICT_PCRE_MATCH_HINT(if_rule) = DICT_PCRE_MATCH_HINT(&engine); + if_rule->endif_rule = 0; + return ((DICT_PCRE_RULE *) if_rule); + } + + /* + * The ENDIF operator takes no patterns and no replacement text. + */ + else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) { + DICT_PCRE_RULE *rule; + + p += 5; + + /* + * Warn about out-of-place ENDIFs. + */ + if (nesting == 0) { + msg_warn("pcre map %s, line %d: ignoring ENDIF without matching IF", + mapname, lineno); + return (0); + } + + /* + * Warn about out-of-place text. + */ + while (*p && ISSPACE(*p)) + ++p; + if (*p) + msg_warn("pcre map %s, line %d: ignoring extra text after ENDIF", + mapname, lineno); + + /* + * Save the result. + */ + rule = dict_pcre_rule_alloc(DICT_PCRE_OP_ENDIF, lineno, + sizeof(DICT_PCRE_RULE)); + return (rule); + } + + /* + * Unrecognized input. + */ + else { + msg_warn("pcre map %s, line %d: ignoring unrecognized request", + mapname, lineno); + return (0); + } +} + +/* dict_pcre_open - load and compile a file containing regular expressions */ + +DICT *dict_pcre_open(const char *mapname, int open_flags, int dict_flags) +{ + const char myname[] = "dict_pcre_open"; + DICT_PCRE *dict_pcre; + VSTREAM *map_fp = 0; + struct stat st; + VSTRING *why = 0; + VSTRING *line_buffer = 0; + DICT_PCRE_RULE *last_rule = 0; + DICT_PCRE_RULE *rule; + int last_line = 0; + int lineno; + int nesting = 0; + char *p; + DICT_PCRE_RULE **rule_stack = 0; + MVECT mvect; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_PCRE_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (map_fp != 0) \ + vstream_fclose(map_fp); \ + if (line_buffer != 0) \ + vstring_free(line_buffer); \ + if (why != 0) \ + vstring_free(why); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_PCRE_OPEN_RETURN(dict_surrogate(DICT_TYPE_PCRE, mapname, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_PCRE, mapname)); + + /* + * Open the configuration file. + */ + if ((map_fp = dict_stream_open(DICT_TYPE_PCRE, mapname, O_RDONLY, + dict_flags, &st, &why)) == 0) + DICT_PCRE_OPEN_RETURN(dict_surrogate(DICT_TYPE_PCRE, mapname, + open_flags, dict_flags, + "%s", vstring_str(why))); + line_buffer = vstring_alloc(100); + + dict_pcre = (DICT_PCRE *) dict_alloc(DICT_TYPE_PCRE, mapname, + sizeof(*dict_pcre)); + dict_pcre->dict.lookup = dict_pcre_lookup; + dict_pcre->dict.close = dict_pcre_close; + dict_pcre->dict.flags = dict_flags | DICT_FLAG_PATTERN; + if (dict_flags & DICT_FLAG_FOLD_MUL) + dict_pcre->dict.fold_buf = vstring_alloc(10); + dict_pcre->head = 0; + dict_pcre->expansion_buf = 0; + +#if HAS_PCRE == 1 + if (dict_pcre_init == 0) { + pcre_malloc = (void *(*) (size_t)) mymalloc; + pcre_free = (void (*) (void *)) myfree; + dict_pcre_init = 1; + } +#endif + dict_pcre->dict.owner.uid = st.st_uid; + dict_pcre->dict.owner.status = (st.st_uid != 0); + + /* + * Parse the pcre table. + */ + while (readllines(line_buffer, map_fp, &last_line, &lineno)) { + p = vstring_str(line_buffer); + trimblanks(p, 0)[0] = 0; /* Trim space at end */ + if (*p == 0) + continue; + rule = dict_pcre_parse_rule(&dict_pcre->dict, mapname, lineno, + p, nesting); + if (rule == 0) + continue; + if (rule->op == DICT_PCRE_OP_IF) { + if (rule_stack == 0) + rule_stack = (DICT_PCRE_RULE **) mvect_alloc(&mvect, + sizeof(*rule_stack), nesting + 1, + (MVECT_FN) 0, (MVECT_FN) 0); + else + rule_stack = + (DICT_PCRE_RULE **) mvect_realloc(&mvect, nesting + 1); + rule_stack[nesting] = rule; + nesting++; + } else if (rule->op == DICT_PCRE_OP_ENDIF) { + DICT_PCRE_IF_RULE *if_rule; + + if (nesting-- <= 0) + /* Already handled in dict_pcre_parse_rule(). */ + msg_panic("%s: ENDIF without IF", myname); + if (rule_stack[nesting]->op != DICT_PCRE_OP_IF) + msg_panic("%s: unexpected rule stack element type %d", + myname, rule_stack[nesting]->op); + if_rule = (DICT_PCRE_IF_RULE *) rule_stack[nesting]; + if_rule->endif_rule = rule; + } + if (last_rule == 0) + dict_pcre->head = rule; + else + last_rule->next = rule; + last_rule = rule; + } + + while (nesting-- > 0) + msg_warn("pcre map %s, line %d: IF has no matching ENDIF", + mapname, rule_stack[nesting]->lineno); + + if (rule_stack) + (void) mvect_free(&mvect); + + dict_file_purge_buffers(&dict_pcre->dict); + DICT_PCRE_OPEN_RETURN(DICT_DEBUG (&dict_pcre->dict)); +} + +#endif /* HAS_PCRE */ diff --git a/src/util/dict_pcre.h b/src/util/dict_pcre.h new file mode 100644 index 0000000..3aa4955 --- /dev/null +++ b/src/util/dict_pcre.h @@ -0,0 +1,43 @@ +#ifndef _DICT_PCRE_H_INCLUDED_ +#define _DICT_PCRE_H_INCLUDED_ + +/*++ +/* NAME +/* dict_pcre 3h +/* SUMMARY +/* dictionary manager interface to PCRE regular expression library +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_PCRE "pcre" + +extern DICT *dict_pcre_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Andrew McNamara +/* andrewm@connect.com.au +/* connect.com.au Pty. Ltd. +/* Level 3, 213 Miller St +/* North Sydney, NSW, Australia +/*--*/ + +#endif diff --git a/src/util/dict_pcre.in b/src/util/dict_pcre.in new file mode 100644 index 0000000..b0644bd --- /dev/null +++ b/src/util/dict_pcre.in @@ -0,0 +1,15 @@ +get true +get true1 +get true2 +get truefalse2 +get 3 +get true3 +get c +get d +get 1235 +get 1234 +get 123 +get bar/find +get bar/whynot +get bar/elbereth +get say/elbereth diff --git a/src/util/dict_pcre.map b/src/util/dict_pcre.map new file mode 100644 index 0000000..23c976a --- /dev/null +++ b/src/util/dict_pcre.map @@ -0,0 +1,28 @@ +if /true/ fodder +/1/ 1 +if /false/ +/2/ 2 +endif fodder +/3/ 3 +endif +/a/!/b/ a!b +/c/ +/(1)(2)(3)(5)/ ($1)($2)($3)($4)($5) +/(1)(2)(3)(4)/ ($1)($2)($3)($4) +/(1)(2)(3)/ ($1)($2)($3) +# trailing whitespace below +if /bar/ +if !/xyzzy/ +/(elbereth)/ ($1) +!/(bogus)/ ($1) +!/find/ Don't have a liquor license +endif +endif +# trailing whitespace above +! +# dangling endif and if +endif +endif +if /./ +if /./ +/(/ unused diff --git a/src/util/dict_pcre.ref b/src/util/dict_pcre.ref new file mode 100644 index 0000000..9b14678 --- /dev/null +++ b/src/util/dict_pcre.ref @@ -0,0 +1,44 @@ +./dict_open: warning: pcre map dict_pcre.map, line 1: ignoring extra text after IF statement: "fodder" +./dict_open: warning: pcre map dict_pcre.map, line 1: do not prepend whitespace to statements between IF and ENDIF +./dict_open: warning: pcre map dict_pcre.map, line 5: ignoring extra text after ENDIF +./dict_open: warning: pcre map dict_pcre.map, line 8: unknown regexp option "!": skipping this rule +./dict_open: warning: pcre map dict_pcre.map, line 9: no replacement text: using empty string +./dict_open: warning: pcre map dict_pcre.map, line 10: out of range replacement index "5": skipping this rule +./dict_open: warning: pcre map dict_pcre.map, line 17: $number found in negative match replacement text: skipping this rule +./dict_open: warning: pcre map dict_pcre.map, line 22: no regexp: skipping this rule +./dict_open: warning: pcre map dict_pcre.map, line 24: ignoring ENDIF without matching IF +./dict_open: warning: pcre map dict_pcre.map, line 25: ignoring ENDIF without matching IF +./dict_open: warning: pcre map dict_pcre.map, line 28: error in regex at offset 1: missing closing parenthesis +./dict_open: warning: pcre map dict_pcre.map, line 27: IF has no matching ENDIF +./dict_open: warning: pcre map dict_pcre.map, line 26: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get true +true: not found +> get true1 +true1=1 +> get true2 +true2: not found +> get truefalse2 +truefalse2=2 +> get 3 +3: not found +> get true3 +true3=3 +> get c +c= +> get d +d: not found +> get 1235 +1235=(1)(2)(3) +> get 1234 +1234=(1)(2)(3)(4) +> get 123 +123=(1)(2)(3) +> get bar/find +bar/find: not found +> get bar/whynot +bar/whynot=Don't have a liquor license +> get bar/elbereth +bar/elbereth=(elbereth) +> get say/elbereth +say/elbereth: not found diff --git a/src/util/dict_pcre_file.in b/src/util/dict_pcre_file.in new file mode 100644 index 0000000..28c0bd5 --- /dev/null +++ b/src/util/dict_pcre_file.in @@ -0,0 +1,4 @@ +get file1 +get file2 +get file3 +get files12 diff --git a/src/util/dict_pcre_file.map b/src/util/dict_pcre_file.map new file mode 100644 index 0000000..4fd12e6 --- /dev/null +++ b/src/util/dict_pcre_file.map @@ -0,0 +1,6 @@ +/file1/ dict_pcre_file1 +/file2/ dict_pcre_file2 +/file3/ dict_pcre_file3 +/files12/ dict_pcre_file1, dict_pcre_file2 +/files13/ dict_pcre_file1, dict_pcre_file3 +/file-comma/ , diff --git a/src/util/dict_pcre_file.ref b/src/util/dict_pcre_file.ref new file mode 100644 index 0000000..727306d --- /dev/null +++ b/src/util/dict_pcre_file.ref @@ -0,0 +1,12 @@ +./dict_open: warning: pcre map dict_pcre_file.map, line 3: open dict_pcre_file3: No such file or directory: skipping this rule +./dict_open: warning: pcre map dict_pcre_file.map, line 5: open dict_pcre_file3: No such file or directory: skipping this rule +./dict_open: warning: pcre map dict_pcre_file.map, line 6: empty pathname list: >>,<<': skipping this rule +owner=untrusted (uid=USER) +> get file1 +file1=dGhpcy1pcy1maWxlMQo= +> get file2 +file2=dGhpcy1pcy1maWxlMgo= +> get file3 +file3: not found +> get files12 +files12=dGhpcy1pcy1maWxlMQoKdGhpcy1pcy1maWxlMgo= diff --git a/src/util/dict_pipe.c b/src/util/dict_pipe.c new file mode 100644 index 0000000..8ce0faa --- /dev/null +++ b/src/util/dict_pipe.c @@ -0,0 +1,189 @@ +/*++ +/* NAME +/* dict_pipe 3 +/* SUMMARY +/* dictionary manager interface for pipelined tables +/* SYNOPSIS +/* #include +/* +/* DICT *dict_pipe_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_pipe_open() opens a pipeline of one or more tables. +/* Example: "\fBpipemap:{\fItype_1:name_1, ..., type_n:name_n\fR}". +/* +/* Each "pipemap:" query is given to the first table. Each +/* lookup result becomes the query for the next table in the +/* pipeline, and the last table produces the final result. +/* When any table lookup produces no result, the pipeline +/* produces no result. +/* +/* The first and last characters of the "pipemap:" table name +/* must be '{' and '}'. Within these, individual maps are +/* separated with comma or whitespace. +/* +/* The open_flags and dict_flags arguments are passed on to +/* the underlying dictionaries. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include "mymalloc.h" +#include "htable.h" +#include "dict.h" +#include "dict_pipe.h" +#include "stringops.h" +#include "vstring.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + ARGV *map_pipe; /* pipelined tables */ + VSTRING *qr_buf; /* query/reply buffer */ +} DICT_PIPE; + +#define STR(x) vstring_str(x) + +/* dict_pipe_lookup - search pipelined tables */ + +static const char *dict_pipe_lookup(DICT *dict, const char *query) +{ + static const char myname[] = "dict_pipe_lookup"; + DICT_PIPE *dict_pipe = (DICT_PIPE *) dict; + DICT *map; + char **cpp; + char *dict_type_name; + const char *result = 0; + + vstring_strcpy(dict_pipe->qr_buf, query); + for (cpp = dict_pipe->map_pipe->argv; (dict_type_name = *cpp) != 0; cpp++) { + if ((map = dict_handle(dict_type_name)) == 0) + msg_panic("%s: dictionary \"%s\" not found", myname, dict_type_name); + if ((result = dict_get(map, STR(dict_pipe->qr_buf))) == 0) + DICT_ERR_VAL_RETURN(dict, map->error, result); + vstring_strcpy(dict_pipe->qr_buf, result); + } + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, STR(dict_pipe->qr_buf)); +} + +/* dict_pipe_close - disassociate from pipelined tables */ + +static void dict_pipe_close(DICT *dict) +{ + DICT_PIPE *dict_pipe = (DICT_PIPE *) dict; + char **cpp; + char *dict_type_name; + + for (cpp = dict_pipe->map_pipe->argv; (dict_type_name = *cpp) != 0; cpp++) + dict_unregister(dict_type_name); + argv_free(dict_pipe->map_pipe); + vstring_free(dict_pipe->qr_buf); + dict_free(dict); +} + +/* dict_pipe_open - open pipelined tables */ + +DICT *dict_pipe_open(const char *name, int open_flags, int dict_flags) +{ + static const char myname[] = "dict_pipe_open"; + DICT_PIPE *dict_pipe; + char *saved_name = 0; + char *dict_type_name; + ARGV *argv = 0; + char **cpp; + DICT *dict; + int match_flags = 0; + struct DICT_OWNER aggr_owner; + size_t len; + + /* + * Clarity first. Let the optimizer worry about redundant code. + */ +#define DICT_PIPE_RETURN(x) do { \ + if (saved_name != 0) \ + myfree(saved_name); \ + if (argv != 0) \ + argv_free(argv); \ + return (x); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_PIPE, name)); + + /* + * Split the table name into its constituent parts. + */ + if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0 + || *(saved_name = mystrndup(name + 1, len - 2)) == 0 + || ((argv = argv_splitq(saved_name, CHARS_COMMA_SP, CHARS_BRACE)), + (argv->argc == 0))) + DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{type:name...}\"", + DICT_TYPE_PIPE, name, + DICT_TYPE_PIPE)); + + /* + * The least-trusted table in the pipeline determines the over-all trust + * level. The first table determines the pattern-matching flags. + */ + DICT_OWNER_AGGREGATE_INIT(aggr_owner); + for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) { + if (msg_verbose) + msg_info("%s: %s", myname, dict_type_name); + if (strchr(dict_type_name, ':') == 0) + DICT_PIPE_RETURN(dict_surrogate(DICT_TYPE_PIPE, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{type:name...}\"", + DICT_TYPE_PIPE, name, + DICT_TYPE_PIPE)); + if ((dict = dict_handle(dict_type_name)) == 0) + dict = dict_open(dict_type_name, open_flags, dict_flags); + dict_register(dict_type_name, dict); + DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner); + if (cpp == argv->argv) + match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN); + } + + /* + * Bundle up the result. + */ + dict_pipe = + (DICT_PIPE *) dict_alloc(DICT_TYPE_PIPE, name, sizeof(*dict_pipe)); + dict_pipe->dict.lookup = dict_pipe_lookup; + dict_pipe->dict.close = dict_pipe_close; + dict_pipe->dict.flags = dict_flags | match_flags; + dict_pipe->dict.owner = aggr_owner; + dict_pipe->qr_buf = vstring_alloc(100); + dict_pipe->map_pipe = argv; + argv = 0; + DICT_PIPE_RETURN(DICT_DEBUG (&dict_pipe->dict)); +} diff --git a/src/util/dict_pipe.h b/src/util/dict_pipe.h new file mode 100644 index 0000000..8d03009 --- /dev/null +++ b/src/util/dict_pipe.h @@ -0,0 +1,37 @@ +#ifndef _DICT_PIPE_H_INCLUDED_ +#define _DICT_PIPE_H_INCLUDED_ + +/*++ +/* NAME +/* dict_pipe 3h +/* SUMMARY +/* dictionary manager interface for pipelined tables +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_PIPE "pipemap" + +extern DICT *dict_pipe_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_pipe_test.in b/src/util/dict_pipe_test.in new file mode 100644 index 0000000..9626dcd --- /dev/null +++ b/src/util/dict_pipe_test.in @@ -0,0 +1,9 @@ +${VALGRIND} ./dict_open 'pipemap:{inline:{k1=v1,k2=v2},inline:{v2=v3}}' read < get k0 +k0: not found +> get k1 +k1: not found +> get k2 +k2=v3 ++ ./dict_open pipemap:{inline:{k1=v1},fail:fail} read +owner=trusted (uid=2147483647) +> get k0 +k0: not found +> get k1 +k1: error diff --git a/src/util/dict_random.c b/src/util/dict_random.c new file mode 100644 index 0000000..36f79b3 --- /dev/null +++ b/src/util/dict_random.c @@ -0,0 +1,179 @@ +/*++ +/* NAME +/* dict_random 3 +/* SUMMARY +/* dictionary manager interface for randomized tables +/* SYNOPSIS +/* #include +/* +/* DICT *dict_random_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_random_open() opens an in-memory, read-only, table. +/* Example: "\fBrandmap:{\fIresult_1, ... ,result_n}\fR". +/* +/* Each table query returns a random choice from the specified +/* results. Other table access methods are not supported. +/* +/* The first and last characters of the "randmap:" table name +/* must be '{' and '}'. Within these, individual maps are +/* separated with comma or whitespace. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + ARGV *replies; /* reply values */ +} DICT_RANDOM; + +#define STR(x) vstring_str(x) + +/* dict_random_lookup - find randomized-table entry */ + +static const char *dict_random_lookup(DICT *dict, const char *unused_query) +{ + DICT_RANDOM *dict_random = (DICT_RANDOM *) dict; + + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, + dict_random->replies->argv[myrand() % dict_random->replies->argc]); +} + +/* dict_random_close - disassociate from randomized table */ + +static void dict_random_close(DICT *dict) +{ + DICT_RANDOM *dict_random = (DICT_RANDOM *) dict; + + if (dict_random->replies) + argv_free(dict_random->replies); + dict_free(dict); +} + +static char *dict_random_parse_name(DICT *dict, ARGV **argv, + const char *string, + const char *delim, + const char *parens) +{ + ARGV *argvp = argv_alloc(1); + char *saved_string = mystrdup(string); + char *bp = saved_string; + char *arg; + VSTRING *b64 = 0; + char *err = 0; + + while ((arg = mystrtokq(&bp, delim, parens)) != 0) { + if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) { + if ((b64 = dict_file_to_b64(dict, arg)) != 0) { + argv_add(argvp, vstring_str(b64), (char *) 0); + } else { + err = dict_file_get_error(dict); + break; + } + } else { + argv_add(argvp, arg, (char *) 0); + } + } + argv_terminate(argvp); + myfree(saved_string); + *argv = argvp; + return (err); +} + +/* dict_random_open - open a randomized table */ + +DICT *dict_random_open(const char *name, int open_flags, int dict_flags) +{ + DICT_RANDOM *dict_random; + char *saved_name = 0; + size_t len; + char *err = 0; + + /* + * Clarity first. Let the optimizer worry about redundant code. + */ +#define DICT_RANDOM_RETURN(x) do { \ + DICT *__d = (x); \ + if (saved_name != 0) \ + myfree(saved_name); \ + if (err != 0) \ + myfree(err); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_RANDOM_RETURN(dict_surrogate(DICT_TYPE_RANDOM, name, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_RANDOM, name)); + + /* + * Bundle up preliminary results. + */ + dict_random = + (DICT_RANDOM *) dict_alloc(DICT_TYPE_RANDOM, name, sizeof(*dict_random)); + dict_random->dict.lookup = dict_random_lookup; + dict_random->dict.close = dict_random_close; + dict_random->dict.flags = dict_flags | DICT_FLAG_PATTERN; + dict_random->replies = 0; + dict_random->dict.owner.status = DICT_OWNER_TRUSTED; + dict_random->dict.owner.uid = 0; + + /* + * Split the table name into its constituent parts. + */ + if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0 + || *(saved_name = mystrndup(name + 1, len - 2)) == 0 + || (err = dict_random_parse_name(&dict_random->dict, + &dict_random->replies, saved_name, + CHARS_COMMA_SP, CHARS_BRACE)) != 0 + || dict_random->replies->argc == 0) { + dict_random_close(&dict_random->dict); + DICT_RANDOM_RETURN(err == 0 ? + dict_surrogate(DICT_TYPE_RANDOM, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{value...}\"", + DICT_TYPE_RANDOM, name, + DICT_TYPE_RANDOM) : + dict_surrogate(DICT_TYPE_RANDOM, name, + open_flags, dict_flags, + "%s", err)); + } + dict_file_purge_buffers(&dict_random->dict); + DICT_RANDOM_RETURN(DICT_DEBUG (&dict_random->dict)); +} diff --git a/src/util/dict_random.h b/src/util/dict_random.h new file mode 100644 index 0000000..b143d11 --- /dev/null +++ b/src/util/dict_random.h @@ -0,0 +1,37 @@ +#ifndef _DICT_RANDOM_H_INCLUDED_ +#define _DICT_RANDOM_H_INCLUDED_ + +/*++ +/* NAME +/* dict_random 3h +/* SUMMARY +/* dictionary manager interface for randomized tables +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_RANDOM "randmap" + +extern DICT *dict_random_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_random.ref b/src/util/dict_random.ref new file mode 100644 index 0000000..0993409 --- /dev/null +++ b/src/util/dict_random.ref @@ -0,0 +1,20 @@ +owner=trusted (uid=0) +> get foo +foo=123 +> get bar +bar=123 +./dict_open: error: bad syntax: "randmap:123"; need "randmap:{value...}" +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: randmap:123 is unavailable. bad syntax: "randmap:123"; need "randmap:{value...}" +foo: error +./dict_open: error: bad syntax: "randmap:{ 123 "; need "randmap:{value...}" +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: randmap:{ 123 is unavailable. bad syntax: "randmap:{ 123 "; need "randmap:{value...}" +foo: error +./dict_open: error: bad syntax: "randmap:{ 123 }x"; need "randmap:{value...}" +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: randmap:{ 123 }x is unavailable. bad syntax: "randmap:{ 123 }x"; need "randmap:{value...}" +foo: error diff --git a/src/util/dict_random_file.ref b/src/util/dict_random_file.ref new file mode 100644 index 0000000..cb01873 --- /dev/null +++ b/src/util/dict_random_file.ref @@ -0,0 +1,10 @@ +./dict_open: error: open fooxx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: randmap:{fooxx} is unavailable. open fooxx: No such file or directory +foo: error +owner=trusted (uid=0) +> get foo +foo=dGhpcy1pcy1maWxlMQo= +> get bar +bar=dGhpcy1pcy1maWxlMQo= diff --git a/src/util/dict_regexp.c b/src/util/dict_regexp.c new file mode 100644 index 0000000..e5e95cf --- /dev/null +++ b/src/util/dict_regexp.c @@ -0,0 +1,874 @@ +/*++ +/* NAME +/* dict_regexp 3 +/* SUMMARY +/* dictionary manager interface to REGEXP regular expression library +/* SYNOPSIS +/* #include +/* +/* DICT *dict_regexp_open(name, dummy, dict_flags) +/* const char *name; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_regexp_open() opens the named file and compiles the contained +/* regular expressions. The result object can be used to match strings +/* against the table. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* regexp_table(5) format of Postfix regular expression tables +/* AUTHOR(S) +/* LaMont Jones +/* lamont@hp.com +/* +/* Based on PCRE dictionary contributed by Andrew McNamara +/* andrewm@connect.com.au +/* connect.com.au Pty. Ltd. +/* Level 3, 213 Miller St +/* North Sydney, NSW, Australia +/* +/* Heavily rewritten by Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" + +#ifdef HAS_POSIX_REGEXP + +#include +#include +#include +#include +#include +#include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "safe.h" +#include "vstream.h" +#include "vstring.h" +#include "stringops.h" +#include "readlline.h" +#include "dict.h" +#include "dict_regexp.h" +#include "mac_parse.h" +#include "warn_stat.h" +#include "mvect.h" + + /* + * Support for IF/ENDIF based on an idea by Bert Driehuis. + */ +#define DICT_REGEXP_OP_MATCH 1 /* Match this regexp */ +#define DICT_REGEXP_OP_IF 2 /* Increase if/endif nesting on match */ +#define DICT_REGEXP_OP_ENDIF 3 /* Decrease if/endif nesting on match */ + + /* + * Regular expression before compiling. + */ +typedef struct { + char *regexp; /* regular expression */ + int options; /* regcomp() options */ + int match; /* positive or negative match */ +} DICT_REGEXP_PATTERN; + + /* + * Compiled generic rule, and subclasses that derive from it. + */ +typedef struct DICT_REGEXP_RULE { + int op; /* DICT_REGEXP_OP_MATCH/IF/ENDIF */ + int lineno; /* source file line number */ + struct DICT_REGEXP_RULE *next; /* next rule in dict */ +} DICT_REGEXP_RULE; + +typedef struct { + DICT_REGEXP_RULE rule; /* generic part */ + regex_t *first_exp; /* compiled primary pattern */ + int first_match; /* positive or negative match */ + regex_t *second_exp; /* compiled secondary pattern */ + int second_match; /* positive or negative match */ + char *replacement; /* replacement text */ + size_t max_sub; /* largest $number in replacement */ +} DICT_REGEXP_MATCH_RULE; + +typedef struct { + DICT_REGEXP_RULE rule; /* generic members */ + regex_t *expr; /* the condition */ + int match; /* positive or negative match */ + struct DICT_REGEXP_RULE *endif_rule;/* matching endif rule */ +} DICT_REGEXP_IF_RULE; + + /* + * Regexp map. + */ +typedef struct { + DICT dict; /* generic members */ + regmatch_t *pmatch; /* matched substring info */ + DICT_REGEXP_RULE *head; /* first rule */ + VSTRING *expansion_buf; /* lookup result */ +} DICT_REGEXP; + + /* + * Macros to make dense code more readable. + */ +#define NULL_SUBSTITUTIONS (0) +#define NULL_MATCH_RESULT ((regmatch_t *) 0) + + /* + * Context for $number expansion callback. + */ +typedef struct { + DICT_REGEXP *dict_regexp; /* the dictionary handle */ + DICT_REGEXP_MATCH_RULE *match_rule; /* the rule we matched */ + const char *lookup_string; /* matched text */ +} DICT_REGEXP_EXPAND_CONTEXT; + + /* + * Context for $number pre-scan callback. + */ +typedef struct { + const char *mapname; /* name of regexp map */ + int lineno; /* where in file */ + size_t max_sub; /* largest $number seen */ + char *literal; /* constant result, $$ -> $ */ +} DICT_REGEXP_PRESCAN_CONTEXT; + + /* + * Compatibility. + */ +#ifndef MAC_PARSE_OK +#define MAC_PARSE_OK 0 +#endif + +/* dict_regexp_expand - replace $number with substring from matched text */ + +static int dict_regexp_expand(int type, VSTRING *buf, void *ptr) +{ + DICT_REGEXP_EXPAND_CONTEXT *ctxt = (DICT_REGEXP_EXPAND_CONTEXT *) ptr; + DICT_REGEXP_MATCH_RULE *match_rule = ctxt->match_rule; + DICT_REGEXP *dict_regexp = ctxt->dict_regexp; + regmatch_t *pmatch; + size_t n; + + /* + * Replace $number by the corresponding substring from the matched text. + * We pre-scanned the replacement text at compile time, so any out of + * range $number means that something impossible has happened. + */ + if (type == MAC_PARSE_VARNAME) { + n = atoi(vstring_str(buf)); + if (n < 1 || n > match_rule->max_sub) + msg_panic("regexp map %s, line %d: out of range replacement index \"%s\"", + dict_regexp->dict.name, match_rule->rule.lineno, + vstring_str(buf)); + pmatch = dict_regexp->pmatch + n; + if (pmatch->rm_so < 0 || pmatch->rm_so == pmatch->rm_eo) + return (MAC_PARSE_UNDEF); /* empty or not matched */ + vstring_strncat(dict_regexp->expansion_buf, + ctxt->lookup_string + pmatch->rm_so, + pmatch->rm_eo - pmatch->rm_so); + return (MAC_PARSE_OK); + } + + /* + * Straight text - duplicate with no substitution. + */ + else { + vstring_strcat(dict_regexp->expansion_buf, vstring_str(buf)); + return (MAC_PARSE_OK); + } +} + +/* dict_regexp_regerror - report regexp compile/execute error */ + +static void dict_regexp_regerror(const char *mapname, int lineno, int error, + const regex_t *expr) +{ + char errbuf[256]; + + (void) regerror(error, expr, errbuf, sizeof(errbuf)); + msg_warn("regexp map %s, line %d: %s", mapname, lineno, errbuf); +} + + /* + * Inlined to reduce function call overhead in the time-critical loop. + */ +#define DICT_REGEXP_REGEXEC(err, map, line, expr, match, str, nsub, pmatch) \ + ((err) = regexec((expr), (str), (nsub), (pmatch), 0), \ + ((err) == REG_NOMATCH ? !(match) : \ + (err) == 0 ? (match) : \ + (dict_regexp_regerror((map), (line), (err), (expr)), 0))) + +/* dict_regexp_lookup - match string and perform optional substitution */ + +static const char *dict_regexp_lookup(DICT *dict, const char *lookup_string) +{ + DICT_REGEXP *dict_regexp = (DICT_REGEXP *) dict; + DICT_REGEXP_RULE *rule; + DICT_REGEXP_IF_RULE *if_rule; + DICT_REGEXP_MATCH_RULE *match_rule; + DICT_REGEXP_EXPAND_CONTEXT expand_context; + int error; + + dict->error = 0; + + if (msg_verbose) + msg_info("dict_regexp_lookup: %s: %s", dict->name, lookup_string); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_MUL) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, lookup_string); + lookup_string = lowercase(vstring_str(dict->fold_buf)); + } + for (rule = dict_regexp->head; rule; rule = rule->next) { + + switch (rule->op) { + + /* + * Search for the first matching primary expression. Limit the + * overhead for substring substitution to the bare minimum. + */ + case DICT_REGEXP_OP_MATCH: + match_rule = (DICT_REGEXP_MATCH_RULE *) rule; + if (!DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno, + match_rule->first_exp, + match_rule->first_match, + lookup_string, + match_rule->max_sub > 0 ? + match_rule->max_sub + 1 : 0, + dict_regexp->pmatch)) + continue; + if (match_rule->second_exp + && !DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno, + match_rule->second_exp, + match_rule->second_match, + lookup_string, + NULL_SUBSTITUTIONS, + NULL_MATCH_RESULT)) + continue; + + /* + * Skip $number substitutions when the replacement text contains + * no $number strings, as learned during the compile time + * pre-scan. The pre-scan already replaced $$ by $. + */ + if (match_rule->max_sub == 0) + return (match_rule->replacement); + + /* + * Perform $number substitutions on the replacement text. We + * pre-scanned the replacement text at compile time. Any macro + * expansion errors at this point mean something impossible has + * happened. + */ + if (!dict_regexp->expansion_buf) + dict_regexp->expansion_buf = vstring_alloc(10); + VSTRING_RESET(dict_regexp->expansion_buf); + expand_context.lookup_string = lookup_string; + expand_context.match_rule = match_rule; + expand_context.dict_regexp = dict_regexp; + + if (mac_parse(match_rule->replacement, dict_regexp_expand, + (void *) &expand_context) & MAC_PARSE_ERROR) + msg_panic("regexp map %s, line %d: bad replacement syntax", + dict->name, rule->lineno); + VSTRING_TERMINATE(dict_regexp->expansion_buf); + return (vstring_str(dict_regexp->expansion_buf)); + + /* + * Conditional. + */ + case DICT_REGEXP_OP_IF: + if_rule = (DICT_REGEXP_IF_RULE *) rule; + if (DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno, + if_rule->expr, if_rule->match, lookup_string, + NULL_SUBSTITUTIONS, NULL_MATCH_RESULT)) + continue; + /* An IF without matching ENDIF has no "endif" rule. */ + if ((rule = if_rule->endif_rule) == 0) + return (0); + /* FALLTHROUGH */ + + /* + * ENDIF after IF. + */ + case DICT_REGEXP_OP_ENDIF: + continue; + + default: + msg_panic("dict_regexp_lookup: impossible operation %d", rule->op); + } + } + return (0); +} + +/* dict_regexp_close - close regexp dictionary */ + +static void dict_regexp_close(DICT *dict) +{ + DICT_REGEXP *dict_regexp = (DICT_REGEXP *) dict; + DICT_REGEXP_RULE *rule; + DICT_REGEXP_RULE *next; + DICT_REGEXP_MATCH_RULE *match_rule; + DICT_REGEXP_IF_RULE *if_rule; + + for (rule = dict_regexp->head; rule; rule = next) { + next = rule->next; + switch (rule->op) { + case DICT_REGEXP_OP_MATCH: + match_rule = (DICT_REGEXP_MATCH_RULE *) rule; + if (match_rule->first_exp) { + regfree(match_rule->first_exp); + myfree((void *) match_rule->first_exp); + } + if (match_rule->second_exp) { + regfree(match_rule->second_exp); + myfree((void *) match_rule->second_exp); + } + if (match_rule->replacement) + myfree((void *) match_rule->replacement); + break; + case DICT_REGEXP_OP_IF: + if_rule = (DICT_REGEXP_IF_RULE *) rule; + if (if_rule->expr) { + regfree(if_rule->expr); + myfree((void *) if_rule->expr); + } + break; + case DICT_REGEXP_OP_ENDIF: + break; + default: + msg_panic("dict_regexp_close: unknown operation %d", rule->op); + } + myfree((void *) rule); + } + if (dict_regexp->pmatch) + myfree((void *) dict_regexp->pmatch); + if (dict_regexp->expansion_buf) + vstring_free(dict_regexp->expansion_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_regexp_get_pat - extract one pattern with options from rule */ + +static int dict_regexp_get_pat(const char *mapname, int lineno, char **bufp, + DICT_REGEXP_PATTERN *pat) +{ + char *p = *bufp; + char re_delim; + + /* + * Process negation operators. + */ + pat->match = 1; + for (;;) { + if (*p == '!') + pat->match = !pat->match; + else if (!ISSPACE(*p)) + break; + p++; + } + if (*p == 0) { + msg_warn("regexp map %s, line %d: no regexp: skipping this rule", + mapname, lineno); + return (0); + } + + /* + * Search for the closing delimiter, handling backslash escape. + */ + re_delim = *p++; + pat->regexp = p; + while (*p) { + if (*p == '\\') { + if (p[1]) + p++; + else + break; + } else if (*p == re_delim) { + break; + } + ++p; + } + if (!*p) { + msg_warn("regexp map %s, line %d: no closing regexp delimiter \"%c\": " + "skipping this rule", mapname, lineno, re_delim); + return (0); + } + *p++ = 0; /* null terminate */ + + /* + * Search for options. + */ + pat->options = REG_EXTENDED | REG_ICASE; + while (*p && !ISSPACE(*p) && *p != '!') { + switch (*p) { + case 'i': + pat->options ^= REG_ICASE; + break; + case 'm': + pat->options ^= REG_NEWLINE; + break; + case 'x': + pat->options ^= REG_EXTENDED; + break; + default: + msg_warn("regexp map %s, line %d: unknown regexp option \"%c\": " + "skipping this rule", mapname, lineno, *p); + return (0); + } + ++p; + } + *bufp = p; + return (1); +} + +/* dict_regexp_get_pats - get the primary and second patterns and flags */ + +static int dict_regexp_get_pats(const char *mapname, int lineno, char **p, + DICT_REGEXP_PATTERN *first_pat, + DICT_REGEXP_PATTERN *second_pat) +{ + + /* + * Get the primary and optional secondary patterns and their flags. + */ + if (dict_regexp_get_pat(mapname, lineno, p, first_pat) == 0) + return (0); + if (**p == '!') { +#if 0 + static int bitrot_warned = 0; + + if (bitrot_warned == 0) { + msg_warn("regexp file %s, line %d: /pattern1/!/pattern2/ goes away," + " use \"if !/pattern2/ ... /pattern1/ ... endif\" instead", + mapname, lineno); + bitrot_warned = 1; + } +#endif + if (dict_regexp_get_pat(mapname, lineno, p, second_pat) == 0) + return (0); + } else { + second_pat->regexp = 0; + } + return (1); +} + +/* dict_regexp_prescan - find largest $number in replacement text */ + +static int dict_regexp_prescan(int type, VSTRING *buf, void *context) +{ + DICT_REGEXP_PRESCAN_CONTEXT *ctxt = (DICT_REGEXP_PRESCAN_CONTEXT *) context; + size_t n; + + /* + * Keep a copy of literal text (with $$ already replaced by $) if and + * only if the replacement text contains no $number expression. This way + * we can avoid having to scan the replacement text at lookup time. + */ + if (type == MAC_PARSE_VARNAME) { + if (ctxt->literal) { + myfree(ctxt->literal); + ctxt->literal = 0; + } + if (!alldig(vstring_str(buf))) { + msg_warn("regexp map %s, line %d: non-numeric replacement index \"%s\"", + ctxt->mapname, ctxt->lineno, vstring_str(buf)); + return (MAC_PARSE_ERROR); + } + n = atoi(vstring_str(buf)); + if (n < 1) { + msg_warn("regexp map %s, line %d: out-of-range replacement index \"%s\"", + ctxt->mapname, ctxt->lineno, vstring_str(buf)); + return (MAC_PARSE_ERROR); + } + if (n > ctxt->max_sub) + ctxt->max_sub = n; + } else if (type == MAC_PARSE_LITERAL && ctxt->max_sub == 0) { + if (ctxt->literal) + msg_panic("regexp map %s, line %d: multiple literals but no $number", + ctxt->mapname, ctxt->lineno); + ctxt->literal = mystrdup(vstring_str(buf)); + } + return (MAC_PARSE_OK); +} + +/* dict_regexp_compile_pat - compile one pattern */ + +static regex_t *dict_regexp_compile_pat(const char *mapname, int lineno, + DICT_REGEXP_PATTERN *pat) +{ + int error; + regex_t *expr; + + expr = (regex_t *) mymalloc(sizeof(*expr)); + error = regcomp(expr, pat->regexp, pat->options); + if (error != 0) { + dict_regexp_regerror(mapname, lineno, error, expr); + myfree((void *) expr); + return (0); + } + return (expr); +} + +/* dict_regexp_rule_alloc - fill in a generic rule structure */ + +static DICT_REGEXP_RULE *dict_regexp_rule_alloc(int op, int lineno, size_t size) +{ + DICT_REGEXP_RULE *rule; + + rule = (DICT_REGEXP_RULE *) mymalloc(size); + rule->op = op; + rule->lineno = lineno; + rule->next = 0; + + return (rule); +} + +/* dict_regexp_parseline - parse one rule */ + +static DICT_REGEXP_RULE *dict_regexp_parseline(DICT *dict, const char *mapname, + int lineno, char *line, + int nesting) +{ + char *p; + + p = line; + + /* + * An ordinary rule takes one or two patterns and replacement text. + */ + if (!ISALNUM(*p)) { + DICT_REGEXP_PATTERN first_pat; + DICT_REGEXP_PATTERN second_pat; + DICT_REGEXP_PRESCAN_CONTEXT prescan_context; + regex_t *first_exp = 0; + regex_t *second_exp; + DICT_REGEXP_MATCH_RULE *match_rule; + + /* + * Get the primary and the optional secondary patterns. + */ + if (!dict_regexp_get_pats(mapname, lineno, &p, &first_pat, &second_pat)) + return (0); + + /* + * Get the replacement text. + */ + while (*p && ISSPACE(*p)) + ++p; + if (!*p) { + msg_warn("regexp map %s, line %d: no replacement text: " + "using empty string", mapname, lineno); + } + + /* + * Find the highest-numbered $number in the replacement text. We can + * speed up pattern matching 1) by passing hints to the regexp + * compiler, setting the REG_NOSUB flag when the replacement text + * contains no $number string; 2) by passing hints to the regexp + * execution code, limiting the amount of text that is made available + * for substitution. + */ + prescan_context.mapname = mapname; + prescan_context.lineno = lineno; + prescan_context.max_sub = 0; + prescan_context.literal = 0; + + /* + * The optimizer will eliminate code duplication and/or dead code. + */ +#define CREATE_MATCHOP_ERROR_RETURN(rval) do { \ + if (first_exp) { \ + regfree(first_exp); \ + myfree((void *) first_exp); \ + } \ + if (prescan_context.literal) \ + myfree(prescan_context.literal); \ + return (rval); \ + } while (0) + + if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) { + VSTRING *base64_buf; + char *err; + + if ((base64_buf = dict_file_to_b64(dict, p)) == 0) { + err = dict_file_get_error(dict); + msg_warn("regexp map %s, line %d: %s: skipping this rule", + mapname, lineno, err); + myfree(err); + CREATE_MATCHOP_ERROR_RETURN(0); + } + p = vstring_str(base64_buf); + } + if (mac_parse(p, dict_regexp_prescan, (void *) &prescan_context) + & MAC_PARSE_ERROR) { + msg_warn("regexp map %s, line %d: bad replacement syntax: " + "skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + + /* + * Compile the primary and the optional secondary pattern. Speed up + * execution when no matched text needs to be substituted into the + * result string, or when the highest numbered substring is less than + * the total number of () subpatterns. + */ + if (prescan_context.max_sub == 0) + first_pat.options |= REG_NOSUB; + if (prescan_context.max_sub > 0 && first_pat.match == 0) { + msg_warn("regexp map %s, line %d: $number found in negative match " + "replacement text: skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + if (prescan_context.max_sub > 0 && (dict->flags & DICT_FLAG_NO_REGSUB)) { + msg_warn("regexp map %s, line %d: " + "regular expression substitution is not allowed: " + "skipping this rule", mapname, lineno); + CREATE_MATCHOP_ERROR_RETURN(0); + } + if ((first_exp = dict_regexp_compile_pat(mapname, lineno, + &first_pat)) == 0) + CREATE_MATCHOP_ERROR_RETURN(0); + if (prescan_context.max_sub > first_exp->re_nsub) { + msg_warn("regexp map %s, line %d: out of range replacement index \"%d\": " + "skipping this rule", mapname, lineno, + (int) prescan_context.max_sub); + CREATE_MATCHOP_ERROR_RETURN(0); + } + if (second_pat.regexp != 0) { + second_pat.options |= REG_NOSUB; + if ((second_exp = dict_regexp_compile_pat(mapname, lineno, + &second_pat)) == 0) + CREATE_MATCHOP_ERROR_RETURN(0); + } else { + second_exp = 0; + } + match_rule = (DICT_REGEXP_MATCH_RULE *) + dict_regexp_rule_alloc(DICT_REGEXP_OP_MATCH, lineno, + sizeof(DICT_REGEXP_MATCH_RULE)); + match_rule->first_exp = first_exp; + match_rule->first_match = first_pat.match; + match_rule->max_sub = prescan_context.max_sub; + match_rule->second_exp = second_exp; + match_rule->second_match = second_pat.match; + if (prescan_context.literal) + match_rule->replacement = prescan_context.literal; + else + match_rule->replacement = mystrdup(p); + return ((DICT_REGEXP_RULE *) match_rule); + } + + /* + * The IF operator takes one pattern but no replacement text. + */ + else if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) { + DICT_REGEXP_PATTERN pattern; + regex_t *expr; + DICT_REGEXP_IF_RULE *if_rule; + + p += 2; + while (*p && ISSPACE(*p)) + p++; + if (!dict_regexp_get_pat(mapname, lineno, &p, &pattern)) + return (0); + while (*p && ISSPACE(*p)) + ++p; + if (*p) { + msg_warn("regexp map %s, line %d: ignoring extra text after" + " IF statement: \"%s\"", mapname, lineno, p); + msg_warn("regexp map %s, line %d: do not prepend whitespace" + " to statements between IF and ENDIF", mapname, lineno); + } + if ((expr = dict_regexp_compile_pat(mapname, lineno, &pattern)) == 0) + return (0); + if_rule = (DICT_REGEXP_IF_RULE *) + dict_regexp_rule_alloc(DICT_REGEXP_OP_IF, lineno, + sizeof(DICT_REGEXP_IF_RULE)); + if_rule->expr = expr; + if_rule->match = pattern.match; + if_rule->endif_rule = 0; + return ((DICT_REGEXP_RULE *) if_rule); + } + + /* + * The ENDIF operator takes no patterns and no replacement text. + */ + else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) { + DICT_REGEXP_RULE *rule; + + p += 5; + if (nesting == 0) { + msg_warn("regexp map %s, line %d: ignoring ENDIF without matching IF", + mapname, lineno); + return (0); + } + while (*p && ISSPACE(*p)) + ++p; + if (*p) + msg_warn("regexp map %s, line %d: ignoring extra text after ENDIF", + mapname, lineno); + rule = dict_regexp_rule_alloc(DICT_REGEXP_OP_ENDIF, lineno, + sizeof(DICT_REGEXP_RULE)); + return (rule); + } + + /* + * Unrecognized input. + */ + else { + msg_warn("regexp map %s, line %d: ignoring unrecognized request", + mapname, lineno); + return (0); + } +} + +/* dict_regexp_open - load and compile a file containing regular expressions */ + +DICT *dict_regexp_open(const char *mapname, int open_flags, int dict_flags) +{ + const char myname[] = "dict_regexp_open"; + DICT_REGEXP *dict_regexp; + VSTREAM *map_fp = 0; + struct stat st; + VSTRING *why = 0; + VSTRING *line_buffer = 0; + DICT_REGEXP_RULE *rule; + DICT_REGEXP_RULE *last_rule = 0; + int lineno; + int last_line = 0; + size_t max_sub = 0; + int nesting = 0; + char *p; + DICT_REGEXP_RULE **rule_stack = 0; + MVECT mvect; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_REGEXP_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (line_buffer != 0) \ + vstring_free(line_buffer); \ + if (map_fp != 0) \ + vstream_fclose(map_fp); \ + if (why != 0) \ + vstring_free(why); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_REGEXP_OPEN_RETURN(dict_surrogate(DICT_TYPE_REGEXP, + mapname, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_REGEXP, mapname)); + + /* + * Open the configuration file. + */ + if ((map_fp = dict_stream_open(DICT_TYPE_REGEXP, mapname, O_RDONLY, + dict_flags, &st, &why)) == 0) + DICT_REGEXP_OPEN_RETURN(dict_surrogate(DICT_TYPE_REGEXP, mapname, + open_flags, dict_flags, + "%s", vstring_str(why))); + line_buffer = vstring_alloc(100); + + dict_regexp = (DICT_REGEXP *) dict_alloc(DICT_TYPE_REGEXP, mapname, + sizeof(*dict_regexp)); + dict_regexp->dict.lookup = dict_regexp_lookup; + dict_regexp->dict.close = dict_regexp_close; + dict_regexp->dict.flags = dict_flags | DICT_FLAG_PATTERN; + if (dict_flags & DICT_FLAG_FOLD_MUL) + dict_regexp->dict.fold_buf = vstring_alloc(10); + dict_regexp->head = 0; + dict_regexp->pmatch = 0; + dict_regexp->expansion_buf = 0; + dict_regexp->dict.owner.uid = st.st_uid; + dict_regexp->dict.owner.status = (st.st_uid != 0); + + /* + * Parse the regexp table. + */ + while (readllines(line_buffer, map_fp, &last_line, &lineno)) { + p = vstring_str(line_buffer); + trimblanks(p, 0)[0] = 0; + if (*p == 0) + continue; + rule = dict_regexp_parseline(&dict_regexp->dict, mapname, lineno, + p, nesting); + if (rule == 0) + continue; + if (rule->op == DICT_REGEXP_OP_MATCH) { + if (((DICT_REGEXP_MATCH_RULE *) rule)->max_sub > max_sub) + max_sub = ((DICT_REGEXP_MATCH_RULE *) rule)->max_sub; + } else if (rule->op == DICT_REGEXP_OP_IF) { + if (rule_stack == 0) + rule_stack = (DICT_REGEXP_RULE **) mvect_alloc(&mvect, + sizeof(*rule_stack), nesting + 1, + (MVECT_FN) 0, (MVECT_FN) 0); + else + rule_stack = + (DICT_REGEXP_RULE **) mvect_realloc(&mvect, nesting + 1); + rule_stack[nesting] = rule; + nesting++; + } else if (rule->op == DICT_REGEXP_OP_ENDIF) { + DICT_REGEXP_IF_RULE *if_rule; + + if (nesting-- <= 0) + /* Already handled in dict_regexp_parseline(). */ + msg_panic("%s: ENDIF without IF", myname); + if (rule_stack[nesting]->op != DICT_REGEXP_OP_IF) + msg_panic("%s: unexpected rule stack element type %d", + myname, rule_stack[nesting]->op); + if_rule = (DICT_REGEXP_IF_RULE *) rule_stack[nesting]; + if_rule->endif_rule = rule; + } + if (last_rule == 0) + dict_regexp->head = rule; + else + last_rule->next = rule; + last_rule = rule; + } + + while (nesting-- > 0) + msg_warn("regexp map %s, line %d: IF has no matching ENDIF", + mapname, rule_stack[nesting]->lineno); + + if (rule_stack) + (void) mvect_free(&mvect); + + /* + * Allocate space for only as many matched substrings as used in the + * replacement text. + */ + if (max_sub > 0) + dict_regexp->pmatch = + (regmatch_t *) mymalloc(sizeof(regmatch_t) * (max_sub + 1)); + + dict_file_purge_buffers(&dict_regexp->dict); + DICT_REGEXP_OPEN_RETURN(DICT_DEBUG (&dict_regexp->dict)); +} + +#endif diff --git a/src/util/dict_regexp.h b/src/util/dict_regexp.h new file mode 100644 index 0000000..2874b27 --- /dev/null +++ b/src/util/dict_regexp.h @@ -0,0 +1,42 @@ +#ifndef _DICT_REGEXP_H_INCLUDED_ +#define _DICT_REGEXP_H_INCLUDED_ + +/*++ +/* NAME +/* dict_regexp 3h +/* SUMMARY +/* dictionary manager interface to REGEXP regular expression library +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_REGEXP "regexp" + +extern DICT *dict_regexp_open(const char *, int, int); + +/* AUTHOR(S) +/* LaMont Jones +/* lamont@hp.com +/* +/* Based on PCRE dictionary contributed by Andrew McNamara +/* andrewm@connect.com.au +/* connect.com.au Pty. Ltd. +/* Level 3, 213 Miller St +/* North Sydney, NSW, Australia +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_regexp.in b/src/util/dict_regexp.in new file mode 100644 index 0000000..d5f3b2e --- /dev/null +++ b/src/util/dict_regexp.in @@ -0,0 +1,17 @@ +get true +get true1 +get true2 +get truefalse2 +get 3 +get true3 +get c +get d +get ab +get aa +get 1235 +get 1234 +get 123 +get bar/find +get bar/whynot +get bar/elbereth +get say/elbereth diff --git a/src/util/dict_regexp.map b/src/util/dict_regexp.map new file mode 100644 index 0000000..7a6cefb --- /dev/null +++ b/src/util/dict_regexp.map @@ -0,0 +1,27 @@ +if /true/ fodder +/1/ 1 +if /false/ +/2/ 2 +endif fodder +/3/ 3 +endif +/a/!/b/ a!b +/c/ +/(1)(2)(3)(5)/ ($1)($2)($3)($4)($5) +/(1)(2)(3)(4)/ ($1)($2)($3)($4) +/(1)(2)(3)/ ($1)($2)($3) +# trailing whitespace below +if /bar/ +if !/xyzzy/ +/(elbereth)/ ($1) +!/(bogus)/ ($1) +!/find/ Don't have a liquor license +endif +endif +# trailing whitespace above +! +# dangling endif and if +endif +endif +if /./ +if /./ diff --git a/src/util/dict_regexp.ref b/src/util/dict_regexp.ref new file mode 100644 index 0000000..2a08a3b --- /dev/null +++ b/src/util/dict_regexp.ref @@ -0,0 +1,46 @@ +./dict_open: warning: regexp map dict_regexp.map, line 1: ignoring extra text after IF statement: "fodder" +./dict_open: warning: regexp map dict_regexp.map, line 1: do not prepend whitespace to statements between IF and ENDIF +./dict_open: warning: regexp map dict_regexp.map, line 5: ignoring extra text after ENDIF +./dict_open: warning: regexp map dict_regexp.map, line 9: no replacement text: using empty string +./dict_open: warning: regexp map dict_regexp.map, line 10: out of range replacement index "5": skipping this rule +./dict_open: warning: regexp map dict_regexp.map, line 17: $number found in negative match replacement text: skipping this rule +./dict_open: warning: regexp map dict_regexp.map, line 22: no regexp: skipping this rule +./dict_open: warning: regexp map dict_regexp.map, line 24: ignoring ENDIF without matching IF +./dict_open: warning: regexp map dict_regexp.map, line 25: ignoring ENDIF without matching IF +./dict_open: warning: regexp map dict_regexp.map, line 27: IF has no matching ENDIF +./dict_open: warning: regexp map dict_regexp.map, line 26: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get true +true: not found +> get true1 +true1=1 +> get true2 +true2: not found +> get truefalse2 +truefalse2=2 +> get 3 +3: not found +> get true3 +true3=3 +> get c +c= +> get d +d: not found +> get ab +ab: not found +> get aa +aa=a!b +> get 1235 +1235=(1)(2)(3) +> get 1234 +1234=(1)(2)(3)(4) +> get 123 +123=(1)(2)(3) +> get bar/find +bar/find: not found +> get bar/whynot +bar/whynot=Don't have a liquor license +> get bar/elbereth +bar/elbereth=(elbereth) +> get say/elbereth +say/elbereth: not found diff --git a/src/util/dict_regexp_file.in b/src/util/dict_regexp_file.in new file mode 100644 index 0000000..fef4146 --- /dev/null +++ b/src/util/dict_regexp_file.in @@ -0,0 +1,3 @@ +get file1 +get file2 +get file3 diff --git a/src/util/dict_regexp_file.map b/src/util/dict_regexp_file.map new file mode 100644 index 0000000..41c6b6a --- /dev/null +++ b/src/util/dict_regexp_file.map @@ -0,0 +1,3 @@ +/file1/ dict_regexp_file1 +/file2/ dict_regexp_file2 +/file3/ dict_regexp_file3 diff --git a/src/util/dict_regexp_file.ref b/src/util/dict_regexp_file.ref new file mode 100644 index 0000000..2218143 --- /dev/null +++ b/src/util/dict_regexp_file.ref @@ -0,0 +1,8 @@ +./dict_open: warning: regexp map dict_regexp_file.map, line 3: open dict_regexp_file3: No such file or directory: skipping this rule +owner=untrusted (uid=USER) +> get file1 +file1=dGhpcy1pcy1maWxlMQo= +> get file2 +file2=dGhpcy1pcy1maWxlMgo= +> get file3 +file3: not found diff --git a/src/util/dict_sdbm.c b/src/util/dict_sdbm.c new file mode 100644 index 0000000..23371dc --- /dev/null +++ b/src/util/dict_sdbm.c @@ -0,0 +1,483 @@ +/*++ +/* NAME +/* dict_sdbm 3 +/* SUMMARY +/* dictionary manager interface to SDBM files +/* SYNOPSIS +/* #include +/* +/* DICT *dict_sdbm_open(path, open_flags, dict_flags) +/* const char *name; +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_sdbm_open() opens the named SDBM database and makes it available +/* via the generic interface described in dict_open(3). +/* DIAGNOSTICS +/* Fatal errors: cannot open file, file write error, out of memory. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* sdbm(3) data base subroutines +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include "sys_defs.h" + +/* System library. */ + +#include +#include +#include +#ifdef HAS_SDBM +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAS_SDBM + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + SDBM *dbm; /* open database */ + VSTRING *key_buf; /* key buffer */ + VSTRING *val_buf; /* result buffer */ +} DICT_SDBM; + +#define SCOPY(buf, data, size) \ + vstring_str(vstring_strncpy(buf ? buf : (buf = vstring_alloc(10)), data, size)) + +/* dict_sdbm_lookup - find database entry */ + +static const char *dict_sdbm_lookup(DICT *dict, const char *name) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + datum dbm_value; + const char *result = 0; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_sdbm_lookup: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + dict->flags &= ~DICT_FLAG_TRY0NULL; + result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (result == 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); + if (dbm_value.dptr != 0) { + dict->flags &= ~DICT_FLAG_TRY1NULL; + result = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); + + return (result); +} + +/* dict_sdbm_update - add or update database entry */ + +static int dict_sdbm_update(DICT *dict, const char *name, const char *value) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + datum dbm_value; + int status; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_sdbm_update: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + dbm_key.dptr = (void *) name; + dbm_value.dptr = (void *) value; + dbm_key.dsize = strlen(name); + dbm_value.dsize = strlen(value); + + /* + * If undecided about appending a null byte to key and value, choose a + * default depending on the platform. + */ + if ((dict->flags & DICT_FLAG_TRY1NULL) + && (dict->flags & DICT_FLAG_TRY0NULL)) { +#ifdef DBM_NO_TRAILING_NULL + dict->flags &= ~DICT_FLAG_TRY1NULL; +#else + dict->flags &= ~DICT_FLAG_TRY0NULL; +#endif + } + + /* + * Optionally append a null byte to key and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dsize++; + dbm_value.dsize++; + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); + + /* + * Do the update. + */ + if ((status = sdbm_store(dict_sdbm->dbm, dbm_key, dbm_value, + (dict->flags & DICT_FLAG_DUP_REPLACE) ? DBM_REPLACE : DBM_INSERT)) < 0) + msg_fatal("error writing SDBM database %s: %m", dict_sdbm->dict.name); + if (status) { + if (dict->flags & DICT_FLAG_DUP_IGNORE) + /* void */ ; + else if (dict->flags & DICT_FLAG_DUP_WARN) + msg_warn("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name); + else + msg_fatal("%s: duplicate entry: \"%s\"", dict_sdbm->dict.name, name); + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); + + return (status); +} + +/* dict_sdbm_delete - delete one entry from the dictionary */ + +static int dict_sdbm_delete(DICT *dict, const char *name) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + int status = 1; + + dict->error = 0; + + /* + * Sanity check. + */ + if ((dict->flags & (DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL)) == 0) + msg_panic("dict_sdbm_delete: no DICT_FLAG_TRY1NULL | DICT_FLAG_TRY0NULL flag"); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, name); + name = lowercase(vstring_str(dict->fold_buf)); + } + + /* + * Acquire an exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); + + /* + * See if this DBM file was written with one null byte appended to key + * and value. + */ + if (dict->flags & DICT_FLAG_TRY1NULL) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name) + 1; + sdbm_clearerr(dict_sdbm->dbm); + if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) { + if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */ + msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY0NULL; /* found */ + } + } + + /* + * See if this DBM file was written with no null byte appended to key and + * value. + */ + if (status > 0 && (dict->flags & DICT_FLAG_TRY0NULL)) { + dbm_key.dptr = (void *) name; + dbm_key.dsize = strlen(name); + sdbm_clearerr(dict_sdbm->dbm); + if ((status = sdbm_delete(dict_sdbm->dbm, dbm_key)) < 0) { + if (sdbm_error(dict_sdbm->dbm) != 0)/* fatal error */ + msg_fatal("error deleting from %s: %m", dict_sdbm->dict.name); + status = 1; /* not found */ + } else { + dict->flags &= ~DICT_FLAG_TRY1NULL; /* found */ + } + } + + /* + * Release the exclusive lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); + + return (status); +} + +/* traverse the dictionary */ + +static int dict_sdbm_sequence(DICT *dict, const int function, + const char **key, const char **value) +{ + const char *myname = "dict_sdbm_sequence"; + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + datum dbm_key; + datum dbm_value; + int status; + + dict->error = 0; + + /* + * Acquire a shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("%s: lock dictionary: %m", dict_sdbm->dict.name); + + /* + * Determine and execute the seek function. It returns the key. + */ + sdbm_clearerr(dict_sdbm->dbm); + switch (function) { + case DICT_SEQ_FUN_FIRST: + dbm_key = sdbm_firstkey(dict_sdbm->dbm); + break; + case DICT_SEQ_FUN_NEXT: + dbm_key = sdbm_nextkey(dict_sdbm->dbm); + break; + default: + msg_panic("%s: invalid function: %d", myname, function); + } + + if (dbm_key.dptr != 0 && dbm_key.dsize > 0) { + + /* + * Copy the key so that it is guaranteed null terminated. + */ + *key = SCOPY(dict_sdbm->key_buf, dbm_key.dptr, dbm_key.dsize); + + /* + * Fetch the corresponding value. + */ + dbm_value = sdbm_fetch(dict_sdbm->dbm, dbm_key); + + if (dbm_value.dptr != 0 && dbm_value.dsize > 0) { + + /* + * Copy the value so that it is guaranteed null terminated. + */ + *value = SCOPY(dict_sdbm->val_buf, dbm_value.dptr, dbm_value.dsize); + status = 0; + } else { + + /* + * Determine if we have hit the last record or an error + * condition. + */ + if (sdbm_error(dict_sdbm->dbm)) + msg_fatal("error seeking %s: %m", dict_sdbm->dict.name); + status = 1; /* no error: eof/not found + * (should not happen!) */ + } + } else { + + /* + * Determine if we have hit the last record or an error condition. + */ + if (sdbm_error(dict_sdbm->dbm)) + msg_fatal("error seeking %s: %m", dict_sdbm->dict.name); + status = 1; /* no error: eof/not found */ + } + + /* + * Release the shared lock. + */ + if ((dict->flags & DICT_FLAG_LOCK) + && myflock(dict->lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("%s: unlock dictionary: %m", dict_sdbm->dict.name); + + return (status); +} + +/* dict_sdbm_close - disassociate from data base */ + +static void dict_sdbm_close(DICT *dict) +{ + DICT_SDBM *dict_sdbm = (DICT_SDBM *) dict; + + sdbm_close(dict_sdbm->dbm); + if (dict_sdbm->key_buf) + vstring_free(dict_sdbm->key_buf); + if (dict_sdbm->val_buf) + vstring_free(dict_sdbm->val_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_sdbm_open - open SDBM data base */ + +DICT *dict_sdbm_open(const char *path, int open_flags, int dict_flags) +{ + DICT_SDBM *dict_sdbm; + struct stat st; + SDBM *dbm; + char *dbm_path; + int lock_fd; + + /* + * Note: DICT_FLAG_LOCK is used only by programs that do fine-grained (in + * the time domain) locking while accessing individual database records. + * + * Programs such as postmap/postalias use their own large-grained (in the + * time domain) locks while rewriting the entire file. + */ + if (dict_flags & DICT_FLAG_LOCK) { + dbm_path = concatenate(path, ".dir", (char *) 0); + if ((lock_fd = open(dbm_path, open_flags, 0644)) < 0) + msg_fatal("open database %s: %m", dbm_path); + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_SHARED) < 0) + msg_fatal("shared-lock database %s for open: %m", dbm_path); + } + + /* + * XXX sdbm_open() has no const in prototype. + */ + if ((dbm = sdbm_open((char *) path, open_flags, 0644)) == 0) + msg_fatal("open database %s.{dir,pag}: %m", path); + + if (dict_flags & DICT_FLAG_LOCK) { + if (myflock(lock_fd, INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) + msg_fatal("unlock database %s for open: %m", dbm_path); + if (close(lock_fd) < 0) + msg_fatal("close database %s: %m", dbm_path); + } + dict_sdbm = (DICT_SDBM *) dict_alloc(DICT_TYPE_SDBM, path, sizeof(*dict_sdbm)); + dict_sdbm->dict.lookup = dict_sdbm_lookup; + dict_sdbm->dict.update = dict_sdbm_update; + dict_sdbm->dict.delete = dict_sdbm_delete; + dict_sdbm->dict.sequence = dict_sdbm_sequence; + dict_sdbm->dict.close = dict_sdbm_close; + dict_sdbm->dict.lock_fd = sdbm_dirfno(dbm); + dict_sdbm->dict.stat_fd = sdbm_pagfno(dbm); + if (fstat(dict_sdbm->dict.stat_fd, &st) < 0) + msg_fatal("dict_sdbm_open: fstat: %m"); + dict_sdbm->dict.mtime = st.st_mtime; + dict_sdbm->dict.owner.uid = st.st_uid; + dict_sdbm->dict.owner.status = (st.st_uid != 0); + + /* + * Warn if the source file is newer than the indexed file, except when + * the source file changed only seconds ago. + */ + if ((dict_flags & DICT_FLAG_LOCK) != 0 + && stat(path, &st) == 0 + && st.st_mtime > dict_sdbm->dict.mtime + && st.st_mtime < time((time_t *) 0) - 100) + msg_warn("database %s is older than source file %s", dbm_path, path); + + close_on_exec(sdbm_pagfno(dbm), CLOSE_ON_EXEC); + close_on_exec(sdbm_dirfno(dbm), CLOSE_ON_EXEC); + dict_sdbm->dict.flags = dict_flags | DICT_FLAG_FIXED; + if ((dict_flags & (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL)) == 0) + dict_sdbm->dict.flags |= (DICT_FLAG_TRY0NULL | DICT_FLAG_TRY1NULL); + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_sdbm->dict.fold_buf = vstring_alloc(10); + dict_sdbm->dbm = dbm; + dict_sdbm->key_buf = 0; + dict_sdbm->val_buf = 0; + + if ((dict_flags & DICT_FLAG_LOCK)) + myfree(dbm_path); + + return (DICT_DEBUG (&dict_sdbm->dict)); +} + +#endif diff --git a/src/util/dict_sdbm.h b/src/util/dict_sdbm.h new file mode 100644 index 0000000..6a1b281 --- /dev/null +++ b/src/util/dict_sdbm.h @@ -0,0 +1,37 @@ +#ifndef _DICT_SDBM_H_INCLUDED_ +#define _DICT_SDBM_H_INCLUDED_ + +/*++ +/* NAME +/* dict_dbm 3h +/* SUMMARY +/* dictionary manager interface to DBM files +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_SDBM "sdbm" + +extern DICT *dict_sdbm_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_seq.in b/src/util/dict_seq.in new file mode 100644 index 0000000..998e7c4 --- /dev/null +++ b/src/util/dict_seq.in @@ -0,0 +1,11 @@ +put 5 5 +put 3 3 +put 4 4 +put 1 1 +put 2 2 +first +next +next +next +next +next diff --git a/src/util/dict_seq.ref b/src/util/dict_seq.ref new file mode 100644 index 0000000..ce7d744 --- /dev/null +++ b/src/util/dict_seq.ref @@ -0,0 +1,22 @@ +> put 5 5 +5=5 +> put 3 3 +3=3 +> put 4 4 +4=4 +> put 1 1 +1=1 +> put 2 2 +2=2 +> first +4=4 +> next +2=2 +> next +5=5 +> next +3=3 +> next +1=1 +> next +not found diff --git a/src/util/dict_sockmap.c b/src/util/dict_sockmap.c new file mode 100644 index 0000000..becad1a --- /dev/null +++ b/src/util/dict_sockmap.c @@ -0,0 +1,384 @@ +/*++ +/* NAME +/* dict_sockmap 3 +/* SUMMARY +/* dictionary manager interface to Sendmail-style socketmap server +/* SYNOPSIS +/* #include +/* +/* DICT *dict_sockmap_open(map, open_flags, dict_flags) +/* const char *map; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_sockmap_open() makes a Sendmail-style socketmap server +/* accessible via the generic dictionary operations described +/* in dict_open(3). The only implemented operation is dictionary +/* lookup. This map type can be useful for simulating a dynamic +/* lookup table. +/* +/* Postfix socketmap names have the form inet:host:port:socketmap-name +/* or unix:pathname:socketmap-name, where socketmap-name +/* specifies the socketmap name that the socketmap server uses. +/* +/* To test this module, build the netstring and dict_open test +/* programs. Run "./netstring nc -l portnumber" as the server, +/* and "./dict_open socketmap:127.0.0.1:portnumber:socketmapname" +/* as the client. +/* PROTOCOL +/* .ad +/* .fi +/* The socketmap class implements a simple protocol: the client +/* sends one request, and the server sends one reply. +/* ENCODING +/* .ad +/* .fi +/* Each request and reply are sent as one netstring object. +/* REQUEST FORMAT +/* .ad +/* .fi +/* .IP " " +/* Search the specified socketmap under the specified key. +/* REPLY FORMAT +/* .ad +/* .fi +/* Replies must be no longer than 100000 characters (not including +/* the netstring encapsulation), and must have the following +/* form: +/* .IP "OK " +/* The requested data was found. +/* .IP "NOTFOUND " +/* The requested data was not found. +/* .IP "TEMP " +/* .IP "TIMEOUT " +/* .IP "PERM " +/* The request failed. The reason, if non-empty, is descriptive +/* text. +/* SECURITY +/* This map cannot be used for security-sensitive information, +/* because neither the connection nor the server are authenticated. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* netstring(3) netstring stream I/O support +/* DIAGNOSTICS +/* Fatal errors: out of memory, unknown host or service name, +/* attempt to update or iterate over map. +/* BUGS +/* The protocol limits are not yet configurable. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * Socket map data structure. + */ +typedef struct { + DICT dict; /* parent class */ + char *sockmap_name; /* on-the-wire socketmap name */ + VSTRING *rdwr_buf; /* read/write buffer */ + HTABLE_INFO *client_info; /* shared endpoint name and handle */ +} DICT_SOCKMAP; + + /* + * Default limits. + */ +#define DICT_SOCKMAP_DEF_TIMEOUT 100 /* connect/read/write timeout */ +#define DICT_SOCKMAP_DEF_MAX_REPLY 100000 /* reply size limit */ +#define DICT_SOCKMAP_DEF_MAX_IDLE 10 /* close idle socket */ +#define DICT_SOCKMAP_DEF_MAX_TTL 100 /* close old socket */ + + /* + * Class variables. + */ +static int dict_sockmap_timeout = DICT_SOCKMAP_DEF_TIMEOUT; +static int dict_sockmap_max_reply = DICT_SOCKMAP_DEF_MAX_REPLY; +static int dict_sockmap_max_idle = DICT_SOCKMAP_DEF_MAX_IDLE; +static int dict_sockmap_max_ttl = DICT_SOCKMAP_DEF_MAX_TTL; + + /* + * The client handle is shared between socketmap instances that have the + * same inet:host:port or unix:pathame information. This could be factored + * out as a general module for reference-counted handles of any kind. + */ +static HTABLE *dict_sockmap_handles; /* shared handles */ + +typedef struct { + AUTO_CLNT *client_handle; /* the client handle */ + int refcount; /* the reference count */ +} DICT_SOCKMAP_REFC_HANDLE; + +#define DICT_SOCKMAP_RH_NAME(ht) (ht)->key +#define DICT_SOCKMAP_RH_HANDLE(ht) \ + ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->client_handle +#define DICT_SOCKMAP_RH_REFCOUNT(ht) \ + ((DICT_SOCKMAP_REFC_HANDLE *) (ht)->value)->refcount + + /* + * Socketmap protocol elements. + */ +#define DICT_SOCKMAP_PROT_OK "OK" +#define DICT_SOCKMAP_PROT_NOTFOUND "NOTFOUND" +#define DICT_SOCKMAP_PROT_TEMP "TEMP" +#define DICT_SOCKMAP_PROT_TIMEOUT "TIMEOUT" +#define DICT_SOCKMAP_PROT_PERM "PERM" + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* dict_sockmap_lookup - socket map lookup */ + +static const char *dict_sockmap_lookup(DICT *dict, const char *key) +{ + const char *myname = "dict_sockmap_lookup"; + DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict; + AUTO_CLNT *sockmap_clnt = DICT_SOCKMAP_RH_HANDLE(dp->client_info); + VSTREAM *fp; + int netstring_err; + char *reply_payload; + int except_count; + const char *error_class; + + if (msg_verbose) + msg_info("%s: key %s", myname, key); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_MUL) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(100); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(STR(dict->fold_buf)); + } + + /* + * We retry connection-level errors once, to make server restarts + * transparent. + */ + for (except_count = 0; /* see below */ ; except_count++) { + + /* + * Look up the stream. + */ + if ((fp = auto_clnt_access(sockmap_clnt)) == 0) { + msg_warn("table %s:%s lookup error: %m", dict->type, dict->name); + dict->error = DICT_ERR_RETRY; + return (0); + } + + /* + * Set up an exception handler. + */ + netstring_setup(fp, dict_sockmap_timeout); + if ((netstring_err = vstream_setjmp(fp)) == 0) { + + /* + * Send the query. This may raise an exception. + */ + vstring_sprintf(dp->rdwr_buf, "%s %s", dp->sockmap_name, key); + NETSTRING_PUT_BUF(fp, dp->rdwr_buf); + + /* + * Receive the response. This may raise an exception. + */ + netstring_get(fp, dp->rdwr_buf, dict_sockmap_max_reply); + + /* + * If we got here, then no exception was raised. + */ + break; + } + + /* + * Handle exceptions. + */ + else { + + /* + * We retry a broken connection only once. + */ + if (except_count == 0 && netstring_err == NETSTRING_ERR_EOF + && errno != ETIMEDOUT) { + auto_clnt_recover(sockmap_clnt); + continue; + } + + /* + * We do not retry other errors. + */ + else { + msg_warn("table %s:%s lookup error: %s", + dict->type, dict->name, + netstring_strerror(netstring_err)); + dict->error = DICT_ERR_RETRY; + return (0); + } + } + } + + /* + * Parse the reply. + */ + VSTRING_TERMINATE(dp->rdwr_buf); + reply_payload = split_at(STR(dp->rdwr_buf), ' '); + if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_OK) == 0) { + dict->error = 0; + return (reply_payload); + } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_NOTFOUND) == 0) { + dict->error = 0; + return (0); + } + /* We got no definitive reply. */ + if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TEMP) == 0) { + error_class = "temporary"; + dict->error = DICT_ERR_RETRY; + } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_TIMEOUT) == 0) { + error_class = "timeout"; + dict->error = DICT_ERR_RETRY; + } else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_PERM) == 0) { + error_class = "permanent"; + dict->error = DICT_ERR_CONFIG; + } else { + error_class = "unknown"; + dict->error = DICT_ERR_RETRY; + } + while (reply_payload && ISSPACE(*reply_payload)) + reply_payload++; + msg_warn("%s:%s socketmap server %s error%s%.200s", + dict->type, dict->name, error_class, + reply_payload && *reply_payload ? ": " : "", + reply_payload && *reply_payload ? + printable(reply_payload, '?') : ""); + return (0); +} + +/* dict_sockmap_close - close socket map */ + +static void dict_sockmap_close(DICT *dict) +{ + const char *myname = "dict_sockmap_close"; + DICT_SOCKMAP *dp = (DICT_SOCKMAP *) dict; + + if (dict_sockmap_handles == 0 || dict_sockmap_handles->used == 0) + msg_panic("%s: attempt to close a non-existent map", myname); + vstring_free(dp->rdwr_buf); + myfree(dp->sockmap_name); + if (--DICT_SOCKMAP_RH_REFCOUNT(dp->client_info) == 0) { + auto_clnt_free(DICT_SOCKMAP_RH_HANDLE(dp->client_info)); + htable_delete(dict_sockmap_handles, + DICT_SOCKMAP_RH_NAME(dp->client_info), myfree); + } + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_sockmap_open - open socket map */ + +DICT *dict_sockmap_open(const char *mapname, int open_flags, int dict_flags) +{ + DICT_SOCKMAP *dp; + char *saved_name = 0; + char *sockmap; + DICT_SOCKMAP_REFC_HANDLE *ref_handle; + HTABLE_INFO *client_info; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_SOCKMAP_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (saved_name != 0) \ + myfree(saved_name); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_SOCKMAP, mapname)); + if (dict_flags & DICT_FLAG_NO_UNAUTH) + DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, + open_flags, dict_flags, + "%s:%s map is not allowed for security-sensitive data", + DICT_TYPE_SOCKMAP, mapname)); + + /* + * Separate the socketmap name from the socketmap server name. + */ + saved_name = mystrdup(mapname); + if ((sockmap = split_at_right(saved_name, ':')) == 0) + DICT_SOCKMAP_OPEN_RETURN(dict_surrogate(DICT_TYPE_SOCKMAP, mapname, + open_flags, dict_flags, + "%s requires server:socketmap argument", + DICT_TYPE_SOCKMAP)); + + /* + * Use one reference-counted client handle for all socketmaps with the + * same inet:host:port or unix:pathname information. + * + * XXX Todo: graceful degradation after endpoint syntax error. + */ + if (dict_sockmap_handles == 0) + dict_sockmap_handles = htable_create(1); + if ((client_info = htable_locate(dict_sockmap_handles, saved_name)) == 0) { + ref_handle = (DICT_SOCKMAP_REFC_HANDLE *) mymalloc(sizeof(*ref_handle)); + client_info = htable_enter(dict_sockmap_handles, + saved_name, (void *) ref_handle); + /* XXX Late initialization, so we can reuse macros for consistency. */ + DICT_SOCKMAP_RH_REFCOUNT(client_info) = 1; + DICT_SOCKMAP_RH_HANDLE(client_info) = + auto_clnt_create(saved_name, dict_sockmap_timeout, + dict_sockmap_max_idle, dict_sockmap_max_ttl); + } else + DICT_SOCKMAP_RH_REFCOUNT(client_info) += 1; + + /* + * Instantiate a socket map handle. + */ + dp = (DICT_SOCKMAP *) dict_alloc(DICT_TYPE_SOCKMAP, mapname, sizeof(*dp)); + dp->rdwr_buf = vstring_alloc(100); + dp->sockmap_name = mystrdup(sockmap); + dp->client_info = client_info; + dp->dict.lookup = dict_sockmap_lookup; + dp->dict.close = dict_sockmap_close; + /* Don't look up parent domains or network superblocks. */ + dp->dict.flags = dict_flags | DICT_FLAG_PATTERN; + + DICT_SOCKMAP_OPEN_RETURN(DICT_DEBUG (&dp->dict)); +} diff --git a/src/util/dict_sockmap.h b/src/util/dict_sockmap.h new file mode 100644 index 0000000..b81212a --- /dev/null +++ b/src/util/dict_sockmap.h @@ -0,0 +1,37 @@ +#ifndef _DICT_SOCKMAP_H_INCLUDED_ +#define _DICT_SOCKMAP_H_INCLUDED_ + +/*++ +/* NAME +/* dict_sockmap 3h +/* SUMMARY +/* dictionary manager interface to Sendmail-stye socketmap. +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_SOCKMAP "socketmap" + +extern DICT *dict_sockmap_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_static.c b/src/util/dict_static.c new file mode 100644 index 0000000..448dde0 --- /dev/null +++ b/src/util/dict_static.c @@ -0,0 +1,151 @@ +/*++ +/* NAME +/* dict_static 3 +/* SUMMARY +/* dictionary manager interface to static variables +/* SYNOPSIS +/* #include +/* +/* DICT *dict_static_open(name, name, dict_flags) +/* const char *name; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_static_open() implements a dummy dictionary that returns +/* as lookup result the dictionary name, regardless of the lookup +/* key value. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* jeffm +/* ghostgun.com +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include /* sprintf() prototype */ +#include +#include +#include + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "stringops.h" +#include "dict.h" +#include "dict_static.h" + + /* + * Subclass of DICT. + */ +typedef struct { + DICT dict; /* parent class */ + char *value; +} DICT_STATIC; + +#define STR(x) vstring_str(x) + +/* dict_static_lookup - access static value*/ + +static const char *dict_static_lookup(DICT *dict, const char *unused_name) +{ + DICT_STATIC *dict_static = (DICT_STATIC *) dict; + + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, dict_static->value); +} + +/* dict_static_close - close static dictionary */ + +static void dict_static_close(DICT *dict) +{ + DICT_STATIC *dict_static = (DICT_STATIC *) dict; + + if (dict_static->value) + myfree(dict_static->value); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_static_open - make association with static variable */ + +DICT *dict_static_open(const char *name, int open_flags, int dict_flags) +{ + DICT_STATIC *dict_static; + char *err = 0; + char *cp, *saved_name = 0; + const char *value; + VSTRING *base64_buf; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_STATIC_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (saved_name != 0) \ + myfree(saved_name); \ + if (err != 0) \ + myfree(err); \ + return (__d); \ + } while (0) + + /* + * Optionally strip surrounding braces and whitespace. + */ + if (name[0] == CHARS_BRACE[0]) { + saved_name = cp = mystrdup(name); + if ((err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) != 0) + DICT_STATIC_OPEN_RETURN(dict_surrogate(DICT_TYPE_STATIC, name, + open_flags, dict_flags, + "bad %s:name syntax: %s", + DICT_TYPE_STATIC, + err)); + value = cp; + } else { + value = name; + } + + /* + * Bundle up the preliminary result. + */ + dict_static = (DICT_STATIC *) dict_alloc(DICT_TYPE_STATIC, name, + sizeof(*dict_static)); + dict_static->dict.lookup = dict_static_lookup; + dict_static->dict.close = dict_static_close; + dict_static->dict.flags = dict_flags | DICT_FLAG_FIXED; + dict_static->dict.owner.status = DICT_OWNER_TRUSTED; + dict_static->value = 0; + + /* + * Optionally replace the value with file contents. + */ + if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) { + if ((base64_buf = dict_file_to_b64(&dict_static->dict, value)) == 0) { + err = dict_file_get_error(&dict_static->dict); + dict_close(&dict_static->dict); + DICT_STATIC_OPEN_RETURN(dict_surrogate(DICT_TYPE_STATIC, name, + open_flags, dict_flags, + "%s", err)); + } + value = vstring_str(base64_buf); + } + + /* + * Finalize the result. + */ + dict_static->value = mystrdup(value); + dict_file_purge_buffers(&dict_static->dict); + + DICT_STATIC_OPEN_RETURN(DICT_DEBUG (&(dict_static->dict))); +} diff --git a/src/util/dict_static.h b/src/util/dict_static.h new file mode 100644 index 0000000..d4ad1cc --- /dev/null +++ b/src/util/dict_static.h @@ -0,0 +1,35 @@ +#ifndef _DICT_STATIC_H_INCLUDED_ +#define _DICT_STATIC_H_INCLUDED_ + +/*++ +/* NAME +/* dict_static 3h +/* SUMMARY +/* dictionary manager interface to static settings +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_STATIC "static" + +extern DICT *dict_static_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* jeffm +/* ghostgun.com +/*--*/ + +#endif diff --git a/src/util/dict_static.ref b/src/util/dict_static.ref new file mode 100644 index 0000000..550264a --- /dev/null +++ b/src/util/dict_static.ref @@ -0,0 +1,14 @@ +owner=trusted (uid=2147483647) +> get foo +foo=fooxx +> get bar +bar=fooxx +./dict_open: error: bad static:name syntax: missing '}' in "{ foo xx " +owner=trusted (uid=2147483647) +./dict_open: error: bad static:name syntax: syntax error after '}' in "{ foo xx }x" +owner=trusted (uid=2147483647) +owner=trusted (uid=2147483647) +> get foo +foo=foo xx +> get bar +bar=foo xx diff --git a/src/util/dict_static_file.ref b/src/util/dict_static_file.ref new file mode 100644 index 0000000..259f9fb --- /dev/null +++ b/src/util/dict_static_file.ref @@ -0,0 +1,10 @@ +./dict_open: error: open fooxx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: static:fooxx is unavailable. open fooxx: No such file or directory +foo: error +owner=trusted (uid=2147483647) +> get file1 +file1=dGhpcy1pcy1maWxlMQo= +> get file2 +file2=dGhpcy1pcy1maWxlMQo= diff --git a/src/util/dict_stream.c b/src/util/dict_stream.c new file mode 100644 index 0000000..e28ad71 --- /dev/null +++ b/src/util/dict_stream.c @@ -0,0 +1,274 @@ +/*++ +/* NAME +/* dict_stream 3 +/* SUMMARY +/* +/* SYNOPSIS +/* #include +/* +/* VSTREAM *dict_stream_open( +/* const char *dict_type, +/* const char *mapname, +/* int open_flags, +/* int dict_flags, +/* struct stat * st, +/* VSTRING **why) +/* DESCRIPTION +/* dict_stream_open() opens a dictionary, which can be specified +/* as a file name, or as inline text enclosed with {}. If successful, +/* dict_stream_open() returns a non-null VSTREAM pointer. Otherwise, +/* it returns an error text through the why argument, allocating +/* storage for the error text if the why argument points to a +/* null pointer. +/* +/* When the dictionary file is specified inline, dict_stream_open() +/* removes the outer {} from the mapname value, and removes leading +/* or trailing comma or whitespace from the result. It then expects +/* to find zero or more rules enclosed in {}, separated by comma +/* and/or whitespace. dict_stream() writes each rule as one text +/* line to an in-memory stream, without its enclosing {} and without +/* leading or trailing whitespace. The result value is a VSTREAM +/* pointer for the in-memory stream that can be read as a regular +/* file. +/* .sp +/* inline-file = "{" 0*(0*wsp-comma rule-spec) 0*wsp-comma "}" +/* .sp +/* rule-spec = "{" 0*wsp rule-text 0*wsp "}" +/* .sp +/* rule-text = any text containing zero or more balanced {} +/* .sp +/* wsp-comma = wsp | "," +/* .sp +/* wsp = whitespace +/* +/* Arguments: +/* .IP dict_type +/* .IP open_flags +/* .IP dict_flags +/* The same as with dict_open(3). +/* .IP mapname +/* Pathname of a file with dictionary content, or inline dictionary +/* content as specified above. +/* .IP st +/* File metadata with the file owner, or fake metadata with the +/* real UID and GID of the dict_stream_open() caller. This is +/* used for "taint" tracking (zero=trusted, non-zero=untrusted). +/* IP why +/* Pointer to pointer to error message storage. dict_stream_open() +/* updates this storage when reporting an error, and allocates +/* memory if why points to a null pointer. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* dict_inline_to_multiline - convert inline map spec to multiline text */ + +static char *dict_inline_to_multiline(VSTRING *vp, const char *mapname) +{ + char *saved_name = mystrdup(mapname); + char *bp = saved_name; + char *cp; + char *err = 0; + + VSTRING_RESET(vp); + /* Strip the {} from the map "name". */ + err = extpar(&bp, CHARS_BRACE, EXTPAR_FLAG_NONE); + /* Extract zero or more rules inside {}. */ + while (err == 0 && (cp = mystrtokq(&bp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) + if ((err = extpar(&cp, CHARS_BRACE, EXTPAR_FLAG_STRIP)) == 0) + /* Write rule to in-memory file. */ + vstring_sprintf_append(vp, "%s\n", cp); + VSTRING_TERMINATE(vp); + myfree(saved_name); + return (err); +} + +/* dict_stream_open - open inline configuration or configuration file */ + +VSTREAM *dict_stream_open(const char *dict_type, const char *mapname, + int open_flags, int dict_flags, + struct stat * st, VSTRING **why) +{ + VSTRING *inline_buf = 0; + VSTREAM *map_fp; + char *err = 0; + +#define RETURN_0_WITH_REASON(...) do { \ + if (*why == 0) \ + *why = vstring_alloc(100); \ + vstring_sprintf(*why, __VA_ARGS__); \ + if (inline_buf != 0) \ + vstring_free(inline_buf); \ + if (err != 0) \ + myfree(err); \ + return (0); \ + } while (0) + + if (mapname[0] == CHARS_BRACE[0]) { + inline_buf = vstring_alloc(100); + if ((err = dict_inline_to_multiline(inline_buf, mapname)) != 0) + RETURN_0_WITH_REASON("%s map: %s", dict_type, err); + map_fp = vstream_memopen(inline_buf, O_RDONLY); + vstream_control(map_fp, VSTREAM_CTL_OWN_VSTRING, VSTREAM_CTL_END); + st->st_uid = getuid(); /* geteuid()? */ + st->st_gid = getgid(); /* getegid()? */ + return (map_fp); + } else { + if ((map_fp = vstream_fopen(mapname, open_flags, 0)) == 0) + RETURN_0_WITH_REASON("open %s: %m", mapname); + if (fstat(vstream_fileno(map_fp), st) < 0) + msg_fatal("fstat %s: %m", mapname); + return (map_fp); + } +} + +#ifdef TEST + +#include + +int main(int argc, char **argv) +{ + struct testcase { + const char *title; + const char *mapname; /* starts with brace */ + const char *expect_err; /* null or message */ + const char *expect_cont; /* null or content */ + }; + +#define EXP_NOERR 0 +#define EXP_NOCONT 0 + +#define STRING_OR(s, text_if_null) ((s) ? (s) : (text_if_null)) +#define DICT_TYPE_TEST "test" + + const char rule_spec_error[] = DICT_TYPE_TEST " map: " + "syntax error after '}' in \"{blah blah}x\""; + const char inline_config_error[] = DICT_TYPE_TEST " map: " + "syntax error after '}' in \"{{foo bar}, {blah blah}}x\""; + struct testcase testcases[] = { + {"normal", + "{{foo bar}, {blah blah}}", EXP_NOERR, "foo bar\nblah blah\n" + }, + {"trims leading/trailing wsp around rule-text", + "{{ foo bar }, { blah blah }}", EXP_NOERR, "foo bar\nblah blah\n" + }, + {"trims leading/trailing comma-wsp around rule-spec", + "{, ,{foo bar}, {blah blah}, ,}", EXP_NOERR, "foo bar\nblah blah\n" + }, + {"empty inline-file", + "{, }", EXP_NOERR, "" + }, + {"propagates extpar error for inline-file", + "{{foo bar}, {blah blah}}x", inline_config_error, EXP_NOCONT + }, + {"propagates extpar error for rule-spec", + "{{foo bar}, {blah blah}x}", rule_spec_error, EXP_NOCONT + }, + 0, + }; + struct testcase *tp; + VSTRING *act_err = 0; + VSTRING *act_cont = vstring_alloc(100); + VSTREAM *fp; + struct stat st; + ssize_t exp_len; + ssize_t act_len; + int pass; + int fail; + + for (pass = fail = 0, tp = testcases; tp->title; tp++) { + int test_passed = 0; + + msg_info("RUN test case %ld %s", (long) (tp - testcases), tp->title); + +#if 0 + msg_info("title=%s", tp->title); + msg_info("mapname=%s", tp->mapname); + msg_info("expect_err=%s", STRING_OR_NULL(tp->expect_err)); + msg_info("expect_cont=%s", STRINGOR_NULL(tp->expect_cont)); +#endif + + if (act_err) + VSTRING_RESET(act_err); + fp = dict_stream_open(DICT_TYPE_TEST, tp->mapname, O_RDONLY, + 0, &st, &act_err); + if (fp) { + if (tp->expect_err) { + msg_warn("test case %s: got stream, expected error", tp->title); + } else if (!tp->expect_err && act_err && LEN(act_err) > 0) { + msg_warn("test case %s: got error '%s', expected noerror", + tp->title, STR(act_err)); + } else if (!tp->expect_cont) { + msg_warn("test case %s: got stream, expected nostream", + tp->title); + } else { + exp_len = strlen(tp->expect_cont); + if ((act_len = vstream_fread_buf(fp, act_cont, 2 * exp_len)) < 0) { + msg_warn("test case %s: content read error", tp->title); + } else { + VSTRING_TERMINATE(act_cont); + if (strcmp(tp->expect_cont, STR(act_cont)) != 0) { + msg_warn("test case %s: got content '%s', expected '%s'", + tp->title, STR(act_cont), tp->expect_cont); + } else { + test_passed = 1; + } + } + } + } else { + if (!tp->expect_err) { + msg_warn("test case %s: got nostream, expected noerror", + tp->title); + } else if (tp->expect_cont) { + msg_warn("test case %s: got nostream, expected stream", + tp->title); + } else if (strcmp(STR(act_err), tp->expect_err) != 0) { + msg_warn("test case %s: got error '%s', expected '%s'", + tp->title, STR(act_err), tp->expect_err); + } else { + test_passed = 1; + } + + } + if (test_passed) { + msg_info("PASS test %ld", (long) (tp - testcases)); + pass++; + } else { + msg_info("FAIL test %ld", (long) (tp - testcases)); + fail++; + } + if (fp) + vstream_fclose(fp); + } + if (act_err) + vstring_free(act_err); + vstring_free(act_cont); + msg_info("PASS=%d FAIL=%d", pass, fail); + return (fail > 0); +} + +#endif /* TEST */ diff --git a/src/util/dict_stream.ref b/src/util/dict_stream.ref new file mode 100644 index 0000000..87c30e5 --- /dev/null +++ b/src/util/dict_stream.ref @@ -0,0 +1,13 @@ +unknown: RUN test case 0 normal +unknown: PASS test 0 +unknown: RUN test case 1 trims leading/trailing wsp around rule-text +unknown: PASS test 1 +unknown: RUN test case 2 trims leading/trailing comma-wsp around rule-spec +unknown: PASS test 2 +unknown: RUN test case 3 empty inline-file +unknown: PASS test 3 +unknown: RUN test case 4 propagates extpar error for inline-file +unknown: PASS test 4 +unknown: RUN test case 5 propagates extpar error for rule-spec +unknown: PASS test 5 +unknown: PASS=6 FAIL=0 diff --git a/src/util/dict_surrogate.c b/src/util/dict_surrogate.c new file mode 100644 index 0000000..a23cba3 --- /dev/null +++ b/src/util/dict_surrogate.c @@ -0,0 +1,180 @@ +/*++ +/* NAME +/* dict_surrogate 3 +/* SUMMARY +/* surrogate table for graceful "open" failure +/* SYNOPSIS +/* #include +/* +/* DICT *dict_surrogate(dict_type, dict_name, +/* open_flags, dict_flags, +/* format, ...) +/* const char *dict_type; +/* const char *dict_name; +/* int open_flags; +/* int dict_flags; +/* const char *format; +/* +/* int dict_allow_surrogate; +/* DESCRIPTION +/* dict_surrogate() either terminates the program with a fatal +/* error, or provides a dummy dictionary that fails all +/* operations with an error message, allowing the program to +/* continue with reduced functionality. +/* +/* The global dict_allow_surrogate variable controls the choice +/* between fatal error or reduced functionality. The default +/* value is zero (fatal error). This is appropriate for user +/* commands; the non-default is more appropriate for daemons. +/* +/* Arguments: +/* .IP dict_type +/* .IP dict_name +/* .IP open_flags +/* .IP dict_flags +/* The parameters to the failed dictionary open() request. +/* .IP format, ... +/* The reason why the table could not be opened. This text is +/* logged immediately as an "error" class message, and is logged +/* as a "warning" class message upon every attempt to access the +/* surrogate dictionary, before returning a "failed" completion +/* status. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + char *reason; /* open failure reason */ +} DICT_SURROGATE; + +/* dict_surrogate_sequence - fail lookup */ + +static int dict_surrogate_sequence(DICT *dict, int unused_func, + const char **key, const char **value) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); +} + +/* dict_surrogate_update - fail lookup */ + +static int dict_surrogate_update(DICT *dict, const char *unused_name, + const char *unused_value) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); +} + +/* dict_surrogate_lookup - fail lookup */ + +static const char *dict_surrogate_lookup(DICT *dict, const char *unused_name) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, (char *) 0); +} + +/* dict_surrogate_delete - fail delete */ + +static int dict_surrogate_delete(DICT *dict, const char *unused_name) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + msg_warn("%s:%s is unavailable. %s", + dict->type, dict->name, dp->reason); + DICT_ERR_VAL_RETURN(dict, DICT_ERR_RETRY, DICT_STAT_ERROR); +} + +/* dict_surrogate_close - close fail dictionary */ + +static void dict_surrogate_close(DICT *dict) +{ + DICT_SURROGATE *dp = (DICT_SURROGATE *) dict; + + myfree((void *) dp->reason); + dict_free(dict); +} + +int dict_allow_surrogate = 0; + +/* dict_surrogate - terminate or provide surrogate dictionary */ + +DICT *dict_surrogate(const char *dict_type, const char *dict_name, + int open_flags, int dict_flags, + const char *fmt,...) +{ + va_list ap; + va_list ap2; + DICT_SURROGATE *dp; + VSTRING *buf; + void (*log_fn) (const char *, va_list); + int saved_errno = errno; + + /* + * Initialize argument lists. + */ + va_start(ap, fmt); + VA_COPY(ap2, ap); + + /* + * Log the problem immediately when it is detected. The table may not be + * accessed in every program execution (that is the whole point of + * continuing with reduced functionality) but we don't want the problem + * to remain unnoticed until long after a configuration mistake is made. + */ + log_fn = dict_allow_surrogate ? vmsg_error : vmsg_fatal; + log_fn(fmt, ap); + va_end(ap); + + /* + * Log the problem upon each access. + */ + dp = (DICT_SURROGATE *) dict_alloc(dict_type, dict_name, sizeof(*dp)); + dp->dict.lookup = dict_surrogate_lookup; + if (open_flags & O_RDWR) { + dp->dict.update = dict_surrogate_update; + dp->dict.delete = dict_surrogate_delete; + } + dp->dict.sequence = dict_surrogate_sequence; + dp->dict.close = dict_surrogate_close; + dp->dict.flags = dict_flags | DICT_FLAG_PATTERN; + dp->dict.owner.status = DICT_OWNER_TRUSTED; + buf = vstring_alloc(10); + errno = saved_errno; + vstring_vsprintf(buf, fmt, ap2); + va_end(ap2); + dp->reason = vstring_export(buf); + return (DICT_DEBUG (&dp->dict)); +} diff --git a/src/util/dict_tcp.c b/src/util/dict_tcp.c new file mode 100644 index 0000000..922f449 --- /dev/null +++ b/src/util/dict_tcp.c @@ -0,0 +1,315 @@ +/*++ +/* NAME +/* dict_tcp 3 +/* SUMMARY +/* dictionary manager interface to tcp-based lookup tables +/* SYNOPSIS +/* #include +/* +/* DICT *dict_tcp_open(map, open_flags, dict_flags) +/* const char *map; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_tcp_open() makes a TCP server accessible via the generic +/* dictionary operations described in dict_open(3). +/* The only implemented operation is dictionary lookup. This map +/* type can be useful for simulating a dynamic lookup table. +/* +/* Map names have the form host:port. +/* +/* The TCP map class implements a very simple protocol: the client +/* sends a request, and the server sends one reply. Requests and +/* replies are sent as one line of ASCII text, terminated by the +/* ASCII newline character. Request and reply parameters (see below) +/* are separated by whitespace. +/* ENCODING +/* .ad +/* .fi +/* In request and reply parameters, the character % and any non-printing +/* and whitespace characters must be replaced by %XX, XX being the +/* corresponding ASCII hexadecimal character value. The hexadecimal codes +/* can be specified in any case (upper, lower, mixed). +/* REQUEST FORMAT +/* .ad +/* .fi +/* Requests are strings that serve as lookup key in the simulated +/* table. +/* .IP "get SPACE key NEWLINE" +/* Look up data under the specified key. +/* .IP "put SPACE key SPACE value NEWLINE" +/* This request is currently not implemented. +/* REPLY FORMAT +/* .ad +/* .fi +/* Replies must be no longer than 4096 characters including the +/* newline terminator, and must have the following form: +/* .IP "500 SPACE text NEWLINE" +/* In case of a lookup request, the requested data does not exist. +/* In case of an update request, the request was rejected. +/* The text gives the nature of the problem. +/* .IP "400 SPACE text NEWLINE" +/* This indicates an error condition. The text gives the nature of +/* the problem. The client should retry the request later. +/* .IP "200 SPACE text NEWLINE" +/* The request was successful. In the case of a lookup request, +/* the text contains an encoded version of the requested data. +/* SECURITY +/* This map must not be used for security sensitive information, +/* because neither the connection nor the server are authenticated. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* hex_quote(3) http-style quoting +/* DIAGNOSTICS +/* Fatal errors: out of memory, unknown host or service name, +/* attempt to update or iterate over map. +/* BUGS +/* Only the lookup method is currently implemented. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + VSTRING *raw_buf; /* raw I/O buffer */ + VSTRING *hex_buf; /* quoted I/O buffer */ + VSTREAM *fp; /* I/O stream */ +} DICT_TCP; + +#define DICT_TCP_MAXTRY 10 /* attempts before giving up */ +#define DICT_TCP_TMOUT 100 /* connect/read/write timeout */ +#define DICT_TCP_MAXLEN 4096 /* server reply size limit */ + +#define STR(x) vstring_str(x) + +/* dict_tcp_connect - connect to TCP server */ + +static int dict_tcp_connect(DICT_TCP *dict_tcp) +{ + int fd; + + /* + * Connect to the server. Enforce a time limit on all operations so that + * we do not get stuck. + */ + if ((fd = inet_connect(dict_tcp->dict.name, NON_BLOCKING, DICT_TCP_TMOUT)) < 0) { + msg_warn("connect to TCP map %s: %m", dict_tcp->dict.name); + return (-1); + } + dict_tcp->fp = vstream_fdopen(fd, O_RDWR); + vstream_control(dict_tcp->fp, + CA_VSTREAM_CTL_TIMEOUT(DICT_TCP_TMOUT), + CA_VSTREAM_CTL_END); + + /* + * Allocate per-map I/O buffers on the fly. + */ + if (dict_tcp->raw_buf == 0) { + dict_tcp->raw_buf = vstring_alloc(10); + dict_tcp->hex_buf = vstring_alloc(10); + } + return (0); +} + +/* dict_tcp_disconnect - disconnect from TCP server */ + +static void dict_tcp_disconnect(DICT_TCP *dict_tcp) +{ + (void) vstream_fclose(dict_tcp->fp); + dict_tcp->fp = 0; +} + +/* dict_tcp_lookup - request TCP server */ + +static const char *dict_tcp_lookup(DICT *dict, const char *key) +{ + DICT_TCP *dict_tcp = (DICT_TCP *) dict; + const char *myname = "dict_tcp_lookup"; + int tries; + char *start; + int last_ch; + +#define RETURN(errval, result) { dict->error = errval; return (result); } + + if (msg_verbose) + msg_info("%s: key %s", myname, key); + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_MUL) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + for (tries = 0; /* see below */ ; /* see below */ ) { + + /* + * Connect to the server, or use an existing connection. + */ + if (dict_tcp->fp != 0 || dict_tcp_connect(dict_tcp) == 0) { + + /* + * Send request and receive response. Both are %XX quoted and + * both are terminated by newline. This encoding is convenient + * for data that is mostly text. + */ + hex_quote(dict_tcp->hex_buf, key); + vstream_fprintf(dict_tcp->fp, "get %s\n", STR(dict_tcp->hex_buf)); + if (msg_verbose) + msg_info("%s: send: get %s", myname, STR(dict_tcp->hex_buf)); + last_ch = vstring_get_nonl_bound(dict_tcp->hex_buf, dict_tcp->fp, + DICT_TCP_MAXLEN); + if (last_ch == '\n') + break; + + /* + * Disconnect from the server if it can't talk to us. + */ + if (last_ch < 0) + msg_warn("read TCP map reply from %s: unexpected EOF (%m)", + dict_tcp->dict.name); + else + msg_warn("read TCP map reply from %s: text longer than %d", + dict_tcp->dict.name, DICT_TCP_MAXLEN); + dict_tcp_disconnect(dict_tcp); + } + + /* + * Try to connect a limited number of times before giving up. + */ + if (++tries >= DICT_TCP_MAXTRY) + RETURN(DICT_ERR_RETRY, 0); + + /* + * Sleep between attempts, instead of hammering the server. + */ + sleep(1); + } + if (msg_verbose) + msg_info("%s: recv: %s", myname, STR(dict_tcp->hex_buf)); + + /* + * Check the general reply syntax. If the reply is malformed, disconnect + * and try again later. + */ + if (start = STR(dict_tcp->hex_buf), + !ISDIGIT(start[0]) || !ISDIGIT(start[1]) + || !ISDIGIT(start[2]) || !ISSPACE(start[3]) + || !hex_unquote(dict_tcp->raw_buf, start + 4)) { + msg_warn("read TCP map reply from %s: malformed reply: %.100s", + dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_')); + dict_tcp_disconnect(dict_tcp); + RETURN(DICT_ERR_RETRY, 0); + } + + /* + * Examine the reply status code. If the reply is malformed, disconnect + * and try again later. + */ + switch (start[0]) { + default: + msg_warn("read TCP map reply from %s: bad status code: %.100s", + dict_tcp->dict.name, printable(STR(dict_tcp->hex_buf), '_')); + dict_tcp_disconnect(dict_tcp); + RETURN(DICT_ERR_RETRY, 0); + case '4': + if (msg_verbose) + msg_info("%s: soft error: %s", + myname, printable(STR(dict_tcp->hex_buf), '_')); + dict_tcp_disconnect(dict_tcp); + RETURN(DICT_ERR_RETRY, 0); + case '5': + if (msg_verbose) + msg_info("%s: not found: %s", + myname, printable(STR(dict_tcp->hex_buf), '_')); + RETURN(DICT_ERR_NONE, 0); + case '2': + if (msg_verbose) + msg_info("%s: found: %s", + myname, printable(STR(dict_tcp->raw_buf), '_')); + RETURN(DICT_ERR_NONE, STR(dict_tcp->raw_buf)); + } +} + +/* dict_tcp_close - close TCP map */ + +static void dict_tcp_close(DICT *dict) +{ + DICT_TCP *dict_tcp = (DICT_TCP *) dict; + + if (dict_tcp->fp) + (void) vstream_fclose(dict_tcp->fp); + if (dict_tcp->raw_buf) + vstring_free(dict_tcp->raw_buf); + if (dict_tcp->hex_buf) + vstring_free(dict_tcp->hex_buf); + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_tcp_open - open TCP map */ + +DICT *dict_tcp_open(const char *map, int open_flags, int dict_flags) +{ + DICT_TCP *dict_tcp; + + /* + * Sanity checks. + */ + if (dict_flags & DICT_FLAG_NO_UNAUTH) + return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags, + "%s:%s map is not allowed for security sensitive data", + DICT_TYPE_TCP, map)); + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_TCP, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_TCP, map)); + + /* + * Create the dictionary handle. Do not open the connection until the + * first request is made. + */ + dict_tcp = (DICT_TCP *) dict_alloc(DICT_TYPE_TCP, map, sizeof(*dict_tcp)); + dict_tcp->fp = 0; + dict_tcp->raw_buf = dict_tcp->hex_buf = 0; + dict_tcp->dict.lookup = dict_tcp_lookup; + dict_tcp->dict.close = dict_tcp_close; + dict_tcp->dict.flags = dict_flags | DICT_FLAG_PATTERN; + if (dict_flags & DICT_FLAG_FOLD_MUL) + dict_tcp->dict.fold_buf = vstring_alloc(10); + + return (DICT_DEBUG (&dict_tcp->dict)); +} diff --git a/src/util/dict_tcp.h b/src/util/dict_tcp.h new file mode 100644 index 0000000..9814c61 --- /dev/null +++ b/src/util/dict_tcp.h @@ -0,0 +1,37 @@ +#ifndef _DICT_TCP_H_INCLUDED_ +#define _DICT_TCP_H_INCLUDED_ + +/*++ +/* NAME +/* dict_tcp 3h +/* SUMMARY +/* dictionary manager interface to tcp-based lookup tables +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_TCP "tcp" + +extern DICT *dict_tcp_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_test.c b/src/util/dict_test.c new file mode 100644 index 0000000..ead61b2 --- /dev/null +++ b/src/util/dict_test.c @@ -0,0 +1,166 @@ + /* + * Proof-of-concept test program. Create, update or read a database. Type + * '?' for a list of commands. + */ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static NORETURN usage(char *myname) +{ + msg_fatal("usage: %s type:file read|write|create [flags...]", myname); +} + +void dict_test(int argc, char **argv) +{ + VSTRING *keybuf = vstring_alloc(1); + VSTRING *inbuf = vstring_alloc(1); + DICT *dict; + char *dict_name; + int open_flags; + char *bufp; + char *cmd; + const char *key; + const char *value; + int ch; + int dict_flags = 0; + int n; + int rc; + +#define USAGE "verbose|del key|get key|put key=value|first|next|masks|flags" + + signal(SIGPIPE, SIG_IGN); + + msg_vstream_init(argv[0], VSTREAM_ERR); + while ((ch = GETOPT(argc, argv, "v")) > 0) { + switch (ch) { + default: + usage(argv[0]); + case 'v': + msg_verbose++; + break; + } + } + optind = OPTIND; + if (argc - optind < 2) + usage(argv[0]); + if (strcasecmp(argv[optind + 1], "create") == 0) + open_flags = O_CREAT | O_RDWR | O_TRUNC; + else if (strcasecmp(argv[optind + 1], "write") == 0) + open_flags = O_RDWR; + else if (strcasecmp(argv[optind + 1], "read") == 0) + open_flags = O_RDONLY; + else + msg_fatal("unknown access mode: %s", argv[2]); + for (n = 2; argv[optind + n]; n++) + dict_flags |= dict_flags_mask(argv[optind + 2]); + if ((dict_flags & DICT_FLAG_OPEN_LOCK) == 0) + dict_flags |= DICT_FLAG_LOCK; + if ((dict_flags & (DICT_FLAG_DUP_WARN | DICT_FLAG_DUP_IGNORE)) == 0) + dict_flags |= DICT_FLAG_DUP_REPLACE; + dict_flags |= DICT_FLAG_UTF8_REQUEST; + vstream_fflush(VSTREAM_OUT); + dict_name = argv[optind]; + dict_allow_surrogate = 1; + util_utf8_enable = 1; + dict = dict_open(dict_name, open_flags, dict_flags); + dict_register(dict_name, dict); + vstream_printf("owner=%s (uid=%ld)\n", + dict->owner.status == DICT_OWNER_TRUSTED ? "trusted" : + dict->owner.status == DICT_OWNER_UNTRUSTED ? "untrusted" : + dict->owner.status == DICT_OWNER_UNKNOWN ? "unspecified" : + "error", (long) dict->owner.uid); + vstream_fflush(VSTREAM_OUT); + + while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) { + bufp = vstring_str(inbuf); + if (!isatty(0)) { + vstream_printf("> %s\n", bufp); + vstream_fflush(VSTREAM_OUT); + } + if (*bufp == '#') + continue; + if ((cmd = mystrtok(&bufp, " ")) == 0) { + vstream_printf("usage: %s\n", USAGE); + vstream_fflush(VSTREAM_OUT); + continue; + } + if (dict_changed_name()) + msg_warn("dictionary has changed"); + key = *bufp ? vstring_str(unescape(keybuf, mystrtok(&bufp, " ="))) : 0; + value = mystrtok(&bufp, " ="); + if (strcmp(cmd, "verbose") == 0 && !key) { + msg_verbose++; + } else if (strcmp(cmd, "del") == 0 && key && !value) { + if ((rc = dict_del(dict, key)) > 0) + vstream_printf("%s: not found\n", key); + else if (rc < 0) + vstream_printf("%s: error\n", key); + else + vstream_printf("%s: deleted\n", key); + } else if (strcmp(cmd, "get") == 0 && key && !value) { + if ((value = dict_get(dict, key)) == 0) { + vstream_printf("%s: %s\n", key, dict->error ? + "error" : "not found"); + } else { + vstream_printf("%s=%s\n", key, value); + } + } else if (strcmp(cmd, "put") == 0 && key && value) { + if (dict_put(dict, key, value) != 0) + vstream_printf("%s: %s\n", key, dict->error ? + "error" : "not updated"); + } else if (strcmp(cmd, "first") == 0 && !key && !value) { + if (dict_seq(dict, DICT_SEQ_FUN_FIRST, &key, &value) == 0) + vstream_printf("%s=%s\n", key, value); + else + vstream_printf("%s\n", dict->error ? + "error" : "not found"); + } else if (strcmp(cmd, "next") == 0 && !key && !value) { + if (dict_seq(dict, DICT_SEQ_FUN_NEXT, &key, &value) == 0) + vstream_printf("%s=%s\n", key, value); + else + vstream_printf("%s\n", dict->error ? + "error" : "not found"); + } else if (strcmp(cmd, "flags") == 0 && !key && !value) { + vstream_printf("dict flags %s\n", + dict_flags_str(dict->flags)); + } else if (strcmp(cmd, "masks") == 0 && !key && !value) { + vstream_printf("DICT_FLAG_IMPL_MASK %s\n", + dict_flags_str(DICT_FLAG_IMPL_MASK)); + vstream_printf("DICT_FLAG_PARANOID %s\n", + dict_flags_str(DICT_FLAG_PARANOID)); + vstream_printf("DICT_FLAG_RQST_MASK %s\n", + dict_flags_str(DICT_FLAG_RQST_MASK)); + vstream_printf("DICT_FLAG_INST_MASK %s\n", + dict_flags_str(DICT_FLAG_INST_MASK)); + } else { + vstream_printf("usage: %s\n", USAGE); + } + vstream_fflush(VSTREAM_OUT); + } + vstring_free(keybuf); + vstring_free(inbuf); + dict_close(dict); +} diff --git a/src/util/dict_test.in b/src/util/dict_test.in new file mode 100644 index 0000000..ef838a1 --- /dev/null +++ b/src/util/dict_test.in @@ -0,0 +1,10 @@ +del bar +get bar +get nonexist +del nonexist +get foo +del foo +put baz bazval +get baz +del baz +get baz diff --git a/src/util/dict_test.ref b/src/util/dict_test.ref new file mode 100644 index 0000000..54e91f8 --- /dev/null +++ b/src/util/dict_test.ref @@ -0,0 +1,20 @@ +owner=untrusted (uid=USER) +> del bar +bar: deleted +> get bar +bar: not found +> get nonexist +nonexist: not found +> del nonexist +nonexist: not found +> get foo +foo=fooval +> del foo +foo: deleted +> put baz bazval +> get baz +baz=bazval +> del baz +baz: deleted +> get baz +baz: not found diff --git a/src/util/dict_thash.c b/src/util/dict_thash.c new file mode 100644 index 0000000..69eb17b --- /dev/null +++ b/src/util/dict_thash.c @@ -0,0 +1,255 @@ +/*++ +/* NAME +/* dict_thash 3 +/* SUMMARY +/* dictionary manager interface to hashed flat text files +/* SYNOPSIS +/* #include +/* +/* DICT *dict_thash_open(path, open_flags, dict_flags) +/* const char *name; +/* const char *path; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_thash_open() opens the named flat text file, creates +/* an in-memory hash table, and makes it available via the +/* generic interface described in dict_open(3). The input +/* format is as with postmap(1). +/* DIAGNOSTICS +/* Fatal errors: cannot open file, out of memory. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#define STR vstring_str +#define LEN VSTRING_LEN + +/* dict_thash_open - open flat text data base */ + +DICT *dict_thash_open(const char *path, int open_flags, int dict_flags) +{ + DICT *dict; + VSTREAM *fp = 0; /* DICT_THASH_OPEN_RETURN() */ + struct stat st; + time_t before; + time_t after; + VSTRING *line_buffer = 0; /* DICT_THASH_OPEN_RETURN() */ + int lineno; + int last_line; + char *key; + char *value; + + /* + * Let the optimizer worry about eliminating redundant code. + */ +#define DICT_THASH_OPEN_RETURN(d) do { \ + DICT *__d = (d); \ + if (fp != 0) \ + vstream_fclose(fp); \ + if (line_buffer != 0) \ + vstring_free(line_buffer); \ + return (__d); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_THASH, path)); + + /* + * Read the flat text file into in-memory hash. Read the file again if it + * may have changed while we were reading. + */ + for (before = time((time_t *) 0); /* see below */ ; before = after) { + if ((fp = vstream_fopen(path, open_flags, 0644)) == 0) { + DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, + open_flags, dict_flags, + "open database %s: %m", path)); + } + + /* + * Reuse the "internal" dictionary type. + */ + dict = dict_open3(DICT_TYPE_HT, path, open_flags, dict_flags); + dict_type_override(dict, DICT_TYPE_THASH); + + /* + * XXX This duplicates the parser in postmap.c. + */ + if (line_buffer == 0) + line_buffer = vstring_alloc(100); + last_line = 0; + while (readllines(line_buffer, fp, &last_line, &lineno)) { + int in_quotes = 0; + + /* + * First some UTF-8 checks sans casefolding. + */ + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) + && allascii(STR(line_buffer)) == 0 + && valid_utf8_string(STR(line_buffer), LEN(line_buffer)) == 0) { + msg_warn("%s, line %d: non-UTF-8 input \"%s\"" + " -- ignoring this line", + VSTREAM_PATH(fp), lineno, STR(line_buffer)); + continue; + } + + /* + * Split on the first whitespace character, then trim leading and + * trailing whitespace from key and value. + */ + for (value = STR(line_buffer); *value; value++) { + if (*value == '\\') { + if (*++value == 0) + break; + } else if (ISSPACE(*value)) { + if (!in_quotes) + break; + } else if (*value == '"') { + in_quotes = !in_quotes; + } + } + if (in_quotes) { + msg_warn("%s, line %d: unbalanced '\"' in '%s'" + " -- ignoring this line", + VSTREAM_PATH(fp), lineno, STR(line_buffer)); + continue; + } + if (*value) + *value++ = 0; + while (ISSPACE(*value)) + value++; + trimblanks(value, 0)[0] = 0; + + /* + * Leave the key in quoted form, for consistency with postmap.c + * and dict_inline.c. + */ + key = STR(line_buffer); + + /* + * Enforce the "key whitespace value" format. Disallow missing + * keys or missing values. + */ + if (*key == 0 || *value == 0) { + msg_warn("%s, line %d: expected format: key whitespace value" + " -- ignoring this line", path, lineno); + continue; + } + if (key[strlen(key) - 1] == ':') + msg_warn("%s, line %d: record is in \"key: value\" format;" + " is this an alias file?", path, lineno); + + /* + * Optionally treat the value as a filename, and replace the value + * with the BASE64-encoded content of the named file. + */ + if (dict_flags & DICT_FLAG_SRC_RHS_IS_FILE) { + VSTRING *base64_buf; + char *err; + + if ((base64_buf = dict_file_to_b64(dict, value)) == 0) { + err = dict_file_get_error(dict); + msg_warn("%s, line %d: %s: skipping this entry", + VSTREAM_PATH(fp), lineno, err); + myfree(err); + continue; + } + value = vstring_str(base64_buf); + } + + /* + * Store the value under the key. Handle duplicates + * appropriately. XXX Move this into dict_ht, but 1) that map + * ignores duplicates by default and we would have to check that + * we won't break existing code that depends on such behavior; 2) + * by inlining the checks here we can degrade gracefully instead + * of terminating with a fatal error. See comment in + * dict_inline.c. + */ + if (dict->lookup(dict, key) != 0) { + if (dict_flags & DICT_FLAG_DUP_IGNORE) { + /* void */ ; + } else if (dict_flags & DICT_FLAG_DUP_REPLACE) { + dict->update(dict, key, value); + } else if (dict_flags & DICT_FLAG_DUP_WARN) { + msg_warn("%s, line %d: duplicate entry: \"%s\"", + path, lineno, key); + } else { + dict->close(dict); + DICT_THASH_OPEN_RETURN(dict_surrogate(DICT_TYPE_THASH, path, + open_flags, dict_flags, + "%s, line %d: duplicate entry: \"%s\"", + path, lineno, key)); + } + } else { + dict->update(dict, key, value); + } + } + + /* + * See if the source file is hot. + */ + if (fstat(vstream_fileno(fp), &st) < 0) + msg_fatal("fstat %s: %m", path); + if (vstream_fclose(fp)) + msg_fatal("read %s: %m", path); + fp = 0; /* DICT_THASH_OPEN_RETURN() */ + after = time((time_t *) 0); + if (st.st_mtime < before - 1 || st.st_mtime > after) + break; + + /* + * Yes, it is hot. Discard the result and read the file again. + */ + dict->close(dict); + if (msg_verbose > 1) + msg_info("pausing to let file %s cool down", path); + doze(300000); + } + + dict->owner.uid = st.st_uid; + dict->owner.status = (st.st_uid != 0); + + DICT_THASH_OPEN_RETURN(DICT_DEBUG (dict)); +} diff --git a/src/util/dict_thash.h b/src/util/dict_thash.h new file mode 100644 index 0000000..878366a --- /dev/null +++ b/src/util/dict_thash.h @@ -0,0 +1,37 @@ +#ifndef _DICT_THASH_H_INCLUDED_ +#define _DICT_THASH_H_INCLUDED_ + +/*++ +/* NAME +/* dict_thash 3h +/* SUMMARY +/* dictionary manager interface to flat text files +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_THASH "texthash" + +extern DICT *dict_thash_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_thash.in b/src/util/dict_thash.in new file mode 100644 index 0000000..89938f6 --- /dev/null +++ b/src/util/dict_thash.in @@ -0,0 +1,5 @@ +"the answer is 42 +xxx: yyy +xxx +aaa bbb +aaa bbb diff --git a/src/util/dict_thash.map b/src/util/dict_thash.map new file mode 100644 index 0000000..cce1144 --- /dev/null +++ b/src/util/dict_thash.map @@ -0,0 +1,17 @@ +"the answer is" 42 +ABCDEF 012345 +allascii.c 915 +alldig.c 928 +allprint.c 943 +allspace.c 931 +argv.c 5271 +argv_split.c 2780 +attr_clnt.c 5813 +attr_print0.c 7243 +attr_print64.c 8104 +attr_print_plain.c 7086 +attr_scan0.c 15454 +attr_scan64.c 17256 +attr_scan_plain.c 16924 +auto_clnt.c 9819 +the answer is 42 diff --git a/src/util/dict_thash.ref b/src/util/dict_thash.ref new file mode 100644 index 0000000..efdc207 --- /dev/null +++ b/src/util/dict_thash.ref @@ -0,0 +1,6 @@ +postmap: warning: dict_thash.in, line 1: unbalanced '"' in '"the answer is 42' -- ignoring this line +postmap: warning: dict_thash.in, line 2: record is in "key: value" format; is this an alias file? +postmap: warning: dict_thash.in, line 3: expected format: key whitespace value -- ignoring this line +postmap: warning: dict_thash.in, line 5: duplicate entry: "aaa" +aaa bbb +xxx: yyy diff --git a/src/util/dict_union.c b/src/util/dict_union.c new file mode 100644 index 0000000..80df03b --- /dev/null +++ b/src/util/dict_union.c @@ -0,0 +1,202 @@ +/*++ +/* NAME +/* dict_union 3 +/* SUMMARY +/* dictionary manager interface for union of tables +/* SYNOPSIS +/* #include +/* +/* DICT *dict_union_open(name, open_flags, dict_flags) +/* const char *name; +/* int open_flags; +/* int dict_flags; +/* DESCRIPTION +/* dict_union_open() opens a sequence of one or more tables. +/* Example: "\fBunionmap:{\fItype_1:name_1, ..., type_n:name_n\fR}". +/* +/* Each "unionmap:" query is given to each table in the specified +/* order. All found results are concatenated, separated by +/* comma. The unionmap table produces no result when all +/* lookup tables return no result. +/* +/* The first and last characters of a "unionmap:" table name +/* must be '{' and '}'. Within these, individual maps are +/* separated with comma or whitespace. +/* +/* The open_flags and dict_flags arguments are passed on to +/* the underlying dictionaries. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ + ARGV *map_union; /* pipelined tables */ + VSTRING *re_buf; /* reply buffer */ +} DICT_UNION; + +#define STR(x) vstring_str(x) + +/* dict_union_lookup - search a bunch of tables and combine the results */ + +static const char *dict_union_lookup(DICT *dict, const char *query) +{ + static const char myname[] = "dict_union_lookup"; + DICT_UNION *dict_union = (DICT_UNION *) dict; + DICT *map; + char **cpp; + char *dict_type_name; + const char *result = 0; + + /* + * After Roel van Meer, postfix-users mailing list, Sept 2014. + */ + VSTRING_RESET(dict_union->re_buf); + for (cpp = dict_union->map_union->argv; (dict_type_name = *cpp) != 0; cpp++) { + if ((map = dict_handle(dict_type_name)) == 0) + msg_panic("%s: dictionary \"%s\" not found", myname, dict_type_name); + if ((result = dict_get(map, query)) != 0) { + if (VSTRING_LEN(dict_union->re_buf) > 0) + VSTRING_ADDCH(dict_union->re_buf, ','); + vstring_strcat(dict_union->re_buf, result); + } else if (map->error != 0) { + DICT_ERR_VAL_RETURN(dict, map->error, 0); + } + } + DICT_ERR_VAL_RETURN(dict, DICT_ERR_NONE, + VSTRING_LEN(dict_union->re_buf) > 0 ? + STR(dict_union->re_buf) : 0); +} + +/* dict_union_close - disassociate from a bunch of tables */ + +static void dict_union_close(DICT *dict) +{ + DICT_UNION *dict_union = (DICT_UNION *) dict; + char **cpp; + char *dict_type_name; + + for (cpp = dict_union->map_union->argv; (dict_type_name = *cpp) != 0; cpp++) + dict_unregister(dict_type_name); + argv_free(dict_union->map_union); + vstring_free(dict_union->re_buf); + dict_free(dict); +} + +/* dict_union_open - open a bunch of tables */ + +DICT *dict_union_open(const char *name, int open_flags, int dict_flags) +{ + static const char myname[] = "dict_union_open"; + DICT_UNION *dict_union; + char *saved_name = 0; + char *dict_type_name; + ARGV *argv = 0; + char **cpp; + DICT *dict; + int match_flags = 0; + struct DICT_OWNER aggr_owner; + size_t len; + + /* + * Clarity first. Let the optimizer worry about redundant code. + */ +#define DICT_UNION_RETURN(x) do { \ + if (saved_name != 0) \ + myfree(saved_name); \ + if (argv != 0) \ + argv_free(argv); \ + return (x); \ + } while (0) + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name, + open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_UNION, name)); + + /* + * Split the table name into its constituent parts. + */ + if ((len = balpar(name, CHARS_BRACE)) == 0 || name[len] != 0 + || *(saved_name = mystrndup(name + 1, len - 2)) == 0 + || ((argv = argv_splitq(saved_name, CHARS_COMMA_SP, CHARS_BRACE)), + (argv->argc == 0))) + DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{type:name...}\"", + DICT_TYPE_UNION, name, + DICT_TYPE_UNION)); + + /* + * The least-trusted table in the set determines the over-all trust + * level. The first table determines the pattern-matching flags. + */ + DICT_OWNER_AGGREGATE_INIT(aggr_owner); + for (cpp = argv->argv; (dict_type_name = *cpp) != 0; cpp++) { + if (msg_verbose) + msg_info("%s: %s", myname, dict_type_name); + if (strchr(dict_type_name, ':') == 0) + DICT_UNION_RETURN(dict_surrogate(DICT_TYPE_UNION, name, + open_flags, dict_flags, + "bad syntax: \"%s:%s\"; " + "need \"%s:{type:name...}\"", + DICT_TYPE_UNION, name, + DICT_TYPE_UNION)); + if ((dict = dict_handle(dict_type_name)) == 0) + dict = dict_open(dict_type_name, open_flags, dict_flags); + dict_register(dict_type_name, dict); + DICT_OWNER_AGGREGATE_UPDATE(aggr_owner, dict->owner); + if (cpp == argv->argv) + match_flags = dict->flags & (DICT_FLAG_FIXED | DICT_FLAG_PATTERN); + } + + /* + * Bundle up the result. + */ + dict_union = + (DICT_UNION *) dict_alloc(DICT_TYPE_UNION, name, sizeof(*dict_union)); + dict_union->dict.lookup = dict_union_lookup; + dict_union->dict.close = dict_union_close; + dict_union->dict.flags = dict_flags | match_flags; + dict_union->dict.owner = aggr_owner; + dict_union->re_buf = vstring_alloc(100); + dict_union->map_union = argv; + argv = 0; + DICT_UNION_RETURN(DICT_DEBUG (&dict_union->dict)); +} diff --git a/src/util/dict_union.h b/src/util/dict_union.h new file mode 100644 index 0000000..9554f84 --- /dev/null +++ b/src/util/dict_union.h @@ -0,0 +1,37 @@ +#ifndef _DICT_UNION_H_INCLUDED_ +#define _DICT_UNION_H_INCLUDED_ + +/*++ +/* NAME +/* dict_union 3h +/* SUMMARY +/* dictionary manager interface for union of tables +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_UNION "unionmap" + +extern DICT *dict_union_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_union_test.in b/src/util/dict_union_test.in new file mode 100644 index 0000000..9d111d4 --- /dev/null +++ b/src/util/dict_union_test.in @@ -0,0 +1,7 @@ +${VALGRIND} ./dict_open 'unionmap:{static:one,static:two,inline:{foo=three}}' read < get foo +foo=one,two,three +> get bar +bar=one,two ++ ./dict_open unionmap:{static:one,fail:fail} read +owner=trusted (uid=2147483647) +> get foo +foo: error diff --git a/src/util/dict_unix.c b/src/util/dict_unix.c new file mode 100644 index 0000000..4635344 --- /dev/null +++ b/src/util/dict_unix.c @@ -0,0 +1,204 @@ +/*++ +/* NAME +/* dict_unix 3 +/* SUMMARY +/* dictionary manager interface to UNIX tables +/* SYNOPSIS +/* #include +/* +/* DICT *dict_unix_open(map, dummy, dict_flags) +/* const char *map; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_unix_open() makes the specified UNIX table accessible via +/* the generic dictionary operations described in dict_open(3). +/* The \fIdummy\fR argument is not used. +/* +/* Known map names: +/* .IP passwd.byname +/* The table is the UNIX password database. The key is a login name. +/* The result is a password file entry in passwd(5) format. +/* .IP group.byname +/* The table is the UNIX group database. The key is a group name. +/* The result is a group file entry in group(5) format. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* DIAGNOSTICS +/* Fatal errors: out of memory, unknown map name, attempt to update map. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "vstring.h" +#include "stringops.h" +#include "dict.h" +#include "dict_unix.h" + +/* Application-specific. */ + +typedef struct { + DICT dict; /* generic members */ +} DICT_UNIX; + +/* dict_unix_getpwnam - find password table entry */ + +static const char *dict_unix_getpwnam(DICT *dict, const char *key) +{ + struct passwd *pwd; + static VSTRING *buf; + static int sanity_checked; + + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + if ((pwd = getpwnam(key)) == 0) { + if (sanity_checked == 0) { + sanity_checked = 1; + errno = 0; + if (getpwuid(0) == 0) { + msg_warn("cannot access UNIX password database: %m"); + dict->error = DICT_ERR_RETRY; + } + } + return (0); + } else { + if (buf == 0) + buf = vstring_alloc(10); + sanity_checked = 1; + vstring_sprintf(buf, "%s:%s:%ld:%ld:%s:%s:%s", + pwd->pw_name, pwd->pw_passwd, (long) pwd->pw_uid, + (long) pwd->pw_gid, pwd->pw_gecos, pwd->pw_dir, + pwd->pw_shell); + return (vstring_str(buf)); + } +} + +/* dict_unix_getgrnam - find group table entry */ + +static const char *dict_unix_getgrnam(DICT *dict, const char *key) +{ + struct group *grp; + static VSTRING *buf; + char **cpp; + static int sanity_checked; + + dict->error = 0; + + /* + * Optionally fold the key. + */ + if (dict->flags & DICT_FLAG_FOLD_FIX) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + vstring_strcpy(dict->fold_buf, key); + key = lowercase(vstring_str(dict->fold_buf)); + } + if ((grp = getgrnam(key)) == 0) { + if (sanity_checked == 0) { + sanity_checked = 1; + errno = 0; + if (getgrgid(0) == 0) { + msg_warn("cannot access UNIX group database: %m"); + dict->error = DICT_ERR_RETRY; + } + } + return (0); + } else { + if (buf == 0) + buf = vstring_alloc(10); + sanity_checked = 1; + vstring_sprintf(buf, "%s:%s:%ld:", + grp->gr_name, grp->gr_passwd, (long) grp->gr_gid); + for (cpp = grp->gr_mem; *cpp; cpp++) { + vstring_strcat(buf, *cpp); + if (cpp[1]) + VSTRING_ADDCH(buf, ','); + } + VSTRING_TERMINATE(buf); + return (vstring_str(buf)); + } +} + +/* dict_unix_close - close UNIX map */ + +static void dict_unix_close(DICT *dict) +{ + if (dict->fold_buf) + vstring_free(dict->fold_buf); + dict_free(dict); +} + +/* dict_unix_open - open UNIX map */ + +DICT *dict_unix_open(const char *map, int open_flags, int dict_flags) +{ + DICT_UNIX *dict_unix; + struct dict_unix_lookup { + char *name; + const char *(*lookup) (DICT *, const char *); + }; + static struct dict_unix_lookup dict_unix_lookup[] = { + "passwd.byname", dict_unix_getpwnam, + "group.byname", dict_unix_getgrnam, + 0, + }; + struct dict_unix_lookup *lp; + + /* + * Sanity checks. + */ + if (open_flags != O_RDONLY) + return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags, + "%s:%s map requires O_RDONLY access mode", + DICT_TYPE_UNIX, map)); + + /* + * "Open" the database. + */ + for (lp = dict_unix_lookup; /* void */ ; lp++) { + if (lp->name == 0) + return (dict_surrogate(DICT_TYPE_UNIX, map, open_flags, dict_flags, + "unknown table: %s:%s", DICT_TYPE_UNIX, map)); + if (strcmp(map, lp->name) == 0) + break; + } + dict_unix = (DICT_UNIX *) dict_alloc(DICT_TYPE_UNIX, map, + sizeof(*dict_unix)); + dict_unix->dict.lookup = lp->lookup; + dict_unix->dict.close = dict_unix_close; + dict_unix->dict.flags = dict_flags | DICT_FLAG_FIXED; + if (dict_flags & DICT_FLAG_FOLD_FIX) + dict_unix->dict.fold_buf = vstring_alloc(10); + dict_unix->dict.owner.status = DICT_OWNER_TRUSTED; + + return (DICT_DEBUG (&dict_unix->dict)); +} diff --git a/src/util/dict_unix.h b/src/util/dict_unix.h new file mode 100644 index 0000000..b5674b2 --- /dev/null +++ b/src/util/dict_unix.h @@ -0,0 +1,37 @@ +#ifndef _DICT_UNIX_H_INCLUDED_ +#define _DICT_UNIX_H_INCLUDED_ + +/*++ +/* NAME +/* dict_unix 3h +/* SUMMARY +/* dictionary manager interface to UNIX maps +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define DICT_TYPE_UNIX "unix" + +extern DICT *dict_unix_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/dict_utf8.c b/src/util/dict_utf8.c new file mode 100644 index 0000000..f1fc65a --- /dev/null +++ b/src/util/dict_utf8.c @@ -0,0 +1,300 @@ +/*++ +/* NAME +/* dict_utf8 3 +/* SUMMARY +/* dictionary UTF-8 helpers +/* SYNOPSIS +/* #include +/* +/* DICT *dict_utf8_activate( +/* DICT *dict) +/* DESCRIPTION +/* dict_utf8_activate() wraps a dictionary's lookup/update/delete +/* methods with code that enforces UTF-8 checks on keys and +/* values, and that logs a warning when incorrect UTF-8 is +/* encountered. The original dictionary handle becomes invalid. +/* +/* The wrapper code enforces a policy that maximizes application +/* robustness (it avoids the need for new error-handling code +/* paths in application code). Attempts to store non-UTF-8 +/* keys or values are skipped while reporting a non-error +/* status, attempts to look up or delete non-UTF-8 keys are +/* skipped while reporting a non-error status, and lookup +/* results that contain a non-UTF-8 value are blocked while +/* reporting a configuration error. +/* BUGS +/* dict_utf8_activate() does not nest. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include + + /* + * The goal is to maximize robustness: bad UTF-8 should not appear in keys, + * because those are derived from controlled inputs, and values should be + * printable before they are stored. But if we failed to check something + * then it should not result in fatal errors and thus open up the system for + * a denial-of-service attack. + * + * Proposed over-all policy: skip attempts to store invalid UTF-8 lookup keys + * or values. Rationale: some storage may not permit malformed UTF-8. This + * maximizes program robustness. If we get an invalid lookup result, report + * a configuration error. + * + * LOOKUP + * + * If the key is invalid, log a warning and skip the request. Rationale: the + * item cannot exist. + * + * If the lookup result is invalid, log a warning and return a configuration + * error. + * + * UPDATE + * + * If the key is invalid, then log a warning and skip the request. Rationale: + * the item cannot exist. + * + * If the value is invalid, log a warning and skip the request. Rationale: + * storage may not permit malformed UTF-8. This maximizes program + * robustness. + * + * DELETE + * + * If the key is invalid, then skip the request. Rationale: the item cannot + * exist. + */ + +/* dict_utf8_check_fold - casefold or validate string */ + +static char *dict_utf8_check_fold(DICT *dict, const char *string, + CONST_CHAR_STAR *err) +{ + int fold_flag = (dict->flags & DICT_FLAG_FOLD_ANY); + + /* + * Validate UTF-8 without casefolding. + */ + if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) { + if (err) + *err = "malformed UTF-8 or invalid codepoint"; + return (0); + } + + /* + * Casefold UTF-8. + */ + if (fold_flag != 0 + && (fold_flag & ((dict->flags & DICT_FLAG_FIXED) ? + DICT_FLAG_FOLD_FIX : DICT_FLAG_FOLD_MUL))) { + if (dict->fold_buf == 0) + dict->fold_buf = vstring_alloc(10); + return (casefold(dict->fold_buf, string)); + } + return ((char *) string); +} + +/* dict_utf8_check validate UTF-8 string */ + +static int dict_utf8_check(const char *string, CONST_CHAR_STAR *err) +{ + if (!allascii(string) && valid_utf8_string(string, strlen(string)) == 0) { + if (err) + *err = "malformed UTF-8 or invalid codepoint"; + return (0); + } + return (1); +} + +/* dict_utf8_lookup - UTF-8 lookup method wrapper */ + +static const char *dict_utf8_lookup(DICT *dict, const char *key) +{ + DICT_UTF8_BACKUP *backup; + const char *utf8_err; + const char *fold_res; + const char *value; + int saved_flags; + + /* + * Validate and optionally fold the key, and if invalid skip the request. + */ + if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) { + msg_warn("%s:%s: non-UTF-8 key \"%s\": %s", + dict->type, dict->name, key, utf8_err); + dict->error = DICT_ERR_NONE; + return (0); + } + + /* + * Proxy the request with casefolding turned off. + */ + saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY); + dict->flags &= ~DICT_FLAG_FOLD_ANY; + backup = dict->utf8_backup; + value = backup->lookup(dict, fold_res); + dict->flags |= saved_flags; + + /* + * Validate the result, and if invalid fail the request. + */ + if (value != 0 && dict_utf8_check(value, &utf8_err) == 0) { + msg_warn("%s:%s: key \"%s\": non-UTF-8 value \"%s\": %s", + dict->type, dict->name, key, value, utf8_err); + dict->error = DICT_ERR_CONFIG; + return (0); + } else { + return (value); + } +} + +/* dict_utf8_update - UTF-8 update method wrapper */ + +static int dict_utf8_update(DICT *dict, const char *key, const char *value) +{ + DICT_UTF8_BACKUP *backup; + const char *utf8_err; + const char *fold_res; + int saved_flags; + int status; + + /* + * Validate or fold the key, and if invalid skip the request. + */ + if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) { + msg_warn("%s:%s: non-UTF-8 key \"%s\": %s", + dict->type, dict->name, key, utf8_err); + dict->error = DICT_ERR_NONE; + return (DICT_STAT_SUCCESS); + } + + /* + * Validate the value, and if invalid skip the request. + */ + else if (dict_utf8_check(value, &utf8_err) == 0) { + msg_warn("%s:%s: key \"%s\": non-UTF-8 value \"%s\": %s", + dict->type, dict->name, key, value, utf8_err); + dict->error = DICT_ERR_NONE; + return (DICT_STAT_SUCCESS); + } + + /* + * Proxy the request with casefolding turned off. + */ + else { + saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY); + dict->flags &= ~DICT_FLAG_FOLD_ANY; + backup = dict->utf8_backup; + status = backup->update(dict, fold_res, value); + dict->flags |= saved_flags; + return (status); + } +} + +/* dict_utf8_delete - UTF-8 delete method wrapper */ + +static int dict_utf8_delete(DICT *dict, const char *key) +{ + DICT_UTF8_BACKUP *backup; + const char *utf8_err; + const char *fold_res; + int saved_flags; + int status; + + /* + * Validate and optionally fold the key, and if invalid skip the request. + */ + if ((fold_res = dict_utf8_check_fold(dict, key, &utf8_err)) == 0) { + msg_warn("%s:%s: non-UTF-8 key \"%s\": %s", + dict->type, dict->name, key, utf8_err); + dict->error = DICT_ERR_NONE; + return (DICT_STAT_SUCCESS); + } + + /* + * Proxy the request with casefolding turned off. + */ + else { + saved_flags = (dict->flags & DICT_FLAG_FOLD_ANY); + dict->flags &= ~DICT_FLAG_FOLD_ANY; + backup = dict->utf8_backup; + status = backup->delete(dict, fold_res); + dict->flags |= saved_flags; + return (status); + } +} + +/* dict_utf8_activate - wrap a legacy dict object for UTF-8 processing */ + +DICT *dict_utf8_activate(DICT *dict) +{ + const char myname[] = "dict_utf8_activate"; + DICT_UTF8_BACKUP *backup; + + /* + * Sanity check. + */ + if (util_utf8_enable == 0) + msg_panic("%s: Unicode support is not available", myname); + if ((dict->flags & DICT_FLAG_UTF8_REQUEST) == 0) + msg_panic("%s: %s:%s does not request Unicode support", + myname, dict->type, dict->name); + if ((dict->flags & DICT_FLAG_UTF8_ACTIVE) || dict->utf8_backup != 0) + msg_panic("%s: %s:%s Unicode support is already activated", + myname, dict->type, dict->name); + + /* + * Unlike dict_debug(3) we do not put a proxy dict object in front of the + * encapsulated object, because then we would have to bidirectionally + * propagate changes in the data members (errors, flags, jbuf, and so on) + * between proxy object and encapsulated object. + * + * Instead we attach ourselves behind the encapsulated dict object, and + * redirect some function pointers to ourselves. + */ + backup = dict->utf8_backup = (DICT_UTF8_BACKUP *) mymalloc(sizeof(*backup)); + + /* + * Interpose on the lookup/update/delete methods. It is a conscious + * decision not to tinker with the iterator or destructor. + */ + backup->lookup = dict->lookup; + backup->update = dict->update; + backup->delete = dict->delete; + + dict->lookup = dict_utf8_lookup; + dict->update = dict_utf8_update; + dict->delete = dict_utf8_delete; + + /* + * Leave our mark. See sanity check above. + */ + dict->flags |= DICT_FLAG_UTF8_ACTIVE; + + return (dict); +} diff --git a/src/util/dict_utf8_test.in b/src/util/dict_utf8_test.in new file mode 100644 index 0000000..f8d4536 --- /dev/null +++ b/src/util/dict_utf8_test.in @@ -0,0 +1,14 @@ +#!/bin/sh + +LC_ALL=C awk 'BEGIN { + print "flags" + print "verbose" + printf "get foo\n" + printf "put Δημοσθένους.example.com aaa\n" + printf "get Δημοσθένους.example.com\n" + printf "put %c%c%c xxx\n", 128, 128, 128 + printf "get %c%c%c\n", 128, 128, 128 + printf "put xxx %c%c%c\n", 128, 128, 128 + printf "get xxx\n" + exit +}' | ${VALGRIND} ./dict_open internal:whatever write utf8_request diff --git a/src/util/dict_utf8_test.ref b/src/util/dict_utf8_test.ref new file mode 100644 index 0000000..7b825f9 --- /dev/null +++ b/src/util/dict_utf8_test.ref @@ -0,0 +1,18 @@ +owner=trusted (uid=2147483647) +> flags +dict flags fixed|lock|replace|utf8_request|utf8_active +> verbose +> get foo +foo: not found +> put Δημοσθένους.example.com aaa +> get Δημοσθένους.example.com +Δημοσθένους.example.com=aaa +> put €€€ xxx +./dict_open: warning: internal:whatever: non-UTF-8 key "???": malformed UTF-8 or invalid codepoint +> get €€€ +./dict_open: warning: internal:whatever: non-UTF-8 key "???": malformed UTF-8 or invalid codepoint +€€€: not found +> put xxx €€€ +./dict_open: warning: internal:whatever: key "xxx": non-UTF-8 value "???": malformed UTF-8 or invalid codepoint +> get xxx +xxx: not found diff --git a/src/util/dir_forest.c b/src/util/dir_forest.c new file mode 100644 index 0000000..0070177 --- /dev/null +++ b/src/util/dir_forest.c @@ -0,0 +1,110 @@ +/*++ +/* NAME +/* dir_forest 3 +/* SUMMARY +/* file name to directory forest +/* SYNOPSIS +/* #include +/* +/* char *dir_forest(buf, path, depth) +/* VSTRING *buf; +/* const char *path; +/* int depth; +/* DESCRIPTION +/* This module implements support for directory forests: a file +/* organization that introduces one or more levels of intermediate +/* subdirectories in order to reduce the number of files per directory. +/* +/* dir_forest() maps a file basename to a directory forest and +/* returns the resulting string: file name "abcd" becomes "a/b/" +/* and so on. The number of subdirectory levels is adjustable. +/* +/* Arguments: +/* .IP buf +/* A buffer that is overwritten with the result. The result +/* ends in "/" and is null terminated. If a null pointer is +/* specified, the result is written to a private buffer that +/* is overwritten upon each call. +/* .IP path +/* A null-terminated string of printable characters. Characters +/* special to the file system are not permitted. +/* The first subdirectory is named after the first character +/* in \fIpath\fR, and so on. When the path is shorter than the +/* desired number of subdirectory levels, directory names +/* of '_' (underscore) are used as replacement. +/* .IP depth +/* The desired number of subdirectory levels. +/* DIAGNOSTICS +/* Panic: interface violations. Fatal error: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "dir_forest.h" + +/* dir_forest - translate base name to directory forest */ + +char *dir_forest(VSTRING *buf, const char *path, int depth) +{ + const char *myname = "dir_forest"; + static VSTRING *private_buf = 0; + int n; + const char *cp; + int ch; + + /* + * Sanity checks. + */ + if (*path == 0) + msg_panic("%s: empty path", myname); + if (depth < 1) + msg_panic("%s: depth %d", myname, depth); + + /* + * Your buffer or mine? + */ + if (buf == 0) { + if (private_buf == 0) + private_buf = vstring_alloc(1); + buf = private_buf; + } + + /* + * Generate one or more subdirectory levels, depending on the pathname + * contents. When the pathname is short, use underscores instead. + * Disallow non-printable characters or characters that are special to + * the file system. + */ + VSTRING_RESET(buf); + for (cp = path, n = 0; n < depth; n++) { + if ((ch = *cp) == 0) { + ch = '_'; + } else { + if (!ISPRINT(ch) || ch == '.' || ch == '/') + msg_panic("%s: invalid pathname: %s", myname, path); + cp++; + } + VSTRING_ADDCH(buf, ch); + VSTRING_ADDCH(buf, '/'); + } + VSTRING_TERMINATE(buf); + + if (msg_verbose > 1) + msg_info("%s: %s -> %s", myname, path, vstring_str(buf)); + return (vstring_str(buf)); +} diff --git a/src/util/dir_forest.h b/src/util/dir_forest.h new file mode 100644 index 0000000..2c4c363 --- /dev/null +++ b/src/util/dir_forest.h @@ -0,0 +1,35 @@ +#ifndef _DIR_FOREST_H_INCLUDED_ +#define _DIR_FOREST_H_INCLUDED_ + +/*++ +/* NAME +/* dir_forest 3h +/* SUMMARY +/* file name to directory forest +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern char *dir_forest(VSTRING *, const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/doze.c b/src/util/doze.c new file mode 100644 index 0000000..28d5669 --- /dev/null +++ b/src/util/doze.c @@ -0,0 +1,71 @@ +/*++ +/* NAME +/* doze 3 +/* SUMMARY +/* take a nap +/* SYNOPSIS +/* #include +/* +/* void doze(microseconds) +/* unsigned microseconds; +/* DESCRIPTION +/* doze() sleeps for the specified number of microseconds. It is +/* a simple alternative for systems that lack usleep(). +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include +#ifdef USE_SYS_SELECT_H +#include +#endif + +/* Utility library. */ + +#include +#include + +/* doze - sleep a while */ + +void doze(unsigned delay) +{ + struct timeval tv; + +#define MILLION 1000000 + + tv.tv_sec = delay / MILLION; + tv.tv_usec = delay % MILLION; + while (select(0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &tv) < 0) + if (errno != EINTR) + msg_fatal("doze: select: %m"); +} + +#ifdef TEST + +#include + +int main(int argc, char **argv) +{ + unsigned delay; + + if (argc != 2 || (delay = atol(argv[1])) == 0) + msg_fatal("usage: %s microseconds", argv[0]); + doze(delay); + exit(0); +} + +#endif diff --git a/src/util/dummy_read.c b/src/util/dummy_read.c new file mode 100644 index 0000000..639004f --- /dev/null +++ b/src/util/dummy_read.c @@ -0,0 +1,61 @@ +/*++ +/* NAME +/* dummy_read 3 +/* SUMMARY +/* dummy read operation +/* SYNOPSIS +/* #include +/* +/* ssize_t dummy_read(fd, buf, buf_len, timeout, context) +/* int fd; +/* void *buf; +/* size_t len; +/* int timeout; +/* void *context; +/* DESCRIPTION +/* dummy_read() reports an EOF condition without side effects. +/* +/* Arguments: +/* .IP fd +/* File descriptor whose value is logged when verbose logging +/* is turned on. +/* .IP buf +/* Read buffer pointer. Not used. +/* .IP buf_len +/* Read buffer size. Its value is logged when verbose logging is +/* turned on. +/* .IP timeout +/* The deadline in seconds. Not used. +/* .IP context +/* Application context. Not used. +/* DIAGNOSTICS +/* None. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* dummy_read - dummy read operation */ + +ssize_t dummy_read(int fd, void *unused_buf, size_t len, + int unused_timeout, void *unused_context) +{ + if (msg_verbose) + msg_info("dummy_read: fd %d, len %lu", fd, (unsigned long) len); + return (0); +} diff --git a/src/util/dummy_write.c b/src/util/dummy_write.c new file mode 100644 index 0000000..943e2ad --- /dev/null +++ b/src/util/dummy_write.c @@ -0,0 +1,61 @@ +/*++ +/* NAME +/* dummy_write 3 +/* SUMMARY +/* dummy write operation +/* SYNOPSIS +/* #include +/* +/* ssize_t dummy_write(fd, buf, buf_len, timeout, context) +/* int fd; +/* void *buf; +/* size_t len; +/* int timeout; +/* void *context; +/* DESCRIPTION +/* dummy_write() implements a data sink without side effects. +/* +/* Arguments: +/* .IP fd +/* File descriptor whose value is logged when verbose logging +/* is turned on. +/* .IP buf +/* Write buffer pointer. Not used. +/* .IP buf_len +/* Write buffer size. Its value is logged when verbose logging is +/* turned on. +/* .IP timeout +/* The deadline in seconds. Not used. +/* .IP context +/* Application context. Not used. +/* DIAGNOSTICS +/* None. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* dummy_write - dummy write operation */ + +ssize_t dummy_write(int fd, void *unused_buf, size_t len, + int unused_timeout, void *unused_context) +{ + if (msg_verbose) + msg_info("dummy_write: fd %d, len %lu", fd, (unsigned long) len); + return (len); +} diff --git a/src/util/dup2_pass_on_exec.c b/src/util/dup2_pass_on_exec.c new file mode 100644 index 0000000..5286e5b --- /dev/null +++ b/src/util/dup2_pass_on_exec.c @@ -0,0 +1,64 @@ +/*++ +/* NAME +/* dup2_pass_on_exec 1 +/* SUMMARY +/* dup2 close-on-exec behavior test program +/* SYNOPSIS +/* dup2_pass_on_exec +/* DESCRIPTION +/* dup2_pass_on_exec sets the close-on-exec flag on its +/* standard input and then dup2() to duplicate it. +/* Posix-1003.1 specifies in section 6.2.1.2 that dup2(o,n) should behave +/* as: close(n); n = fcntl(o, F_DUPFD, n); as long as o is a valid +/* file-descriptor, n!=o, and 0<=n<=[OPEN_MAX]. +/* Section 6.5.2.2 states that the close-on-exec flag of the result of a +/* successful fcntl(o, F_DUPFD, n) is cleared. +/* +/* At least Ultrix4.3a does not clear the close-on-exec flag of n on +/* dup2(o, n). +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Christian von Roques +/* Forststrasse 71 +/* 76131 Karlsruhe, GERMANY +/*--*/ + +#include +#include +#include +#include + +#define DO(s) if (s < 0) { perror(#s); exit(1); } + +int main(int unused_argc, char **unused_argv) +{ + int res; + + printf("Setting the close-on-exec flag of file-descriptor 0.\n"); + DO(fcntl(0, F_SETFD, 1)); + + printf("Duplicating file-descriptor 0 to 3.\n"); + DO(dup2(0, 3)); + + printf("Testing if the close-on-exec flag of file-descriptor 3 is set.\n"); + DO((res = fcntl(3, F_GETFD, 0))); + if (res & 1) + printf( +"Yes, a newly dup2()ed file-descriptor has the close-on-exec \ +flag cloned.\n\ +THIS VIOLATES Posix1003.1 section 6.2.1.2 or 6.5.2.2!\n\ +You should #define DUP2_DUPS_CLOSE_ON_EXEC in sys_defs.h \ +for your OS.\n"); + else + printf( +"No, a newly dup2()ed file-descriptor has the close-on-exec \ +flag cleared.\n\ +This complies with Posix1003.1 section 6.2.1.2 and 6.5.2.2!\n"); + + return 0; +} diff --git a/src/util/duplex_pipe.c b/src/util/duplex_pipe.c new file mode 100644 index 0000000..04f23f6 --- /dev/null +++ b/src/util/duplex_pipe.c @@ -0,0 +1,49 @@ +/*++ +/* NAME +/* duplex_pipe 3 +/* SUMMARY +/* local IPD +/* SYNOPSIS +/* #include +/* +/* int duplex_pipe(fds) +/* int *fds; +/* DESCRIPTION +/* duplex_pipe() uses whatever local primitive it takes +/* to get a two-way I/O channel. +/* DIAGNOSTICS +/* A null result means success. In case of error, the result +/* is -1 and errno is set to the appropriate number. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries */ + +#include +#include +#include + +/* Utility library. */ + +#include "iostuff.h" +#include "sane_socketpair.h" + +/* duplex_pipe - give me a duplex pipe or bust */ + +int duplex_pipe(int *fds) +{ +#ifdef HAS_DUPLEX_PIPE + return (pipe(fds)); +#else + return (sane_socketpair(AF_UNIX, SOCK_STREAM, 0, fds)); +#endif +} + diff --git a/src/util/edit_file.c b/src/util/edit_file.c new file mode 100644 index 0000000..9d76b93 --- /dev/null +++ b/src/util/edit_file.c @@ -0,0 +1,353 @@ +/*++ +/* NAME +/* edit_file 3 +/* SUMMARY +/* simple cooperative file updating protocol +/* SYNOPSIS +/* #include +/* +/* typedef struct { +/* .in +4 +/* char *tmp_path; /* temp. pathname */ +/* VSTREAM *tmp_fp; /* temp. stream */ +/* /* private members... */ +/* .in -4 +/* } EDIT_FILE; +/* +/* EDIT_FILE *edit_file_open(original_path, output_flags, output_mode) +/* const char *original_path; +/* int output_flags; +/* mode_t output_mode; +/* +/* int edit_file_close(edit_file) +/* EDIT_FILE *edit_file; +/* +/* void edit_file_cleanup(edit_file) +/* EDIT_FILE *edit_file; +/* DESCRIPTION +/* This module implements a simple protocol for cooperative +/* processes to update one file. The idea is to 1) create a +/* new file under a deterministic temporary pathname, 2) +/* populate the new file with updated information, and 3) +/* rename the new file into the place of the original file. +/* This module provides 1) and 3), and leaves 2) to the +/* application. The temporary pathname is deterministic to +/* avoid accumulation of thrash after program crashes. +/* +/* edit_file_open() implements the first phase of the protocol. +/* It creates or opens an output file with a deterministic +/* temporary pathname, obtained by appending the suffix defined +/* with EDIT_FILE_SUFFIX to the specified original file pathname. +/* The original file itself is not opened. edit_file_open() +/* then locks the output file for exclusive access, and verifies +/* that the file still exists under the temporary pathname. +/* At this point in the protocol, the current process controls +/* both the output file content and its temporary pathname. +/* +/* In the second phase, the application opens the original +/* file if needed, and updates the output file via the +/* \fBtmp_fp\fR member of the EDIT_FILE data structure. This +/* phase is not implemented by the edit_file() module. +/* +/* edit_file_close() implements the third and final phase of +/* the protocol. It flushes the output file to persistent +/* storage, and renames the output file from its temporary +/* pathname into the place of the original file. When any of +/* these operations fails, edit_file_close() behaves as if +/* edit_file_cleanup() was called. Regardless of whether these +/* operations succeed, edit_file_close() releases the exclusive +/* lock, closes the output file, and frees up memory that was +/* allocated by edit_file_open(). +/* +/* edit_file_cleanup() aborts the protocol. It discards the +/* output file, releases the exclusive lock, closes the output +/* file, and frees up memory that was allocated by edit_file_open(). +/* +/* Arguments: +/* .IP original_path +/* The pathname of the original file that will be replaced by +/* the output file. The temporary pathname for the output file +/* is obtained by appending the suffix defined with EDIT_FILE_SUFFIX +/* to a copy of the specified original file pathname, and is +/* made available via the \fBtmp_path\fR member of the EDIT_FILE +/* data structure. +/* .IP output_flags +/* Flags for opening the output file. These are as with open(2), +/* except that the O_TRUNC flag is ignored. edit_file_open() +/* always truncates the output file after it has obtained +/* exclusive control over the output file content and temporary +/* pathname. +/* .IP output_mode +/* Permissions for the output file. These are as with open(2), +/* except that the output file is initially created with no +/* group or other access permissions. The specified output +/* file permissions are applied by edit_file_close(). +/* .IP edit_file +/* Pointer to data structure that is returned upon successful +/* completion by edit_file_open(), and that must be passed to +/* edit_file_close() or edit_file_cleanup(). +/* DIAGNOSTICS +/* Fatal errors: memory allocation failure, fstat() failure, +/* unlink() failure, lock failure, ftruncate() failure. +/* +/* edit_file_open() immediately returns a null pointer when +/* it cannot open the output file. +/* +/* edit_file_close() returns zero on success, VSTREAM_EOF on +/* failure. +/* +/* With both functions, the global errno variable indicates +/* the nature of the problem. All errors are relative to the +/* temporary output's pathname. With both functions, this +/* pathname is not available via the EDIT_FILE data structure, +/* because that structure was already destroyed, or not created. +/* BUGS +/* In the non-error case, edit_file_open() will not return +/* until it obtains exclusive control over the output file +/* content and temporary pathname. Applications that are +/* concerned about deadlock should protect the edit_file_open() +/* call with a watchdog timer. +/* +/* When interrupted, edit_file_close() may leave behind a +/* world-readable output file under the temporary pathname. +/* On some systems this can be used to inflict a shared-lock +/* DOS on the protocol. Applications that are concerned about +/* maximal safety should protect the edit_file_close() call +/* with sigdelay() and sigresume() calls, but this introduces +/* the risk that the program will get stuck forever. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Based on code originally by: +/* Victor Duchovni +/* Morgan Stanley +/* +/* Packaged into one module with minor improvements by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include /* rename(2) */ +#include + + /* + * This mask selects all permission bits in the st_mode stat data. There is + * no portable definition (unlike S_IFMT, which is defined for the file type + * bits). For example, BSD / Linux have ALLPERMS, while Solaris has S_IAMB. + */ +#define FILE_PERM_MASK \ + (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) + +/* Utility Library. */ + +#include +#include +#include +#include +#include +#include +#include + + /* + * Do we reuse and truncate an output file that persists after a crash, or + * do we unlink it and create a new file? + */ +#define EDIT_FILE_REUSE_AFTER_CRASH + + /* + * Protocol internals: the temporary file permissions. + */ +#define EDIT_FILE_MODE (S_IRUSR | S_IWUSR) /* temp file mode */ + + /* + * Make complex operations more readable. We could use functions, instead. + * The main thing is that we keep the _alloc and _free code together. + */ +#define EDIT_FILE_ALLOC(ep, path, mode) do { \ + (ep) = (EDIT_FILE *) mymalloc(sizeof(EDIT_FILE)); \ + (ep)->final_path = mystrdup(path); \ + (ep)->final_mode = (mode); \ + (ep)->tmp_path = concatenate((path), EDIT_FILE_SUFFIX, (char *) 0); \ + (ep)->tmp_fp = 0; \ + } while (0) + +#define EDIT_FILE_FREE(ep) do { \ + myfree((ep)->final_path); \ + myfree((ep)->tmp_path); \ + myfree((void *) (ep)); \ + } while (0) + +/* edit_file_open - open and lock file with deterministic temporary pathname */ + +EDIT_FILE *edit_file_open(const char *path, int flags, mode_t mode) +{ + struct stat before_lock; + struct stat after_lock; + int saved_errno; + EDIT_FILE *ep; + + /* + * Initialize. Do not bother to optimize for the error case. + */ + EDIT_FILE_ALLOC(ep, path, mode); + + /* + * As long as the output file can be opened under the temporary pathname, + * this code can loop or block forever. + * + * Applications that are concerned about deadlock should protect the + * edit_file_open() call with a watchdog timer. + */ + for ( /* void */ ; /* void */ ; (void) vstream_fclose(ep->tmp_fp)) { + + /* + * Try to open the output file under the temporary pathname. This + * succeeds or fails immediately. To avoid creating a shared-lock DOS + * opportunity after we crash, we create the output file with no + * group or other permissions, and set the final permissions at the + * end (this is one reason why we try to get exclusive control over + * the output file instead of the original file). We postpone file + * truncation until we have obtained exclusive control over the file + * content and temporary pathname. If the open operation fails, we + * give up immediately. The caller can retry the call if desirable. + * + * XXX If we replace the vstream_fopen() call by safe_open(), then we + * should replace the stat() call below by lstat(). + */ + if ((ep->tmp_fp = vstream_fopen(ep->tmp_path, flags & ~(O_TRUNC), + EDIT_FILE_MODE)) == 0) { + saved_errno = errno; + EDIT_FILE_FREE(ep); + errno = saved_errno; + return (0); + } + + /* + * At this point we may have opened an existing output file that was + * already locked. Try to lock the open file exclusively. This may + * take some time. + */ + if (myflock(vstream_fileno(ep->tmp_fp), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE) < 0) + msg_fatal("lock %s: %m", ep->tmp_path); + + /* + * At this point we have an exclusive lock, but some other process + * may have renamed or removed the output file while we were waiting + * for the lock. If that is the case, back out and try again. + */ + if (fstat(vstream_fileno(ep->tmp_fp), &before_lock) < 0) + msg_fatal("open %s: %m", ep->tmp_path); + if (stat(ep->tmp_path, &after_lock) < 0 + || before_lock.st_dev != after_lock.st_dev + || before_lock.st_ino != after_lock.st_ino +#ifdef HAS_ST_GEN + || before_lock.st_gen != after_lock.st_gen +#endif + /* No need to compare st_rdev or st_nlink here. */ + ) { + continue; + } + + /* + * At this point we have exclusive control over the output file + * content and its temporary pathname (within the rules of the + * cooperative protocol). But wait, there is more. + * + * There are many opportunities for trouble when opening a pre-existing + * output file. Here are just a few. + * + * - Victor observes that a system crash in the middle of the + * final-phase rename() operation may result in the output file + * having both the temporary pathname and the final pathname. In that + * case we must not write to the output file. + * + * - Wietse observes that crashes may also leave the output file in + * other inconsistent states. To avoid permission-related trouble, we + * simply refuse to work with an output file that has the wrong + * temporary permissions. This won't stop the shared-lock DOS if we + * crash after changing the file permissions, though. + * + * To work around these crash-related problems, remove the temporary + * pathname, back out, and try again. + */ + if (!S_ISREG(after_lock.st_mode) +#ifndef EDIT_FILE_REUSE_AFTER_CRASH + || after_lock.st_size > 0 +#endif + || after_lock.st_nlink > 1 + || (after_lock.st_mode & FILE_PERM_MASK) != EDIT_FILE_MODE) { + if (unlink(ep->tmp_path) < 0 && errno != ENOENT) + msg_fatal("unlink %s: %m", ep->tmp_path); + continue; + } + + /* + * Settle the final details. + */ +#ifdef EDIT_FILE_REUSE_AFTER_CRASH + if (ftruncate(vstream_fileno(ep->tmp_fp), 0) < 0) + msg_fatal("truncate %s: %m", ep->tmp_path); +#endif + return (ep); + } +} + +/* edit_file_cleanup - clean up without completing the protocol */ + +void edit_file_cleanup(EDIT_FILE *ep) +{ + + /* + * Don't touch the file after we lose the exclusive lock! + */ + if (unlink(ep->tmp_path) < 0 && errno != ENOENT) + msg_fatal("unlink %s: %m", ep->tmp_path); + (void) vstream_fclose(ep->tmp_fp); + EDIT_FILE_FREE(ep); +} + +/* edit_file_close - rename the file into place and close the file */ + +int edit_file_close(EDIT_FILE *ep) +{ + VSTREAM *fp = ep->tmp_fp; + int fd = vstream_fileno(fp); + int saved_errno; + + /* + * The rename/unlock portion of the protocol is relatively simple. The + * only things that really matter here are that we change permissions as + * late as possible, and that we rename the file to its final pathname + * before we lose the exclusive lock. + * + * Applications that are concerned about maximal safety should protect the + * edit_file_close() call with sigdelay() and sigresume() calls. It is + * not safe for us to call these functions directly, because the calls do + * not nest. It is also not nice to force every caller to run with + * interrupts turned off. + */ + if (vstream_fflush(fp) < 0 + || fchmod(fd, ep->final_mode) < 0 +#ifdef HAS_FSYNC + || fsync(fd) < 0 +#endif + || rename(ep->tmp_path, ep->final_path) < 0) { + saved_errno = errno; + edit_file_cleanup(ep); + errno = saved_errno; + return (VSTREAM_EOF); + } else { + (void) vstream_fclose(ep->tmp_fp); + EDIT_FILE_FREE(ep); + return (0); + } +} diff --git a/src/util/edit_file.h b/src/util/edit_file.h new file mode 100644 index 0000000..bd13a29 --- /dev/null +++ b/src/util/edit_file.h @@ -0,0 +1,53 @@ +#ifndef _EDIT_FILE_H_INCLUDED_ +#define _EDIT_FILE_H_INCLUDED_ + +/*++ +/* NAME +/* edit_file 3h +/* SUMMARY +/* simple cooperative file updating protocol +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +typedef struct { + /* Private. */ + char *final_path; + mode_t final_mode; + /* Public. */ + char *tmp_path; + VSTREAM *tmp_fp; +} EDIT_FILE; + +#define EDIT_FILE_SUFFIX ".tmp" + +extern EDIT_FILE *edit_file_open(const char *, int, mode_t); +extern int WARN_UNUSED_RESULT edit_file_close(EDIT_FILE *); +extern void edit_file_cleanup(EDIT_FILE *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/environ.c b/src/util/environ.c new file mode 100644 index 0000000..4b6c59e --- /dev/null +++ b/src/util/environ.c @@ -0,0 +1,156 @@ + /* + * From: TCP Wrapper. + * + * Many systems have putenv() but no setenv(). Other systems have setenv() but + * no putenv() (MIPS). Still other systems have neither (NeXT). This is a + * re-implementation that hopefully ends all problems. + * + * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands. + */ +#include "sys_defs.h" + +#ifdef MISSING_SETENV_PUTENV + +#include +#include +#include + +extern char **environ; + +static int addenv(char *); /* append entry to environment */ +static int allocated = 0; /* environ is, or is not, allocated */ + +#define DO_CLOBBER 1 + +/* namelength - determine length of name in "name=whatever" */ + +static ssize_t namelength(const char *name) +{ + char *equal; + + equal = strchr(name, '='); + return ((equal == 0) ? strlen(name) : (equal - name)); +} + +/* findenv - given name, locate name=value */ + +static char **findenv(const char *name, ssize_t len) +{ + char **envp; + + for (envp = environ; envp && *envp; envp++) + if (strncmp(name, *envp, len) == 0 && (*envp)[len] == '=') + return (envp); + return (0); +} + +#if 0 + +/* getenv - given name, locate value */ + +char *getenv(const char *name) +{ + ssize_t len = namelength(name); + char **envp = findenv(name, len); + + return (envp ? *envp + len + 1 : 0); +} + +/* putenv - update or append environment (name,value) pair */ + +int putenv(const char *nameval) +{ + char *equal = strchr(nameval, '='); + char *value = (equal ? equal : ""); + + return (setenv(nameval, value, DO_CLOBBER)); +} + +/* unsetenv - remove variable from environment */ + +void unsetenv(const char *name) +{ + char **envp; + + while ((envp = findenv(name, namelength(name))) != 0) + while (envp[0] = envp[1]) + envp++; +} + +#endif + +/* setenv - update or append environment (name,value) pair */ + +int setenv(const char *name, const char *value, int clobber) +{ + char *destination; + char **envp; + ssize_t l_name; /* length of name part */ + unsigned int l_nameval; /* length of name=value */ + + /* Permit name= and =value. */ + + l_name = namelength(name); + envp = findenv(name, l_name); + if (envp != 0 && clobber == 0) + return (0); + if (*value == '=') + value++; + l_nameval = l_name + strlen(value) + 1; + + /* + * Use available memory if the old value is long enough. Never free an + * old name=value entry because it may not be allocated. + */ + + destination = (envp != 0 && strlen(*envp) >= l_nameval) ? + *envp : malloc(l_nameval + 1); + if (destination == 0) + return (-1); + strncpy(destination, name, l_name); + destination[l_name] = '='; + strcpy(destination + l_name + 1, value); + return ((envp == 0) ? addenv(destination) : (*envp = destination, 0)); +} + +/* cmalloc - malloc and copy block of memory */ + +static char *cmalloc(ssize_t new_len, char *old, ssize_t old_len) +{ + char *new = malloc(new_len); + + if (new != 0) + memcpy(new, old, old_len); + return (new); +} + +/* addenv - append environment entry */ + +static int addenv(char *nameval) +{ + char **envp; + ssize_t n_used; /* number of environment entries */ + ssize_t l_used; /* bytes used excl. terminator */ + ssize_t l_need; /* bytes needed incl. terminator */ + + for (envp = environ; envp && *envp; envp++) + /* void */ ; + n_used = envp - environ; + l_used = n_used * sizeof(*envp); + l_need = l_used + 2 * sizeof(*envp); + + envp = allocated ? + (char **) realloc((char *) environ, l_need) : + (char **) cmalloc(l_need, (char *) environ, l_used); + if (envp == 0) { + return (-1); + } else { + allocated = 1; + environ = envp; + environ[n_used++] = nameval; /* add new entry */ + environ[n_used] = 0; /* terminate list */ + return (0); + } +} + +#endif diff --git a/src/util/events.c b/src/util/events.c new file mode 100644 index 0000000..c2157bb --- /dev/null +++ b/src/util/events.c @@ -0,0 +1,1261 @@ +/*++ +/* NAME +/* events 3 +/* SUMMARY +/* event manager +/* SYNOPSIS +/* #include +/* +/* time_t event_time() +/* +/* void event_loop(delay) +/* int delay; +/* +/* time_t event_request_timer(callback, context, delay) +/* void (*callback)(int event, void *context); +/* void *context; +/* int delay; +/* +/* int event_cancel_timer(callback, context) +/* void (*callback)(int event, void *context); +/* void *context; +/* +/* void event_enable_read(fd, callback, context) +/* int fd; +/* void (*callback)(int event, void *context); +/* void *context; +/* +/* void event_enable_write(fd, callback, context) +/* int fd; +/* void (*callback)(int event, void *context); +/* void *context; +/* +/* void event_disable_readwrite(fd) +/* int fd; +/* +/* void event_drain(time_limit) +/* int time_limit; +/* +/* void event_fork(void) +/* DESCRIPTION +/* This module delivers I/O and timer events. +/* Multiple I/O streams and timers can be monitored simultaneously. +/* Events are delivered via callback routines provided by the +/* application. When requesting an event, the application can provide +/* private context that is passed back when the callback routine is +/* executed. +/* +/* event_time() returns a cached value of the current time. +/* +/* event_loop() monitors all I/O channels for which the application has +/* expressed interest, and monitors the timer request queue. +/* It notifies the application whenever events of interest happen. +/* A negative delay value causes the function to pause until something +/* happens; a positive delay value causes event_loop() to return when +/* the next event happens or when the delay time in seconds is over, +/* whatever happens first. A zero delay effectuates a poll. +/* +/* Note: in order to avoid race conditions, event_loop() cannot +/* not be called recursively. +/* +/* event_request_timer() causes the specified callback function to +/* be called with the specified context argument after \fIdelay\fR +/* seconds, or as soon as possible thereafter. The delay should +/* not be negative (the manifest EVENT_NULL_DELAY provides for +/* convenient zero-delay notification). +/* The event argument is equal to EVENT_TIME. +/* Only one timer request can be active per (callback, context) pair. +/* Calling event_request_timer() with an existing (callback, context) +/* pair does not schedule a new event, but updates the time of event +/* delivery. The result is the absolute time at which the timer is +/* scheduled to go off. +/* +/* event_cancel_timer() cancels the specified (callback, context) request. +/* The application is allowed to cancel non-existing requests. The result +/* value is the amount of time left before the timer would have gone off, +/* or -1 in case of no pending timer. +/* +/* event_enable_read() (event_enable_write()) enables read (write) events +/* on the named I/O channel. It is up to the application to assemble +/* partial reads or writes. +/* An I/O channel cannot handle more than one request at the +/* same time. The application is allowed to enable an event that +/* is already enabled (same channel, same read or write operation, +/* but perhaps a different callback or context). On systems with +/* kernel-based event filters this is preferred usage, because +/* each disable and enable request would cost a system call. +/* +/* The manifest constants EVENT_NULL_CONTEXT and EVENT_NULL_TYPE +/* provide convenient null values. +/* +/* The callback routine has the following arguments: +/* .IP fd +/* The stream on which the event happened. +/* .IP event +/* An indication of the event type: +/* .RS +/* .IP EVENT_READ +/* read event, +/* .IP EVENT_WRITE +/* write event, +/* .IP EVENT_XCPT +/* exception (actually, any event other than read or write). +/* .RE +/* .IP context +/* Application context given to event_enable_read() (event_enable_write()). +/* .PP +/* event_disable_readwrite() disables further I/O events on the specified +/* I/O channel. The application is allowed to cancel non-existing +/* I/O event requests. +/* +/* event_drain() repeatedly calls event_loop() until no more timer +/* events or I/O events are pending or until the time limit is reached. +/* This routine must not be called from an event_whatever() callback +/* routine. Note: this function assumes that no new I/O events +/* will be registered. +/* +/* event_fork() must be called by a child process after it is +/* created with fork(), to re-initialize event processing. +/* DIAGNOSTICS +/* Panics: interface violations. Fatal errors: out of memory, +/* system call failure. Warnings: the number of available +/* file descriptors is much less than FD_SETSIZE. +/* BUGS +/* This module is based on event selection. It assumes that the +/* event_loop() routine is called frequently. This approach is +/* not suitable for applications with compute-bound loops that +/* take a significant amount of time. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include /* XXX: 44BSD uses bzero() */ +#include +#include +#include +#include /* offsetof() */ +#include /* bzero() prototype for 44BSD */ +#include /* INT_MAX */ + +#ifdef USE_SYS_SELECT_H +#include +#endif + +/* Application-specific. */ + +#include "mymalloc.h" +#include "msg.h" +#include "iostuff.h" +#include "ring.h" +#include "events.h" + +#if !defined(EVENTS_STYLE) +#error "must define EVENTS_STYLE" +#endif + + /* + * Traditional BSD-style select(2). Works everywhere, but has a built-in + * upper bound on the number of file descriptors, and that limit is hard to + * change on Linux. Is sometimes emulated with SYSV-style poll(2) which + * doesn't have the file descriptor limit, but unfortunately does not help + * to improve the performance of servers with lots of connections. + */ +#define EVENT_ALLOC_INCR 10 + +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) +typedef fd_set EVENT_MASK; + +#define EVENT_MASK_BYTE_COUNT(mask) sizeof(*(mask)) +#define EVENT_MASK_ZERO(mask) FD_ZERO(mask) +#define EVENT_MASK_SET(fd, mask) FD_SET((fd), (mask)) +#define EVENT_MASK_ISSET(fd, mask) FD_ISSET((fd), (mask)) +#define EVENT_MASK_CLR(fd, mask) FD_CLR((fd), (mask)) +#define EVENT_MASK_CMP(m1, m2) memcmp((m1), (m2), EVENT_MASK_BYTE_COUNT(m1)) +#else + + /* + * Kernel-based event filters (kqueue, /dev/poll, epoll). We use the + * following file descriptor mask structure which is expanded on the fly. + */ +typedef struct { + char *data; /* bit mask */ + size_t data_len; /* data byte count */ +} EVENT_MASK; + + /* Bits per byte, byte in vector, bit offset in byte, bytes per set. */ +#define EVENT_MASK_NBBY (8) +#define EVENT_MASK_FD_BYTE(fd, mask) \ + (((unsigned char *) (mask)->data)[(fd) / EVENT_MASK_NBBY]) +#define EVENT_MASK_FD_BIT(fd) (1 << ((fd) % EVENT_MASK_NBBY)) +#define EVENT_MASK_BYTES_NEEDED(len) \ + (((len) + (EVENT_MASK_NBBY -1)) / EVENT_MASK_NBBY) +#define EVENT_MASK_BYTE_COUNT(mask) ((mask)->data_len) + + /* Memory management. */ +#define EVENT_MASK_ALLOC(mask, bit_len) do { \ + size_t _byte_len = EVENT_MASK_BYTES_NEEDED(bit_len); \ + (mask)->data = mymalloc(_byte_len); \ + memset((mask)->data, 0, _byte_len); \ + (mask)->data_len = _byte_len; \ + } while (0) +#define EVENT_MASK_REALLOC(mask, bit_len) do { \ + size_t _byte_len = EVENT_MASK_BYTES_NEEDED(bit_len); \ + size_t _old_len = (mask)->data_len; \ + (mask)->data = myrealloc((mask)->data, _byte_len); \ + if (_byte_len > _old_len) \ + memset((mask)->data + _old_len, 0, _byte_len - _old_len); \ + (mask)->data_len = _byte_len; \ + } while (0) +#define EVENT_MASK_FREE(mask) myfree((mask)->data) + + /* Set operations, modeled after FD_ZERO/SET/ISSET/CLR. */ +#define EVENT_MASK_ZERO(mask) \ + memset((mask)->data, 0, (mask)->data_len) +#define EVENT_MASK_SET(fd, mask) \ + (EVENT_MASK_FD_BYTE((fd), (mask)) |= EVENT_MASK_FD_BIT(fd)) +#define EVENT_MASK_ISSET(fd, mask) \ + (EVENT_MASK_FD_BYTE((fd), (mask)) & EVENT_MASK_FD_BIT(fd)) +#define EVENT_MASK_CLR(fd, mask) \ + (EVENT_MASK_FD_BYTE((fd), (mask)) &= ~EVENT_MASK_FD_BIT(fd)) +#define EVENT_MASK_CMP(m1, m2) \ + memcmp((m1)->data, (m2)->data, EVENT_MASK_BYTE_COUNT(m1)) +#endif + + /* + * I/O events. + */ +typedef struct EVENT_FDTABLE EVENT_FDTABLE; + +struct EVENT_FDTABLE { + EVENT_NOTIFY_RDWR_FN callback; + char *context; +}; +static EVENT_MASK event_rmask; /* enabled read events */ +static EVENT_MASK event_wmask; /* enabled write events */ +static EVENT_MASK event_xmask; /* for bad news mostly */ +static int event_fdlimit; /* per-process open file limit */ +static EVENT_FDTABLE *event_fdtable; /* one slot per file descriptor */ +static int event_fdslots; /* number of file descriptor slots */ +static int event_max_fd = -1; /* highest fd number seen */ + + /* + * FreeBSD kqueue supports no system call to find out what descriptors are + * registered in the kernel-based filter. To implement our own sanity checks + * we maintain our own descriptor bitmask. + * + * FreeBSD kqueue does support application context pointers. Unfortunately, + * changing that information would cost a system call, and some of the + * competitors don't support application context. To keep the implementation + * simple we maintain our own table with call-back information. + * + * FreeBSD kqueue silently unregisters a descriptor from its filter when the + * descriptor is closed, so our information could get out of sync with the + * kernel. But that will never happen, because we have to meticulously + * unregister a file descriptor before it is closed, to avoid errors on + * systems that are built with EVENTS_STYLE == EVENTS_STYLE_SELECT. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_KQUEUE) +#include + + /* + * Some early FreeBSD implementations don't have the EV_SET macro. + */ +#ifndef EV_SET +#define EV_SET(kp, id, fi, fl, ffl, da, ud) do { \ + (kp)->ident = (id); \ + (kp)->filter = (fi); \ + (kp)->flags = (fl); \ + (kp)->fflags = (ffl); \ + (kp)->data = (da); \ + (kp)->udata = (ud); \ + } while(0) +#endif + + /* + * Macros to initialize the kernel-based filter; see event_init(). + */ +static int event_kq; /* handle to event filter */ + +#define EVENT_REG_INIT_HANDLE(er, n) do { \ + er = event_kq = kqueue(); \ + } while (0) +#define EVENT_REG_INIT_TEXT "kqueue" + +#define EVENT_REG_FORK_HANDLE(er, n) do { \ + (void) close(event_kq); \ + EVENT_REG_INIT_HANDLE(er, (n)); \ + } while (0) + + /* + * Macros to update the kernel-based filter; see event_enable_read(), + * event_enable_write() and event_disable_readwrite(). + */ +#define EVENT_REG_FD_OP(er, fh, ev, op) do { \ + struct kevent dummy; \ + EV_SET(&dummy, (fh), (ev), (op), 0, 0, 0); \ + (er) = kevent(event_kq, &dummy, 1, 0, 0, 0); \ + } while (0) + +#define EVENT_REG_ADD_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EV_ADD) +#define EVENT_REG_ADD_READ(e, f) EVENT_REG_ADD_OP((e), (f), EVFILT_READ) +#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_ADD_OP((e), (f), EVFILT_WRITE) +#define EVENT_REG_ADD_TEXT "kevent EV_ADD" + +#define EVENT_REG_DEL_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EV_DELETE) +#define EVENT_REG_DEL_READ(e, f) EVENT_REG_DEL_OP((e), (f), EVFILT_READ) +#define EVENT_REG_DEL_WRITE(e, f) EVENT_REG_DEL_OP((e), (f), EVFILT_WRITE) +#define EVENT_REG_DEL_TEXT "kevent EV_DELETE" + + /* + * Macros to retrieve event buffers from the kernel; see event_loop(). + */ +typedef struct kevent EVENT_BUFFER; + +#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \ + struct timespec ts; \ + struct timespec *tsp; \ + if ((delay) < 0) { \ + tsp = 0; \ + } else { \ + tsp = &ts; \ + ts.tv_nsec = 0; \ + ts.tv_sec = (delay); \ + } \ + (event_count) = kevent(event_kq, (struct kevent *) 0, 0, (event_buf), \ + (buflen), (tsp)); \ + } while (0) +#define EVENT_BUFFER_READ_TEXT "kevent" + + /* + * Macros to process event buffers from the kernel; see event_loop(). + */ +#define EVENT_GET_FD(bp) ((bp)->ident) +#define EVENT_GET_TYPE(bp) ((bp)->filter) +#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) == EVFILT_READ) +#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) == EVFILT_WRITE) + +#endif + + /* + * Solaris /dev/poll does not support application context, so we have to + * maintain our own. This has the benefit of avoiding an expensive system + * call just to change a call-back function or argument. + * + * Solaris /dev/poll does have a way to query if a specific descriptor is + * registered. However, we maintain a descriptor mask anyway because a) it + * avoids having to make an expensive system call to find out if something + * is registered, b) some EVENTS_STYLE_MUMBLE implementations need a + * descriptor bitmask anyway and c) we use the bitmask already to implement + * sanity checks. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_DEVPOLL) +#include +#include + + /* + * Macros to initialize the kernel-based filter; see event_init(). + */ +static int event_pollfd; /* handle to file descriptor set */ + +#define EVENT_REG_INIT_HANDLE(er, n) do { \ + er = event_pollfd = open("/dev/poll", O_RDWR); \ + if (event_pollfd >= 0) close_on_exec(event_pollfd, CLOSE_ON_EXEC); \ + } while (0) +#define EVENT_REG_INIT_TEXT "open /dev/poll" + +#define EVENT_REG_FORK_HANDLE(er, n) do { \ + (void) close(event_pollfd); \ + EVENT_REG_INIT_HANDLE(er, (n)); \ + } while (0) + + /* + * Macros to update the kernel-based filter; see event_enable_read(), + * event_enable_write() and event_disable_readwrite(). + */ +#define EVENT_REG_FD_OP(er, fh, ev) do { \ + struct pollfd dummy; \ + dummy.fd = (fh); \ + dummy.events = (ev); \ + (er) = write(event_pollfd, (void *) &dummy, \ + sizeof(dummy)) != sizeof(dummy) ? -1 : 0; \ + } while (0) + +#define EVENT_REG_ADD_READ(e, f) EVENT_REG_FD_OP((e), (f), POLLIN) +#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_FD_OP((e), (f), POLLOUT) +#define EVENT_REG_ADD_TEXT "write /dev/poll" + +#define EVENT_REG_DEL_BOTH(e, f) EVENT_REG_FD_OP((e), (f), POLLREMOVE) +#define EVENT_REG_DEL_TEXT "write /dev/poll" + + /* + * Macros to retrieve event buffers from the kernel; see event_loop(). + */ +typedef struct pollfd EVENT_BUFFER; + +#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \ + struct dvpoll dvpoll; \ + dvpoll.dp_fds = (event_buf); \ + dvpoll.dp_nfds = (buflen); \ + dvpoll.dp_timeout = (delay) < 0 ? -1 : (delay) * 1000; \ + (event_count) = ioctl(event_pollfd, DP_POLL, &dvpoll); \ + } while (0) +#define EVENT_BUFFER_READ_TEXT "ioctl DP_POLL" + + /* + * Macros to process event buffers from the kernel; see event_loop(). + */ +#define EVENT_GET_FD(bp) ((bp)->fd) +#define EVENT_GET_TYPE(bp) ((bp)->revents) +#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) & POLLIN) +#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) & POLLOUT) + +#endif + + /* + * Linux epoll supports no system call to find out what descriptors are + * registered in the kernel-based filter. To implement our own sanity checks + * we maintain our own descriptor bitmask. + * + * Linux epoll does support application context pointers. Unfortunately, + * changing that information would cost a system call, and some of the + * competitors don't support application context. To keep the implementation + * simple we maintain our own table with call-back information. + * + * Linux epoll silently unregisters a descriptor from its filter when the + * descriptor is closed, so our information could get out of sync with the + * kernel. But that will never happen, because we have to meticulously + * unregister a file descriptor before it is closed, to avoid errors on + * systems that are built with EVENTS_STYLE == EVENTS_STYLE_SELECT. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_EPOLL) +#include + + /* + * Macros to initialize the kernel-based filter; see event_init(). + */ +static int event_epollfd; /* epoll handle */ + +#define EVENT_REG_INIT_HANDLE(er, n) do { \ + er = event_epollfd = epoll_create(n); \ + if (event_epollfd >= 0) close_on_exec(event_epollfd, CLOSE_ON_EXEC); \ + } while (0) +#define EVENT_REG_INIT_TEXT "epoll_create" + +#define EVENT_REG_FORK_HANDLE(er, n) do { \ + (void) close(event_epollfd); \ + EVENT_REG_INIT_HANDLE(er, (n)); \ + } while (0) + + /* + * Macros to update the kernel-based filter; see event_enable_read(), + * event_enable_write() and event_disable_readwrite(). + */ +#define EVENT_REG_FD_OP(er, fh, ev, op) do { \ + struct epoll_event dummy; \ + dummy.events = (ev); \ + dummy.data.fd = (fh); \ + (er) = epoll_ctl(event_epollfd, (op), (fh), &dummy); \ + } while (0) + +#define EVENT_REG_ADD_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EPOLL_CTL_ADD) +#define EVENT_REG_ADD_READ(e, f) EVENT_REG_ADD_OP((e), (f), EPOLLIN) +#define EVENT_REG_ADD_WRITE(e, f) EVENT_REG_ADD_OP((e), (f), EPOLLOUT) +#define EVENT_REG_ADD_TEXT "epoll_ctl EPOLL_CTL_ADD" + +#define EVENT_REG_DEL_OP(e, f, ev) EVENT_REG_FD_OP((e), (f), (ev), EPOLL_CTL_DEL) +#define EVENT_REG_DEL_READ(e, f) EVENT_REG_DEL_OP((e), (f), EPOLLIN) +#define EVENT_REG_DEL_WRITE(e, f) EVENT_REG_DEL_OP((e), (f), EPOLLOUT) +#define EVENT_REG_DEL_TEXT "epoll_ctl EPOLL_CTL_DEL" + + /* + * Macros to retrieve event buffers from the kernel; see event_loop(). + */ +typedef struct epoll_event EVENT_BUFFER; + +#define EVENT_BUFFER_READ(event_count, event_buf, buflen, delay) do { \ + (event_count) = epoll_wait(event_epollfd, (event_buf), (buflen), \ + (delay) < 0 ? -1 : (delay) * 1000); \ + } while (0) +#define EVENT_BUFFER_READ_TEXT "epoll_wait" + + /* + * Macros to process event buffers from the kernel; see event_loop(). + */ +#define EVENT_GET_FD(bp) ((bp)->data.fd) +#define EVENT_GET_TYPE(bp) ((bp)->events) +#define EVENT_TEST_READ(bp) (EVENT_GET_TYPE(bp) & EPOLLIN) +#define EVENT_TEST_WRITE(bp) (EVENT_GET_TYPE(bp) & EPOLLOUT) + +#endif + + /* + * Timer events. Timer requests are kept sorted, in a circular list. We use + * the RING abstraction, so we get to use a couple ugly macros. + * + * When a call-back function adds a timer request, we label the request with + * the event_loop() call instance that invoked the call-back. We use this to + * prevent zero-delay timer requests from running in a tight loop and + * starving I/O events. + */ +typedef struct EVENT_TIMER EVENT_TIMER; + +struct EVENT_TIMER { + time_t when; /* when event is wanted */ + EVENT_NOTIFY_TIME_FN callback; /* callback function */ + char *context; /* callback context */ + long loop_instance; /* event_loop() call instance */ + RING ring; /* linkage */ +}; + +static RING event_timer_head; /* timer queue head */ +static long event_loop_instance; /* event_loop() call instance */ + +#define RING_TO_TIMER(r) \ + ((EVENT_TIMER *) ((void *) (r) - offsetof(EVENT_TIMER, ring))) + +#define FOREACH_QUEUE_ENTRY(entry, head) \ + for (entry = ring_succ(head); entry != (head); entry = ring_succ(entry)) + +#define FIRST_TIMER(head) \ + (ring_succ(head) != (head) ? RING_TO_TIMER(ring_succ(head)) : 0) + + /* + * Other private data structures. + */ +static time_t event_present; /* cached time of day */ + +#define EVENT_INIT_NEEDED() (event_present == 0) + +/* event_init - set up tables and such */ + +static void event_init(void) +{ + EVENT_FDTABLE *fdp; + int err; + + if (!EVENT_INIT_NEEDED()) + msg_panic("event_init: repeated call"); + + /* + * Initialize the file descriptor masks and the call-back table. Where + * possible we extend these data structures on the fly. With select(2) + * based implementations we can only handle FD_SETSIZE open files. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + if ((event_fdlimit = open_limit(FD_SETSIZE)) < 0) + msg_fatal("unable to determine open file limit"); +#else + if ((event_fdlimit = open_limit(INT_MAX)) < 0) + msg_fatal("unable to determine open file limit"); +#endif + if (event_fdlimit < FD_SETSIZE / 2 && event_fdlimit < 256) + msg_warn("could allocate space for only %d open files", event_fdlimit); + event_fdslots = EVENT_ALLOC_INCR; + event_fdtable = (EVENT_FDTABLE *) + mymalloc(sizeof(EVENT_FDTABLE) * event_fdslots); + for (fdp = event_fdtable; fdp < event_fdtable + event_fdslots; fdp++) { + fdp->callback = 0; + fdp->context = 0; + } + + /* + * Initialize the I/O event request masks. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + EVENT_MASK_ZERO(&event_rmask); + EVENT_MASK_ZERO(&event_wmask); + EVENT_MASK_ZERO(&event_xmask); +#else + EVENT_MASK_ALLOC(&event_rmask, event_fdslots); + EVENT_MASK_ALLOC(&event_wmask, event_fdslots); + EVENT_MASK_ALLOC(&event_xmask, event_fdslots); + + /* + * Initialize the kernel-based filter. + */ + EVENT_REG_INIT_HANDLE(err, event_fdslots); + if (err < 0) + msg_fatal("%s: %m", EVENT_REG_INIT_TEXT); +#endif + + /* + * Initialize timer stuff. + */ + ring_init(&event_timer_head); + (void) time(&event_present); + + /* + * Avoid an infinite initialization loop. + */ + if (EVENT_INIT_NEEDED()) + msg_panic("event_init: unable to initialize"); +} + +/* event_extend - make room for more descriptor slots */ + +static void event_extend(int fd) +{ + const char *myname = "event_extend"; + int old_slots = event_fdslots; + int new_slots = (event_fdslots > fd / 2 ? + 2 * old_slots : fd + EVENT_ALLOC_INCR); + EVENT_FDTABLE *fdp; + +#ifdef EVENT_REG_UPD_HANDLE + int err; + +#endif + + if (msg_verbose > 2) + msg_info("%s: fd %d", myname, fd); + event_fdtable = (EVENT_FDTABLE *) + myrealloc((void *) event_fdtable, sizeof(EVENT_FDTABLE) * new_slots); + event_fdslots = new_slots; + for (fdp = event_fdtable + old_slots; + fdp < event_fdtable + new_slots; fdp++) { + fdp->callback = 0; + fdp->context = 0; + } + + /* + * Initialize the I/O event request masks. + */ +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + EVENT_MASK_REALLOC(&event_rmask, new_slots); + EVENT_MASK_REALLOC(&event_wmask, new_slots); + EVENT_MASK_REALLOC(&event_xmask, new_slots); +#endif +#ifdef EVENT_REG_UPD_HANDLE + EVENT_REG_UPD_HANDLE(err, new_slots); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_UPD_TEXT); +#endif +} + +/* event_time - look up cached time of day */ + +time_t event_time(void) +{ + if (EVENT_INIT_NEEDED()) + event_init(); + + return (event_present); +} + +/* event_drain - loop until all pending events are done */ + +void event_drain(int time_limit) +{ + EVENT_MASK zero_mask; + time_t max_time; + + if (EVENT_INIT_NEEDED()) + return; + +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + EVENT_MASK_ZERO(&zero_mask); +#else + EVENT_MASK_ALLOC(&zero_mask, event_fdslots); +#endif + (void) time(&event_present); + max_time = event_present + time_limit; + while (event_present < max_time + && (event_timer_head.pred != &event_timer_head + || EVENT_MASK_CMP(&zero_mask, &event_xmask) != 0)) { + event_loop(1); +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + if (EVENT_MASK_BYTE_COUNT(&zero_mask) + != EVENT_MASK_BYTES_NEEDED(event_fdslots)) + EVENT_MASK_REALLOC(&zero_mask, event_fdslots); +#endif + } +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + EVENT_MASK_FREE(&zero_mask); +#endif +} + +/* event_fork - resume event processing after fork() */ + +void event_fork(void) +{ +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + EVENT_FDTABLE *fdp; + int err; + int fd; + + /* + * No event was ever registered, so there's nothing to be done. + */ + if (EVENT_INIT_NEEDED()) + return; + + /* + * Close the existing filter handle and open a new kernel-based filter. + */ + EVENT_REG_FORK_HANDLE(err, event_fdslots); + if (err < 0) + msg_fatal("%s: %m", EVENT_REG_INIT_TEXT); + + /* + * Populate the new kernel-based filter with events that were registered + * in the parent process. + */ + for (fd = 0; fd <= event_max_fd; fd++) { + if (EVENT_MASK_ISSET(fd, &event_wmask)) { + EVENT_MASK_CLR(fd, &event_wmask); + fdp = event_fdtable + fd; + event_enable_write(fd, fdp->callback, fdp->context); + } else if (EVENT_MASK_ISSET(fd, &event_rmask)) { + EVENT_MASK_CLR(fd, &event_rmask); + fdp = event_fdtable + fd; + event_enable_read(fd, fdp->callback, fdp->context); + } + } +#endif +} + +/* event_enable_read - enable read events */ + +void event_enable_read(int fd, EVENT_NOTIFY_RDWR_FN callback, void *context) +{ + const char *myname = "event_enable_read"; + EVENT_FDTABLE *fdp; + int err; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * Sanity checks. + */ + if (fd < 0 || fd >= event_fdlimit) + msg_panic("%s: bad file descriptor: %d", myname, fd); + + if (msg_verbose > 2) + msg_info("%s: fd %d", myname, fd); + + if (fd >= event_fdslots) + event_extend(fd); + + /* + * Disallow mixed (i.e. read and write) requests on the same descriptor. + */ + if (EVENT_MASK_ISSET(fd, &event_wmask)) + msg_panic("%s: fd %d: read/write I/O request", myname, fd); + + /* + * Postfix 2.4 allows multiple event_enable_read() calls on the same + * descriptor without requiring event_disable_readwrite() calls between + * them. With kernel-based filters (kqueue, /dev/poll, epoll) it's + * wasteful to make system calls when we change only application + * call-back information. It has a noticeable effect on smtp-source + * performance. + */ + if (EVENT_MASK_ISSET(fd, &event_rmask) == 0) { + EVENT_MASK_SET(fd, &event_xmask); + EVENT_MASK_SET(fd, &event_rmask); + if (event_max_fd < fd) + event_max_fd = fd; +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + EVENT_REG_ADD_READ(err, fd); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_ADD_TEXT); +#endif + } + fdp = event_fdtable + fd; + if (fdp->callback != callback || fdp->context != context) { + fdp->callback = callback; + fdp->context = context; + } +} + +/* event_enable_write - enable write events */ + +void event_enable_write(int fd, EVENT_NOTIFY_RDWR_FN callback, void *context) +{ + const char *myname = "event_enable_write"; + EVENT_FDTABLE *fdp; + int err; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * Sanity checks. + */ + if (fd < 0 || fd >= event_fdlimit) + msg_panic("%s: bad file descriptor: %d", myname, fd); + + if (msg_verbose > 2) + msg_info("%s: fd %d", myname, fd); + + if (fd >= event_fdslots) + event_extend(fd); + + /* + * Disallow mixed (i.e. read and write) requests on the same descriptor. + */ + if (EVENT_MASK_ISSET(fd, &event_rmask)) + msg_panic("%s: fd %d: read/write I/O request", myname, fd); + + /* + * Postfix 2.4 allows multiple event_enable_write() calls on the same + * descriptor without requiring event_disable_readwrite() calls between + * them. With kernel-based filters (kqueue, /dev/poll, epoll) it's + * incredibly wasteful to make unregister and register system calls when + * we change only application call-back information. It has a noticeable + * effect on smtp-source performance. + */ + if (EVENT_MASK_ISSET(fd, &event_wmask) == 0) { + EVENT_MASK_SET(fd, &event_xmask); + EVENT_MASK_SET(fd, &event_wmask); + if (event_max_fd < fd) + event_max_fd = fd; +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) + EVENT_REG_ADD_WRITE(err, fd); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_ADD_TEXT); +#endif + } + fdp = event_fdtable + fd; + if (fdp->callback != callback || fdp->context != context) { + fdp->callback = callback; + fdp->context = context; + } +} + +/* event_disable_readwrite - disable request for read or write events */ + +void event_disable_readwrite(int fd) +{ + const char *myname = "event_disable_readwrite"; + EVENT_FDTABLE *fdp; + int err; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * Sanity checks. + */ + if (fd < 0 || fd >= event_fdlimit) + msg_panic("%s: bad file descriptor: %d", myname, fd); + + if (msg_verbose > 2) + msg_info("%s: fd %d", myname, fd); + + /* + * Don't complain when there is nothing to cancel. The request may have + * been canceled from another thread. + */ + if (fd >= event_fdslots) + return; +#if (EVENTS_STYLE != EVENTS_STYLE_SELECT) +#ifdef EVENT_REG_DEL_BOTH + /* XXX Can't seem to disable READ and WRITE events selectively. */ + if (EVENT_MASK_ISSET(fd, &event_rmask) + || EVENT_MASK_ISSET(fd, &event_wmask)) { + EVENT_REG_DEL_BOTH(err, fd); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT); + } +#else + if (EVENT_MASK_ISSET(fd, &event_rmask)) { + EVENT_REG_DEL_READ(err, fd); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT); + } else if (EVENT_MASK_ISSET(fd, &event_wmask)) { + EVENT_REG_DEL_WRITE(err, fd); + if (err < 0) + msg_fatal("%s: %s: %m", myname, EVENT_REG_DEL_TEXT); + } +#endif /* EVENT_REG_DEL_BOTH */ +#endif /* != EVENTS_STYLE_SELECT */ + EVENT_MASK_CLR(fd, &event_xmask); + EVENT_MASK_CLR(fd, &event_rmask); + EVENT_MASK_CLR(fd, &event_wmask); + fdp = event_fdtable + fd; + fdp->callback = 0; + fdp->context = 0; +} + +/* event_request_timer - (re)set timer */ + +time_t event_request_timer(EVENT_NOTIFY_TIME_FN callback, void *context, int delay) +{ + const char *myname = "event_request_timer"; + RING *ring; + EVENT_TIMER *timer; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * Sanity checks. + */ + if (delay < 0) + msg_panic("%s: invalid delay: %d", myname, delay); + + /* + * Make sure we schedule this event at the right time. + */ + time(&event_present); + + /* + * See if they are resetting an existing timer request. If so, take the + * request away from the timer queue so that it can be inserted at the + * right place. + */ + FOREACH_QUEUE_ENTRY(ring, &event_timer_head) { + timer = RING_TO_TIMER(ring); + if (timer->callback == callback && timer->context == context) { + timer->when = event_present + delay; + timer->loop_instance = event_loop_instance; + ring_detach(ring); + if (msg_verbose > 2) + msg_info("%s: reset 0x%lx 0x%lx %d", myname, + (long) callback, (long) context, delay); + break; + } + } + + /* + * If not found, schedule a new timer request. + */ + if (ring == &event_timer_head) { + timer = (EVENT_TIMER *) mymalloc(sizeof(EVENT_TIMER)); + timer->when = event_present + delay; + timer->callback = callback; + timer->context = context; + timer->loop_instance = event_loop_instance; + if (msg_verbose > 2) + msg_info("%s: set 0x%lx 0x%lx %d", myname, + (long) callback, (long) context, delay); + } + + /* + * Timer requests are kept sorted to reduce lookup overhead in the event + * loop. + * + * XXX Append the new request after existing requests for the same time + * slot. The event_loop() routine depends on this to avoid starving I/O + * events when a call-back function schedules a zero-delay timer request. + */ + FOREACH_QUEUE_ENTRY(ring, &event_timer_head) { + if (timer->when < RING_TO_TIMER(ring)->when) + break; + } + ring_prepend(ring, &timer->ring); + + return (timer->when); +} + +/* event_cancel_timer - cancel timer */ + +int event_cancel_timer(EVENT_NOTIFY_TIME_FN callback, void *context) +{ + const char *myname = "event_cancel_timer"; + RING *ring; + EVENT_TIMER *timer; + int time_left = -1; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * See if they are canceling an existing timer request. Do not complain + * when the request is not found. It might have been canceled from some + * other thread. + */ + FOREACH_QUEUE_ENTRY(ring, &event_timer_head) { + timer = RING_TO_TIMER(ring); + if (timer->callback == callback && timer->context == context) { + if ((time_left = timer->when - event_present) < 0) + time_left = 0; + ring_detach(ring); + myfree((void *) timer); + break; + } + } + if (msg_verbose > 2) + msg_info("%s: 0x%lx 0x%lx %d", myname, + (long) callback, (long) context, time_left); + return (time_left); +} + +/* event_loop - wait for the next event */ + +void event_loop(int delay) +{ + const char *myname = "event_loop"; + static int nested; + +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + fd_set rmask; + fd_set wmask; + fd_set xmask; + struct timeval tv; + struct timeval *tvp; + int new_max_fd; + +#else + EVENT_BUFFER event_buf[100]; + EVENT_BUFFER *bp; + +#endif + int event_count; + EVENT_TIMER *timer; + int fd; + EVENT_FDTABLE *fdp; + int select_delay; + + if (EVENT_INIT_NEEDED()) + event_init(); + + /* + * XXX Also print the select() masks? + */ + if (msg_verbose > 2) { + RING *ring; + + FOREACH_QUEUE_ENTRY(ring, &event_timer_head) { + timer = RING_TO_TIMER(ring); + msg_info("%s: time left %3d for 0x%lx 0x%lx", myname, + (int) (timer->when - event_present), + (long) timer->callback, (long) timer->context); + } + } + + /* + * Find out when the next timer would go off. Timer requests are sorted. + * If any timer is scheduled, adjust the delay appropriately. + */ + if ((timer = FIRST_TIMER(&event_timer_head)) != 0) { + event_present = time((time_t *) 0); + if ((select_delay = timer->when - event_present) < 0) { + select_delay = 0; + } else if (delay >= 0 && select_delay > delay) { + select_delay = delay; + } + } else { + select_delay = delay; + } + if (msg_verbose > 2) + msg_info("event_loop: select_delay %d", select_delay); + + /* + * Negative delay means: wait until something happens. Zero delay means: + * poll. Positive delay means: wait at most this long. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + if (select_delay < 0) { + tvp = 0; + } else { + tvp = &tv; + tv.tv_usec = 0; + tv.tv_sec = select_delay; + } + + /* + * Pause until the next event happens. When select() has a problem, don't + * go into a tight loop. Allow select() to be interrupted due to the + * arrival of a signal. + */ + rmask = event_rmask; + wmask = event_wmask; + xmask = event_xmask; + + event_count = select(event_max_fd + 1, &rmask, &wmask, &xmask, tvp); + if (event_count < 0) { + if (errno != EINTR) + msg_fatal("event_loop: select: %m"); + return; + } +#else + EVENT_BUFFER_READ(event_count, event_buf, + sizeof(event_buf) / sizeof(event_buf[0]), + select_delay); + if (event_count < 0) { + if (errno != EINTR) + msg_fatal("event_loop: " EVENT_BUFFER_READ_TEXT ": %m"); + return; + } +#endif + + /* + * Before entering the application call-back routines, make sure we + * aren't being called from a call-back routine. Doing so would make us + * vulnerable to all kinds of race conditions. + */ + if (nested++ > 0) + msg_panic("event_loop: recursive call"); + + /* + * Deliver timer events. Allow the application to add/delete timer queue + * requests while it is being called back. Requests are sorted: we keep + * running over the timer request queue from the start, and stop when we + * reach the future or the list end. We also stop when we reach a timer + * request that was added by a call-back that was invoked from this + * event_loop() call instance, for reasons that are explained below. + * + * To avoid dangling pointer problems 1) we must remove a request from the + * timer queue before delivering its event to the application and 2) we + * must look up the next timer request *after* calling the application. + * The latter complicates the handling of zero-delay timer requests that + * are added by event_loop() call-back functions. + * + * XXX When a timer event call-back function adds a new timer request, + * event_request_timer() labels the request with the event_loop() call + * instance that invoked the timer event call-back. We use this instance + * label here to prevent zero-delay timer requests from running in a + * tight loop and starving I/O events. To make this solution work, + * event_request_timer() appends a new request after existing requests + * for the same time slot. + */ + event_present = time((time_t *) 0); + event_loop_instance += 1; + + while ((timer = FIRST_TIMER(&event_timer_head)) != 0) { + if (timer->when > event_present) + break; + if (timer->loop_instance == event_loop_instance) + break; + ring_detach(&timer->ring); /* first this */ + if (msg_verbose > 2) + msg_info("%s: timer 0x%lx 0x%lx", myname, + (long) timer->callback, (long) timer->context); + timer->callback(EVENT_TIME, timer->context); /* then this */ + myfree((void *) timer); + } + + /* + * Deliver I/O events. Allow the application to cancel event requests + * while it is being called back. To this end, we keep an eye on the + * contents of event_xmask, so that we deliver only events that are still + * wanted. We do not change the event request masks. It is up to the + * application to determine when a read or write is complete. + */ +#if (EVENTS_STYLE == EVENTS_STYLE_SELECT) + if (event_count > 0) { + for (new_max_fd = 0, fd = 0; fd <= event_max_fd; fd++) { + if (FD_ISSET(fd, &event_xmask)) { + new_max_fd = fd; + /* In case event_fdtable is updated. */ + fdp = event_fdtable + fd; + if (FD_ISSET(fd, &xmask)) { + if (msg_verbose > 2) + msg_info("%s: exception fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, (long) fdp->context); + fdp->callback(EVENT_XCPT, fdp->context); + } else if (FD_ISSET(fd, &wmask)) { + if (msg_verbose > 2) + msg_info("%s: write fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, (long) fdp->context); + fdp->callback(EVENT_WRITE, fdp->context); + } else if (FD_ISSET(fd, &rmask)) { + if (msg_verbose > 2) + msg_info("%s: read fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, (long) fdp->context); + fdp->callback(EVENT_READ, fdp->context); + } + } + } + event_max_fd = new_max_fd; + } +#else + for (bp = event_buf; bp < event_buf + event_count; bp++) { + fd = EVENT_GET_FD(bp); + if (fd < 0 || fd > event_max_fd) + msg_panic("%s: bad file descriptor: %d", myname, fd); + if (EVENT_MASK_ISSET(fd, &event_xmask)) { + fdp = event_fdtable + fd; + if (EVENT_TEST_READ(bp)) { + if (msg_verbose > 2) + msg_info("%s: read fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, (long) fdp->context); + fdp->callback(EVENT_READ, fdp->context); + } else if (EVENT_TEST_WRITE(bp)) { + if (msg_verbose > 2) + msg_info("%s: write fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, + (long) fdp->context); + fdp->callback(EVENT_WRITE, fdp->context); + } else { + if (msg_verbose > 2) + msg_info("%s: other fd=%d act=0x%lx 0x%lx", myname, + fd, (long) fdp->callback, (long) fdp->context); + fdp->callback(EVENT_XCPT, fdp->context); + } + } + } +#endif + nested--; +} + +#ifdef TEST + + /* + * Proof-of-concept test program for the event manager. Schedule a series of + * events at one-second intervals and let them happen, while echoing any + * lines read from stdin. + */ +#include +#include +#include + +/* timer_event - display event */ + +static void timer_event(int unused_event, void *context) +{ + printf("%ld: %s\n", (long) event_present, context); + fflush(stdout); +} + +/* echo - echo text received on stdin */ + +static void echo(int unused_event, void *unused_context) +{ + char buf[BUFSIZ]; + + if (fgets(buf, sizeof(buf), stdin) == 0) + exit(0); + printf("Result: %s", buf); +} + +/* request - request a bunch of timer events */ + +static void request(int unused_event, void *unused_context) +{ + event_request_timer(timer_event, "3 first", 3); + event_request_timer(timer_event, "3 second", 3); + event_request_timer(timer_event, "4 first", 4); + event_request_timer(timer_event, "4 second", 4); + event_request_timer(timer_event, "2 first", 2); + event_request_timer(timer_event, "2 second", 2); + event_request_timer(timer_event, "1 first", 1); + event_request_timer(timer_event, "1 second", 1); + event_request_timer(timer_event, "0 first", 0); + event_request_timer(timer_event, "0 second", 0); +} + +int main(int argc, void **argv) +{ + if (argv[1]) + msg_verbose = atoi(argv[1]); + event_request_timer(request, (void *) 0, 0); + event_enable_read(fileno(stdin), echo, (void *) 0); + event_drain(10); + exit(0); +} + +#endif diff --git a/src/util/events.h b/src/util/events.h new file mode 100644 index 0000000..b7f2047 --- /dev/null +++ b/src/util/events.h @@ -0,0 +1,67 @@ +#ifndef _EVENTS_H_INCLUDED_ +#define _EVENTS_H_INCLUDED_ + +/*++ +/* NAME +/* events 3h +/* SUMMARY +/* event manager +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * External interface. + */ +typedef void (*EVENT_NOTIFY_FN) (int, void *); + +#define EVENT_NOTIFY_TIME_FN EVENT_NOTIFY_FN /* legacy */ +#define EVENT_NOTIFY_RDWR_FN EVENT_NOTIFY_FN /* legacy */ + +extern time_t event_time(void); +extern void event_enable_read(int, EVENT_NOTIFY_RDWR_FN, void *); +extern void event_enable_write(int, EVENT_NOTIFY_RDWR_FN, void *); +extern void event_disable_readwrite(int); +extern time_t event_request_timer(EVENT_NOTIFY_TIME_FN, void *, int); +extern int event_cancel_timer(EVENT_NOTIFY_TIME_FN, void *); +extern void event_loop(int); +extern void event_drain(int); +extern void event_fork(void); + + /* + * Event codes. + */ +#define EVENT_READ (1<<0) /* read event */ +#define EVENT_WRITE (1<<1) /* write event */ +#define EVENT_XCPT (1<<2) /* exception */ +#define EVENT_TIME (1<<3) /* timer event */ + +#define EVENT_ERROR EVENT_XCPT + + /* + * Dummies. + */ +#define EVENT_NULL_TYPE (0) +#define EVENT_NULL_CONTEXT ((void *) 0) +#define EVENT_NULL_DELAY (0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* CREATION DATE +/* Wed Jan 29 17:00:03 EST 1997 +/*--*/ + +#endif diff --git a/src/util/exec_command.c b/src/util/exec_command.c new file mode 100644 index 0000000..8629b0c --- /dev/null +++ b/src/util/exec_command.c @@ -0,0 +1,112 @@ +/*++ +/* NAME +/* exec_command 3 +/* SUMMARY +/* execute command +/* SYNOPSIS +/* #include +/* +/* NORETURN exec_command(command) +/* const char *command; +/* DESCRIPTION +/* \fIexec_command\fR() replaces the current process by an instance +/* of \fIcommand\fR. This routine uses a simple heuristic to avoid +/* the overhead of running a command shell interpreter. +/* DIAGNOSTICS +/* This routine never returns. All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#ifdef USE_PATHS_H +#include +#endif +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Application-specific. */ + +#define SPACE_TAB " \t" + +/* exec_command - exec command */ + +NORETURN exec_command(const char *command) +{ + ARGV *argv; + + /* + * Character filter. In this particular case, we allow space and tab in + * addition to the regular character set. + */ + static char ok_chars[] = "1234567890!@%-_=+:,./\ +abcdefghijklmnopqrstuvwxyz\ +ABCDEFGHIJKLMNOPQRSTUVWXYZ" SPACE_TAB; + + /* + * See if this command contains any shell magic characters. + */ + if (command[strspn(command, ok_chars)] == 0 + && command[strspn(command, SPACE_TAB)] != 0) { + + /* + * No shell meta characters found, so we can try to avoid the overhead + * of running a shell. Just split the command on whitespace and exec + * the result directly. + */ + argv = argv_split(command, SPACE_TAB); + (void) execvp(argv->argv[0], argv->argv); + + /* + * Auch. Perhaps they're using some shell built-in command. + */ + if (errno != ENOENT || strchr(argv->argv[0], '/') != 0) + msg_fatal("execvp %s: %m", argv->argv[0]); + + /* + * Not really necessary, but... + */ + argv_free(argv); + } + + /* + * Pass the command to a shell. + */ + (void) execl(_PATH_BSHELL, "sh", "-c", command, (char *) 0); + msg_fatal("execl %s: %m", _PATH_BSHELL); +} + +#ifdef TEST + + /* + * Yet another proof-of-concept test program. + */ +#include +#include + +int main(int argc, char **argv) +{ + msg_vstream_init(argv[0], VSTREAM_ERR); + if (argc != 2) + msg_fatal("usage: %s 'command'", argv[0]); + exec_command(argv[1]); +} + +#endif diff --git a/src/util/exec_command.h b/src/util/exec_command.h new file mode 100644 index 0000000..4e77211 --- /dev/null +++ b/src/util/exec_command.h @@ -0,0 +1,30 @@ +#ifndef _EXEC_COMMAND_H_INCLUDED_ +#define _EXEC_COMMAND_H_INCLUDED_ + +/*++ +/* NAME +/* exec_command 3h +/* SUMMARY +/* execute command +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern NORETURN exec_command(const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/extpar.c b/src/util/extpar.c new file mode 100644 index 0000000..0b106ba --- /dev/null +++ b/src/util/extpar.c @@ -0,0 +1,109 @@ +/*++ +/* NAME +/* extpar 3 +/* SUMMARY +/* extract text from parentheses +/* SYNOPSIS +/* #include +/* +/* char *extpar(bp, parens, flags) +/* char **bp; +/* const char *parens; +/* int flags; +/* DESCRIPTION +/* extpar() extracts text from an input string that is enclosed +/* in the specified parentheses, and updates the buffer pointer +/* to point to that text. +/* +/* Arguments: +/* .IP bp +/* Pointer to buffer pointer. Both the buffer and the buffer +/* pointer are modified. +/* .IP parens +/* One matching pair of parentheses, opening parenthesis first. +/* .IP flags +/* EXTPAR_FLAG_NONE, or the bitwise OR of one or more flags: +/* .RS +/* .IP EXTPAR_FLAG_EXTRACT +/* This flag is intended to instruct extpar() callers that +/* extpar() should be invoked. It has no effect on expar() +/* itself. +/* .IP EXTPAR_FLAG_STRIP +/* Skip whitespace after the opening parenthesis, and trim +/* whitespace before the closing parenthesis. +/* .RE +/* DIAGNOSTICS +/* In case of error the result value is a dynamically-allocated +/* string with a description of the problem that includes a +/* copy of the offending input. A non-null result value should +/* be destroyed with myfree(). The following describes the errors +/* and the state of the buffer and buffer pointer. +/* .IP "no opening parenthesis at start of text" +/* The buffer pointer points to the input text. +/* .IP "missing closing parenthesis" +/* The buffer pointer points to text as if a closing parenthesis +/* were present at the end of the input. +/* .IP "text after closing parenthesis" +/* The buffer pointer points to text as if the offending text +/* were not present. +/* SEE ALSO +/* balpar(3) determine length of string in parentheses +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include + +/* extpar - extract text from parentheses */ + +char *extpar(char **bp, const char *parens, int flags) +{ + char *cp = *bp; + char *err = 0; + size_t len; + + if (cp[0] != parens[0]) { + err = vstring_export(vstring_sprintf(vstring_alloc(100), + "no '%c' at start of text in \"%s\"", parens[0], cp)); + len = 0; + } else if ((len = balpar(cp, parens)) == 0) { + err = concatenate("missing '", parens + 1, "' in \"", + cp, "\"", (char *) 0); + cp += 1; + } else { + if (cp[len] != 0) + err = concatenate("syntax error after '", parens + 1, "' in \"", + cp, "\"", (char *) 0); + cp += 1; + cp[len -= 2] = 0; + } + if (flags & EXTPAR_FLAG_STRIP) { + trimblanks(cp, len)[0] = 0; + while (ISSPACE(*cp)) + cp++; + } + *bp = cp; + return (err); +} diff --git a/src/util/fifo_listen.c b/src/util/fifo_listen.c new file mode 100644 index 0000000..9ad3440 --- /dev/null +++ b/src/util/fifo_listen.c @@ -0,0 +1,111 @@ +/*++ +/* NAME +/* fifo_listen 3 +/* SUMMARY +/* start fifo listener +/* SYNOPSIS +/* #include +/* +/* int fifo_listen(path, permissions, block_mode) +/* const char *path; +/* int permissions; +/* int block_mode; +/* DESCRIPTION +/* The \fBfifo_listen\fR routine creates the specified named pipe with +/* the specified permissions, opens the FIFO read-write or read-only, +/* depending on the host operating system, and returns the resulting +/* file descriptor. +/* The \fIblock_mode\fR argument is either NON_BLOCKING for +/* a non-blocking socket, or BLOCKING for blocking mode. +/* DIAGNOSTICS +/* Fatal errors: all system call failures. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "iostuff.h" +#include "listen.h" +#include "warn_stat.h" + +#define BUF_LEN 100 + +/* fifo_listen - create fifo listener */ + +int fifo_listen(const char *path, int permissions, int block_mode) +{ + char buf[BUF_LEN]; + static int open_mode = 0; + const char *myname = "fifo_listen"; + struct stat st; + int fd; + int count; + + /* + * Create a named pipe (fifo). Do whatever we can so we don't run into + * trouble when this process is restarted after crash. Make sure that we + * open a fifo and not something else, then change permissions to what we + * wanted them to be, because mkfifo() is subject to umask settings. + * Instead we could zero the umask temporarily before creating the FIFO, + * but that would cost even more system calls. Figure out if the fifo + * needs to be opened O_RDWR or O_RDONLY. Some systems need one, some + * need the other. If we choose the wrong mode, the fifo will stay + * readable, causing the program to go into a loop. + */ + if (unlink(path) && errno != ENOENT) + msg_fatal("%s: remove %s: %m", myname, path); + if (mkfifo(path, permissions) < 0) + msg_fatal("%s: create fifo %s: %m", myname, path); + switch (open_mode) { + case 0: + if ((fd = open(path, O_RDWR | O_NONBLOCK, 0)) < 0) + msg_fatal("%s: open %s: %m", myname, path); + if (readable(fd) == 0) { + open_mode = O_RDWR | O_NONBLOCK; + break; + } else { + open_mode = O_RDONLY | O_NONBLOCK; + if (msg_verbose) + msg_info("open O_RDWR makes fifo readable - trying O_RDONLY"); + (void) close(fd); + /* FALLTRHOUGH */ + } + default: + if ((fd = open(path, open_mode, 0)) < 0) + msg_fatal("%s: open %s: %m", myname, path); + break; + } + + /* + * Make sure we opened a FIFO and skip any cruft that might have + * accumulated before we opened it. + */ + if (fstat(fd, &st) < 0) + msg_fatal("%s: fstat %s: %m", myname, path); + if (S_ISFIFO(st.st_mode) == 0) + msg_fatal("%s: not a fifo: %s", myname, path); + if (fchmod(fd, permissions) < 0) + msg_fatal("%s: fchmod %s: %m", myname, path); + non_blocking(fd, block_mode); + while ((count = peekfd(fd)) > 0 + && read(fd, buf, BUF_LEN < count ? BUF_LEN : count) > 0) + /* void */ ; + return (fd); +} diff --git a/src/util/fifo_open.c b/src/util/fifo_open.c new file mode 100644 index 0000000..1587779 --- /dev/null +++ b/src/util/fifo_open.c @@ -0,0 +1,67 @@ +/*++ +/* NAME +/* fifo_open 1 +/* SUMMARY +/* fifo client test program +/* SYNOPSIS +/* fifo_open +/* DESCRIPTION +/* fifo_open creates a FIFO, then attempts to open it for writing +/* with non-blocking mode enabled. According to the POSIX standard +/* the open should succeed. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include +#include +#include +#include +#include +#include + +#define FIFO_PATH "test-fifo" +#define perrorexit(s) { perror(s); exit(1); } + +static void cleanup(void) +{ + printf("Removing fifo %s...\n", FIFO_PATH); + if (unlink(FIFO_PATH)) + perrorexit("unlink"); + printf("Done.\n"); +} + +static void stuck(int unused_sig) +{ + printf("Non-blocking, write-only open of FIFO blocked\n"); + cleanup(); + exit(1); +} + +int main(int unused_argc, char **unused_argv) +{ + (void) unlink(FIFO_PATH); + printf("Creating fifo %s...\n", FIFO_PATH); + if (mkfifo(FIFO_PATH, 0600) < 0) + perrorexit("mkfifo"); + signal(SIGALRM, stuck); + alarm(5); + printf("Opening fifo %s, non-blocking, write-only mode...\n", FIFO_PATH); + if (open(FIFO_PATH, O_WRONLY | O_NONBLOCK, 0) < 0) { + perror("open"); + cleanup(); + exit(1); + } + printf("Non-blocking, write-only open of FIFO succeeded\n"); + cleanup(); + exit(0); +} diff --git a/src/util/fifo_rdonly_bug.c b/src/util/fifo_rdonly_bug.c new file mode 100644 index 0000000..bf93467 --- /dev/null +++ b/src/util/fifo_rdonly_bug.c @@ -0,0 +1,127 @@ +/*++ +/* NAME +/* fifo_rdonly_bug 1 +/* SUMMARY +/* fifo server test program +/* SYNOPSIS +/* fifo_rdonly_bug +/* DESCRIPTION +/* fifo_rdonly_bug creates a FIFO and opens it read only. It +/* then opens the FIFO for writing, writes one byte, and closes +/* the writing end. On Linux Redhat 4.2 and 5.0, and HP-UX 9.05 +/* and 10.20, select() will report that the FIFO remains readable +/* even after multiple read operations. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define FIFO_PATH "test-fifo" +#define TRIGGER_DELAY 5 + +#define perrorexit(s) { perror(s); exit(1); } + +static void cleanup(void) +{ + printf("Removing fifo %s...\n", FIFO_PATH); + if (unlink(FIFO_PATH)) + perrorexit("unlink"); + printf("Done.\n"); +} + +static void perrorcleanup(char *str) +{ + perror(str); + cleanup(); + exit(0); +} + +static void readable_event(int fd) +{ + char ch; + static int count = 0; + + if (read(fd, &ch, 1) < 0) { + perror("read"); + sleep(1); + } + if (count++ > 5) { + printf("FIFO remains readable after multiple reads.\n"); + cleanup(); + exit(1); + } +} + +int main(int unused_argc, char **unused_argv) +{ + struct timeval tv; + fd_set read_fds; + fd_set except_fds; + int fd; + int fd2; + + (void) unlink(FIFO_PATH); + + printf("Create fifo %s...\n", FIFO_PATH); + if (mkfifo(FIFO_PATH, 0600) < 0) + perrorexit("mkfifo"); + + printf("Open fifo %s, read-only mode...\n", FIFO_PATH); + if ((fd = open(FIFO_PATH, O_RDONLY | O_NONBLOCK, 0)) < 0) + perrorcleanup("open"); + + printf("Write one byte to the fifo, then close it...\n"); + if ((fd2 = open(FIFO_PATH, O_WRONLY, 0)) < 0) + perrorcleanup("open fifo O_WRONLY"); + if (write(fd2, "", 1) < 1) + perrorcleanup("write one byte to fifo"); + if (close(fd2) < 0) + perrorcleanup("close fifo"); + + printf("Selecting the fifo for readability...\n"); + + for (;;) { + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + FD_ZERO(&except_fds); + FD_SET(fd, &except_fds); + tv.tv_sec = 1; + tv.tv_usec = 0; + + switch (select(fd + 1, &read_fds, (fd_set *) 0, &except_fds, &tv)) { + case -1: + perrorexit("select"); + default: + if (FD_ISSET(fd, &except_fds)) { + printf("Exceptional fifo condition! You are not normal!\n"); + readable_event(fd); + } else if (FD_ISSET(fd, &read_fds)) { + printf("Readable fifo condition\n"); + readable_event(fd); + } + break; + case 0: + printf("The fifo is not readable. You're normal.\n"); + cleanup(); + exit(0); + break; + } + } +} diff --git a/src/util/fifo_rdwr_bug.c b/src/util/fifo_rdwr_bug.c new file mode 100644 index 0000000..2486876 --- /dev/null +++ b/src/util/fifo_rdwr_bug.c @@ -0,0 +1,89 @@ +/*++ +/* NAME +/* fifo_rdwr_bug 1 +/* SUMMARY +/* fifo server test program +/* SYNOPSIS +/* fifo_rdwr_bug +/* DESCRIPTION +/* fifo_rdwr_bug creates a FIFO and opens it read-write mode. +/* On BSD/OS 3.1 select() will report that the FIFO is readable +/* even before any data is written to it. Doing an actual read +/* causes the read to block; a non-blocking read fails. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FIFO_PATH "test-fifo" +#define perrorexit(s) { perror(s); exit(1); } + +static void cleanup(void) +{ + printf("Removing fifo %s...\n", FIFO_PATH); + if (unlink(FIFO_PATH)) + perrorexit("unlink"); + printf("Done.\n"); +} + +int main(int unused_argc, char **unused_argv) +{ + struct timeval tv; + fd_set read_fds; + fd_set except_fds; + int fd; + + (void) unlink(FIFO_PATH); + + printf("Creating fifo %s...\n", FIFO_PATH); + if (mkfifo(FIFO_PATH, 0600) < 0) + perrorexit("mkfifo"); + + printf("Opening fifo %s, read-write mode...\n", FIFO_PATH); + if ((fd = open(FIFO_PATH, O_RDWR, 0)) < 0) { + perror("open"); + cleanup(); + exit(1); + } + printf("Selecting the fifo for readability...\n"); + FD_ZERO(&read_fds); + FD_SET(fd, &read_fds); + FD_ZERO(&except_fds); + FD_SET(fd, &except_fds); + tv.tv_sec = 1; + tv.tv_usec = 0; + + switch (select(fd + 1, &read_fds, (fd_set *) 0, &except_fds, &tv)) { + case -1: + perrorexit("select"); + default: + if (FD_ISSET(fd, &read_fds)) { + printf("Opening a fifo read-write makes it readable!!\n"); + break; + } + case 0: + printf("The fifo is not readable, as it should be.\n"); + break; + } + cleanup(); + exit(0); +} diff --git a/src/util/fifo_trigger.c b/src/util/fifo_trigger.c new file mode 100644 index 0000000..e5c5604 --- /dev/null +++ b/src/util/fifo_trigger.c @@ -0,0 +1,168 @@ +/*++ +/* NAME +/* fifo_trigger 3 +/* SUMMARY +/* wakeup fifo server +/* SYNOPSIS +/* #include +/* +/* int fifo_trigger(service, buf, len, timeout) +/* const char *service; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* fifo_trigger() wakes up the named fifo server by writing +/* the contents of the specified buffer to the fifo. There is +/* no guarantee that the written data will actually be received. +/* +/* Arguments: +/* .IP service +/* Name of the communication endpoint. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Deadline in seconds. Specify a value <= 0 to disable +/* the time limit. +/* DIAGNOSTICS +/* The result is zero when the fifo could be opened, -1 otherwise. +/* BUGS +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* fifo_trigger - wakeup fifo server */ + +int fifo_trigger(const char *service, const char *buf, ssize_t len, int timeout) +{ + static VSTRING *why; + const char *myname = "fifo_trigger"; + VSTREAM *fp; + int fd; + + if (why == 0) + why = vstring_alloc(1); + + /* + * Write the request to the service fifo. According to POSIX, the open + * shall always return immediately, and shall return an error when no + * process is reading from the FIFO. + * + * Use safe_open() so that we don't follow symlinks, and so that we don't + * open files with multiple hard links. We're not (yet) going to bother + * the caller with safe_open() specific quirks such as the why argument. + */ + if ((fp = safe_open(service, O_WRONLY | O_NONBLOCK, 0, + (struct stat *) 0, -1, -1, why)) == 0) { + if (msg_verbose) + msg_info("%s: open %s: %s", myname, service, vstring_str(why)); + return (-1); + } + fd = vstream_fileno(fp); + + /* + * Write the request... + */ + non_blocking(fd, timeout > 0 ? NON_BLOCKING : BLOCKING); + if (write_buf(fd, buf, len, timeout) < 0) + if (msg_verbose) + msg_warn("%s: write %s: %m", myname, service); + + /* + * Disconnect. + */ + if (vstream_fclose(fp)) + if (msg_verbose) + msg_warn("%s: close %s: %m", myname, service); + return (0); +} + +#ifdef TEST + + /* + * Set up a FIFO listener, and keep triggering until the listener becomes + * idle, which should never happen. + */ +#include +#include + +#include "events.h" +#include "listen.h" + +#define TEST_FIFO "test-fifo" + +int trig_count; +int wakeup_count; + +static void cleanup(void) +{ + unlink(TEST_FIFO); + exit(1); +} + +static void handler(int sig) +{ + msg_fatal("got signal %d after %d triggers %d wakeups", + sig, trig_count, wakeup_count); +} + +static void read_event(int unused_event, char *context) +{ + int fd = CAST_ANY_PTR_TO_INT(context); + char ch; + + wakeup_count++; + + if (read(fd, &ch, 1) != 1) + msg_fatal("read %s: %m", TEST_FIFO); +} + +int main(int unused_argc, char **unused_argv) +{ + int listen_fd; + + listen_fd = fifo_listen(TEST_FIFO, 0600, NON_BLOCKING); + msg_cleanup(cleanup); + event_enable_read(listen_fd, read_event, CAST_INT_TO_VOID_PTR(listen_fd)); + signal(SIGINT, handler); + signal(SIGALRM, handler); + for (;;) { + alarm(10); + if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0) + msg_fatal("trigger %s: %m", TEST_FIFO); + trig_count++; + if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0) + msg_fatal("trigger %s: %m", TEST_FIFO); + trig_count++; + if (fifo_trigger(TEST_FIFO, "", 1, 0) < 0) + msg_fatal("trigger %s: %m", TEST_FIFO); + trig_count++; + event_loop(-1); + event_loop(-1); + event_loop(-1); + } +} + +#endif diff --git a/src/util/file_limit.c b/src/util/file_limit.c new file mode 100644 index 0000000..5cfae4e --- /dev/null +++ b/src/util/file_limit.c @@ -0,0 +1,96 @@ +/*++ +/* NAME +/* file_limit 3 +/* SUMMARY +/* limit the file size +/* SYNOPSIS +/* #include +/* +/* off_t get_file_limit() +/* +/* void set_file_limit(limit) +/* off_t limit; +/* DESCRIPTION +/* This module manipulates the process-wide file size limit. +/* The limit is specified in bytes. +/* +/* get_file_limit() looks up the process-wide file size limit. +/* +/* set_file_limit() sets the process-wide file size limit to +/* \fIlimit\fR. +/* DIAGNOSTICS +/* All errors are fatal. +/* SEE ALSO +/* setrlimit(2) +/* ulimit(2) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#ifdef USE_ULIMIT +#include +#else +#include +#include +#include +#endif +#include + +/* Utility library. */ + +#include +#include + +#define ULIMIT_BLOCK_SIZE 512 + +/* get_file_limit - get process-wide file size limit */ + +off_t get_file_limit(void) +{ + off_t limit; + +#ifdef USE_ULIMIT + if ((limit = ulimit(UL_GETFSIZE, 0)) < 0) + msg_fatal("ulimit: %m"); + if (limit > OFF_T_MAX / ULIMIT_BLOCK_SIZE) + limit = OFF_T_MAX / ULIMIT_BLOCK_SIZE; + return (limit * ULIMIT_BLOCK_SIZE); +#else + struct rlimit rlim; + + if (getrlimit(RLIMIT_FSIZE, &rlim) < 0) + msg_fatal("getrlimit: %m"); + limit = rlim.rlim_cur; + return (limit < 0 ? OFF_T_MAX : rlim.rlim_cur); +#endif /* USE_ULIMIT */ +} + +/* set_file_limit - process-wide file size limit */ + +void set_file_limit(off_t limit) +{ +#ifdef USE_ULIMIT + if (ulimit(UL_SETFSIZE, limit / ULIMIT_BLOCK_SIZE) < 0) + msg_fatal("ulimit: %m"); +#else + struct rlimit rlim; + + rlim.rlim_cur = rlim.rlim_max = limit; + if (setrlimit(RLIMIT_FSIZE, &rlim) < 0) + msg_fatal("setrlimit: %m"); +#ifdef SIGXFSZ + if (signal(SIGXFSZ, SIG_IGN) == SIG_ERR) + msg_fatal("signal(SIGXFSZ,SIG_IGN): %m"); +#endif +#endif /* USE_ULIMIT */ +} diff --git a/src/util/find_inet.c b/src/util/find_inet.c new file mode 100644 index 0000000..294634c --- /dev/null +++ b/src/util/find_inet.c @@ -0,0 +1,253 @@ +/*++ +/* NAME +/* find_inet 3 +/* SUMMARY +/* inet-domain name services +/* SYNOPSIS +/* #include +/* +/* unsigned find_inet_addr(host) +/* const char *host; +/* +/* int find_inet_port(port, proto) +/* const char *port; +/* const char *proto; +/* DESCRIPTION +/* These functions translate network address information from +/* between printable form to the internal the form used by the +/* BSD TCP/IP network software. +/* +/* find_inet_addr() translates a symbolic or numerical hostname. +/* This function is deprecated. Use hostname_to_hostaddr() instead. +/* +/* find_inet_port() translates a symbolic or numerical port name. +/* BUGS +/* find_inet_addr() ignores all but the first address listed for +/* a symbolic hostname. +/* DIAGNOSTICS +/* Lookup and conversion errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "msg.h" +#include "stringops.h" +#include "find_inet.h" +#include "known_tcp_ports.h" + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +#ifdef TEST +extern NORETURN PRINTFLIKE(1, 2) test_msg_fatal(const char *,...); + +#define msg_fatal test_msg_fatal +#endif + +/* find_inet_addr - translate numerical or symbolic host name */ + +unsigned find_inet_addr(const char *host) +{ + struct in_addr addr; + struct hostent *hp; + + addr.s_addr = inet_addr(host); + if ((addr.s_addr == INADDR_NONE) || (addr.s_addr == 0)) { + if ((hp = gethostbyname(host)) == 0) + msg_fatal("host not found: %s", host); + if (hp->h_addrtype != AF_INET) + msg_fatal("unexpected address family: %d", hp->h_addrtype); + if (hp->h_length != sizeof(addr)) + msg_fatal("unexpected address length %d", hp->h_length); + memcpy((void *) &addr, hp->h_addr, hp->h_length); + } + return (addr.s_addr); +} + +/* find_inet_port - translate numerical or symbolic service name */ + +int find_inet_port(const char *service, const char *protocol) +{ + struct servent *sp; + int port; + + service = filter_known_tcp_port(service); + if (alldig(service) && (port = atoi(service)) != 0) { + if (port < 0 || port > 65535) + msg_fatal("bad port number: %s", service); + return (htons(port)); + } else { + if ((sp = getservbyname(service, protocol)) == 0) + msg_fatal("unknown service: %s/%s", service, protocol); + return (sp->s_port); + } +} + +#ifdef TEST + +#include +#include +#include + +#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; +} + +jmp_buf test_fatal_jbuf; + +#undef msg_fatal + +/* test_msg_fatal - does not return, and does not terminate */ + +void test_msg_fatal(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_warn(fmt, ap); + va_end(ap); + longjmp(test_fatal_jbuf, 1); +} + +struct association { + const char *lhs; /* service name */ + const char *rhs; /* service port */ +}; + +struct test_case { + const char *label; /* identifies test case */ + struct association associations[10]; + const char *service; + const char *proto; + const char *exp_warning; /* expected error */ + int exp_hport; /* expected port, host byte order */ +}; + +struct test_case test_cases[] = { + {"good-symbolic", + /* association */ {{"foobar", "25252"}, 0}, + /* service */ "foobar", + /* proto */ "tcp", + /* exp_warning */ "", + /* exp_hport */ 25252, + }, + {"good-numeric", + /* association */ {{"foobar", "25252"}, 0}, + /* service */ "25252", + /* proto */ "tcp", + /* exp_warning */ "", + /* exp_hport */ 25252, + }, + {"bad-symbolic", + /* association */ {{"foobar", "25252"}, 0}, + /* service */ "an-impossible-name", + /* proto */ "tcp", + /* exp_warning */ "find_inet: warning: unknown service: an-impossible-name/tcp\n", + }, + {"bad-numeric", + /* association */ {{"foobar", "25252"}, 0}, + /* service */ "123456", + /* proto */ "tcp", + /* exp_warning */ "find_inet: warning: bad port number: 123456\n", + }, +}; + +int main(int argc, char **argv) { + struct test_case *tp; + struct association *ap; + int pass = 0; + int fail = 0; + const char *err; + int test_failed; + int nport; + VSTRING *msg_buf; + VSTREAM *memory_stream; + + msg_vstream_init("find_inet", VSTREAM_ERR); + msg_buf = vstring_alloc(100); + + for (tp = test_cases; tp->label != 0; tp++) { + test_failed = 0; + VSTRING_RESET(msg_buf); + VSTRING_TERMINATE(msg_buf); + clear_known_tcp_ports(); + for (err = 0, ap = tp->associations; err == 0 && ap->lhs != 0; ap++) + err = add_known_tcp_port(ap->lhs, ap->rhs); + if (err != 0) { + msg_warn("test case %s: got err: \"%s\"", tp->label, err); + test_failed = 1; + } else { + if ((memory_stream = vstream_memopen(msg_buf, O_WRONLY)) == 0) + msg_fatal("open memory stream: %m"); + vstream_swap(VSTREAM_ERR, memory_stream); + if (setjmp(test_fatal_jbuf) == 0) + nport = find_inet_port(tp->service, tp->proto); + vstream_swap(memory_stream, VSTREAM_ERR); + if (vstream_fclose(memory_stream)) + msg_fatal("close memory stream: %m"); + if (strcmp(STR(msg_buf), tp->exp_warning) != 0) { + msg_warn("test case %s: got error: \"%s\", want: \"%s\"", + tp->label, STR(msg_buf), tp->exp_warning); + test_failed = 1; + } else if (tp->exp_warning[0] == 0) { + if (ntohs(nport) != tp->exp_hport) { + msg_warn("test case %s: got port \"%d\", want: \"%d\"", + tp->label, ntohs(nport), tp->exp_hport); + test_failed = 1; + } + } + } + if (test_failed) { + msg_info("%s: FAIL", tp->label); + fail++; + } else { + msg_info("%s: PASS", tp->label); + pass++; + } + } + msg_info("PASS=%d FAIL=%d", pass, fail); + vstring_free(msg_buf); + exit(fail != 0); +} + +#endif diff --git a/src/util/find_inet.h b/src/util/find_inet.h new file mode 100644 index 0000000..0e5ce72 --- /dev/null +++ b/src/util/find_inet.h @@ -0,0 +1,33 @@ +#ifndef _FIND_INET_H_INCLUDED_ +#define _FIND_INET_H_INCLUDED_ + +/*++ +/* NAME +/* find_inet 3h +/* SUMMARY +/* inet-domain name services +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern unsigned find_inet_addr(const char *); +extern int find_inet_port(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* LAST MODIFICATION +/* Thu Feb 6 12:46:36 EST 1997 +/*--*/ + +#endif diff --git a/src/util/find_inet.ref b/src/util/find_inet.ref new file mode 100644 index 0000000..a6f9cac --- /dev/null +++ b/src/util/find_inet.ref @@ -0,0 +1,5 @@ +find_inet: good-symbolic: PASS +find_inet: good-numeric: PASS +find_inet: bad-symbolic: PASS +find_inet: bad-numeric: PASS +find_inet: PASS=4 FAIL=0 diff --git a/src/util/format_tv.c b/src/util/format_tv.c new file mode 100644 index 0000000..a932bdb --- /dev/null +++ b/src/util/format_tv.c @@ -0,0 +1,160 @@ +/*++ +/* NAME +/* format_tv 3 +/* SUMMARY +/* format time value with sane precision +/* SYNOPSIS +/* #include +/* +/* VSTRING *format_tv(buffer, sec, usec, sig_dig, max_dig) +/* VSTRING *buffer; +/* long sec; +/* long usec; +/* int sig_dig; +/* int max_dig; +/* DESCRIPTION +/* format_tv() formats the specified time as a floating-point +/* number while suppressing irrelevant digits in the output. +/* Large numbers are always rounded up to an integral number +/* of seconds. Small numbers are produced with a limited number +/* of significant digits, provided that the result does not +/* exceed the limit on the total number of digits after the +/* decimal point. Trailing zeros are always omitted from the +/* output. +/* +/* Arguments: +/* .IP buffer +/* The buffer to which the result is appended. +/* .IP sec +/* The seconds portion of the time value. +/* .IP usec +/* The microseconds portion of the time value. +/* .IP sig_dig +/* The maximal number of significant digits when formatting +/* small numbers. Leading nulls don't count as significant, +/* and trailing nulls are not included in the output. Specify +/* a number in the range 1..6. +/* .IP max_dig +/* The maximal number of all digits after the decimal point. +/* Specify a number in the range 0..6. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#define MILLION 1000000 + +/* format_tv - print time with limited precision */ + +VSTRING *format_tv(VSTRING *buf, long sec, long usec, + int sig_dig, int max_dig) +{ + static int pow10[] = {1, 10, 100, 1000, 10000, 100000, 1000000}; + int n; + int rem; + int wid; + int ures; + + /* + * Sanity check. + */ + if (max_dig < 0 || max_dig > 6) + msg_panic("format_tv: bad maximum decimal count %d", max_dig); + if (sec < 0 || usec < 0 || usec > MILLION) + msg_panic("format_tv: bad time %lds %ldus", sec, usec); + if (sig_dig < 1 || sig_dig > 6) + msg_panic("format_tv: bad significant decimal count %d", sig_dig); + ures = MILLION / pow10[max_dig]; + wid = pow10[sig_dig]; + + /* + * Adjust the resolution to suppress irrelevant digits. + */ + if (ures < MILLION) { + if (sec > 0) { + for (n = 1; sec >= n && n <= wid / 10; n *= 10) + /* void */ ; + ures = (MILLION / wid) * n; + } else { + while (usec >= wid * ures) + ures *= 10; + } + } + + /* + * Round up the number if necessary. Leave thrash below the resolution. + */ + if (ures > 1) { + usec += ures / 2; + if (usec >= MILLION) { + sec += 1; + usec -= MILLION; + } + } + + /* + * Format the number. Truncate trailing null and thrash below resolution. + */ + vstring_sprintf_append(buf, "%ld", sec); + if (usec >= ures) { + VSTRING_ADDCH(buf, '.'); + for (rem = usec, n = MILLION / 10; rem >= ures && n > 0; n /= 10) { + VSTRING_ADDCH(buf, "0123456789"[rem / n]); + rem %= n; + } + } + VSTRING_TERMINATE(buf); + return (buf); +} + +#ifdef TEST + +#include +#include +#include + +int main(int argc, char **argv) +{ + VSTRING *in = vstring_alloc(10); + VSTRING *out = vstring_alloc(10); + double tval; + int sec; + int usec; + int sig_dig; + int max_dig; + + while (vstring_get_nonl(in, VSTREAM_IN) > 0) { + vstream_printf(">> %s\n", vstring_str(in)); + if (vstring_str(in)[0] == 0 || vstring_str(in)[0] == '#') + continue; + if (sscanf(vstring_str(in), "%lf %d %d", &tval, &sig_dig, &max_dig) != 3) + msg_fatal("bad input: %s", vstring_str(in)); + sec = (int) tval; /* raw seconds */ + usec = (tval - sec) * MILLION; /* raw microseconds */ + VSTRING_RESET(out); + format_tv(out, sec, usec, sig_dig, max_dig); + vstream_printf("%s\n", vstring_str(out)); + vstream_fflush(VSTREAM_OUT); + } + vstring_free(in); + vstring_free(out); + return (0); +} + +#endif diff --git a/src/util/format_tv.h b/src/util/format_tv.h new file mode 100644 index 0000000..bef787a --- /dev/null +++ b/src/util/format_tv.h @@ -0,0 +1,35 @@ +#ifndef _FORMAT_TV_H_INCLUDED_ +#define _FORMAT_TV_H_INCLUDED_ + +/*++ +/* NAME +/* format_tv 3h +/* SUMMARY +/* format time with limited precision +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern VSTRING *format_tv(VSTRING *, long, long, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/format_tv.in b/src/util/format_tv.in new file mode 100644 index 0000000..a6b5848 --- /dev/null +++ b/src/util/format_tv.in @@ -0,0 +1,68 @@ +# Three digits in, 2/6 digits out, rounding down. +1110 2 6 +111 2 6 +11.1 2 6 +1.11 2 6 +0.111 2 6 +0.0111 2 6 +0.00111 2 6 +0.000111 2 6 +0.0000111 2 6 + +# One digit in. Must not produce spurious digits or trailing nulls. + +1000 2 6 +100 2 6 +10 2 6 +1 2 6 +0.1 2 6 +0.01 2 6 +0.001 2 6 +0.0001 2 6 +0.00001 2 6 +0.0000011 2 6 + +# Three digits in, 2/6 digits out, rounding up. + +996 2 6 +99.6 2 6 +9.96 2 6 +.996 2 6 +.0996 2 6 +.00996 2 6 +.000996 2 6 + +# Three digits in, 1/6 digits out, rounding down. + +1110 1 6 +111 1 6 +11.1 1 6 +1.11 1 6 +0.111 1 6 +0.0111 1 6 +0.00111 1 6 +0.000111 1 6 +0.000011 1 6 + +# One digit in. Must not produce trailing nulls. + +1000 1 6 +100 1 6 +10 1 6 +1 1 6 +0.1 1 6 +0.01 1 6 +0.001 1 6 +0.0001 1 6 +0.00001 1 6 +0.0000011 1 6 + +# Three digits in, 1/6 digits out, rounding up. + +996 1 6 +99.6 1 6 +9.96 1 6 +.996 1 6 +.0996 1 6 +.00996 1 6 +.000996 1 6 diff --git a/src/util/format_tv.ref b/src/util/format_tv.ref new file mode 100644 index 0000000..718c3c7 --- /dev/null +++ b/src/util/format_tv.ref @@ -0,0 +1,120 @@ +>> # Three digits in, 2/6 digits out, rounding down. +>> 1110 2 6 +1110 +>> 111 2 6 +111 +>> 11.1 2 6 +11 +>> 1.11 2 6 +1.1 +>> 0.111 2 6 +0.11 +>> 0.0111 2 6 +0.011 +>> 0.00111 2 6 +0.0011 +>> 0.000111 2 6 +0.00011 +>> 0.0000111 2 6 +0.000011 +>> +>> # One digit in. Must not produce spurious digits or trailing nulls. +>> +>> 1000 2 6 +1000 +>> 100 2 6 +100 +>> 10 2 6 +10 +>> 1 2 6 +1 +>> 0.1 2 6 +0.1 +>> 0.01 2 6 +0.01 +>> 0.001 2 6 +0.001 +>> 0.0001 2 6 +0.0001 +>> 0.00001 2 6 +0.00001 +>> 0.0000011 2 6 +0.000001 +>> +>> # Three digits in, 2/6 digits out, rounding up. +>> +>> 996 2 6 +996 +>> 99.6 2 6 +100 +>> 9.96 2 6 +10 +>> .996 2 6 +1 +>> .0996 2 6 +0.1 +>> .00996 2 6 +0.01 +>> .000996 2 6 +0.001 +>> +>> # Three digits in, 1/6 digits out, rounding down. +>> +>> 1110 1 6 +1110 +>> 111 1 6 +111 +>> 11.1 1 6 +11 +>> 1.11 1 6 +1 +>> 0.111 1 6 +0.1 +>> 0.0111 1 6 +0.01 +>> 0.00111 1 6 +0.001 +>> 0.000111 1 6 +0.0001 +>> 0.000011 1 6 +0.00001 +>> +>> # One digit in. Must not produce trailing nulls. +>> +>> 1000 1 6 +1000 +>> 100 1 6 +100 +>> 10 1 6 +10 +>> 1 1 6 +1 +>> 0.1 1 6 +0.1 +>> 0.01 1 6 +0.01 +>> 0.001 1 6 +0.001 +>> 0.0001 1 6 +0.0001 +>> 0.00001 1 6 +0.00001 +>> 0.0000011 1 6 +0.000001 +>> +>> # Three digits in, 1/6 digits out, rounding up. +>> +>> 996 1 6 +996 +>> 99.6 1 6 +100 +>> 9.96 1 6 +10 +>> .996 1 6 +1 +>> .0996 1 6 +0.1 +>> .00996 1 6 +0.01 +>> .000996 1 6 +0.001 diff --git a/src/util/fsspace.c b/src/util/fsspace.c new file mode 100644 index 0000000..50a4aa7 --- /dev/null +++ b/src/util/fsspace.c @@ -0,0 +1,127 @@ +/*++ +/* NAME +/* fsspace 3 +/* SUMMARY +/* determine available file system space +/* SYNOPSIS +/* #include +/* +/* struct fsspace { +/* .in +4 +/* unsigned long block_size; +/* unsigned long block_free; +/* .in -4 +/* }; +/* +/* void fsspace(path, sp) +/* const char *path; +/* struct fsspace *sp; +/* DESCRIPTION +/* fsspace() returns the amount of available space in the file +/* system specified in \fIpath\fR, in terms of the block size and +/* of the number of available blocks. +/* DIAGNOSTICS +/* All errors are fatal. +/* BUGS +/* Use caution when doing computations with the result from fsspace(). +/* It is easy to cause overflow (by multiplying large numbers) or to +/* cause underflow (by subtracting unsigned numbers). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +#if defined(STATFS_IN_SYS_MOUNT_H) +#include +#include +#elif defined(STATFS_IN_SYS_VFS_H) +#include +#elif defined(STATVFS_IN_SYS_STATVFS_H) +#include +#elif defined(STATFS_IN_SYS_STATFS_H) +#include +#else +#ifdef USE_STATFS +#error "please specify the include file with `struct statfs'" +#else +#error "please specify the include file with `struct statvfs'" +#endif +#endif + +/* Utility library. */ + +#include +#include + +/* fsspace - find amount of available file system space */ + +void fsspace(const char *path, struct fsspace * sp) +{ + const char *myname = "fsspace"; + +#ifdef USE_STATFS +#ifdef USE_STRUCT_FS_DATA /* Ultrix */ + struct fs_data fsbuf; + + if (statfs(path, &fsbuf) < 0) + msg_fatal("statfs %s: %m", path); + sp->block_size = 1024; + sp->block_free = fsbuf.fd_bfreen; +#else + struct statfs fsbuf; + + if (statfs(path, &fsbuf) < 0) + msg_fatal("statfs %s: %m", path); + sp->block_size = fsbuf.f_bsize; + sp->block_free = fsbuf.f_bavail; +#endif +#endif +#ifdef USE_STATVFS + struct statvfs fsbuf; + + if (statvfs(path, &fsbuf) < 0) + msg_fatal("statvfs %s: %m", path); + sp->block_size = fsbuf.f_frsize; + sp->block_free = fsbuf.f_bavail; +#endif + if (msg_verbose) + msg_info("%s: %s: block size %lu, blocks free %lu", + myname, path, sp->block_size, sp->block_free); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: print free space unit and count for all + * listed file systems. + */ + +#include + +int main(int argc, char **argv) +{ + struct fsspace sp; + + if (argc == 1) + msg_fatal("usage: %s filesystem...", argv[0]); + + while (--argc && *++argv) { + fsspace(*argv, &sp); + vstream_printf("%10s: block size %lu, blocks free %lu\n", + *argv, sp.block_size, sp.block_free); + vstream_fflush(VSTREAM_OUT); + } + return (0); +} + +#endif diff --git a/src/util/fsspace.h b/src/util/fsspace.h new file mode 100644 index 0000000..c118e50 --- /dev/null +++ b/src/util/fsspace.h @@ -0,0 +1,33 @@ +#ifndef _FSSPACE_H_INCLUDED_ +#define _FSSPACE_H_INCLUDED_ + +/*++ +/* NAME +/* fsspace 3h +/* SUMMARY +/* determine available file system space +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ +struct fsspace { + unsigned long block_size; /* block size */ + unsigned long block_free; /* free space */ +}; + +extern void fsspace(const char *, struct fsspace *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/fullname.c b/src/util/fullname.c new file mode 100644 index 0000000..a9d4b32 --- /dev/null +++ b/src/util/fullname.c @@ -0,0 +1,111 @@ +/*++ +/* NAME +/* fullname 3 +/* SUMMARY +/* lookup personal name of invoking user +/* SYNOPSIS +/* #include +/* +/* const char *fullname() +/* DESCRIPTION +/* fullname() looks up the personal name of the invoking user. +/* The result is volatile. Make a copy if it is to be used for +/* an appreciable amount of time. +/* +/* On UNIX systems, fullname() first tries to use the NAME environment +/* variable, provided that the environment can be trusted. +/* If that fails, fullname() extracts the username from the GECOS +/* field of the user's password-file entry, replacing any occurrence +/* of "&" by the login name, first letter capitalized. +/* +/* A null result means that no full name information was found. +/* SEE ALSO +/* safe_getenv(3) safe getenv() interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include "vstring.h" +#include "safe.h" +#include "fullname.h" + +/* fullname - get name of user */ + +const char *fullname(void) +{ + static VSTRING *result; + char *cp; + int ch; + uid_t uid; + struct passwd *pwd; + + if (result == 0) + result = vstring_alloc(10); + + /* + * Try the environment. + */ + if ((cp = safe_getenv("NAME")) != 0) + return (vstring_str(vstring_strcpy(result, cp))); + + /* + * Try the password file database. + */ + uid = getuid(); + if ((pwd = getpwuid(uid)) == 0) + return (0); + + /* + * Replace all `&' characters by the login name of this user, first + * letter capitalized. Although the full name comes from the protected + * password file, the actual data is specified by the user so we should + * not trust its sanity. + */ + VSTRING_RESET(result); + for (cp = pwd->pw_gecos; (ch = *(unsigned char *) cp) != 0; cp++) { + if (ch == ',' || ch == ';' || ch == '%') + break; + if (ch == '&') { + if (pwd->pw_name[0]) { + VSTRING_ADDCH(result, TOUPPER(pwd->pw_name[0])); + vstring_strcat(result, pwd->pw_name + 1); + } + } else { + VSTRING_ADDCH(result, ch); + } + } + VSTRING_TERMINATE(result); + return (vstring_str(result)); +} + +#ifdef TEST + +#include + +int main(int unused_argc, char **unused_argv) +{ + const char *cp = fullname(); + + printf("%s\n", cp ? cp : "null!"); + return (0); +} + +#endif diff --git a/src/util/fullname.h b/src/util/fullname.h new file mode 100644 index 0000000..f24492a --- /dev/null +++ b/src/util/fullname.h @@ -0,0 +1,29 @@ +#ifndef _FULLNAME_H_INCLUDED_ +#define _FULLNAME_H_INCLUDED_ + +/*++ +/* NAME +/* fullname 3h +/* SUMMARY +/* lookup personal name of invoking user +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern const char *fullname(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/gccw.c b/src/util/gccw.c new file mode 100644 index 0000000..d3fd985 --- /dev/null +++ b/src/util/gccw.c @@ -0,0 +1,58 @@ + /* + * This is a regression test for all the things that gcc is meant to warn + * about. + * + * gcc version 3 breaks several tests: + * + * -W does not report missing return value + * + * -Wunused does not report unused parameter + */ + +#include +#include + +jmp_buf jbuf; + + /* -Wmissing-prototypes: no previous prototype for 'test1' */ + /* -Wimplicit: return type defaults to `int' */ +test1(void) +{ + /* -Wunused: unused variable `foo' */ + int foo; + + /* -Wparentheses: suggest parentheses around && within || */ + printf("%d\n", 1 && 2 || 3 && 4); + /* -W: statement with no effect */ + 0; + /* BROKEN in gcc 3 */ + /* -W: control reaches end of non-void function */ +} + + + /* -W??????: unused parameter `foo' */ +void test2(int foo) +{ + enum { + a = 10, b = 15} moe; + int bar; + + /* -Wuninitialized: 'bar' might be used uninitialized in this function */ + /* -Wformat: format argument is not a pointer (arg 2) */ + printf("%s\n", bar); + /* -Wformat: too few arguments for format */ + printf("%s%s\n", "bar"); + /* -Wformat: too many arguments for format */ + printf("%s\n", "bar", "bar"); + + /* -Wswitch: enumeration value `b' not handled in switch */ + switch (moe) { + case a: + return; + } +} + + /* -Wstrict-prototypes: function declaration isn't a prototype */ +void test3() +{ +} diff --git a/src/util/gccw.ref b/src/util/gccw.ref new file mode 100644 index 0000000..7616c29 --- /dev/null +++ b/src/util/gccw.ref @@ -0,0 +1,18 @@ +gcc -Wall -Wno-comment -Wformat -Wimplicit -Wmissing-prototypes -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized -Wunused -DUSE_TLS -DHAS_PCRE -I/usr/local/include -DSNAPSHOT -g -O -I. -DFREEBSD5 -c gccw.c +gccw.c: At top level: +gccw.c: At top level: +gccw.c: In function 'test1': +gccw.c: In function 'test2': +gccw.c:20: warning: no previous prototype for 'test1' +gccw.c:20: warning: return type defaults to 'int' +gccw.c:22: warning: unused variable 'foo' +gccw.c:25: warning: suggest parentheses around && within || +gccw.c:27: warning: statement with no effect +gccw.c:30: warning: control reaches end of non-void function +gccw.c:35: warning: no previous prototype for 'test2' +gccw.c:38: warning: 'bar' might be used uninitialized in this function +gccw.c:42: warning: format argument is not a pointer (arg 2) +gccw.c:44: warning: too few arguments for format +gccw.c:46: warning: too many arguments for format +gccw.c:52: warning: enumeration value 'b' not handled in switch +gccw.c:57: warning: function declaration isn't a prototype diff --git a/src/util/get_domainname.c b/src/util/get_domainname.c new file mode 100644 index 0000000..0897f52 --- /dev/null +++ b/src/util/get_domainname.c @@ -0,0 +1,66 @@ +/*++ +/* NAME +/* get_domainname 3 +/* SUMMARY +/* network domain name lookup +/* SYNOPSIS +/* #include +/* +/* const char *get_domainname() +/* DESCRIPTION +/* get_domainname() returns the local domain name as obtained +/* by stripping the hostname component from the result from +/* get_hostname(). The result is the hostname when get_hostname() +/* does not return a FQDN form ("foo"), or its result has only two +/* components ("foo.com"). +/* DIAGNOSTICS +/* Fatal errors: no hostname, invalid hostname. +/* SEE ALSO +/* get_hostname(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include "mymalloc.h" +#include "get_hostname.h" +#include "get_domainname.h" + +/* Local stuff. */ + +static char *my_domain_name; + +/* get_domainname - look up my domain name */ + +const char *get_domainname(void) +{ + const char *host; + const char *dot; + + /* + * Use the hostname when it is not a FQDN ("foo"), or when the hostname + * actually is a domain name ("foo.com"). + */ + if (my_domain_name == 0) { + host = get_hostname(); + if ((dot = strchr(host, '.')) == 0 || strchr(dot + 1, '.') == 0) { + my_domain_name = mystrdup(host); + } else { + my_domain_name = mystrdup(dot + 1); + } + } + return (my_domain_name); +} diff --git a/src/util/get_domainname.h b/src/util/get_domainname.h new file mode 100644 index 0000000..177e3fd --- /dev/null +++ b/src/util/get_domainname.h @@ -0,0 +1,29 @@ +#ifndef _GET_DOMAINNAME_H_INCLUDED_ +#define _GET_DOMAINNAME_H_INCLUDED_ + +/*++ +/* NAME +/* get_domainname 3h +/* SUMMARY +/* network domain name lookup +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface */ + +extern const char *get_domainname(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/get_hostname.c b/src/util/get_hostname.c new file mode 100644 index 0000000..eb2cfea --- /dev/null +++ b/src/util/get_hostname.c @@ -0,0 +1,84 @@ +/*++ +/* NAME +/* get_hostname 3 +/* SUMMARY +/* network name lookup +/* SYNOPSIS +/* #include +/* +/* const char *get_hostname() +/* DESCRIPTION +/* get_hostname() returns the local hostname as obtained +/* via gethostname() or its moral equivalent. This routine +/* goes to great length to avoid dependencies on any network +/* services. +/* DIAGNOSTICS +/* Fatal errors: no hostname, invalid hostname. +/* SEE ALSO +/* valid_hostname(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +#if (MAXHOSTNAMELEN < 256) +#undef MAXHOSTNAMELEN +#define MAXHOSTNAMELEN 256 +#endif + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "valid_hostname.h" +#include "get_hostname.h" + +/* Local stuff. */ + +static char *my_host_name; + +/* get_hostname - look up my host name */ + +const char *get_hostname(void) +{ + char namebuf[MAXHOSTNAMELEN + 1]; + + /* + * The gethostname() call is not (or not yet) in ANSI or POSIX, but it is + * part of the socket interface library. We avoid the more politically- + * correct uname() routine because that has no portable way of dealing + * with long (FQDN) hostnames. + * + * DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION. IT BREAKS MAILDIR DELIVERY + * AND OTHER THINGS WHEN THE MACHINE NAME IS NOT FOUND IN /ETC/HOSTS OR + * CAUSES PROCESSES TO HANG WHEN THE NETWORK IS DISCONNECTED. + * + * POSTFIX NO LONGER NEEDS A FULLY QUALIFIED HOSTNAME. INSTEAD POSTFIX WILL + * USE A DEFAULT DOMAIN NAME "LOCALDOMAIN". + */ + if (my_host_name == 0) { + /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */ + if (gethostname(namebuf, sizeof(namebuf)) < 0) + msg_fatal("gethostname: %m"); + namebuf[MAXHOSTNAMELEN] = 0; + /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */ + if (valid_hostname(namebuf, DO_GRIPE) == 0) + msg_fatal("unable to use my own hostname"); + /* DO NOT CALL GETHOSTBYNAME FROM THIS FUNCTION */ + my_host_name = mystrdup(namebuf); + } + return (my_host_name); +} diff --git a/src/util/get_hostname.h b/src/util/get_hostname.h new file mode 100644 index 0000000..32d2ab2 --- /dev/null +++ b/src/util/get_hostname.h @@ -0,0 +1,29 @@ +#ifndef _GET_HOSTNAME_H_INCLUDED_ +#define _GET_HOSTNAME_H_INCLUDED_ + +/*++ +/* NAME +/* get_hostname 3h +/* SUMMARY +/* network name lookup +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface */ + +extern const char *get_hostname(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/hash_fnv.c b/src/util/hash_fnv.c new file mode 100644 index 0000000..10e97f0 --- /dev/null +++ b/src/util/hash_fnv.c @@ -0,0 +1,107 @@ +/*++ +/* NAME +/* hash_fnv 3 +/* SUMMARY +/* Fowler/Noll/Vo hash function +/* SYNOPSIS +/* #include +/* +/* HASH_FNV_T hash_fnv( +/* const void *src, +/* size_t len) +/* DESCRIPTION +/* hash_fnv() implements a modified FNV type 1a hash function. +/* +/* To thwart collision attacks, the hash function is seeded +/* once from /dev/urandom, and if that is unavailable, from +/* wallclock time, monotonic system clocks, and the process +/* ID. To disable seeding (typically, for regression tests), +/* specify the NORANDOMIZE environment variable; the value +/* does not matter. +/* +/* This function implements a workaround for a "sticky state" +/* problem with FNV hash functions: when an input produces a +/* zero intermediate hash state, and the next input byte is +/* zero, then the operations "hash ^= 0" and "hash *= FNV_prime" +/* would not change the hash value. To avoid this, hash_fnv() +/* adds 1 to each input byte. Compile with -DSTRICT_FNV1A to +/* get the standard behavior. +/* +/* The default HASH_FNV_T result type is uint64_t. When compiled +/* with -DUSE_FNV_32BIT, the result type is uint32_t. On ancient +/* systems without , define HASH_FNV_T on the compiler +/* command line as an unsigned 32-bit or 64-bit integer type, +/* and specify -DUSE_FNV_32BIT when HASH_FNV_T is a 32-bit type. +/* SEE ALSO +/* http://www.isthe.com/chongo/tech/comp/fnv/index.html +/* https://softwareengineering.stackexchange.com/questions/49550/ +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library + */ +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Application-specific. + */ +#ifdef USE_FNV_32BIT +#define FNV_prime 0x01000193UL +#define FNV_offset_basis 0x811c9dc5UL +#else +#define FNV_prime 0x00000100000001B3ULL +#define FNV_offset_basis 0xcbf29ce484222325ULL +#endif + +/* hash_fnv - modified FNV 1a hash */ + +HASH_FNV_T hash_fnv(const void *src, size_t len) +{ + static HASH_FNV_T basis = FNV_offset_basis; + static int randomize = 1; + HASH_FNV_T hash; + + /* + * Initialize. + */ + if (randomize) { + if (!getenv("NORANDOMIZE")) { + HASH_FNV_T seed; + + ldseed(&seed, sizeof(seed)); + basis ^= seed; + } + randomize = 0; + } + +#ifdef STRICT_FNV1A +#define FNV_NEXT_BYTE(s) ((HASH_FNV_T) * (const unsigned char *) s++) +#else +#define FNV_NEXT_BYTE(s) (1 + (HASH_FNV_T) * (const unsigned char *) s++) +#endif + + hash = basis; + while (len-- > 0) { + hash ^= FNV_NEXT_BYTE(src); + hash *= FNV_prime; + } + return (hash); +} diff --git a/src/util/hash_fnv.h b/src/util/hash_fnv.h new file mode 100644 index 0000000..dbbb383 --- /dev/null +++ b/src/util/hash_fnv.h @@ -0,0 +1,39 @@ +#ifndef _HASH_FNV_H_INCLUDED_ +#define _HASH_FNV_H_INCLUDED_ + +/*++ +/* NAME +/* hash_fnv 3h +/* SUMMARY +/* Fowler/Noll/Vo hash function +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +#ifndef HASH_FNV_T +#include +#ifdef USE_FNV_32BIT +#define HASH_FNV_T uint32_t +#else /* USE_FNV_32BIT */ +#define HASH_FNV_T uint64_t +#endif /* USE_FNV_32BIT */ +#endif /* HASH_FNV_T */ + +extern HASH_FNV_T hash_fnv(const void *, size_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/hex_code.c b/src/util/hex_code.c new file mode 100644 index 0000000..3dfcb98 --- /dev/null +++ b/src/util/hex_code.c @@ -0,0 +1,243 @@ +/*++ +/* NAME +/* hex_code 3 +/* SUMMARY +/* encode/decode data, hexadecimal style +/* SYNOPSIS +/* #include +/* +/* VSTRING *hex_encode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* +/* VSTRING *hex_decode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* +/* VSTRING *hex_encode_opt(result, in, len, flags) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* int flags; +/* +/* VSTRING *hex_decode_opt(result, in, len, flags) +/* VSTRING *result; +/* const char *in; +/* ssize_t len; +/* int flags; +/* DESCRIPTION +/* hex_encode() takes a block of len bytes and encodes it as one +/* upper-case null-terminated string. The result value is +/* the result argument. +/* +/* hex_decode() performs the opposite transformation on +/* lower-case, upper-case or mixed-case input. The result +/* value is the result argument. The result is null terminated, +/* whether or not that makes sense. +/* +/* hex_encode_opt() enables extended functionality as controlled +/* with \fIflags\fR. +/* .IP HEX_ENCODE_FLAG_NONE +/* The default: a self-documenting flag that enables no +/* functionality. +/* .IP HEX_ENCODE_FLAG_USE_COLON +/* Inserts one ":" between bytes. +/* .PP +/* hex_decode_opt() enables extended functionality as controlled +/* with \fIflags\fR. +/* .IP HEX_DECODE_FLAG_NONE +/* The default: a self-documenting flag that enables no +/* functionality. +/* .IP HEX_DECODE_FLAG_ALLOW_COLON +/* Allows, but does not require, one ":" between bytes. +/* DIAGNOSTICS +/* hex_decode() returns a null pointer when the input contains +/* characters not in the hexadecimal alphabet. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +static const unsigned char hex_chars[] = "0123456789ABCDEF"; + +#define UCHAR_PTR(x) ((const unsigned char *)(x)) + +/* hex_encode - ABI compatibility */ + +#undef hex_encode + +VSTRING *hex_encode(VSTRING *result, const char *in, ssize_t len) +{ + return (hex_encode_opt(result, in, len, HEX_ENCODE_FLAG_NONE)); +} + +/* hex_encode_opt - raw data to encoded */ + +VSTRING *hex_encode_opt(VSTRING *result, const char *in, ssize_t len, int flags) +{ + const unsigned char *cp; + int ch; + ssize_t count; + + VSTRING_RESET(result); + for (cp = UCHAR_PTR(in), count = len; count > 0; count--, cp++) { + ch = *cp; + VSTRING_ADDCH(result, hex_chars[(ch >> 4) & 0xf]); + VSTRING_ADDCH(result, hex_chars[ch & 0xf]); + if ((flags & HEX_ENCODE_FLAG_USE_COLON) && count > 1) + VSTRING_ADDCH(result, ':'); + } + VSTRING_TERMINATE(result); + return (result); +} + +/* hex_decode - ABI compatibility wrapper */ + +#undef hex_decode + +VSTRING *hex_decode(VSTRING *result, const char *in, ssize_t len) +{ + return (hex_decode_opt(result, in, len, HEX_DECODE_FLAG_NONE)); +} + +/* hex_decode_opt - encoded data to raw */ + +VSTRING *hex_decode_opt(VSTRING *result, const char *in, ssize_t len, int flags) +{ + const unsigned char *cp; + ssize_t count; + unsigned int hex; + unsigned int bin; + + VSTRING_RESET(result); + for (cp = UCHAR_PTR(in), count = len; count > 0; cp += 2, count -= 2) { + if (count < 2) + return (0); + hex = cp[0]; + if (hex >= '0' && hex <= '9') + bin = (hex - '0') << 4; + else if (hex >= 'A' && hex <= 'F') + bin = (hex - 'A' + 10) << 4; + else if (hex >= 'a' && hex <= 'f') + bin = (hex - 'a' + 10) << 4; + else + return (0); + hex = cp[1]; + if (hex >= '0' && hex <= '9') + bin |= (hex - '0'); + else if (hex >= 'A' && hex <= 'F') + bin |= (hex - 'A' + 10); + else if (hex >= 'a' && hex <= 'f') + bin |= (hex - 'a' + 10); + else + return (0); + VSTRING_ADDCH(result, bin); + + /* + * Support *colon-separated* input (no leading or trailing colons). + * After decoding "xx", skip a possible ':' preceding "yy" in + * "xx:yy". + */ + if ((flags & HEX_DECODE_FLAG_ALLOW_COLON) + && count > 4 && cp[2] == ':') { + ++cp; + --count; + } + } + VSTRING_TERMINATE(result); + return (result); +} + +#ifdef TEST +#include + + /* + * Proof-of-concept test program: convert to hexadecimal and back. + */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *b1 = vstring_alloc(1); + VSTRING *b2 = vstring_alloc(1); + char *test = "this is a test"; + ARGV *argv; + +#define DECODE(b,x,l) { \ + if (hex_decode((b),(x),(l)) == 0) \ + msg_panic("bad hex: %s", (x)); \ + } +#define VERIFY(b,t) { \ + if (strcmp((b), (t)) != 0) \ + msg_panic("bad test: %s", (b)); \ + } + + hex_encode(b1, test, strlen(test)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), test); + + hex_encode(b1, test, strlen(test)); + hex_encode(b2, STR(b1), LEN(b1)); + hex_encode(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), test); + + hex_encode(b1, test, strlen(test)); + hex_encode(b2, STR(b1), LEN(b1)); + hex_encode(b1, STR(b2), LEN(b2)); + hex_encode(b2, STR(b1), LEN(b1)); + hex_encode(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), test); + + hex_encode_opt(b1, test, strlen(test), HEX_ENCODE_FLAG_USE_COLON); + argv = argv_split(STR(b1), ":"); + if (argv->argc != strlen(test)) + msg_panic("HEX_ENCODE_FLAG_USE_COLON"); + if (hex_decode_opt(b2, STR(b1), LEN(b1), HEX_DECODE_FLAG_ALLOW_COLON) == 0) + msg_panic("HEX_DECODE_FLAG_ALLOW_COLON"); + VERIFY(STR(b2), test); + argv_free(argv); + + vstring_free(b1); + vstring_free(b2); + return (0); +} + +#endif diff --git a/src/util/hex_code.h b/src/util/hex_code.h new file mode 100644 index 0000000..720977a --- /dev/null +++ b/src/util/hex_code.h @@ -0,0 +1,54 @@ +#ifndef _HEX_CODE_H_INCLUDED_ +#define _HEX_CODE_H_INCLUDED_ + +/*++ +/* NAME +/* hex_code 3h +/* SUMMARY +/* encode/decode data, hexadecimal style +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define HEX_ENCODE_FLAG_NONE (0) +#define HEX_ENCODE_FLAG_USE_COLON (1<<0) + +#define HEX_DECODE_FLAG_NONE (0) +#define HEX_DECODE_FLAG_ALLOW_COLON (1<<0) + +extern VSTRING *hex_encode(VSTRING *, const char *, ssize_t); +extern VSTRING *WARN_UNUSED_RESULT hex_decode(VSTRING *, const char *, ssize_t); +extern VSTRING *hex_encode_opt(VSTRING *, const char *, ssize_t, int); +extern VSTRING *WARN_UNUSED_RESULT hex_decode_opt(VSTRING *, const char *, ssize_t, int); + +#define hex_encode(res, in, len) \ + hex_encode_opt((res), (in), (len), HEX_ENCODE_FLAG_NONE) +#define hex_decode(res, in, len) \ + hex_decode_opt((res), (in), (len), HEX_DECODE_FLAG_NONE) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/hex_quote.c b/src/util/hex_quote.c new file mode 100644 index 0000000..7089385 --- /dev/null +++ b/src/util/hex_quote.c @@ -0,0 +1,153 @@ +/*++ +/* NAME +/* hex_quote 3 +/* SUMMARY +/* quote/unquote text, HTTP style. +/* SYNOPSIS +/* #include +/* +/* VSTRING *hex_quote(hex, raw) +/* VSTRING *hex; +/* const char *raw; +/* +/* VSTRING *hex_unquote(raw, hex) +/* VSTRING *raw; +/* const char *hex; +/* DESCRIPTION +/* hex_quote() takes a null-terminated string and replaces non-printable +/* and whitespace characters and the % by %XX, XX being the two-digit +/* hexadecimal equivalent. +/* The hexadecimal codes are produced as upper-case characters. The result +/* value is the hex argument. +/* +/* hex_unquote() performs the opposite transformation. This function +/* understands lowercase, uppercase, and mixed case %XX sequences. The +/* result value is the raw argument in case of success, a null pointer +/* otherwise. +/* BUGS +/* hex_quote() cannot process null characters in data. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include + +/* Utility library. */ + +#include "msg.h" +#include "vstring.h" +#include "hex_quote.h" + +/* Application-specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* hex_quote - raw data to quoted */ + +VSTRING *hex_quote(VSTRING *hex, const char *raw) +{ + const char *cp; + int ch; + + VSTRING_RESET(hex); + for (cp = raw; (ch = *(unsigned const char *) cp) != 0; cp++) { + if (ch != '%' && !ISSPACE(ch) && ISPRINT(ch)) { + VSTRING_ADDCH(hex, ch); + } else { + vstring_sprintf_append(hex, "%%%02X", ch); + } + } + VSTRING_TERMINATE(hex); + return (hex); +} + +/* hex_unquote - quoted data to raw */ + +VSTRING *hex_unquote(VSTRING *raw, const char *hex) +{ + const char *cp; + int ch; + + VSTRING_RESET(raw); + for (cp = hex; (ch = *cp) != 0; cp++) { + if (ch == '%') { + if (ISDIGIT(cp[1])) + ch = (cp[1] - '0') << 4; + else if (cp[1] >= 'a' && cp[1] <= 'f') + ch = (cp[1] - 'a' + 10) << 4; + else if (cp[1] >= 'A' && cp[1] <= 'F') + ch = (cp[1] - 'A' + 10) << 4; + else + return (0); + if (ISDIGIT(cp[2])) + ch |= (cp[2] - '0'); + else if (cp[2] >= 'a' && cp[2] <= 'f') + ch |= (cp[2] - 'a' + 10); + else if (cp[2] >= 'A' && cp[2] <= 'F') + ch |= (cp[2] - 'A' + 10); + else + return (0); + cp += 2; + } + VSTRING_ADDCH(raw, ch); + } + VSTRING_TERMINATE(raw); + return (raw); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: convert to hex and back. + */ +#include + +#define BUFLEN 1024 + +static ssize_t read_buf(VSTREAM *fp, VSTRING *buf) +{ + ssize_t len; + + len = vstream_fread_buf(fp, buf, BUFLEN); + VSTRING_TERMINATE(buf); + return (len); +} + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *raw = vstring_alloc(BUFLEN); + VSTRING *hex = vstring_alloc(100); + ssize_t len; + + while ((len = read_buf(VSTREAM_IN, raw)) > 0) { + hex_quote(hex, STR(raw)); + if (hex_unquote(raw, STR(hex)) == 0) + msg_fatal("bad input: %.100s", STR(hex)); + if (LEN(raw) != len) + msg_fatal("len %ld != raw len %ld", (long) len, (long) LEN(raw)); + if (vstream_fwrite(VSTREAM_OUT, STR(raw), LEN(raw)) != LEN(raw)) + msg_fatal("write error: %m"); + } + vstream_fflush(VSTREAM_OUT); + vstring_free(raw); + vstring_free(hex); + return (0); +} + +#endif diff --git a/src/util/hex_quote.h b/src/util/hex_quote.h new file mode 100644 index 0000000..d72ec57 --- /dev/null +++ b/src/util/hex_quote.h @@ -0,0 +1,36 @@ +#ifndef _HEX_QUOTE_H_INCLUDED_ +#define _HEX_QUOTE_H_INCLUDED_ + +/*++ +/* NAME +/* hex_quote 3h +/* SUMMARY +/* quote/unquote text, HTTP style. +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern VSTRING *hex_quote(VSTRING *, const char *); +extern VSTRING *hex_unquote(VSTRING *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/host_port.c b/src/util/host_port.c new file mode 100644 index 0000000..c4e8616 --- /dev/null +++ b/src/util/host_port.c @@ -0,0 +1,203 @@ +/*++ +/* NAME +/* host_port 3 +/* SUMMARY +/* split string into host and port, destroy string +/* SYNOPSIS +/* #include +/* +/* const char *host_port(string, host, def_host, port, def_service) +/* char *string; +/* char **host; +/* char *def_host; +/* char **port; +/* char *def_service; +/* DESCRIPTION +/* host_port() splits a string into substrings with the host +/* name or address, and the service name or port number. +/* The input string is modified. +/* +/* Host/domain names are validated with valid_utf8_hostname(), +/* and host addresses are validated with valid_hostaddr(). +/* +/* The following input formats are understood (null means +/* a null pointer argument): +/* +/* When def_service is not null, and def_host is null: +/* +/* [host]:port, [host]:, [host] +/* +/* host:port, host:, host +/* +/* When def_host is not null, and def_service is null: +/* +/* :port, port +/* +/* Other combinations of def_service and def_host are +/* not supported and produce undefined results. +/* DIAGNOSTICS +/* The result is a null pointer in case of success. +/* In case of problems the result is a string pointer with +/* the problem type. +/* CLIENT EXAMPLE +/* .ad +/* .fi +/* Typical client usage allows the user to omit the service port, +/* in which case the client connects to a pre-determined default +/* port: +/* .nf +/* .na +/* +/* buf = mystrdup(endpoint); +/* if ((parse_error = host_port(buf, &host, NULL, &port, defport)) != 0) +/* msg_fatal("%s in \"%s\"", parse_error, endpoint); +/* if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) +/* msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr)); +/* myfree(buf); +/* SERVER EXAMPLE +/* .ad +/* .fi +/* Typical server usage allows the user to omit the host, meaning +/* listen on all available network addresses: +/* .nf +/* .na +/* +/* buf = mystrdup(endpoint); +/* if ((parse_error = host_port(buf, &host, "", &port, NULL)) != 0) +/* msg_fatal("%s in \"%s\"", parse_error, endpoint); +/* if (*host == 0) +/* host = 0; +/* if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res)) != 0) +/* msg_fatal("%s: %s", endpoint, MAI_STRERROR(aierr)); +/* myfree(buf); +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include /* XXX util_utf8_enable */ +#include + +/* Global library. */ + +#include + + /* + * Point-fix workaround. The libutil library should be email agnostic, but + * we can't rip up the library APIs in the stable releases. + */ +#include +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif +#define IPV6_COL "IPv6:" /* RFC 2821 */ +#define IPV6_COL_LEN (sizeof(IPV6_COL) - 1) +#define HAS_IPV6_COL(str) (strncasecmp((str), IPV6_COL, IPV6_COL_LEN) == 0) + +/* host_port - parse string into host and port, destroy string */ + +const char *host_port(char *buf, char **host, char *def_host, + char **port, char *def_service) +{ + char *cp = buf; + int ipv6 = 0; + + /*- + * [host]:port, [host]:, [host]. + * [ipv6:ipv6addr]:port, [ipv6:ipv6addr]:, [ipv6:ipv6addr]. + */ + if (*cp == '[') { + ++cp; + if ((ipv6 = HAS_IPV6_COL(cp)) != 0) + cp += IPV6_COL_LEN; + *host = cp; + if ((cp = split_at(cp, ']')) == 0) + return ("missing \"]\""); + if (*cp && *cp++ != ':') + return ("garbage after \"]\""); + if (ipv6 && !valid_ipv6_hostaddr(*host, DONT_GRIPE)) + return ("malformed IPv6 address"); + *port = *cp ? cp : def_service; + } + + /* + * host:port, host:, host, :port, port. + */ + else { + if ((cp = split_at_right(buf, ':')) != 0) { + *host = *buf ? buf : def_host; + *port = *cp ? cp : def_service; + } else { + *host = def_host ? def_host : (*buf ? buf : 0); + *port = def_service ? def_service : (*buf ? buf : 0); + } + } + if (*host == 0) + return ("missing host information"); + if (*port == 0) + return ("missing service information"); + + /* + * Final sanity checks. We're still sloppy, allowing bare numerical + * network addresses instead of requiring proper [ipaddress] forms. + */ + if (*host != def_host + && !valid_utf8_hostname(util_utf8_enable, *host, DONT_GRIPE) + && !valid_hostaddr(*host, DONT_GRIPE)) + return ("valid hostname or network address required"); + if (*port != def_service && ISDIGIT(**port) && !alldig(*port)) + return ("garbage after numerical service"); + return (0); +} + +#ifdef TEST + +#include +#include +#include + +#define STR(x) vstring_str(x) + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *in_buf = vstring_alloc(10); + VSTRING *parse_buf = vstring_alloc(10); + char *host; + char *port; + const char *err; + + while (vstring_fgets_nonl(in_buf, VSTREAM_IN)) { + vstream_printf(">> %s\n", STR(in_buf)); + vstream_fflush(VSTREAM_OUT); + if (*STR(in_buf) == '#') + continue; + vstring_strcpy(parse_buf, STR(in_buf)); + if ((err = host_port(STR(parse_buf), &host, (char *) 0, &port, "default-service")) != 0) { + msg_warn("%s in %s", err, STR(in_buf)); + } else { + vstream_printf("host %s port %s\n", host, port); + vstream_fflush(VSTREAM_OUT); + } + } + vstring_free(in_buf); + vstring_free(parse_buf); + return (0); +} + +#endif diff --git a/src/util/host_port.h b/src/util/host_port.h new file mode 100644 index 0000000..f2fecbb --- /dev/null +++ b/src/util/host_port.h @@ -0,0 +1,35 @@ +#ifndef _HOST_PORT_H_INCLUDED_ +#define _HOST_PORT_H_INCLUDED_ + +/*++ +/* NAME +/* host_port 3h +/* SUMMARY +/* split string into host and port, destroy string +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern const char *WARN_UNUSED_RESULT host_port(char *, char **, char *, + char **, char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/host_port.in b/src/util/host_port.in new file mode 100644 index 0000000..1608222 --- /dev/null +++ b/src/util/host_port.in @@ -0,0 +1,16 @@ +hhh:ppp +hhh: +hhh +[hhh]:ppp +[hhh]: +[hhh] +#[hhh:ppp] +#[hhh:] +hhh:1pp +[hh.] +hh. +999 +[::1] +[ipv6:::1] +[ipv6:127.0.0.1] +[ipv6:example.com] diff --git a/src/util/host_port.ref b/src/util/host_port.ref new file mode 100644 index 0000000..1d79745 --- /dev/null +++ b/src/util/host_port.ref @@ -0,0 +1,30 @@ +>> hhh:ppp +host hhh port ppp +>> hhh: +host hhh port default-service +>> hhh +host hhh port default-service +>> [hhh]:ppp +host hhh port ppp +>> [hhh]: +host hhh port default-service +>> [hhh] +host hhh port default-service +>> #[hhh:ppp] +>> #[hhh:] +>> hhh:1pp +unknown: warning: garbage after numerical service in hhh:1pp +>> [hh.] +unknown: warning: valid hostname or network address required in [hh.] +>> hh. +unknown: warning: valid hostname or network address required in hh. +>> 999 +unknown: warning: valid hostname or network address required in 999 +>> [::1] +host ::1 port default-service +>> [ipv6:::1] +host ::1 port default-service +>> [ipv6:127.0.0.1] +unknown: warning: malformed IPv6 address in [ipv6:127.0.0.1] +>> [ipv6:example.com] +unknown: warning: malformed IPv6 address in [ipv6:example.com] diff --git a/src/util/htable.c b/src/util/htable.c new file mode 100644 index 0000000..1c08e97 --- /dev/null +++ b/src/util/htable.c @@ -0,0 +1,439 @@ +/*++ +/* NAME +/* htable 3 +/* SUMMARY +/* hash table manager +/* SYNOPSIS +/* #include +/* +/* typedef struct { +/* .in +4 +/* char *key; +/* void *value; +/* /* private fields... */ +/* .in -4 +/* } HTABLE_INFO; +/* +/* HTABLE *htable_create(size) +/* int size; +/* +/* HTABLE_INFO *htable_enter(table, key, value) +/* HTABLE *table; +/* const char *key; +/* void *value; +/* +/* char *htable_find(table, key) +/* HTABLE *table; +/* const char *key; +/* +/* HTABLE_INFO *htable_locate(table, key) +/* HTABLE *table; +/* const char *key; +/* +/* void htable_delete(table, key, free_fn) +/* HTABLE *table; +/* const char *key; +/* void (*free_fn)(void *); +/* +/* void htable_free(table, free_fn) +/* HTABLE *table; +/* void (*free_fn)(void *); +/* +/* void htable_walk(table, action, ptr) +/* HTABLE *table; +/* void (*action)(HTABLE_INFO *, void *ptr); +/* void *ptr; +/* +/* HTABLE_INFO **htable_list(table) +/* HTABLE *table; +/* +/* HTABLE_INFO *htable_sequence(table, how) +/* HTABLE *table; +/* int how; +/* DESCRIPTION +/* This module maintains one or more hash tables. Each table entry +/* consists of a unique string-valued lookup key and a generic +/* character-pointer value. +/* The tables are automatically resized when they fill up. When the +/* values to be remembered are not character pointers, proper casts +/* should be used or the code will not be portable. +/* +/* htable_create() creates a table of the specified size and returns a +/* pointer to the result. The lookup keys are saved with mystrdup(). +/* htable_enter() stores a (key, value) pair into the specified table +/* and returns a pointer to the resulting entry. The code does not +/* check if an entry with that key already exists: use htable_locate() +/* for updating an existing entry. +/* +/* htable_find() returns the value that was stored under the given key, +/* or a null pointer if it was not found. In order to distinguish +/* a null value from a non-existent value, use htable_locate(). +/* +/* htable_locate() returns a pointer to the entry that was stored +/* for the given key, or a null pointer if it was not found. +/* +/* htable_delete() removes one entry that was stored under the given key. +/* If the free_fn argument is not a null pointer, the corresponding +/* function is called with as argument the non-zero value stored under +/* the key. +/* +/* htable_free() destroys a hash table, including contents. If the free_fn +/* argument is not a null pointer, the corresponding function is called +/* for each table entry, with as argument the non-zero value stored +/* with the entry. +/* +/* htable_walk() invokes the action function for each table entry, with +/* a pointer to the entry as its argument. The ptr argument is passed +/* on to the action function. +/* +/* htable_list() returns a null-terminated list of pointers to +/* all elements in the named table. The list should be passed to +/* myfree(). +/* +/* htable_sequence() returns the first or next element depending +/* on the value of the "how" argument. Specify HTABLE_SEQ_FIRST +/* to start a new sequence, HTABLE_SEQ_NEXT to continue, and +/* HTABLE_SEQ_STOP to terminate a sequence early. The caller +/* must not delete an element before it is visited. +/* RESTRICTIONS +/* A callback function should not modify the hash table that is +/* specified to its caller. +/* DIAGNOSTICS +/* The following conditions are reported and cause the program to +/* terminate immediately: memory allocation failure; an attempt +/* to delete a non-existent entry. +/* SEE ALSO +/* mymalloc(3) memory management wrapper +/* hash_fnv(3) Fowler/Noll/Vo hash function +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* C library */ + +#include +#include + +/* Local stuff */ + +#include "mymalloc.h" +#include "msg.h" +#include "htable.h" + +/* htable_hash - hash a string */ + +#ifndef NO_HASH_FNV +#include "hash_fnv.h" + +#define htable_hash(s, size) (hash_fnv((s), strlen(s)) % (size)) + +#else + +static size_t htable_hash(const char *s, size_t size) +{ + size_t h = 0; + size_t g; + + /* + * From the "Dragon" book by Aho, Sethi and Ullman. + */ + + while (*s) { + h = (h << 4U) + *(unsigned const char *) s++; + if ((g = (h & 0xf0000000)) != 0) { + h ^= (g >> 24U); + h ^= g; + } + } + return (h % size); +} + +#endif + +/* htable_link - insert element into table */ + +#define htable_link(table, element) { \ + HTABLE_INFO **_h = table->data + htable_hash(element->key, table->size);\ + element->prev = 0; \ + if ((element->next = *_h) != 0) \ + (*_h)->prev = element; \ + *_h = element; \ + table->used++; \ +} + +/* htable_size - allocate and initialize hash table */ + +static void htable_size(HTABLE *table, size_t size) +{ + HTABLE_INFO **h; + + size |= 1; + + table->data = h = (HTABLE_INFO **) mymalloc(size * sizeof(HTABLE_INFO *)); + table->size = size; + table->used = 0; + + while (size-- > 0) + *h++ = 0; +} + +/* htable_create - create initial hash table */ + +HTABLE *htable_create(ssize_t size) +{ + HTABLE *table; + + table = (HTABLE *) mymalloc(sizeof(HTABLE)); + htable_size(table, size < 13 ? 13 : size); + table->seq_bucket = table->seq_element = 0; + return (table); +} + +/* htable_grow - extend existing table */ + +static void htable_grow(HTABLE *table) +{ + HTABLE_INFO *ht; + HTABLE_INFO *next; + size_t old_size = table->size; + HTABLE_INFO **h = table->data; + HTABLE_INFO **old_entries = h; + + htable_size(table, 2 * old_size); + + while (old_size-- > 0) { + for (ht = *h++; ht; ht = next) { + next = ht->next; + htable_link(table, ht); + } + } + myfree((void *) old_entries); +} + +/* htable_enter - enter (key, value) pair */ + +HTABLE_INFO *htable_enter(HTABLE *table, const char *key, void *value) +{ + HTABLE_INFO *ht; + + if (table->used >= table->size) + htable_grow(table); + ht = (HTABLE_INFO *) mymalloc(sizeof(HTABLE_INFO)); + ht->key = mystrdup(key); + ht->value = value; + htable_link(table, ht); + return (ht); +} + +/* htable_find - lookup value */ + +void *htable_find(HTABLE *table, const char *key) +{ + HTABLE_INFO *ht; + +#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0)) + + if (table) + for (ht = table->data[htable_hash(key, table->size)]; ht; ht = ht->next) + if (STREQ(key, ht->key)) + return (ht->value); + return (0); +} + +/* htable_locate - lookup entry */ + +HTABLE_INFO *htable_locate(HTABLE *table, const char *key) +{ + HTABLE_INFO *ht; + +#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0)) + + if (table) + for (ht = table->data[htable_hash(key, table->size)]; ht; ht = ht->next) + if (STREQ(key, ht->key)) + return (ht); + return (0); +} + +/* htable_delete - delete one entry */ + +void htable_delete(HTABLE *table, const char *key, void (*free_fn) (void *)) +{ + if (table) { + HTABLE_INFO *ht; + HTABLE_INFO **h = table->data + htable_hash(key, table->size); + +#define STREQ(x,y) (x == y || (x[0] == y[0] && strcmp(x,y) == 0)) + + for (ht = *h; ht; ht = ht->next) { + if (STREQ(key, ht->key)) { + if (ht->next) + ht->next->prev = ht->prev; + if (ht->prev) + ht->prev->next = ht->next; + else + *h = ht->next; + table->used--; + myfree(ht->key); + if (free_fn && ht->value) + (*free_fn) (ht->value); + myfree((void *) ht); + return; + } + } + msg_panic("htable_delete: unknown_key: \"%s\"", key); + } +} + +/* htable_free - destroy hash table */ + +void htable_free(HTABLE *table, void (*free_fn) (void *)) +{ + if (table) { + ssize_t i = table->size; + HTABLE_INFO *ht; + HTABLE_INFO *next; + HTABLE_INFO **h = table->data; + + while (i-- > 0) { + for (ht = *h++; ht; ht = next) { + next = ht->next; + myfree(ht->key); + if (free_fn && ht->value) + (*free_fn) (ht->value); + myfree((void *) ht); + } + } + myfree((void *) table->data); + table->data = 0; + if (table->seq_bucket) + myfree((void *) table->seq_bucket); + table->seq_bucket = 0; + myfree((void *) table); + } +} + +/* htable_walk - iterate over hash table */ + +void htable_walk(HTABLE *table, void (*action) (HTABLE_INFO *, void *), + void *ptr) { + if (table) { + ssize_t i = table->size; + HTABLE_INFO **h = table->data; + HTABLE_INFO *ht; + + while (i-- > 0) + for (ht = *h++; ht; ht = ht->next) + (*action) (ht, ptr); + } +} + +/* htable_list - list all table members */ + +HTABLE_INFO **htable_list(HTABLE *table) +{ + HTABLE_INFO **list; + HTABLE_INFO *member; + ssize_t count = 0; + ssize_t i; + + if (table != 0) { + list = (HTABLE_INFO **) mymalloc(sizeof(*list) * (table->used + 1)); + for (i = 0; i < table->size; i++) + for (member = table->data[i]; member != 0; member = member->next) + list[count++] = member; + } else { + list = (HTABLE_INFO **) mymalloc(sizeof(*list)); + } + list[count] = 0; + return (list); +} + +/* htable_sequence - dict(3) compatibility iterator */ + +HTABLE_INFO *htable_sequence(HTABLE *table, int how) +{ + if (table == 0) + return (0); + + switch (how) { + case HTABLE_SEQ_FIRST: /* start new sequence */ + if (table->seq_bucket) + myfree((void *) table->seq_bucket); + table->seq_bucket = htable_list(table); + table->seq_element = table->seq_bucket; + return (*(table->seq_element)++); + case HTABLE_SEQ_NEXT: /* next element */ + if (table->seq_element && *table->seq_element) + return (*(table->seq_element)++); + /* FALLTHROUGH */ + default: /* terminate sequence */ + if (table->seq_bucket) { + myfree((void *) table->seq_bucket); + table->seq_bucket = table->seq_element = 0; + } + return (0); + } +} + +#ifdef TEST +#include +#include + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *buf = vstring_alloc(10); + ssize_t count = 0; + HTABLE *hash; + HTABLE_INFO **ht_info; + HTABLE_INFO **ht; + HTABLE_INFO *info; + ssize_t i; + ssize_t r; + int op; + + /* + * Load a large number of strings and delete them in a random order. + */ + hash = htable_create(10); + while (vstring_get(buf, VSTREAM_IN) != VSTREAM_EOF) + htable_enter(hash, vstring_str(buf), CAST_INT_TO_VOID_PTR(count++)); + if (count != hash->used) + msg_panic("%ld entries stored, but %lu entries exist", + (long) count, (unsigned long) hash->used); + for (i = 0, op = HTABLE_SEQ_FIRST; htable_sequence(hash, op) != 0; + i++, op = HTABLE_SEQ_NEXT) + /* void */ ; + if (i != hash->used) + msg_panic("%ld entries found, but %lu entries exist", + (long) i, (unsigned long) hash->used); + ht_info = htable_list(hash); + for (i = 0; i < hash->used; i++) { + r = myrand() % hash->used; + info = ht_info[i]; + ht_info[i] = ht_info[r]; + ht_info[r] = info; + } + for (ht = ht_info; *ht; ht++) + htable_delete(hash, ht[0]->key, (void (*) (void *)) 0); + if (hash->used > 0) + msg_panic("%ld entries not deleted", (long) hash->used); + myfree((void *) ht_info); + htable_free(hash, (void (*) (void *)) 0); + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/htable.h b/src/util/htable.h new file mode 100644 index 0000000..bba43e8 --- /dev/null +++ b/src/util/htable.h @@ -0,0 +1,70 @@ +#ifndef _HTABLE_H_INCLUDED_ +#define _HTABLE_H_INCLUDED_ + +/*++ +/* NAME +/* htable 3h +/* SUMMARY +/* hash table manager +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* Structure of one hash table entry. */ + +typedef struct HTABLE_INFO { + char *key; /* lookup key */ + void *value; /* associated value */ + struct HTABLE_INFO *next; /* colliding entry */ + struct HTABLE_INFO *prev; /* colliding entry */ +} HTABLE_INFO; + + /* Structure of one hash table. */ + +typedef struct HTABLE { + ssize_t size; /* length of entries array */ + ssize_t used; /* number of entries in table */ + HTABLE_INFO **data; /* entries array, auto-resized */ + HTABLE_INFO **seq_bucket; /* current sequence hash bucket */ + HTABLE_INFO **seq_element; /* current sequence element */ +} HTABLE; + +extern HTABLE *htable_create(ssize_t); +extern HTABLE_INFO *htable_enter(HTABLE *, const char *, void *); +extern HTABLE_INFO *htable_locate(HTABLE *, const char *); +extern void *htable_find(HTABLE *, const char *); +extern void htable_delete(HTABLE *, const char *, void (*) (void *)); +extern void htable_free(HTABLE *, void (*) (void *)); +extern void htable_walk(HTABLE *, void (*) (HTABLE_INFO *, void *), void *); +extern HTABLE_INFO **htable_list(HTABLE *); +extern HTABLE_INFO *htable_sequence(HTABLE *, int); + +#define HTABLE_SEQ_FIRST 0 +#define HTABLE_SEQ_NEXT 1 +#define HTABLE_SEQ_STOP (-1) + + /* + * Correct only when casting (char *) to (void *). + */ +#define HTABLE_ACTION_FN_CAST(f) ((void *)(HTABLE_INFO *, void *)) (f) +#define HTABLE_FREE_FN_CAST(f) ((void *)(void *)) (f) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* CREATION DATE +/* Fri Feb 14 13:43:19 EST 1997 +/* LAST MODIFICATION +/* %E% %U% +/* VERSION/RELEASE +/* %I% +/*--*/ + +#endif diff --git a/src/util/inet_addr_host.c b/src/util/inet_addr_host.c new file mode 100644 index 0000000..d2c9d84 --- /dev/null +++ b/src/util/inet_addr_host.c @@ -0,0 +1,173 @@ +/*++ +/* NAME +/* inet_addr_host 3 +/* SUMMARY +/* determine all host internet interface addresses +/* SYNOPSIS +/* #include +/* +/* int inet_addr_host(addr_list, hostname) +/* INET_ADDR_LIST *addr_list; +/* const char *hostname; +/* DESCRIPTION +/* inet_addr_host() determines all interface addresses of the +/* named host. The host may be specified as a symbolic name, +/* or as a numerical address. An empty host expands as the +/* wild-card address. Address results are appended to +/* the specified address list. The result value is the number +/* of addresses appended to the list. +/* DIAGNOSTICS +/* Fatal errors: out of memory. +/* BUGS +/* This code uses the name service, so it talks to the network, +/* and that may not be desirable. +/* SEE ALSO +/* inet_addr_list(3) address list management +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* inet_addr_host - look up address list for host */ + +int inet_addr_host(INET_ADDR_LIST *addr_list, const char *hostname) +{ + const char *myname = "inet_addr_host"; + int sock; + struct addrinfo *res0; + struct addrinfo *res; + int aierr; + ssize_t hostnamelen; + const char *hname; + const char *serv; + int initial_count = addr_list->used; + const INET_PROTO_INFO *proto_info; + + /* + * The use of square brackets around an IPv6 addresses is required, even + * though we don't enforce it as it'd make the code unnecessarily + * complicated. + * + * XXX AIX 5.1 getaddrinfo() does not allow "0" as service, regardless of + * whether or not a host is specified. + */ + if (*hostname == 0) { + hname = 0; + serv = "1"; + } else if (*hostname == '[' + && hostname[(hostnamelen = strlen(hostname)) - 1] == ']') { + hname = mystrndup(hostname + 1, hostnamelen - 2); + serv = 0; + } else { + hname = hostname; + serv = 0; + } + + proto_info = inet_proto_info(); + if ((aierr = hostname_to_sockaddr(hname, serv, SOCK_STREAM, &res0)) == 0) { + for (res = res0; res; res = res->ai_next) { + + /* + * Safety net. + */ + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + msg_info("%s: skipping address family %d for host \"%s\"", + myname, res->ai_family, hostname); + continue; + } + + /* + * On Linux systems it is not unusual for user-land to be out of + * sync with kernel-land. When this is the case we try to be + * helpful and filter out address families that the library + * claims to understand but that are not supported by the kernel. + */ + if ((sock = socket(res->ai_family, SOCK_STREAM, 0)) < 0) { + msg_warn("%s: skipping address family %d: %m", + myname, res->ai_family); + continue; + } + if (close(sock)) + msg_warn("%s: close socket: %m", myname); + + inet_addr_list_append(addr_list, res->ai_addr); + } + freeaddrinfo(res0); + } + if (hname && hname != hostname) + myfree((void *) hname); + + return (addr_list->used - initial_count); +} + +#ifdef TEST + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + INET_ADDR_LIST list; + struct sockaddr_storage *sa; + MAI_HOSTADDR_STR hostaddr; + INET_PROTO_INFO *proto_info; + + msg_vstream_init(argv[0], VSTREAM_ERR); + + if (argc < 3) + msg_fatal("usage: %s protocols hostname...", argv[0]); + + proto_info = inet_proto_init(argv[0], argv[1]); + argv += 1; + + while (--argc && *++argv) { + inet_addr_list_init(&list); + if (inet_addr_host(&list, *argv) == 0) + msg_fatal("not found: %s", *argv); + + for (sa = list.addrs; sa < list.addrs + list.used; sa++) { + SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + vstream_printf("%s\t%s\n", *argv, hostaddr.buf); + } + vstream_fflush(VSTREAM_OUT); + inet_addr_list_free(&list); + } + return (0); +} + +#endif diff --git a/src/util/inet_addr_host.h b/src/util/inet_addr_host.h new file mode 100644 index 0000000..39d150a --- /dev/null +++ b/src/util/inet_addr_host.h @@ -0,0 +1,35 @@ +#ifndef INET_ADDR_HOST_H_INCLUDED_ +#define INET_ADDR_HOST_H_INCLUDED_ + +/*++ +/* NAME +/* inet_addr_host 3h +/* SUMMARY +/* determine all host internet interface addresses +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern int inet_addr_host(INET_ADDR_LIST *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/inet_addr_list.c b/src/util/inet_addr_list.c new file mode 100644 index 0000000..e579b17 --- /dev/null +++ b/src/util/inet_addr_list.c @@ -0,0 +1,186 @@ +/*++ +/* NAME +/* inet_addr_list 3 +/* SUMMARY +/* internet address list manager +/* SYNOPSIS +/* #include +/* +/* void inet_addr_list_init(list) +/* INET_ADDR_LIST *list; +/* +/* void inet_addr_list_append(list,addr) +/* INET_ADDR_LIST *list; +/* struct sockaddr *addr; +/* +/* void inet_addr_list_uniq(list) +/* INET_ADDR_LIST *list; +/* +/* void inet_addr_list_free(list) +/* INET_ADDR_LIST *list; +/* DESCRIPTION +/* This module maintains simple lists of internet addresses. +/* +/* inet_addr_list_init() initializes a user-provided structure +/* so that it can be used by inet_addr_list_append() and by +/* inet_addr_list_free(). +/* +/* inet_addr_list_append() appends the specified address to +/* the specified list, extending the list on the fly. +/* +/* inet_addr_list_uniq() sorts the specified address list and +/* eliminates duplicates. +/* +/* inet_addr_list_free() reclaims memory used for the +/* specified address list. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* inet_addr_list_init - initialize internet address list */ + +void inet_addr_list_init(INET_ADDR_LIST *list) +{ + int init_size; + + list->used = 0; + list->size = 0; + init_size = 2; + list->addrs = (struct sockaddr_storage *) + mymalloc(sizeof(*list->addrs) * init_size); + list->size = init_size; +} + +/* inet_addr_list_append - append address to internet address list */ + +void inet_addr_list_append(INET_ADDR_LIST *list, + struct sockaddr *addr) +{ + const char *myname = "inet_addr_list_append"; + MAI_HOSTADDR_STR hostaddr; + int new_size; + + if (msg_verbose > 1) { + SOCKADDR_TO_HOSTADDR(addr, SOCK_ADDR_LEN(addr), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_info("%s: %s", myname, hostaddr.buf); + } + if (list->used >= list->size) { + new_size = list->size * 2; + list->addrs = (struct sockaddr_storage *) + myrealloc((void *) list->addrs, sizeof(*list->addrs) * new_size); + list->size = new_size; + } + memcpy(list->addrs + list->used++, addr, SOCK_ADDR_LEN(addr)); +} + +/* inet_addr_list_comp - compare addresses */ + +static int inet_addr_list_comp(const void *a, const void *b) +{ + + /* + * In case (struct *) != (void *). + */ + return (sock_addr_cmp_addr(SOCK_ADDR_PTR(a), SOCK_ADDR_PTR(b))); +} + +/* inet_addr_list_uniq - weed out duplicates */ + +void inet_addr_list_uniq(INET_ADDR_LIST *list) +{ + int n; + int m; + + /* + * Put the identical members right next to each other. + */ + qsort((void *) list->addrs, list->used, + sizeof(list->addrs[0]), inet_addr_list_comp); + + /* + * Nuke the duplicates. Postcondition after while loop: m is the largest + * index for which list->addrs[n] == list->addrs[m]. + */ + for (m = n = 0; m < list->used; m++, n++) { + if (m != n) + list->addrs[n] = list->addrs[m]; + while (m + 1 < list->used + && inet_addr_list_comp((void *) &(list->addrs[n]), + (void *) &(list->addrs[m + 1])) == 0) + m += 1; + } + list->used = n; +} + +/* inet_addr_list_free - destroy internet address list */ + +void inet_addr_list_free(INET_ADDR_LIST *list) +{ + myfree((void *) list->addrs); +} + +#ifdef TEST +#include + + /* + * Duplicate elimination needs to be tested. + */ +#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); + } +} + +int main(int argc, char **argv) +{ + INET_ADDR_LIST list; + INET_PROTO_INFO *proto_info; + + proto_info = inet_proto_init(argv[0], INET_PROTO_NAME_ALL); + inet_addr_list_init(&list); + while (--argc && *++argv) + if (inet_addr_host(&list, *argv) == 0) + msg_fatal("host not found: %s", *argv); + msg_info("list before sort/uniq"); + inet_addr_list_print(&list); + inet_addr_list_uniq(&list); + msg_info("list after sort/uniq"); + inet_addr_list_print(&list); + inet_addr_list_free(&list); + return (0); +} + +#endif diff --git a/src/util/inet_addr_list.h b/src/util/inet_addr_list.h new file mode 100644 index 0000000..8a109c1 --- /dev/null +++ b/src/util/inet_addr_list.h @@ -0,0 +1,44 @@ +#ifndef _INET_ADDR_LIST_H_INCLUDED_ +#define _INET_ADDR_LIST_H_INCLUDED_ + +/*++ +/* NAME +/* inet_addr_list 3h +/* SUMMARY +/* internet address list manager +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include /* generic name/addr API */ + + /* + * External interface. + */ +typedef struct INET_ADDR_LIST { + int used; /* nr of elements in use */ + int size; /* actual list size */ + struct sockaddr_storage *addrs; /* payload */ +} INET_ADDR_LIST; + +extern void inet_addr_list_init(INET_ADDR_LIST *); +extern void inet_addr_list_free(INET_ADDR_LIST *); +extern void inet_addr_list_uniq(INET_ADDR_LIST *); +extern void inet_addr_list_append(INET_ADDR_LIST *, struct sockaddr *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/inet_addr_list.in b/src/util/inet_addr_list.in new file mode 100644 index 0000000..5c72a1b --- /dev/null +++ b/src/util/inet_addr_list.in @@ -0,0 +1,9 @@ +168.100.3.2 +168.100.3.2 +168.100.3.1 +168.100.3.3 +168.100.3.3 +168.100.3.3 +168.100.3.4 +168.100.3.1 +168.100.3.4 diff --git a/src/util/inet_addr_list.ref b/src/util/inet_addr_list.ref new file mode 100644 index 0000000..9fbf48e --- /dev/null +++ b/src/util/inet_addr_list.ref @@ -0,0 +1,15 @@ +unknown: list before sort/uniq +unknown: 168.100.3.2 +unknown: 168.100.3.2 +unknown: 168.100.3.1 +unknown: 168.100.3.3 +unknown: 168.100.3.3 +unknown: 168.100.3.3 +unknown: 168.100.3.4 +unknown: 168.100.3.1 +unknown: 168.100.3.4 +unknown: list after sort/uniq +unknown: 168.100.3.1 +unknown: 168.100.3.2 +unknown: 168.100.3.3 +unknown: 168.100.3.4 diff --git a/src/util/inet_addr_local.c b/src/util/inet_addr_local.c new file mode 100644 index 0000000..e48803a --- /dev/null +++ b/src/util/inet_addr_local.c @@ -0,0 +1,621 @@ +/*++ +/* NAME +/* inet_addr_local 3 +/* SUMMARY +/* determine if IP address is local +/* SYNOPSIS +/* #include +/* +/* int inet_addr_local(addr_list, mask_list, addr_family_list) +/* INET_ADDR_LIST *addr_list; +/* INET_ADDR_LIST *mask_list; +/* unsigned *addr_family; +/* DESCRIPTION +/* inet_addr_local() determines all active IP interface addresses +/* of the local system. Any address found is appended to the +/* specified address list. The result value is the number of +/* active interfaces found. +/* +/* The mask_list is either a null pointer, or it is a list that +/* receives the netmasks of the interface addresses that were found. +/* +/* The addr_family_list specifies one or more of AF_INET or AF_INET6. +/* DIAGNOSTICS +/* Fatal errors: out of memory. +/* SEE ALSO +/* inet_addr_list(3) address list management +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Dean C. Strik +/* Department ICT +/* Eindhoven University of Technology +/* P.O. Box 513 +/* 5600 MB Eindhoven, Netherlands +/* E-mail: +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_SYS_SOCKIO_H +#include +#endif +#include +#include +#ifdef HAS_IPV6 /* Linux only? */ +#include +#include +#endif +#ifdef HAVE_GETIFADDRS +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * Postfix needs its own interface address information to determine whether + * or not it is an MX host for some destination; without this information, + * mail would loop between MX hosts. Postfix also needs its interface + * addresses to figure out whether or not it is final destination for + * addresses of the form username@[ipaddress]. + * + * Postfix needs its own interface netmask information when no explicit + * mynetworks setting is given in main.cf, and "mynetworks_style = subnet". + * The mynetworks parameter controls, among others, what mail clients are + * allowed to relay mail through Postfix. + * + * Different systems have different ways to find out this information. We will + * therefore use OS dependent methods. An overview: + * + * - Use getifaddrs() when available. This supports both IPv4/IPv6 addresses. + * The implementation however is not present in all major operating systems. + * + * - Use SIOCGLIFCONF when available. This supports both IPv4/IPv6 addresses. + * With SIOCGLIFNETMASK we can obtain the netmask for either address family. + * Again, this is not present in all major operating systems. + * + * - On Linux, glibc's getifaddrs(3) has returned IPv4 information for some + * time, but IPv6 information was not returned until 2.3.3. With older Linux + * versions we get IPv4 interface information with SIOCGIFCONF, and read + * IPv6 address/prefix information from a file in the /proc filesystem. + * + * - On other systems we expect SIOCGIFCONF to return IPv6 addresses. Since + * SIOCGIFNETMASK does not work reliably for IPv6 addresses, we always set + * the prefix length to /128 (host), and expect the user to configure a more + * appropriate mynetworks setting if needed. + * + * XXX: Each lookup method is implemented by its own function, so we duplicate + * some code. In this case, I think this is better than really drowning in + * the #ifdefs... + * + * -- Dean Strik (dcs) + */ + +#ifndef HAVE_GETIFADDRS + +/* ial_socket - make socket for ioctl() operations */ + +static int ial_socket(int af) +{ + const char *myname = "inet_addr_local[socket]"; + int sock; + + /* + * The host may not be actually configured with IPv6. When IPv6 support + * is not actually in the kernel, don't consider failure to create an + * IPv6 socket as fatal. This could be tuned better though. For other + * families, the error is fatal. + * + * XXX Now that Postfix controls protocol support centrally with the + * inet_proto(3) module, this workaround should no longer be needed. + */ + if ((sock = socket(af, SOCK_DGRAM, 0)) < 0) { +#ifdef HAS_IPV6 + if (af == AF_INET6) { + if (msg_verbose) + msg_warn("%s: socket: %m", myname); + return (-1); + } +#endif + msg_fatal("%s: socket: %m", myname); + } + return (sock); +} + +#endif + +#ifdef HAVE_GETIFADDRS + +/* + * The getifaddrs(3) function, introduced by BSD/OS, provides a + * platform-independent way of requesting interface addresses, + * including IPv6 addresses. The implementation however is not + * present in all major operating systems. + */ + +/* ial_getifaddrs - determine IP addresses using getifaddrs(3) */ + +static int ial_getifaddrs(INET_ADDR_LIST *addr_list, + INET_ADDR_LIST *mask_list, + int af) +{ + const char *myname = "inet_addr_local[getifaddrs]"; + struct ifaddrs *ifap, *ifa; + struct sockaddr *sa, *sam; + + if (getifaddrs(&ifap) < 0) + msg_fatal("%s: getifaddrs: %m", myname); + + /* + * Get the address of each IP network interface. According to BIND we + * must include interfaces that are down because the machine may still + * receive packets for that address (yes, via some other interface). + * Having no way to verify this claim on every machine, I will give them + * the benefit of the doubt. + * + * FIX 200501: The IPv6 patch did not report NetBSD loopback interfaces; + * fixed by replacing IFF_RUNNING by IFF_UP. + * + * FIX 200501: The IPV6 patch did not skip wild-card interface addresses + * (tested on FreeBSD). + */ + for (ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (!(ifa->ifa_flags & IFF_UP) || ifa->ifa_addr == 0) + continue; + sa = ifa->ifa_addr; + if (af != AF_UNSPEC && sa->sa_family != af) + continue; + sam = ifa->ifa_netmask; + if (sam == 0) { + /* XXX In mynetworks, a null netmask would match everyone. */ + msg_warn("ignoring interface with null netmask, address family %d", + sa->sa_family); + continue; + } + switch (sa->sa_family) { + case AF_INET: + if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY) + continue; + break; +#ifdef HAS_IPV6 + case AF_INET6: + if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa))) + continue; + break; +#endif + default: + continue; + } + + inet_addr_list_append(addr_list, sa); + if (mask_list != 0) { + + /* + * Unfortunately, sa_len/sa_family may be broken in the netmask + * sockaddr structure. We must fix this manually to have correct + * addresses. --dcs + */ +#ifdef HAS_SA_LEN + sam->sa_len = sa->sa_family == AF_INET6 ? + sizeof(struct sockaddr_in6) : + sizeof(struct sockaddr_in); +#endif + sam->sa_family = sa->sa_family; + inet_addr_list_append(mask_list, sam); + } + } + freeifaddrs(ifap); + return (0); +} + +#elif defined(HAS_SIOCGLIF) /* HAVE_GETIFADDRS */ + +/* + * The SIOCLIF* ioctls are the successors of SIOCGIF* on the Solaris + * and HP/UX operating systems. The data is stored in sockaddr_storage + * structure. Both IPv4 and IPv6 addresses are returned though these + * calls. + */ +#define NEXT_INTERFACE(lifr) (lifr + 1) +#define LIFREQ_SIZE(lifr) sizeof(lifr[0]) + +/* ial_siocglif - determine IP addresses using ioctl(SIOCGLIF*) */ + +static int ial_siocglif(INET_ADDR_LIST *addr_list, + INET_ADDR_LIST *mask_list, + int af) +{ + const char *myname = "inet_addr_local[siocglif]"; + struct lifconf lifc; + struct lifreq *lifr; + struct lifreq *lifr_mask; + struct lifreq *the_end; + struct sockaddr *sa; + int sock; + VSTRING *buf; + + /* + * See also comments in ial_siocgif() + */ + if (af != AF_INET && af != AF_INET6) + msg_fatal("%s: address family was %d, must be AF_INET (%d) or " + "AF_INET6 (%d)", myname, af, AF_INET, AF_INET6); + sock = ial_socket(af); + if (sock < 0) + return (0); + buf = vstring_alloc(1024); + for (;;) { + memset(&lifc, 0, sizeof(lifc)); + lifc.lifc_family = AF_UNSPEC; /* XXX Why??? */ + lifc.lifc_len = vstring_avail(buf); + lifc.lifc_buf = vstring_str(buf); + if (ioctl(sock, SIOCGLIFCONF, (char *) &lifc) < 0) { + if (errno != EINVAL) + msg_fatal("%s: ioctl SIOCGLIFCONF: %m", myname); + } else if (lifc.lifc_len < vstring_avail(buf) / 2) + break; + VSTRING_SPACE(buf, vstring_avail(buf) * 2); + } + + the_end = (struct lifreq *) (lifc.lifc_buf + lifc.lifc_len); + for (lifr = lifc.lifc_req; lifr < the_end;) { + sa = (struct sockaddr *) &lifr->lifr_addr; + if (sa->sa_family != af) { + lifr = NEXT_INTERFACE(lifr); + continue; + } + if (af == AF_INET) { + if (SOCK_ADDR_IN_ADDR(sa).s_addr == INADDR_ANY) { + lifr = NEXT_INTERFACE(lifr); + continue; + } +#ifdef HAS_IPV6 + } else if (af == AF_INET6) { + if (IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa))) { + lifr = NEXT_INTERFACE(lifr); + continue; + } + } +#endif + inet_addr_list_append(addr_list, sa); + if (mask_list) { + lifr_mask = (struct lifreq *) mymalloc(sizeof(struct lifreq)); + memcpy((void *) lifr_mask, (void *) lifr, sizeof(struct lifreq)); + if (ioctl(sock, SIOCGLIFNETMASK, lifr_mask) < 0) + msg_fatal("%s: ioctl(SIOCGLIFNETMASK): %m", myname); + /* XXX: Check whether sa_len/family are honoured --dcs */ + inet_addr_list_append(mask_list, + (struct sockaddr *) &lifr_mask->lifr_addr); + myfree((void *) lifr_mask); + } + lifr = NEXT_INTERFACE(lifr); + } + vstring_free(buf); + (void) close(sock); + return (0); +} + +#else /* HAVE_SIOCGLIF */ + +/* + * The classic SIOCGIF* ioctls. Modern BSD operating systems will + * also return IPv6 addresses through these structure. Note however + * that recent versions of these operating systems have getifaddrs. + */ +#if defined(_SIZEOF_ADDR_IFREQ) +#define NEXT_INTERFACE(ifr) ((struct ifreq *) \ + ((char *) ifr + _SIZEOF_ADDR_IFREQ(*ifr))) +#define IFREQ_SIZE(ifr) _SIZEOF_ADDR_IFREQ(*ifr) +#elif defined(HAS_SA_LEN) +#define NEXT_INTERFACE(ifr) ((struct ifreq *) \ + ((char *) ifr + sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len)) +#define IFREQ_SIZE(ifr) (sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len) +#else +#define NEXT_INTERFACE(ifr) (ifr + 1) +#define IFREQ_SIZE(ifr) sizeof(ifr[0]) +#endif + +/* ial_siocgif - determine IP addresses using ioctl(SIOCGIF*) */ + +static int ial_siocgif(INET_ADDR_LIST *addr_list, + INET_ADDR_LIST *mask_list, + int af) +{ + const char *myname = "inet_addr_local[siocgif]"; + struct in_addr addr; + struct ifconf ifc; + struct ifreq *ifr; + struct ifreq *ifr_mask; + struct ifreq *the_end; + int sock; + VSTRING *buf; + + /* + * Get the network interface list. XXX The socket API appears to have no + * function that returns the number of network interfaces, so we have to + * guess how much space is needed to store the result. + * + * On BSD-derived systems, ioctl SIOCGIFCONF returns as much information as + * possible, leaving it up to the application to repeat the request with + * a larger buffer if the result caused a tight fit. + * + * Other systems, such as Solaris 2.5, generate an EINVAL error when the + * buffer is too small for the entire result. Workaround: ignore EINVAL + * errors and repeat the request with a larger buffer. The downside is + * that the program can run out of memory due to a non-memory problem, + * making it more difficult than necessary to diagnose the real problem. + */ + sock = ial_socket(af); + if (sock < 0) + return (0); + buf = vstring_alloc(1024); + for (;;) { + ifc.ifc_len = vstring_avail(buf); + ifc.ifc_buf = vstring_str(buf); + if (ioctl(sock, SIOCGIFCONF, (char *) &ifc) < 0) { + if (errno != EINVAL) + msg_fatal("%s: ioctl SIOCGIFCONF: %m", myname); + } else if (ifc.ifc_len < vstring_avail(buf) / 2) + break; + VSTRING_SPACE(buf, vstring_avail(buf) * 2); + } + + the_end = (struct ifreq *) (ifc.ifc_buf + ifc.ifc_len); + for (ifr = ifc.ifc_req; ifr < the_end;) { + if (ifr->ifr_addr.sa_family != af) { + ifr = NEXT_INTERFACE(ifr); + continue; + } + if (af == AF_INET) { + addr = ((struct sockaddr_in *) & ifr->ifr_addr)->sin_addr; + if (addr.s_addr != INADDR_ANY) { + inet_addr_list_append(addr_list, &ifr->ifr_addr); + if (mask_list) { + ifr_mask = (struct ifreq *) mymalloc(IFREQ_SIZE(ifr)); + memcpy((void *) ifr_mask, (void *) ifr, IFREQ_SIZE(ifr)); + if (ioctl(sock, SIOCGIFNETMASK, ifr_mask) < 0) + msg_fatal("%s: ioctl SIOCGIFNETMASK: %m", myname); + + /* + * Note that this SIOCGIFNETMASK has truly screwed up the + * contents of sa_len/sa_family. We must fix this + * manually to have correct addresses. --dcs + */ + ifr_mask->ifr_addr.sa_family = af; +#ifdef HAS_SA_LEN + ifr_mask->ifr_addr.sa_len = sizeof(struct sockaddr_in); +#endif + inet_addr_list_append(mask_list, &ifr_mask->ifr_addr); + myfree((void *) ifr_mask); + } + } + } +#ifdef HAS_IPV6 + else if (af == AF_INET6) { + struct sockaddr *sa; + + sa = SOCK_ADDR_PTR(&ifr->ifr_addr); + if (!(IN6_IS_ADDR_UNSPECIFIED(&SOCK_ADDR_IN6_ADDR(sa)))) { + inet_addr_list_append(addr_list, sa); + if (mask_list) { + /* XXX Assume /128 for everything */ + struct sockaddr_in6 mask6; + + mask6 = *SOCK_ADDR_IN6_PTR(sa); + memset((void *) &mask6.sin6_addr, ~0, + sizeof(mask6.sin6_addr)); + inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask6)); + } + } + } +#endif + ifr = NEXT_INTERFACE(ifr); + } + vstring_free(buf); + (void) close(sock); + return (0); +} + +#endif /* HAVE_SIOCGLIF */ + +#ifdef HAS_PROCNET_IFINET6 + +/* + * Older Linux versions lack proper calls to retrieve IPv6 interface + * addresses. Instead, the addresses can be read from a file in the + * /proc tree. The most important issue with this approach however + * is that the /proc tree may not always be available, for example + * in a chrooted environment or in "hardened" (sic) installations. + */ + +/* ial_procnet_ifinet6 - determine IPv6 addresses using /proc/net/if_inet6 */ + +static int ial_procnet_ifinet6(INET_ADDR_LIST *addr_list, + INET_ADDR_LIST *mask_list) +{ + const char *myname = "inet_addr_local[procnet_ifinet6]"; + FILE *fp; + char buf[BUFSIZ]; + unsigned plen; + VSTRING *addrbuf; + struct sockaddr_in6 addr; + struct sockaddr_in6 mask; + + /* + * Example: 00000000000000000000000000000001 01 80 10 80 lo + * + * Fields: address, interface index, prefix length, scope value + * (net/ipv6.h), interface flags (linux/rtnetlink.h), device name. + * + * FIX 200501 The IPv6 patch used fscanf(), which will hang on unexpected + * input. Use fgets() + sscanf() instead. + */ + if ((fp = fopen(_PATH_PROCNET_IFINET6, "r")) != 0) { + addrbuf = vstring_alloc(MAI_V6ADDR_BYTES + 1); + memset((void *) &addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; +#ifdef HAS_SA_LEN + addr.sin6_len = sizeof(addr); +#endif + mask = addr; + while (fgets(buf, sizeof(buf), fp) != 0) { + /* 200501 hex_decode() is light-weight compared to getaddrinfo(). */ + if (hex_decode(addrbuf, buf, MAI_V6ADDR_BYTES * 2) == 0 + || sscanf(buf + MAI_V6ADDR_BYTES * 2, " %*x %x", &plen) != 1 + || plen > MAI_V6ADDR_BITS) { + msg_warn("unexpected data in %s - skipping IPv6 configuration", + _PATH_PROCNET_IFINET6); + break; + } + /* vstring_str(addrbuf) has worst-case alignment. */ + addr.sin6_addr = *(struct in6_addr *) vstring_str(addrbuf); + inet_addr_list_append(addr_list, SOCK_ADDR_PTR(&addr)); + + memset((void *) &mask.sin6_addr, ~0, sizeof(mask.sin6_addr)); + mask_addr((unsigned char *) &mask.sin6_addr, + sizeof(mask.sin6_addr), plen); + inet_addr_list_append(mask_list, SOCK_ADDR_PTR(&mask)); + } + vstring_free(addrbuf); + fclose(fp); /* FIX 200501 */ + } else { + msg_warn("can't open %s (%m) - skipping IPv6 configuration", + _PATH_PROCNET_IFINET6); + } + return (0); +} + +#endif /* HAS_PROCNET_IFINET6 */ + +/* inet_addr_local - find all IP addresses for this host */ + +int inet_addr_local(INET_ADDR_LIST *addr_list, INET_ADDR_LIST *mask_list, + unsigned *addr_family_list) +{ + const char *myname = "inet_addr_local"; + int initial_count = addr_list->used; + unsigned family; + int count; + + while ((family = *addr_family_list++) != 0) { + + /* + * IP Version 4 + */ + if (family == AF_INET) { + count = addr_list->used; +#if defined(HAVE_GETIFADDRS) + ial_getifaddrs(addr_list, mask_list, AF_INET); +#elif defined (HAS_SIOCGLIF) + ial_siocglif(addr_list, mask_list, AF_INET); +#else + ial_siocgif(addr_list, mask_list, AF_INET); +#endif + if (msg_verbose) + msg_info("%s: configured %d IPv4 addresses", + myname, addr_list->used - count); + } + + /* + * IP Version 6 + */ +#ifdef HAS_IPV6 + else if (family == AF_INET6) { + count = addr_list->used; +#if defined(HAVE_GETIFADDRS) + ial_getifaddrs(addr_list, mask_list, AF_INET6); +#elif defined(HAS_PROCNET_IFINET6) + ial_procnet_ifinet6(addr_list, mask_list); +#elif defined(HAS_SIOCGLIF) + ial_siocglif(addr_list, mask_list, AF_INET6); +#else + ial_siocgif(addr_list, mask_list, AF_INET6); +#endif + if (msg_verbose) + msg_info("%s: configured %d IPv6 addresses", myname, + addr_list->used - count); + } +#endif + + /* + * Something's not right. + */ + else + msg_panic("%s: unknown address family %d", myname, family); + } + return (addr_list->used - initial_count); +} + +#ifdef TEST + +#include +#include +#include +#include + +int main(int unused_argc, char **argv) +{ + INET_ADDR_LIST addr_list; + INET_ADDR_LIST mask_list; + MAI_HOSTADDR_STR hostaddr; + MAI_HOSTADDR_STR hostmask; + struct sockaddr *sa; + int i; + INET_PROTO_INFO *proto_info; + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + + proto_info = inet_proto_init(argv[0], + argv[1] ? argv[1] : INET_PROTO_NAME_ALL); + inet_addr_list_init(&addr_list); + inet_addr_list_init(&mask_list); + inet_addr_local(&addr_list, &mask_list, proto_info->ai_family_list); + + if (addr_list.used == 0) + msg_fatal("cannot find any active network interfaces"); + + if (addr_list.used == 1) + msg_warn("found only one active network interface"); + + for (i = 0; i < addr_list.used; i++) { + sa = SOCK_ADDR_PTR(addr_list.addrs + i); + SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa), + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + sa = SOCK_ADDR_PTR(mask_list.addrs + i); + SOCKADDR_TO_HOSTADDR(SOCK_ADDR_PTR(sa), SOCK_ADDR_LEN(sa), + &hostmask, (MAI_SERVPORT_STR *) 0, 0); + vstream_printf("%s/%s\n", hostaddr.buf, hostmask.buf); + vstream_fflush(VSTREAM_OUT); + } + inet_addr_list_free(&addr_list); + inet_addr_list_free(&mask_list); + return (0); +} + +#endif diff --git a/src/util/inet_addr_local.h b/src/util/inet_addr_local.h new file mode 100644 index 0000000..cb6b3f2 --- /dev/null +++ b/src/util/inet_addr_local.h @@ -0,0 +1,35 @@ +#ifndef _INET_ADDR_LOCAL_H_INCLUDED_ +#define _INET_ADDR_LOCAL_H_INCLUDED_ + +/*++ +/* NAME +/* inet_addr_local 3h +/* SUMMARY +/* determine if IP address is local +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern int inet_addr_local(INET_ADDR_LIST *, INET_ADDR_LIST *, unsigned *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/inet_connect.c b/src/util/inet_connect.c new file mode 100644 index 0000000..0f5542e --- /dev/null +++ b/src/util/inet_connect.c @@ -0,0 +1,189 @@ +/*++ +/* NAME +/* inet_connect 3 +/* SUMMARY +/* connect to TCP listener +/* SYNOPSIS +/* #include +/* +/* int inet_windowsize; +/* +/* int inet_connect(addr, block_mode, timeout) +/* const char *addr; +/* int block_mode; +/* int timeout; +/* DESCRIPTION +/* inet_connect connects to a TCP listener at +/* the specified address, and returns the resulting file descriptor. +/* +/* Specify an inet_windowsize value > 0 to override the TCP +/* window size that the client advertises to the server. +/* +/* Arguments: +/* .IP addr +/* The destination to connect to. The format is host:port. If no +/* host is specified, a port on the local host is assumed. +/* Host and port information may be given in numerical form +/* or as symbolical names. +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* .IP timeout +/* Bounds the number of seconds that the operation may take. Specify +/* a value <= 0 to disable the time limit. +/* DIAGNOSTICS +/* The result is -1 when the connection could not be made. +/* The nature of the error is available via the global \fIerrno\fR +/* variable. +/* Fatal errors: other system call failures. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System interfaces. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "iostuff.h" +#include "host_port.h" +#include "sane_connect.h" +#include "connect.h" +#include "timed_connect.h" +#include "myaddrinfo.h" +#include "sock_addr.h" +#include "inet_proto.h" + +static int inet_connect_one(struct addrinfo *, int, int); + +/* inet_connect - connect to TCP listener */ + +int inet_connect(const char *addr, int block_mode, int timeout) +{ + char *buf; + char *host; + char *port; + const char *parse_err; + struct addrinfo *res; + struct addrinfo *res0; + int aierr; + int sock; + MAI_HOSTADDR_STR hostaddr; + const INET_PROTO_INFO *proto_info; + int found; + + /* + * Translate address information to internal form. No host defaults to + * the local host. + */ + buf = mystrdup(addr); + if ((parse_err = host_port(buf, &host, "localhost", &port, (char *) 0)) != 0) + msg_fatal("%s: %s", addr, parse_err); + if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res0)) != 0) + msg_warn("host or service %s not found: %s", + addr, MAI_STRERROR(aierr)); + myfree(buf); + if (aierr) { + errno = EADDRNOTAVAIL; /* for up-stream "%m" */ + return (-1); + } + proto_info = inet_proto_info(); + for (sock = -1, found = 0, res = res0; res != 0; res = res->ai_next) { + + /* + * Safety net. + */ + if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + msg_info("skipping address family %d for host %s", + res->ai_family, host); + continue; + } + found++; + + /* + * In case of multiple addresses, show what address we're trying now. + */ + if (msg_verbose) { + SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen, + &hostaddr, (MAI_SERVPORT_STR *) 0, 0); + msg_info("trying... [%s]", hostaddr.buf); + } + if ((sock = inet_connect_one(res, block_mode, timeout)) < 0) { + if (msg_verbose) + msg_info("%m"); + } else + break; + } + if (found == 0) + msg_fatal("host not found: %s", addr); + freeaddrinfo(res0); + return (sock); +} + +/* inet_connect_one - try to connect to one address */ + +static int inet_connect_one(struct addrinfo * res, int block_mode, int timeout) +{ + int sock; + + /* + * Create a client socket. + */ + sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sock < 0) + return (-1); + + /* + * Window scaling workaround. + */ + if (inet_windowsize > 0) + set_inet_windowsize(sock, inet_windowsize); + + /* + * Timed connect. + */ + if (timeout > 0) { + non_blocking(sock, NON_BLOCKING); + if (timed_connect(sock, res->ai_addr, res->ai_addrlen, timeout) < 0) { + close(sock); + return (-1); + } + if (block_mode != NON_BLOCKING) + non_blocking(sock, block_mode); + return (sock); + } + + /* + * Maybe block until connected. + */ + else { + non_blocking(sock, block_mode); + if (sane_connect(sock, res->ai_addr, res->ai_addrlen) < 0 + && errno != EINPROGRESS) { + close(sock); + return (-1); + } + return (sock); + } +} diff --git a/src/util/inet_listen.c b/src/util/inet_listen.c new file mode 100644 index 0000000..31800cd --- /dev/null +++ b/src/util/inet_listen.c @@ -0,0 +1,189 @@ +/*++ +/* NAME +/* inet_listen 3 +/* SUMMARY +/* start TCP listener +/* SYNOPSIS +/* #include +/* +/* int inet_windowsize; +/* +/* int inet_listen(addr, backlog, block_mode) +/* const char *addr; +/* int backlog; +/* int block_mode; +/* +/* int inet_accept(fd) +/* int fd; +/* DESCRIPTION +/* The \fBinet_listen\fR routine starts a TCP listener +/* on the specified address, with the specified backlog, and returns +/* the resulting file descriptor. +/* +/* inet_accept() accepts a connection and sanitizes error results. +/* +/* Specify an inet_windowsize value > 0 to override the TCP +/* window size that the server advertises to the client. +/* +/* Arguments: +/* .IP addr +/* The communication endpoint to listen on. The syntax is "host:port". +/* Host and port may be specified in symbolic form or numerically. +/* A null host field means listen on all network interfaces. +/* .IP backlog +/* This argument is passed on to the \fIlisten(2)\fR routine. +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* .IP fd +/* File descriptor returned by inet_listen(). +/* DIAGNOSTICS +/* Fatal errors: inet_listen() aborts upon any system call failure. +/* inet_accept() leaves all error handling up to the caller. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include +#include +#include +#ifndef MAXHOSTNAMELEN +#include +#endif +#include +#include +#include + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "host_port.h" +#include "iostuff.h" +#include "listen.h" +#include "sane_accept.h" +#include "myaddrinfo.h" +#include "sock_addr.h" +#include "inet_proto.h" + +/* inet_listen - create TCP listener */ + +int inet_listen(const char *addr, int backlog, int block_mode) +{ + struct addrinfo *res; + struct addrinfo *res0; + int aierr; + int sock; + int on = 1; + char *buf; + char *host; + char *port; + const char *parse_err; + MAI_HOSTADDR_STR hostaddr; + MAI_SERVPORT_STR portnum; + const INET_PROTO_INFO *proto_info; + + /* + * Translate address information to internal form. + */ + buf = mystrdup(addr); + if ((parse_err = host_port(buf, &host, "", &port, (char *) 0)) != 0) + msg_fatal("%s: %s", addr, parse_err); + if (*host == 0) + host = 0; + if ((aierr = hostname_to_sockaddr(host, port, SOCK_STREAM, &res0)) != 0) + msg_fatal("%s: %s", addr, MAI_STRERROR(aierr)); + myfree(buf); + /* No early returns or res0 leaks. */ + + proto_info = inet_proto_info(); + for (res = res0; /* see below */ ; res = res->ai_next) { + + /* + * No usable address found. + */ + if (res == 0) + msg_fatal("%s: host found but no usable address", addr); + + /* + * Safety net. + */ + if (strchr((char *) proto_info->sa_family_list, res->ai_family) != 0) + break; + + msg_info("skipping address family %d for %s", res->ai_family, addr); + } + + /* + * Show what address we're trying. + */ + if (msg_verbose) { + SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen, + &hostaddr, &portnum, 0); + msg_info("trying... [%s]:%s", hostaddr.buf, portnum.buf); + } + + /* + * Create a listener socket. + */ + if ((sock = socket(res->ai_family, res->ai_socktype, 0)) < 0) + msg_fatal("socket: %m"); +#ifdef HAS_IPV6 +#if defined(IPV6_V6ONLY) && !defined(BROKEN_AI_PASSIVE_NULL_HOST) + if (res->ai_family == AF_INET6 + && setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + (void *) &on, sizeof(on)) < 0) + msg_fatal("setsockopt(IPV6_V6ONLY): %m"); +#endif +#endif + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + (void *) &on, sizeof(on)) < 0) + msg_fatal("setsockopt(SO_REUSEADDR): %m"); +#if defined(SO_REUSEPORT_LB) + if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT_LB, + (void *) &on, sizeof(on)) < 0) + msg_fatal("setsockopt(SO_REUSEPORT_LB): %m"); +#elif defined(SO_REUSEPORT) + if (setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, + (void *) &on, sizeof(on)) < 0) + msg_fatal("setsockopt(SO_REUSEPORT): %m"); +#endif + if (bind(sock, res->ai_addr, res->ai_addrlen) < 0) { + SOCKADDR_TO_HOSTADDR(res->ai_addr, res->ai_addrlen, + &hostaddr, &portnum, 0); + msg_fatal("bind %s port %s: %m", hostaddr.buf, portnum.buf); + } + freeaddrinfo(res0); + non_blocking(sock, block_mode); + if (inet_windowsize > 0) + set_inet_windowsize(sock, inet_windowsize); + if (listen(sock, backlog) < 0) + msg_fatal("listen: %m"); + return (sock); +} + +/* inet_accept - accept connection */ + +int inet_accept(int fd) +{ + struct sockaddr_storage ss; + SOCKADDR_SIZE ss_len = sizeof(ss); + + return (sane_accept(fd, (struct sockaddr *) &ss, &ss_len)); +} diff --git a/src/util/inet_proto.c b/src/util/inet_proto.c new file mode 100644 index 0000000..fedf761 --- /dev/null +++ b/src/util/inet_proto.c @@ -0,0 +1,328 @@ +/*++ +/* NAME +/* inet_proto 3 +/* SUMMARY +/* convert protocol names to assorted constants +/* SYNOPSIS +/* #include +/* +/* typedef struct { +/* .in +4 +/* unsigned ai_family; /* PF_UNSPEC, PF_INET, or PF_INET6 */ +/* unsigned *ai_family_list; /* PF_INET and/or PF_INET6 */ +/* unsigned *dns_atype_list;/* TAAAA and/or TA */ +/* unsigned char *sa_family_list;/* AF_INET6 and/or AF_INET */ +/* .in -4 +/* } INET_PROTO_INFO; +/* +/* const INET_PROTO_INFO *inet_proto_init(context, protocols) +/* +/* const INET_PROTO_INFO *inet_proto_info() +/* DESCRIPTION +/* inet_proto_init() converts a string with protocol names +/* into null-terminated lists of appropriate constants used +/* by Postfix library routines. The idea is that one should +/* be able to configure an MTA for IPv4 only, without having +/* to recompile code (what a concept). +/* +/* Unfortunately, some compilers won't link initialized data +/* without a function call into the same source module, so +/* we invoke inet_proto_info() in order to access the result +/* from inet_proto_init() from within library routines. +/* inet_proto_info() also conveniently initializes the data +/* to built-in defaults. +/* +/* Arguments: +/* .IP context +/* Typically, a configuration parameter name. +/* .IP protocols +/* Null-terminated string with protocol names separated by +/* whitespace and/or commas: +/* .RS +/* .IP INET_PROTO_NAME_ALL +/* Enable all available IP protocols. +/* .IP INET_PROTO_NAME_IPV4 +/* Enable IP version 4 support. +/* .IP INET_PROTO_NAME_IPV6 +/* Enable IP version 6 support. +/* .RS +/* .PP +/* Results: +/* .IP ai_family +/* Only one of PF_UNSPEC, PF_INET, or PF_INET6. This can be +/* used as input for the getaddrinfo() and getnameinfo() +/* routines. +/* .IP ai_family_list +/* One or more of PF_INET or PF_INET6. This can be used as +/* input for the inet_addr_local() routine. +/* .IP dns_atype_list +/* One or more of T_AAAA or T_A. This can be used as input for +/* the dns_lookup_v() and dns_lookup_l() routines. +/* .IP sa_family_list +/* One or more of AF_INET6 or AF_INET. This can be used as an +/* output filter for the results from the getaddrinfo() and +/* getnameinfo() routines. +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* This module will warn and turn off support for any protocol +/* that is requested but unavailable. +/* +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#ifdef RESOLVE_H_NEEDS_STDIO_H +#include +#endif +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + + /* + * Application-specific. + */ + + /* + * Run-time initialization, so we can work around LINUX where IPv6 falls + * flat on its face because it is not turned on in the kernel. + */ +INET_PROTO_INFO *inet_proto_table = 0; + + /* + * Infrastructure: lookup table with the protocol names that we support. + */ +#define INET_PROTO_MASK_IPV4 (1<<0) +#define INET_PROTO_MASK_IPV6 (1<<1) + +static const NAME_MASK proto_table[] = { +#ifdef HAS_IPV6 + INET_PROTO_NAME_ALL, INET_PROTO_MASK_IPV4 | INET_PROTO_MASK_IPV6, + INET_PROTO_NAME_IPV6, INET_PROTO_MASK_IPV6, +#else + INET_PROTO_NAME_ALL, INET_PROTO_MASK_IPV4, +#endif + INET_PROTO_NAME_IPV4, INET_PROTO_MASK_IPV4, + 0, +}; + +/* make_uchar_vector - create and initialize uchar vector */ + +static unsigned char *make_uchar_vector(int len,...) +{ + const char *myname = "make_uchar_vector"; + va_list ap; + int count; + unsigned char *vp; + + va_start(ap, len); + if (len <= 0) + msg_panic("%s: bad vector length: %d", myname, len); + vp = (unsigned char *) mymalloc(sizeof(*vp) * len); + for (count = 0; count < len; count++) + vp[count] = va_arg(ap, unsigned); + va_end(ap); + return (vp); +} + +/* make_unsigned_vector - create and initialize integer vector */ + +static unsigned *make_unsigned_vector(int len,...) +{ + const char *myname = "make_unsigned_vector"; + va_list ap; + int count; + unsigned *vp; + + va_start(ap, len); + if (len <= 0) + msg_panic("%s: bad vector length: %d", myname, len); + vp = (unsigned *) mymalloc(sizeof(*vp) * len); + for (count = 0; count < len; count++) + vp[count] = va_arg(ap, unsigned); + va_end(ap); + return (vp); +} + +/* inet_proto_free - destroy data */ + +static void inet_proto_free(INET_PROTO_INFO *pf) +{ + myfree((void *) pf->ai_family_list); + myfree((void *) pf->dns_atype_list); + myfree((void *) pf->sa_family_list); + myfree((void *) pf); +} + +/* inet_proto_init - convert protocol names to library inputs */ + +const INET_PROTO_INFO *inet_proto_init(const char *context, const char *protocols) +{ + const char *myname = "inet_proto"; + INET_PROTO_INFO *pf; + int inet_proto_mask; + int sock; + + /* + * Avoid run-time errors when all network protocols are disabled. We + * can't look up interface information, and we can't convert explicit + * names or addresses. + */ + inet_proto_mask = name_mask(context, proto_table, protocols); +#ifdef HAS_IPV6 + if (inet_proto_mask & INET_PROTO_MASK_IPV6) { + if ((sock = socket(PF_INET6, SOCK_STREAM, 0)) >= 0) { + close(sock); + } else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) { + msg_warn("%s: disabling IPv6 name/address support: %m", context); + inet_proto_mask &= ~INET_PROTO_MASK_IPV6; + } else { + msg_fatal("socket: %m"); + } + } +#endif + if (inet_proto_mask & INET_PROTO_MASK_IPV4) { + if ((sock = socket(PF_INET, SOCK_STREAM, 0)) >= 0) { + close(sock); + } else if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) { + msg_warn("%s: disabling IPv4 name/address support: %m", context); + inet_proto_mask &= ~INET_PROTO_MASK_IPV4; + } else { + msg_fatal("socket: %m"); + } + } + + /* + * Store address family etc. info as null-terminated vectors. If that + * breaks because we must be able to store nulls, we'll deal with the + * additional complexity. + * + * XXX Use compile-time initialized data templates instead of building the + * reply on the fly. + */ + switch (inet_proto_mask) { +#ifdef HAS_IPV6 + case INET_PROTO_MASK_IPV6: + pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf)); + pf->ai_family = PF_INET6; + pf->ai_family_list = make_unsigned_vector(2, PF_INET6, 0); + pf->dns_atype_list = make_unsigned_vector(2, T_AAAA, 0); + pf->sa_family_list = make_uchar_vector(2, AF_INET6, 0); + break; + case (INET_PROTO_MASK_IPV6 | INET_PROTO_MASK_IPV4): + pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf)); + pf->ai_family = PF_UNSPEC; + pf->ai_family_list = make_unsigned_vector(3, PF_INET, PF_INET6, 0); + pf->dns_atype_list = make_unsigned_vector(3, T_A, T_AAAA, 0); + pf->sa_family_list = make_uchar_vector(3, AF_INET, AF_INET6, 0); + break; +#endif + case INET_PROTO_MASK_IPV4: + pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf)); + pf->ai_family = PF_INET; + pf->ai_family_list = make_unsigned_vector(2, PF_INET, 0); + pf->dns_atype_list = make_unsigned_vector(2, T_A, 0); + pf->sa_family_list = make_uchar_vector(2, AF_INET, 0); + break; + case 0: + pf = (INET_PROTO_INFO *) mymalloc(sizeof(*pf)); + pf->ai_family = PF_UNSPEC; + pf->ai_family_list = make_unsigned_vector(1, 0); + pf->dns_atype_list = make_unsigned_vector(1, 0); + pf->sa_family_list = make_uchar_vector(1, 0); + break; + default: + msg_panic("%s: bad inet_proto_mask 0x%x", myname, inet_proto_mask); + } + if (inet_proto_table) + inet_proto_free(inet_proto_table); + return (inet_proto_table = pf); +} + +#ifdef TEST + + /* + * Small driver for unit tests. + */ + +static char *print_unsigned_vector(VSTRING *buf, unsigned *vector) +{ + unsigned *p; + + VSTRING_RESET(buf); + for (p = vector; *p; p++) { + vstring_sprintf_append(buf, "%u", *p); + if (p[1]) + VSTRING_ADDCH(buf, ' '); + } + VSTRING_TERMINATE(buf); + return (vstring_str(buf)); +} + +static char *print_uchar_vector(VSTRING *buf, unsigned char *vector) +{ + unsigned char *p; + + VSTRING_RESET(buf); + for (p = vector; *p; p++) { + vstring_sprintf_append(buf, "%u", *p); + if (p[1]) + VSTRING_ADDCH(buf, ' '); + } + VSTRING_TERMINATE(buf); + return (vstring_str(buf)); +} + +int main(int argc, char **argv) +{ + const char *myname = argv[0]; + INET_PROTO_INFO *pf; + VSTRING *buf; + + if (argc < 2) + msg_fatal("usage: %s protocol(s)...", myname); + + buf = vstring_alloc(10); + while (*++argv) { + msg_info("=== %s ===", *argv); + inet_proto_init(myname, *argv); + pf = inet_proto_table; + msg_info("ai_family = %u", pf->ai_family); + msg_info("ai_family_list = %s", + print_unsigned_vector(buf, pf->ai_family_list)); + msg_info("dns_atype_list = %s", + print_unsigned_vector(buf, pf->dns_atype_list)); + msg_info("sa_family_list = %s", + print_uchar_vector(buf, pf->sa_family_list)); + } + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/inet_proto.h b/src/util/inet_proto.h new file mode 100644 index 0000000..9175eae --- /dev/null +++ b/src/util/inet_proto.h @@ -0,0 +1,56 @@ +#ifndef _INET_PROTO_INFO_H_INCLUDED_ +#define _INET_PROTO_INFO_H_INCLUDED_ + +/*++ +/* NAME +/* inet_proto_info 3h +/* SUMMARY +/* convert protocol names to assorted constants +/* SYNOPSIS +/* #include + DESCRIPTION + .nf + + /* + * External interface. + */ +typedef struct { + unsigned int ai_family; /* PF_UNSPEC, PF_INET, or PF_INET6 */ + unsigned int *ai_family_list; /* PF_INET and/or PF_INET6 */ + unsigned int *dns_atype_list; /* TAAAA and/or TA */ + unsigned char *sa_family_list; /* AF_INET6 and/or AF_INET */ +} INET_PROTO_INFO; + + /* + * Some compilers won't link initialized data unless we call a function in + * the same source file. Therefore, inet_proto_info() is a function instead + * of a global variable. + */ +#define inet_proto_info() \ + (inet_proto_table ? (const INET_PROTO_INFO*) inet_proto_table : \ + inet_proto_init("default protocol setting", DEF_INET_PROTOCOLS)) + +extern const INET_PROTO_INFO *inet_proto_init(const char *, const char *); +extern INET_PROTO_INFO *inet_proto_table; + +#define INET_PROTO_NAME_IPV6 "ipv6" +#define INET_PROTO_NAME_IPV4 "ipv4" +#define INET_PROTO_NAME_ALL "all" + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/inet_trigger.c b/src/util/inet_trigger.c new file mode 100644 index 0000000..b1734ee --- /dev/null +++ b/src/util/inet_trigger.c @@ -0,0 +1,130 @@ +/*++ +/* NAME +/* inet_trigger 3 +/* SUMMARY +/* wakeup INET-domain server +/* SYNOPSIS +/* #include +/* +/* int inet_trigger(service, buf, len, timeout) +/* char *service; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* inet_trigger() wakes up the named INET-domain server by making +/* a brief connection to it and by writing the contents of the +/* named buffer. +/* +/* The connection is closed by a background thread. Some kernels +/* cannot handle client-side disconnect before the server has +/* received the message. +/* +/* Arguments: +/* .IP service +/* Name of the communication endpoint. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Deadline in seconds. Specify a value <= 0 to disable +/* the time limit. +/* DIAGNOSTICS +/* The result is zero in case of success, -1 in case of problems. +/* BUGS +/* SEE ALSO +/* inet_connect(3), INET-domain client +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +struct inet_trigger { + int fd; + char *service; +}; + +/* inet_trigger_event - disconnect from peer */ + +static void inet_trigger_event(int event, void *context) +{ + struct inet_trigger *ip = (struct inet_trigger *) context; + static const char *myname = "inet_trigger_event"; + + /* + * Disconnect. + */ + if (event == EVENT_TIME) + msg_warn("%s: read timeout for service %s", myname, ip->service); + event_disable_readwrite(ip->fd); + event_cancel_timer(inet_trigger_event, context); + if (close(ip->fd) < 0) + msg_warn("%s: close %s: %m", myname, ip->service); + myfree(ip->service); + myfree((void *) ip); +} + + +/* inet_trigger - wakeup INET-domain server */ + +int inet_trigger(const char *service, const char *buf, ssize_t len, int timeout) +{ + const char *myname = "inet_trigger"; + struct inet_trigger *ip; + int fd; + + if (msg_verbose > 1) + msg_info("%s: service %s", myname, service); + + /* + * Connect... + */ + if ((fd = inet_connect(service, BLOCKING, timeout)) < 0) { + if (msg_verbose) + msg_warn("%s: connect to %s: %m", myname, service); + return (-1); + } + close_on_exec(fd, CLOSE_ON_EXEC); + ip = (struct inet_trigger *) mymalloc(sizeof(*ip)); + ip->fd = fd; + ip->service = mystrdup(service); + + /* + * Write the request... + */ + if (write_buf(fd, buf, len, timeout) < 0 + || write_buf(fd, "", 1, timeout) < 0) + if (msg_verbose) + msg_warn("%s: write to %s: %m", myname, service); + + /* + * Wakeup when the peer disconnects, or when we lose patience. + */ + if (timeout > 0) + event_request_timer(inet_trigger_event, (void *) ip, timeout + 100); + event_enable_read(fd, inet_trigger_event, (void *) ip); + return (0); +} diff --git a/src/util/inet_windowsize.c b/src/util/inet_windowsize.c new file mode 100644 index 0000000..e982149 --- /dev/null +++ b/src/util/inet_windowsize.c @@ -0,0 +1,82 @@ +/*++ +/* NAME +/* inet_windowsize 3 +/* SUMMARY +/* TCP window scaling control +/* SYNOPSIS +/* #include +/* +/* int inet_windowsize; +/* +/* void set_inet_windowsize(sock, windowsize) +/* int sock; +/* int windowsize; +/* DESCRIPTION +/* set_inet_windowsize() overrides the default TCP window size +/* with the specified value. When called before listen() or +/* accept(), this works around broken infrastructure that +/* mis-handles TCP window scaling options. +/* +/* The global inet_windowsize variable is available for other +/* routines to remember that they wish to override the default +/* TCP window size. The variable is not accessed by the +/* set_inet_windowsize() function itself. +/* +/* Arguments: +/* .IP sock +/* TCP communication endpoint, before the connect(2) or listen(2) call. +/* .IP windowsize +/* The preferred TCP window size. This must be > 0. +/* DIAGNOSTICS +/* Panic: interface violation. +/* Warnings: some error return from setsockopt(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include + +/* Utility library. */ + +#include +#include + +/* Application storage. */ + + /* + * Tunable to work around broken routers. + */ +int inet_windowsize = 0; + +/* set_inet_windowsize - set TCP send/receive window size */ + +void set_inet_windowsize(int sock, int windowsize) +{ + + /* + * Sanity check. + */ + if (windowsize <= 0) + msg_panic("inet_windowsize: bad window size %d", windowsize); + + /* + * Generic implementation: set the send and receive buffer size before + * listen() or connect(). + */ + if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *) &windowsize, + sizeof(windowsize)) < 0) + msg_warn("setsockopt SO_SNDBUF %d: %m", windowsize); + if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *) &windowsize, + sizeof(windowsize)) < 0) + msg_warn("setsockopt SO_RCVBUF %d: %m", windowsize); +} diff --git a/src/util/iostuff.h b/src/util/iostuff.h new file mode 100644 index 0000000..0c54d03 --- /dev/null +++ b/src/util/iostuff.h @@ -0,0 +1,73 @@ +#ifndef _IOSTUFF_H_INCLUDED_ +#define _IOSTUFF_H_INCLUDED_ + +/*++ +/* NAME +/* iostuff 3h +/* SUMMARY +/* miscellaneous I/O primitives +/* SYNOPSIS +/* #include +/* DESCRIPTION + + /* + * External interface. + */ +extern int non_blocking(int, int); +extern int close_on_exec(int, int); +extern int open_limit(int); +extern int poll_fd(int, int, int, int, int); +extern off_t get_file_limit(void); +extern void set_file_limit(off_t); +extern ssize_t peekfd(int); +extern ssize_t write_buf(int, const char *, ssize_t, int); +extern ssize_t timed_read(int, void *, size_t, int, void *); +extern ssize_t timed_write(int, const void *, size_t, int, void *); +extern void doze(unsigned); +extern void rand_sleep(unsigned, unsigned); +extern int duplex_pipe(int *); +extern int stream_recv_fd(int); +extern int stream_send_fd(int, int); +extern int unix_recv_fd(int); +extern int unix_send_fd(int, int); +extern ssize_t dummy_read(int, void *, size_t, int, void *); +extern ssize_t dummy_write(int, void *, size_t, int, void *); + +#define readable(fd) poll_fd((fd), POLL_FD_READ, 0, 1, 0) +#define writable(fd) poll_fd((fd), POLL_FD_WRITE, 0, 1, 0) + +#define read_wait(fd, timeout) poll_fd((fd), POLL_FD_READ, (timeout), 0, -1) +#define write_wait(fd, timeout) poll_fd((fd), POLL_FD_WRITE, (timeout), 0, -1) + +extern int inet_windowsize; +extern void set_inet_windowsize(int, int); + +#define POLL_FD_READ 0 +#define POLL_FD_WRITE 1 + +#define BLOCKING 0 +#define NON_BLOCKING 1 + +#define CLOSE_ON_EXEC 1 +#define PASS_ON_EXEC 0 + +extern int unix_pass_fd_fix; +extern void set_unix_pass_fd_fix(const char *); + +#define UNIX_PASS_FD_FIX_NONE (0) +#define UNIX_PASS_FD_FIX_CMSG_LEN (1<<0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* CREATION DATE +/* Sat Jan 25 16:54:13 EST 1997 +/*--*/ + +#endif diff --git a/src/util/ip_match.c b/src/util/ip_match.c new file mode 100644 index 0000000..aeea799 --- /dev/null +++ b/src/util/ip_match.c @@ -0,0 +1,676 @@ +/*++ +/* NAME +/* ip_match 3 +/* SUMMARY +/* IP address pattern matching +/* SYNOPSIS +/* #include +/* +/* char *ip_match_parse(byte_codes, pattern) +/* VSTRING *byte_codes; +/* char *pattern; +/* +/* char *ip_match_save(byte_codes) +/* const VSTRING *byte_codes; +/* +/* int ip_match_execute(byte_codes, addr_bytes) +/* cost char *byte_codes; +/* const char *addr_bytes; +/* +/* char *ip_match_dump(printable, byte_codes) +/* VSTRING *printable; +/* const char *byte_codes; +/* DESCRIPTION +/* This module supports IP address pattern matching. See below +/* for a description of the supported address pattern syntax. +/* +/* This implementation aims to minimize the cost of encoding +/* the pattern in internal form, while still providing good +/* matching performance in the typical case. The first byte +/* of an encoded pattern specifies the expected address family +/* (for example, AF_INET); other details of the encoding are +/* private and are subject to change. +/* +/* ip_match_parse() converts the user-specified pattern to +/* internal form. The result value is a null pointer in case +/* of success, or a pointer into the byte_codes buffer with a +/* detailed problem description. +/* +/* ip_match_save() saves the result from ip_match_parse() for +/* longer-term usage. The result should be passed to myfree(). +/* +/* ip_match_execute() matches a binary network in addr_bytes +/* against a byte-code array in byte_codes. It is an error to +/* use different address families for the byte_codes and addr_bytes +/* arguments (the first byte-code value contains the expected +/* address family). The result is non-zero in case of success. +/* +/* ip_match_dump() produces an ASCII dump of a byte-code array. +/* The dump is supposed to be identical to the input pattern +/* modulo upper/lower case or leading nulls with IPv6). This +/* function is primarily a debugging aid. +/* +/* Arguments +/* .IP addr_bytes +/* Binary network address in network-byte order. +/* .IP byte_codes +/* Byte-code array produced by ip_match_parse(). +/* .IP pattern +/* Human-readable address pattern. +/* .IP printable +/* storage for ASCII dump of a byte-code array. +/* IPV4 PATTERN SYNTAX +/* .ad +/* .fi +/* An IPv4 address pattern has four fields separated by ".". +/* Each field is either a decimal number, or a sequence inside +/* "[]" that contains one or more ";"-separated decimal +/* numbers or number..number ranges. +/* +/* Examples of patterns are 1.2.3.4 (matches itself, as one +/* would expect) and 1.2.3.[2,4,6..8] (matches 1.2.3.2, 1.2.3.4, +/* 1.2.3.6, 1.2.3.7, 1.2.3.8). +/* +/* Thus, any pattern field can be a sequence inside "[]", but +/* a "[]" sequence cannot span multiple address fields, and +/* a pattern field cannot contain both a number and a "[]" +/* sequence at the same time. +/* +/* This means that the pattern 1.2.[3.4] is not valid (the +/* sequence [3.4] cannot span two address fields) and the +/* pattern 1.2.3.3[6..9] is also not valid (the last field +/* cannot be both number 3 and sequence [6..9] at the same +/* time). +/* +/* The syntax for IPv4 patterns is as follows: +/* +/* .in +5 +/* v4pattern = v4field "." v4field "." v4field "." v4field +/* .br +/* v4field = v4octet | "[" v4sequence "]" +/* .br +/* v4octet = any decimal number in the range 0 through 255 +/* .br +/* v4sequence = v4seq_member | v4sequence ";" v4seq_member +/* .br +/* v4seq_member = v4octet | v4octet ".." v4octet +/* .in +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + + /* + * Token values. The in-band values are also used as byte-code values. + */ +#define IP_MATCH_CODE_OPEN '[' /* in-band */ +#define IP_MATCH_CODE_CLOSE ']' /* in-band */ +#define IP_MATCH_CODE_OVAL 'N' /* in-band */ +#define IP_MATCH_CODE_RANGE 'R' /* in-band */ +#define IP_MATCH_CODE_EOF '\0' /* in-band */ +#define IP_MATCH_CODE_ERR 256 /* out-of-band */ + + /* + * SLMs. + */ +#define STR vstring_str +#define LEN VSTRING_LEN + +/* ip_match_save - make longer-term copy of byte code */ + +char *ip_match_save(const VSTRING *byte_codes) +{ + char *dst; + + dst = mymalloc(LEN(byte_codes)); + return (memcpy(dst, STR(byte_codes), LEN(byte_codes))); +} + +/* ip_match_dump - byte-code pretty printer */ + +char *ip_match_dump(VSTRING *printable, const char *byte_codes) +{ + const char *myname = "ip_match_dump"; + const unsigned char *bp; + int octet_count = 0; + int ch; + + /* + * Sanity check. Use different dumping loops for AF_INET and AF_INET6. + */ + if (*byte_codes != AF_INET) + msg_panic("%s: malformed byte-code header", myname); + + /* + * Pretty-print and sanity-check the byte codes. Note: the loops in this + * code have no auto-increment at the end of the iteration. Instead, each + * byte-code handler bumps the byte-code pointer appropriately. + */ + VSTRING_RESET(printable); + bp = (const unsigned char *) byte_codes + 1; + for (;;) { + + /* + * Simple numeric field. + */ + if ((ch = *bp++) == IP_MATCH_CODE_OVAL) { + vstring_sprintf_append(printable, "%d", *bp); + bp += 1; + } + + /* + * Wild-card numeric field. + */ + else if (ch == IP_MATCH_CODE_OPEN) { + vstring_sprintf_append(printable, "["); + for (;;) { + /* Numeric range. */ + if ((ch = *bp++) == IP_MATCH_CODE_RANGE) { + vstring_sprintf_append(printable, "%d..%d", bp[0], bp[1]); + bp += 2; + } + /* Number. */ + else if (ch == IP_MATCH_CODE_OVAL) { + vstring_sprintf_append(printable, "%d", *bp); + bp += 1; + } + /* End-of-wildcard. */ + else if (ch == IP_MATCH_CODE_CLOSE) { + break; + } + /* Corruption. */ + else { + msg_panic("%s: unexpected byte code (decimal %d) " + "after \"%s\"", myname, ch, STR(printable)); + } + /* Output the wild-card field separator and repeat the loop. */ + if (*bp != IP_MATCH_CODE_CLOSE) + vstring_sprintf_append(printable, ";"); + } + vstring_sprintf_append(printable, "]"); + } + + /* + * Corruption. + */ + else { + msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"", + myname, ch, STR(printable)); + } + + /* + * Require four octets, not one more, not one less. + */ + if (++octet_count == 4) { + if (*bp != 0) + msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"", + myname, ch, STR(printable)); + return (STR(printable)); + } + if (*bp == 0) + msg_panic("%s: truncated byte code after \"%s\"", + myname, STR(printable)); + + /* + * Output the address field separator and repeat the loop. + */ + vstring_sprintf_append(printable, "."); + } +} + +/* ip_match_print_code_prefix - printable byte-code prefix */ + +static char *ip_match_print_code_prefix(const char *byte_codes, size_t len) +{ + static VSTRING *printable = 0; + const char *fmt; + const char *bp; + + /* + * This is primarily for emergency debugging so we don't care about + * non-reentrancy. + */ + if (printable == 0) + printable = vstring_alloc(100); + else + VSTRING_RESET(printable); + + /* + * Use decimal for IPv4 and hexadecimal otherwise, so that address octet + * values are easy to recognize. + */ + fmt = (*byte_codes == AF_INET ? "%d " : "%02x "); + for (bp = byte_codes; bp < byte_codes + len; bp++) + vstring_sprintf_append(printable, fmt, *(const unsigned char *) bp); + + return (STR(printable)); +} + +/* ip_match_execute - byte-code matching engine */ + +int ip_match_execute(const char *byte_codes, const char *addr_bytes) +{ + const char *myname = "ip_match_execute"; + const unsigned char *bp; + const unsigned char *ap; + int octet_count = 0; + int ch; + int matched; + + /* + * Sanity check. Use different execute loops for AF_INET and AF_INET6. + */ + if (*byte_codes != AF_INET) + msg_panic("%s: malformed byte-code header (decimal %d)", + myname, *(const unsigned char *) byte_codes); + + /* + * Match the address bytes against the byte codes. Avoid problems with + * (char -> int) sign extension on architectures with signed characters. + */ + bp = (const unsigned char *) byte_codes + 1; + ap = (const unsigned char *) addr_bytes; + + for (octet_count = 0; octet_count < 4; octet_count++, ap++) { + + /* + * Simple numeric field. + */ + if ((ch = *bp++) == IP_MATCH_CODE_OVAL) { + if (*ap == *bp) + bp += 1; + else + return (0); + } + + /* + * Wild-card numeric field. + */ + else if (ch == IP_MATCH_CODE_OPEN) { + matched = 0; + for (;;) { + /* Numeric range. */ + if ((ch = *bp++) == IP_MATCH_CODE_RANGE) { + if (!matched) + matched = (*ap >= bp[0] && *ap <= bp[1]); + bp += 2; + } + /* Number. */ + else if (ch == IP_MATCH_CODE_OVAL) { + if (!matched) + matched = (*ap == *bp); + bp += 1; + } + /* End-of-wildcard. */ + else if (ch == IP_MATCH_CODE_CLOSE) { + break; + } + /* Corruption. */ + else { + size_t len = (const char *) bp - byte_codes - 1; + + msg_panic("%s: unexpected byte code (decimal %d) " + "after \"%s\"", myname, ch, + ip_match_print_code_prefix(byte_codes, len)); + } + } + if (matched == 0) + return (0); + } + + /* + * Corruption. + */ + else { + size_t len = (const char *) bp - byte_codes - 1; + + msg_panic("%s: unexpected byte code (decimal %d) after \"%s\"", + myname, ch, ip_match_print_code_prefix(byte_codes, len)); + } + } + return (1); +} + +/* ip_match_next_token - carve out the next token from input pattern */ + +static int ip_match_next_token(char **pstart, char **psaved_start, int *poval) +{ + unsigned char *cp; + int oval; /* octet value */ + int type; /* token value */ + + /* + * Return a literal, error, or EOF token. Update the read pointer to the + * start of the next token or leave it at the string terminator. + */ +#define IP_MATCH_RETURN_TOK(next, type) \ + do { *pstart = (char *) (next); return (type); } while (0) + + /* + * Return a token that contains an IPv4 address octet value. + */ +#define IP_MATCH_RETURN_TOK_VAL(next, type, oval) do { \ + *poval = (oval); IP_MATCH_RETURN_TOK((next), type); \ + } while (0) + + /* + * Light-weight tokenizer. Each result is an IPv4 address octet value, a + * literal character value, error, or EOF. + */ + *psaved_start = *pstart; + cp = (unsigned char *) *pstart; + if (ISDIGIT(*cp)) { + oval = *cp - '0'; + type = IP_MATCH_CODE_OVAL; + for (cp += 1; ISDIGIT(*cp); cp++) { + oval *= 10; + oval += *cp - '0'; + if (oval > 255) + type = IP_MATCH_CODE_ERR; + } + IP_MATCH_RETURN_TOK_VAL(cp, type, oval); + } else { + IP_MATCH_RETURN_TOK(*cp ? cp + 1 : cp, *cp); + } +} + +/* ipmatch_print_parse_error - formatted parsing error, with context */ + +static void PRINTFLIKE(5, 6) ipmatch_print_parse_error(VSTRING *reply, + char *start, + char *here, + char *next, + const char *fmt,...) +{ + va_list ap; + int start_width; + int here_width; + + /* + * Format the error type. + */ + va_start(ap, fmt); + vstring_vsprintf(reply, fmt, ap); + va_end(ap); + + /* + * Format the error context. The syntax is complex enough that it is + * worth the effort to precisely indicate what input is in error. + * + * XXX Workaround for %.*s to avoid output when a zero width is specified. + */ + if (start != 0) { + start_width = here - start; + here_width = next - here; + vstring_sprintf_append(reply, " at \"%.*s>%.*s<%s\"", + start_width, start_width == 0 ? "" : start, + here_width, here_width == 0 ? "" : here, next); + } +} + +/* ip_match_parse - parse an entire wild-card address pattern */ + +char *ip_match_parse(VSTRING *byte_codes, char *pattern) +{ + int octet_count; + char *saved_cp; + char *cp; + int token_type; + int look_ahead; + int oval; + int saved_oval; + + /* + * Simplify this if we change to {} for wildcard notation. + */ +#define FIND_TERMINATOR(start, cp) do { \ + int _level = 0; \ + for (cp = (start) ; *cp; cp++) { \ + if (*cp == '[') _level++; \ + if (*cp != ']') continue; \ + if (--_level == 0) break; \ + } \ + } while (0) + + /* + * Strip [] around the entire pattern. + */ + if (*pattern == '[') { + FIND_TERMINATOR(pattern, cp); + if (cp[0] == 0) { + vstring_sprintf(byte_codes, "missing \"]\" character"); + return (STR(byte_codes)); + } + if (cp[1] == 0) { + *cp = 0; + pattern += 1; + } + } + + /* + * Sanity check. In this case we can't show any error context. + */ + if (*pattern == 0) { + vstring_sprintf(byte_codes, "empty address pattern"); + return (STR(byte_codes)); + } + + /* + * Simple parser with on-the-fly encoding. For now, IPv4 support only. + * Use different parser loops for IPv4 and IPv6. + */ + VSTRING_RESET(byte_codes); + VSTRING_ADDCH(byte_codes, AF_INET); + octet_count = 0; + cp = pattern; + + /* + * Require four address fields separated by ".", each field containing a + * numeric octet value or a sequence inside []. The loop head has no test + * and does not step the loop variable. The tokenizer advances the loop + * variable, and the loop termination logic is inside the loop. + */ + for (;;) { + switch (token_type = ip_match_next_token(&cp, &saved_cp, &oval)) { + + /* + * Numeric address field. + */ + case IP_MATCH_CODE_OVAL: + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL); + VSTRING_ADDCH(byte_codes, oval); + break; + + /* + * Wild-card address field. + */ + case IP_MATCH_CODE_OPEN: + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OPEN); + /* Require ";"-separated numbers or numeric ranges. */ + for (;;) { + token_type = ip_match_next_token(&cp, &saved_cp, &oval); + if (token_type == IP_MATCH_CODE_OVAL) { + saved_oval = oval; + look_ahead = ip_match_next_token(&cp, &saved_cp, &oval); + /* Numeric range. */ + if (look_ahead == '.') { + /* Brute-force parsing. */ + if (ip_match_next_token(&cp, &saved_cp, &oval) == '.' + && ip_match_next_token(&cp, &saved_cp, &oval) + == IP_MATCH_CODE_OVAL + && saved_oval <= oval) { + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_RANGE); + VSTRING_ADDCH(byte_codes, saved_oval); + VSTRING_ADDCH(byte_codes, oval); + look_ahead = + ip_match_next_token(&cp, &saved_cp, &oval); + } else { + ipmatch_print_parse_error(byte_codes, pattern, + saved_cp, cp, + "numeric range error"); + return (STR(byte_codes)); + } + } + /* Single number. */ + else { + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_OVAL); + VSTRING_ADDCH(byte_codes, saved_oval); + } + /* Require ";" or end-of-wildcard. */ + token_type = look_ahead; + if (token_type == ';') { + continue; + } else if (token_type == IP_MATCH_CODE_CLOSE) { + break; + } else { + ipmatch_print_parse_error(byte_codes, pattern, + saved_cp, cp, + "need \";\" or \"%c\"", + IP_MATCH_CODE_CLOSE); + return (STR(byte_codes)); + } + } else { + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "need decimal number 0..255"); + return (STR(byte_codes)); + } + } + VSTRING_ADDCH(byte_codes, IP_MATCH_CODE_CLOSE); + break; + + /* + * Invalid field. + */ + default: + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "need decimal number 0..255 or \"%c\"", + IP_MATCH_CODE_OPEN); + return (STR(byte_codes)); + } + octet_count += 1; + + /* + * Require four address fields. Not one more, not one less. + */ + if (octet_count == 4) { + if (*cp != 0) { + (void) ip_match_next_token(&cp, &saved_cp, &oval); + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "garbage after pattern"); + return (STR(byte_codes)); + } + VSTRING_ADDCH(byte_codes, 0); + return (0); + } + + /* + * Require "." before the next address field. + */ + if (ip_match_next_token(&cp, &saved_cp, &oval) != '.') { + ipmatch_print_parse_error(byte_codes, pattern, saved_cp, cp, + "need \".\""); + return (STR(byte_codes)); + } + } +} + +#ifdef TEST + + /* + * Dummy main program for regression tests. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + VSTRING *byte_codes = vstring_alloc(100); + VSTRING *line_buf = vstring_alloc(100); + char *bufp; + char *err; + char *user_pattern; + char *user_address; + int echo_input = !isatty(0); + + /* + * Iterate over the input stream. The input format is a pattern, followed + * by optional addresses to match against. + */ + while (vstring_fgets_nonl(line_buf, VSTREAM_IN)) { + bufp = STR(line_buf); + if (echo_input) { + vstream_printf("> %s\n", bufp); + vstream_fflush(VSTREAM_OUT); + } + if (*bufp == '#') + continue; + if ((user_pattern = mystrtok(&bufp, " \t")) == 0) + continue; + + /* + * Parse and dump the pattern. + */ + if ((err = ip_match_parse(byte_codes, user_pattern)) != 0) { + vstream_printf("Error: %s\n", err); + } else { + vstream_printf("Code: %s\n", + ip_match_dump(line_buf, STR(byte_codes))); + } + vstream_fflush(VSTREAM_OUT); + + /* + * Match the optional patterns. + */ + while ((user_address = mystrtok(&bufp, " \t")) != 0) { + struct in_addr netw_addr; + + switch (inet_pton(AF_INET, user_address, &netw_addr)) { + case 1: + vstream_printf("Match %s: %s\n", user_address, + ip_match_execute(STR(byte_codes), + (char *) &netw_addr.s_addr) ? + "yes" : "no"); + break; + case 0: + vstream_printf("bad address syntax: %s\n", user_address); + break; + case -1: + vstream_printf("%s: %m\n", user_address); + break; + } + vstream_fflush(VSTREAM_OUT); + } + } + vstring_free(line_buf); + vstring_free(byte_codes); + exit(0); +} + +#endif diff --git a/src/util/ip_match.h b/src/util/ip_match.h new file mode 100644 index 0000000..781a04a --- /dev/null +++ b/src/util/ip_match.h @@ -0,0 +1,38 @@ +#ifndef _IP_MATCH_H_INCLUDED_ +#define _IP_MATCH_H_INCLUDED_ + +/*++ +/* NAME +/* ip_match 3h +/* SUMMARY +/* IP address pattern matching +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern char *ip_match_parse(VSTRING *, char *); +extern char *ip_match_save(const VSTRING *); +extern char *ip_match_dump(VSTRING *, const char *); +extern int ip_match_execute(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/ip_match.in b/src/util/ip_match.in new file mode 100644 index 0000000..eec13e3 --- /dev/null +++ b/src/util/ip_match.in @@ -0,0 +1,26 @@ +1.2.3.4 +1.2.300.4 +1.2.3000.4 +1.2.3. +1.2.3 +a +1.2.3;4 +1.2.[3].4 +1.2.[].4 +1.2.[.4 +1.2.].4 +1.2.[1..127;128..255].5 +1.2.[1-255].5 +1.2.[1..127.128..255].5 +1.2.3.[4] +1.2.3.[4..1] +1.2.3.[4.1] +1.2.3.[4.x] +1.2.3.[x] +1.2.3.4x +1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6 +1.2.[3;5;7;9;11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6 +[1;2].3.4.5 1.3.4.5 2.3.4.5 3.3.4.5 +[[1;2].3.4.5] 1.3.4.5 2.3.4.5 3.3.4.5 +[[1;2].3.4.5 +1;2].3.4.5 diff --git a/src/util/ip_match.ref b/src/util/ip_match.ref new file mode 100644 index 0000000..04b291f --- /dev/null +++ b/src/util/ip_match.ref @@ -0,0 +1,69 @@ +> 1.2.3.4 +Code: 1.2.3.4 +> 1.2.300.4 +Error: need decimal number 0..255 or "[" at "1.2.>300<.4" +> 1.2.3000.4 +Error: need decimal number 0..255 or "[" at "1.2.>3000<.4" +> 1.2.3. +Error: need decimal number 0..255 or "[" at "1.2.3.><" +> 1.2.3 +Error: need "." at "1.2.3><" +> a +Error: need decimal number 0..255 or "[" at ">a<" +> 1.2.3;4 +Error: need "." at "1.2.3>;<4" +> 1.2.[3].4 +Code: 1.2.[3].4 +> 1.2.[].4 +Error: need decimal number 0..255 at "1.2.[>]<.4" +> 1.2.[.4 +Error: need decimal number 0..255 at "1.2.[>.<4" +> 1.2.].4 +Error: need decimal number 0..255 or "[" at "1.2.>]<.4" +> 1.2.[1..127;128..255].5 +Code: 1.2.[1..127;128..255].5 +> 1.2.[1-255].5 +Error: need ";" or "]" at "1.2.[1>-<255].5" +> 1.2.[1..127.128..255].5 +Error: need ";" or "]" at "1.2.[1..127>.<128..255].5" +> 1.2.3.[4] +Code: 1.2.3.[4] +> 1.2.3.[4..1] +Error: numeric range error at "1.2.3.[4..>1<]" +> 1.2.3.[4.1] +Error: numeric range error at "1.2.3.[4.>1<]" +> 1.2.3.[4.x] +Error: numeric range error at "1.2.3.[4.>x<]" +> 1.2.3.[x] +Error: need decimal number 0..255 at "1.2.3.[>x<]" +> 1.2.3.4x +Error: garbage after pattern at "1.2.3.4>x<" +> 1.2.[3..11].5 1.2.3.5 1.2.2.5 1.2.11.5 1.2.12.5 1.2.11.6 +Code: 1.2.[3..11].5 +Match 1.2.3.5: yes +Match 1.2.2.5: no +Match 1.2.11.5: yes +Match 1.2.12.5: no +Match 1.2.11.6: no +> 1.2.[3;5;7;9;11].5 1.2.3.5 1.2.2.5 1.2.4.5 1.2.11.5 1.2.12.5 1.2.11.6 +Code: 1.2.[3;5;7;9;11].5 +Match 1.2.3.5: yes +Match 1.2.2.5: no +Match 1.2.4.5: no +Match 1.2.11.5: yes +Match 1.2.12.5: no +Match 1.2.11.6: no +> [1;2].3.4.5 1.3.4.5 2.3.4.5 3.3.4.5 +Code: [1;2].3.4.5 +Match 1.3.4.5: yes +Match 2.3.4.5: yes +Match 3.3.4.5: no +> [[1;2].3.4.5] 1.3.4.5 2.3.4.5 3.3.4.5 +Code: [1;2].3.4.5 +Match 1.3.4.5: yes +Match 2.3.4.5: yes +Match 3.3.4.5: no +> [[1;2].3.4.5 +Error: missing "]" character +> 1;2].3.4.5 +Error: need "." at "1>;<2].3.4.5" diff --git a/src/util/killme_after.c b/src/util/killme_after.c new file mode 100644 index 0000000..34e0434 --- /dev/null +++ b/src/util/killme_after.c @@ -0,0 +1,69 @@ +/*++ +/* NAME +/* killme_after 3 +/* SUMMARY +/* programmed death +/* SYNOPSIS +/* #include +/* +/* void killme_after(seconds) +/* unsigned int seconds; +/* DESCRIPTION +/* The killme_after() function does a best effort to terminate +/* the process after the specified time, should it still exist. +/* It is meant to be used in a signal handler, as an insurance +/* against getting stuck somewhere while preparing for exit. +/* DIAGNOSTICS +/* None. This routine does a best effort, damn the torpedoes. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include + +/* killme_after - self-assured death */ + +void killme_after(unsigned int seconds) +{ + struct sigaction sig_action; + + /* + * Schedule an ALARM signal, and make sure the signal will be delivered + * even if we are being called from a signal handler and SIGALRM delivery + * is blocked. + * + * Undocumented: when a process runs with PID 1, Linux won't deliver a + * signal unless the process specifies a handler (i.e. SIG_DFL is treated + * as SIG_IGN). Conveniently, _exit() can be used directly as a signal + * handler. This changes the wait status that a parent would see, but in + * the case of "init" mode on Linux, no-one would care. + */ + alarm(0); + sigemptyset(&sig_action.sa_mask); + sig_action.sa_flags = 0; + sig_action.sa_handler = (getpid() == 1 ? _exit : SIG_DFL); + sigaction(SIGALRM, &sig_action, (struct sigaction *) 0); + alarm(seconds); + sigaddset(&sig_action.sa_mask, SIGALRM); + sigprocmask(SIG_UNBLOCK, &sig_action.sa_mask, (sigset_t *) 0); +} diff --git a/src/util/killme_after.h b/src/util/killme_after.h new file mode 100644 index 0000000..9505b2d --- /dev/null +++ b/src/util/killme_after.h @@ -0,0 +1,30 @@ +#ifndef _KILLME_AFTER_H_INCLUDED_ +#define _KILLME_AFTER_H_INCLUDED_ + +/*++ +/* NAME +/* killme_after 3h +/* SUMMARY +/* programmed death +/* SYNOPSIS +/* #include "killme_after.h" +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void killme_after(unsigned int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/known_tcp_ports.c b/src/util/known_tcp_ports.c new file mode 100644 index 0000000..1d524d4 --- /dev/null +++ b/src/util/known_tcp_ports.c @@ -0,0 +1,253 @@ +/*++ +/* NAME +/* known_tcp_ports 3 +/* SUMMARY +/* reduce dependency on the services(5) database +/* SYNOPSIS +/* #include +/* +/* const char *add_known_tcp_port( +/* const char *name) +/* const char *port) +/* +/* const char *filter_known_tcp_port( +/* const char *name_or_port) +/* +/* void clear_known_tcp_ports(void) +/* AUXILIARY FUNCTIONS +/* char *export_known_tcp_ports( +/* VSTRING *result) +/* DESCRIPTION +/* This module reduces dependency on the services(5) database. +/* +/* add_known_tcp_port() associates a symbolic name with a numerical +/* port. The function returns a pointer to error text if the +/* arguments are malformed or if the symbolic name already has +/* an association. +/* +/* filter_known_tcp_port() returns the argument if it does not +/* specify a symbolic name, or if the argument specifies a symbolic +/* name that is not associated with a numerical port. Otherwise, +/* it returns the associated numerical port. +/* +/* clear_known_tcp_ports() destroys all name-number associations. +/* string. +/* +/* export_known_tcp_ports() overwrites a VSTRING with all known +/* name=port associations, sorted by service name, and separated +/* by whitespace. The result is pointer to the VSTRING payload. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library + */ +#include +#include +#include + + /* + * Utility library + */ +#include +#include +#include + + /* + * Application-specific. + */ +#include + +#define STR(x) vstring_str(x) + +static HTABLE *known_tcp_ports; + +/* add_known_tcp_port - associate symbolic name with numerical port */ + +const char *add_known_tcp_port(const char *name, const char *port) +{ + if (alldig(name)) + return ("numerical service name"); + if (!alldig(port)) + return ("non-numerical service port"); + if (known_tcp_ports == 0) + known_tcp_ports = htable_create(10); + if (htable_locate(known_tcp_ports, name) != 0) + return ("duplicate service name"); + (void) htable_enter(known_tcp_ports, name, mystrdup(port)); + return (0); +} + +/* filter_known_tcp_port - replace argument if associated with known port */ + +const char *filter_known_tcp_port(const char *name_or_port) +{ + HTABLE_INFO *ht; + + if (name_or_port == 0 || known_tcp_ports == 0 || alldig(name_or_port)) { + return (name_or_port); + } else if ((ht = htable_locate(known_tcp_ports, name_or_port)) != 0) { + return (ht->value); + } else { + return (name_or_port); + } +} + +/* clear_known_tcp_ports - destroy all name-port associations */ + +void clear_known_tcp_ports(void) +{ + htable_free(known_tcp_ports, myfree); + known_tcp_ports = 0; +} + +/* compare_ht_keys - compare table keys */ + +static int compare_ht_keys(const void *a, const void *b) +{ + HTABLE_INFO **ap = (HTABLE_INFO **) a; + HTABLE_INFO **bp = (HTABLE_INFO **) b; + + return (strcmp((const char *) ap[0]->key, (const char *) bp[0]->key)); +} + +/* export_known_tcp_ports - sorted dump */ + +char *export_known_tcp_ports(VSTRING *out) +{ + HTABLE_INFO **list; + HTABLE_INFO **ht; + + VSTRING_RESET(out); + if (known_tcp_ports) { + list = htable_list(known_tcp_ports); + qsort((void *) list, known_tcp_ports->used, sizeof(*list), + compare_ht_keys); + for (ht = list; *ht; ht++) + vstring_sprintf_append(out, "%s%s=%s", ht > list ? " " : "", + ht[0]->key, (const char *) ht[0]->value); + myfree((void *) list); + } + VSTRING_TERMINATE(out); + return (STR(out)); +} + +#ifdef TEST + +#include + +struct association { + const char *lhs; /* service name */ + const char *rhs; /* service port */ +}; + +struct probe { + const char *query; /* query */ + const char *exp_reply; /* expected reply */ +}; + +struct test_case { + const char *label; /* identifies test case */ + struct association associations[10]; + const char *exp_err; /* expected error */ + const char *exp_export; /* expected export output */ + struct probe probes[10]; +}; + +struct test_case test_cases[] = { + {"good", + /* association */ {{"smtp", "25"}, {"lmtp", "24"}, 0}, + /* error */ 0, + /* export */ "lmtp=24 smtp=25", + /* probe */ {{"smtp", "25"}, {"1", "1"}, {"x", "x"}, {"lmtp", "24"}, 0} + }, + {"duplicate lhs", + /* association */ {{"smtp", "25"}, {"smtp", "100"}, 0}, + /* error */ "duplicate service name" + }, + {"numerical lhs", + /* association */ {{"100", "100"}, 0}, + /* error */ "numerical service name" + }, + {"symbolic rhs", + /* association */ {{"smtp", "lmtp"}, 0}, + /* error */ "non-numerical service port" + }, + {"uninitialized", + /* association */ {0}, + /* error */ 0, + /* export */ "", + /* probe */ {{"smtp", "smtp"}, {"1", "1"}, {"x", "x"}, 0} + }, + 0, +}; + +int main(int argc, char **argv) +{ + VSTRING *export_buf; + struct test_case *tp; + struct association *ap; + struct probe *pp; + int pass = 0; + int fail = 0; + const char *err; + int test_failed; + const char *reply; + const char *export; + +#define STRING_OR_NULL(s) ((s) ? (s) : "(null)") + + export_buf = vstring_alloc(100); + for (tp = test_cases; tp->label != 0; tp++) { + test_failed = 0; + for (err = 0, ap = tp->associations; err == 0 && ap->lhs != 0; ap++) + err = add_known_tcp_port(ap->lhs, ap->rhs); + if (!err != !tp->exp_err) { + msg_warn("test case %s: got error: \"%s\", want: \"%s\"", + tp->label, STRING_OR_NULL(err), STRING_OR_NULL(tp->exp_err)); + test_failed = 1; + } else if (err != 0) { + if (strcmp(err, tp->exp_err) != 0) { + msg_warn("test case %s: got err: \"%s\", want: \"%s\"", + tp->label, err, tp->exp_err); + test_failed = 1; + } + } else { + export = export_known_tcp_ports(export_buf); + if (strcmp(export, tp->exp_export) != 0) { + msg_warn("test case %s: got export: \"%s\", want: \"%s\"", + tp->label, export, tp->exp_export); + test_failed = 1; + } + for (pp = tp->probes; test_failed == 0 && pp->query != 0; pp++) { + reply = filter_known_tcp_port(pp->query); + if (strcmp(reply, pp->exp_reply) != 0) { + msg_warn("test case %s: got reply: \"%s\", want: \"%s\"", + tp->label, reply, pp->exp_reply); + test_failed = 1; + } + } + } + clear_known_tcp_ports(); + if (test_failed) { + msg_info("%s: FAIL", tp->label); + fail++; + } else { + msg_info("%s: PASS", tp->label); + pass++; + } + } + msg_info("PASS=%d FAIL=%d", pass, fail); + vstring_free(export_buf); + exit(fail != 0); +} + +#endif diff --git a/src/util/known_tcp_ports.h b/src/util/known_tcp_ports.h new file mode 100644 index 0000000..bc254f2 --- /dev/null +++ b/src/util/known_tcp_ports.h @@ -0,0 +1,38 @@ +#ifndef _KNOWN_TCP_PORTS_H_INCLUDED_ +#define _KNOWN_TCP_PORTS_H_INCLUDED_ + +/*++ +/* NAME +/* known_tcp_port 3h +/* SUMMARY +/* reduce dependency on the services(5) database +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern const char *add_known_tcp_port(const char *name, const char *port); +extern const char *filter_known_tcp_port(const char *name_or_port); +extern void clear_known_tcp_ports(void); +extern char *export_known_tcp_ports(VSTRING *out); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/known_tcp_ports.ref b/src/util/known_tcp_ports.ref new file mode 100644 index 0000000..adcf182 --- /dev/null +++ b/src/util/known_tcp_ports.ref @@ -0,0 +1,6 @@ +unknown: good: PASS +unknown: duplicate lhs: PASS +unknown: numerical lhs: PASS +unknown: symbolic rhs: PASS +unknown: uninitialized: PASS +unknown: PASS=5 FAIL=0 diff --git a/src/util/ldseed.c b/src/util/ldseed.c new file mode 100644 index 0000000..c231152 --- /dev/null +++ b/src/util/ldseed.c @@ -0,0 +1,138 @@ +/*++ +/* NAME +/* ldseed 3 +/* SUMMARY +/* seed for non-cryptographic applications +/* SYNOPSIS +/* #include +/* +/* void ldseed( +/* void *dst, +/* size_t len) +/* DESCRIPTION +/* ldseed() preferably extracts pseudo-random bits from +/* /dev/urandom, a non-blocking device that is available on +/* modern systems. +/* +/* On systems where /dev/urandom is unavailable or does not +/* immediately return the requested amount of randomness, +/* ldseed() falls back to a combination of wallclock time, +/* the time since boot, and the process ID. +/* BUGS +/* With Linux "the O_NONBLOCK flag has no effect when opening +/* /dev/urandom", but reads "can incur an appreciable delay +/* when requesting large amounts of data". Apparently, "large" +/* means more than 256 bytes. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library + */ +#include +#include +#include +#include +#include +#include +#include +#include /* CHAR_BIT */ + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Different systems have different names for non-wallclock time. + */ +#ifdef CLOCK_UPTIME +#define NON_WALLTIME_CLOCK CLOCK_UPTIME +#elif defined(CLOCK_BOOTTIME) +#define NON_WALLTIME_CLOCK CLOCK_BOOTTIME +#elif defined(CLOCK_MONOTONIC) +#define NON_WALLTIME_CLOCK CLOCK_MONOTONIC +#elif defined(CLOCK_HIGHRES) +#define NON_WALLTIME_CLOCK CLOCK_HIGHRES +#endif + +/* ldseed - best-effort, low-dependency seed */ + +void ldseed(void *dst, size_t len) +{ + int count; + int fd; + int n; + time_t fallback = 0; + + /* + * Medium-quality seed. + */ + if ((fd = open("/dev/urandom", O_RDONLY)) > 0) { + non_blocking(fd, NON_BLOCKING); + count = read(fd, dst, len); + (void) close(fd); + if (count == len) + return; + } + + /* + * Low-quality seed. Based on 1) the time since boot (good when an + * attacker knows the program start time but not the system boot time), + * and 2) absolute time (good when an attacker does not know the program + * start time). Assumes a system with better than microsecond resolution, + * and a network stack that does not leak the time since boot, for + * example, through TCP or ICMP timestamps. With those caveats, this seed + * is good for 20-30 bits of randomness. + */ +#ifdef NON_WALLTIME_CLOCK + { + struct timespec ts; + + if (clock_gettime(NON_WALLTIME_CLOCK, &ts) != 0) + msg_fatal("clock_gettime() failed: %m"); + fallback += ts.tv_sec ^ ts.tv_nsec; + } +#elif defined(USE_GETHRTIME) + fallback += gethrtime(); +#endif + +#ifdef CLOCK_REALTIME + { + struct timespec ts; + + if (clock_gettime(CLOCK_REALTIME, &ts) != 0) + msg_fatal("clock_gettime() failed: %m"); + fallback += ts.tv_sec ^ ts.tv_nsec; + } +#else + { + struct timeval tv; + + if (GETTIMEOFDAY(&tv) != 0) + msg_fatal("gettimeofday() failed: %m"); + fallback += tv.tv_sec + tv.tv_usec; + } +#endif + fallback += getpid(); + + /* + * Copy the least significant bytes first, because those are the most + * volatile. + */ + for (n = 0; n < sizeof(fallback) && n < len; n++) { + *(char *) dst++ ^= (fallback & 0xff); + fallback >>= CHAR_BIT; + } + return; +} diff --git a/src/util/ldseed.h b/src/util/ldseed.h new file mode 100644 index 0000000..891986e --- /dev/null +++ b/src/util/ldseed.h @@ -0,0 +1,30 @@ +#ifndef _LDSEED_H_INCLUDED_ +#define _LDSEED_H_INCLUDED_ + +/*++ +/* NAME +/* ldseed 3h +/* SUMMARY +/* seed for non-cryptographic applications +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void ldseed(void *, size_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/line_number.c b/src/util/line_number.c new file mode 100644 index 0000000..557e053 --- /dev/null +++ b/src/util/line_number.c @@ -0,0 +1,71 @@ +/*++ +/* NAME +/* line_number 3 +/* SUMMARY +/* line number utilities +/* SYNOPSIS +/* #include +/* +/* char *format_line_number(result, first, last) +/* VSTRING *buffer; +/* ssize_t first; +/* ssize_t lastl +/* DESCRIPTION +/* format_line_number() formats a line number or number range. +/* The output is - when the numbers +/* differ, when the numbers are identical. +/* .IP result +/* Result buffer, or null-pointer. In the latter case the +/* result is stored in a static buffer that is overwritten +/* with subsequent calls. The function result value is a +/* pointer into the result buffer. +/* .IP first +/* First line number. +/* .IP last +/* Last line number. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include + +/* format_line_number - pretty-print line number or number range */ + +char *format_line_number(VSTRING *result, ssize_t first, ssize_t last) +{ + static VSTRING *buf; + + /* + * Your buffer or mine? + */ + if (result == 0) { + if (buf == 0) + buf = vstring_alloc(10); + result = buf; + } + + /* + * Print a range only when the numbers differ. + */ + vstring_sprintf(result, "%ld", (long) first); + if (first != last) + vstring_sprintf_append(result, "-%ld", (long) last); + + return (vstring_str(result)); +} diff --git a/src/util/line_number.h b/src/util/line_number.h new file mode 100644 index 0000000..0a53d15 --- /dev/null +++ b/src/util/line_number.h @@ -0,0 +1,35 @@ +#ifndef _LINE_NUMBER_H_INCLUDED_ +#define _LINE_NUMBER_H_INCLUDED_ + +/*++ +/* NAME +/* line_number 3h +/* SUMMARY +/* line number utilities +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern char *format_line_number(VSTRING *, ssize_t, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/line_wrap.c b/src/util/line_wrap.c new file mode 100644 index 0000000..0f399e8 --- /dev/null +++ b/src/util/line_wrap.c @@ -0,0 +1,122 @@ +/*++ +/* NAME +/* line_wrap 3 +/* SUMMARY +/* wrap long lines upon output +/* SYNOPSIS +/* #include +/* +/* void line_wrap(string, len, indent, output_fn, context) +/* const char *buf; +/* int len; +/* int indent; +/* void (*output_fn)(const char *str, int len, int indent, void *context); +/* void *context; +/* DESCRIPTION +/* The \fBline_wrap\fR routine outputs the specified string via +/* the specified output function, and attempts to keep output lines +/* shorter than the specified length. The routine does not attempt to +/* break long words that do not fit on a single line. Upon output, +/* trailing whitespace is stripped. +/* +/* Arguments +/* .IP string +/* The input, which cannot contain any newline characters. +/* .IP len +/* The desired maximal output line length. +/* .IP indent +/* The desired amount of indentation of the second etc. output lines +/* with respect to the first output line. A negative indent causes +/* only the first line to be indented; a positive indent causes all +/* but the first line to be indented. A zero count causes no indentation. +/* .IP output_fn +/* The output function that is called with as arguments a string +/* pointer, a string length, a non-negative indentation count, and +/* application context. A typical implementation looks like this: +/* .sp +/* .nf +/* .na +void print(const char *str, int len, int indent, void *context) +{ + VSTREAM *fp = (VSTREAM *) context; + + vstream_fprintf(fp, "%*s%.*s", indent, "", len, str); +} +/* .fi +/* .ad +/* .IP context +/* Application context that is passed on to the output function. +/* For example, a VSTREAM pointer, or a structure that contains +/* a VSTREAM pointer. +/* BUGS +/* No tab expansion and no backspace processing. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include + +/* line_wrap - wrap long lines upon output */ + +void line_wrap(const char *str, int len, int indent, LINE_WRAP_FN output_fn, + void *context) +{ + const char *start_line; + const char *word; + const char *next_word; + const char *next_space; + int line_len; + int curr_len; + int curr_indent; + + if (indent < 0) { + curr_indent = -indent; + curr_len = len + indent; + } else { + curr_indent = 0; + curr_len = len; + } + + /* + * At strategic positions, output what we have seen, after stripping off + * trailing blanks. + */ + for (start_line = word = str; word != 0; word = next_word) { + next_space = word + strcspn(word, " \t"); + if (word > start_line) { + if (next_space - start_line > curr_len) { + line_len = word - start_line; + while (line_len > 0 && ISSPACE(start_line[line_len - 1])) + line_len--; + output_fn(start_line, line_len, curr_indent, context); + while (*word && ISSPACE(*word)) + word++; + if (start_line == str) { + curr_indent += indent; + curr_len -= indent; + } + start_line = word; + } + } + next_word = *next_space ? next_space + 1 : 0; + } + line_len = strlen(start_line); + while (line_len > 0 && ISSPACE(start_line[line_len - 1])) + line_len--; + output_fn(start_line, line_len, curr_indent, context); +} diff --git a/src/util/line_wrap.h b/src/util/line_wrap.h new file mode 100644 index 0000000..32c3548 --- /dev/null +++ b/src/util/line_wrap.h @@ -0,0 +1,31 @@ +#ifndef _LINE_WRAP_H_INCLUDED_ +#define _LINE_WRAP_H_INCLUDED_ + +/*++ +/* NAME +/* line_wrap 3h +/* SUMMARY +/* wrap long lines upon output +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +typedef void (*LINE_WRAP_FN) (const char *, int, int, void *); +extern void line_wrap(const char *, int, int, LINE_WRAP_FN, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/listen.h b/src/util/listen.h new file mode 100644 index 0000000..131755c --- /dev/null +++ b/src/util/listen.h @@ -0,0 +1,53 @@ +#ifndef _LISTEN_H_INCLUDED_ +#define _LISTEN_H_INCLUDED_ + +/*++ +/* NAME +/* listen 3h +/* SUMMARY +/* listener interface file +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Listener external interface. + */ +extern int unix_listen(const char *, int, int); +extern int inet_listen(const char *, int, int); +extern int fifo_listen(const char *, int, int); +extern int stream_listen(const char *, int, int); +extern int unix_dgram_listen(const char *, int); + +extern int inet_accept(int); +extern int unix_accept(int); +extern int stream_accept(int); + +extern int WARN_UNUSED_RESULT recv_pass_attr(int, HTABLE **, int, ssize_t); +extern int pass_accept(int); +extern int pass_accept_attr(int, HTABLE **); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/lmdb_cache_test_1.sh b/src/util/lmdb_cache_test_1.sh new file mode 100644 index 0000000..f5d4767 --- /dev/null +++ b/src/util/lmdb_cache_test_1.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +# Torture test: run update and purge operations in parallel processes. +# This will result in some purge operations not finding all entries, +# but the final sequential purge should eliminate all. + +set -e + +rm -f foo.lmdb +./dict_cache < +/* +/* void load_file(path, action, context) +/* const char *path; +/* void (*action)(VSTREAM, void *); +/* void *context; +/* DESCRIPTION +/* This routine reads a file and reads it again when the +/* file changed recently. +/* +/* Arguments: +/* .IP path +/* The file to be opened, read-only. +/* .IP action +/* The function that presumably reads the file. +/* .IP context +/* Application-specific context for the action routine. +/* DIAGNOSTICS +/* Fatal errors: out of memory, cannot open file. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* load_file - load file with some prejudice */ + +void load_file(const char *path, LOAD_FILE_FN action, void *context) +{ + VSTREAM *fp; + struct stat st; + time_t before; + time_t after; + + /* + * Read the file again if it is hot. This may result in reading a partial + * parameter name or missing end marker when a file changes in the middle + * of a read. + */ + for (before = time((time_t *) 0); /* see below */ ; before = after) { + if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", path); + action(fp, context); + if (fstat(vstream_fileno(fp), &st) < 0) + msg_fatal("fstat %s: %m", path); + if (vstream_ferror(fp) || vstream_fclose(fp)) + msg_fatal("read %s: %m", path); + after = time((time_t *) 0); + if (st.st_mtime < before - 1 || st.st_mtime > after) + break; + if (msg_verbose) + msg_info("pausing to let %s cool down", path); + doze(300000); + } +} diff --git a/src/util/load_file.h b/src/util/load_file.h new file mode 100644 index 0000000..3e635f3 --- /dev/null +++ b/src/util/load_file.h @@ -0,0 +1,32 @@ +#ifndef LOAD_FILE_H_INCLUDED_ +#define LOAD_FILE_H_INCLUDED_ + +/*++ +/* NAME +/* load_file 3h +/* SUMMARY +/* load file with some prejudice +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +typedef void (*LOAD_FILE_FN)(VSTREAM *, void *); + +extern void load_file(const char *, LOAD_FILE_FN, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/load_lib.c b/src/util/load_lib.c new file mode 100644 index 0000000..44e540d --- /dev/null +++ b/src/util/load_lib.c @@ -0,0 +1,146 @@ +/*++ +/* NAME +/* load_lib 3 +/* SUMMARY +/* library loading wrappers +/* SYNOPSIS +/* #include +/* +/* void load_library_symbols(const char *, LIB_FN *, LIB_DP *); +/* const char *libname; +/* LIB_FN *libfuncs; +/* LIB_DP *libdata; +/* DESCRIPTION +/* load_library_symbols() loads the specified shared object +/* and looks up the function or data pointers for the specified +/* symbols. All errors are fatal. +/* +/* Arguments: +/* .IP libname +/* shared-library pathname. +/* .IP libfuncs +/* Array of LIB_FN structures. The last name member must be null. +/* .IP libdata +/* Array of LIB_DP structures. The last name member must be null. +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Problems are reported via the msg(3) diagnostics routines: +/* library not found, symbols not found, other fatal errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* LaMont Jones +/* Hewlett-Packard Company +/* 3404 Harmony Road +/* Fort Collins, CO 80528, USA +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System libraries. + */ +#include "sys_defs.h" +#include +#include +#include +#ifdef USE_DYNAMIC_MAPS +#if defined(HAS_DLOPEN) +#include +#elif defined(HAS_SHL_LOAD) +#include +#else +#error "USE_DYNAMIC_LIBS requires HAS_DLOPEN or HAS_SHL_LOAD" +#endif + + /* + * Utility library. + */ +#include +#include + +/* load_library_symbols - load shared library and look up symbols */ + +void load_library_symbols(const char *libname, LIB_FN *libfuncs, + LIB_DP *libdata) +{ + static const char myname[] = "load_library_symbols"; + LIB_FN *fn; + LIB_DP *dp; + +#if defined(HAS_DLOPEN) + void *handle; + char *emsg; + + /* + * XXX This is basically how FreeBSD dlfunc() silences a compiler warning + * about a data/function pointer conversion. The solution below is non- + * portable: it assumes that both data and function pointers are the same + * in size, and that both have the same representation. + */ + union { + void *dptr; /* data pointer */ + void (*fptr) (void); /* function pointer */ + } non_portable_union; + + if ((handle = dlopen(libname, RTLD_NOW)) == 0) { + emsg = dlerror(); + msg_fatal("%s: dlopen failure loading %s: %s", myname, libname, + emsg ? emsg : "don't know why"); + } + if (libfuncs) { + for (fn = libfuncs; fn->name; fn++) { + if ((non_portable_union.dptr = dlsym(handle, fn->name)) == 0) { + emsg = dlerror(); + msg_fatal("%s: dlsym failure looking up %s in %s: %s", myname, + fn->name, libname, emsg ? emsg : "don't know why"); + } + fn->fptr = non_portable_union.fptr; + if (msg_verbose > 1) + msg_info("loaded %s = %p", fn->name, non_portable_union.dptr); + } + } + if (libdata) { + for (dp = libdata; dp->name; dp++) { + if ((dp->dptr = dlsym(handle, dp->name)) == 0) { + emsg = dlerror(); + msg_fatal("%s: dlsym failure looking up %s in %s: %s", myname, + dp->name, libname, emsg ? emsg : "don't know why"); + } + if (msg_verbose > 1) + msg_info("loaded %s = %p", dp->name, dp->dptr); + } + } +#elif defined(HAS_SHL_LOAD) + shl_t handle; + + handle = shl_load(libname, BIND_IMMEDIATE, 0); + + if (libfuncs) { + for (fn = libfuncs; fn->name; fn++) { + if (shl_findsym(&handle, fn->name, TYPE_PROCEDURE, &fn->fptr) != 0) + msg_fatal("%s: shl_findsym failure looking up %s in %s: %m", + myname, fn->name, libname); + if (msg_verbose > 1) + msg_info("loaded %s = %p", fn->name, (void *) fn->fptr); + } + } + if (libdata) { + for (dp = libdata; dp->name; dp++) { + if (shl_findsym(&handle, dp->name, TYPE_DATA, &dp->dptr) != 0) + msg_fatal("%s: shl_findsym failure looking up %s in %s: %m", + myname, dp->name, libname); + if (msg_verbose > 1) + msg_info("loaded %s = %p", dp->name, dp->dptr); + } + } +#endif +} + +#endif diff --git a/src/util/load_lib.h b/src/util/load_lib.h new file mode 100644 index 0000000..1c999c5 --- /dev/null +++ b/src/util/load_lib.h @@ -0,0 +1,46 @@ +#ifndef _LOAD_LIB_H_INCLUDED_ +#define _LOAD_LIB_H_INCLUDED_ + +/*++ +/* NAME +/* load_lib 3h +/* SUMMARY +/* library loading wrappers +/* SYNOPSIS +/* #include "load_lib.h" +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +/* NULL name terminates list */ +typedef struct LIB_FN { + const char *name; + void (*fptr)(void); +} LIB_FN; + +typedef struct LIB_DP { + const char *name; + void *dptr; +} LIB_DP; + +extern void load_library_symbols(const char *, LIB_FN *, LIB_DP *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* LaMont Jones +/* Hewlett-Packard Company +/* 3404 Harmony Road +/* Fort Collins, CO 80528, USA +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/logwriter.c b/src/util/logwriter.c new file mode 100644 index 0000000..aea2767 --- /dev/null +++ b/src/util/logwriter.c @@ -0,0 +1,124 @@ +/*++ +/* NAME +/* logwriter 3 +/* SUMMARY +/* logfile writer +/* SYNOPSIS +/* #include +/* +/* VSTREAM *logwriter_open_or_die( +/* const char *path) +/* +/* int logwriter_write( +/* VSTREAM *file, +/* const char *buffer. +/* ssize_t buflen) +/* +/* int logwriter_close( +/* VSTREAM *file) +/* +/* int logwriter_one_shot( +/* const char *path, +/* const char *buffer, +/* ssize_t buflen) +/* DESCRIPTION +/* This module manages a logfile writer. +/* +/* logwriter_open_or_die() safely opens the specified file in +/* write+append mode. File open/create errors are fatal. +/* +/* logwriter_write() writes the buffer plus newline to the +/* open logfile. The result is zero if successful, VSTREAM_EOF +/* if the operation failed. +/* +/* logwriter_close() closes the logfile and destroys the VSTREAM +/* instance. The result is zero if there were no errors writing +/* the file, VSTREAM_EOF otherwise. +/* +/* logwriter_one_shot() combines all the above operations. The +/* result is zero if successful, VSTREAM_EOF if any operation +/* failed. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + + /* + * Application-specific. + */ + +/* logwriter_open_or_die - open logfile */ + +VSTREAM *logwriter_open_or_die(const char *path) +{ + VSTREAM *fp; + VSTRING *why = vstring_alloc(100); + +#define NO_STATP ((struct stat *) 0) +#define NO_CHOWN (-1) +#define NO_CHGRP (-1) + + fp = safe_open(path, O_CREAT | O_WRONLY | O_APPEND, 0644, + NO_STATP, NO_CHOWN, NO_CHGRP, why); + if (fp == 0) + msg_fatal("open logfile '%s': %s", path, vstring_str(why)); + close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); + vstring_free(why); + return (fp); +} + +/* logwriter_write - append to logfile */ + +int logwriter_write(VSTREAM *fp, const char *buf, ssize_t len) +{ + if (len < 0) + msg_panic("logwriter_write: negative length %ld", (long) len); + if (vstream_fwrite(fp, buf, len) != len) + return (VSTREAM_EOF); + VSTREAM_PUTC('\n', fp); + return (vstream_fflush(fp)); +} + +/* logwriter_close - close logfile */ + +int logwriter_close(VSTREAM *fp) +{ + return (vstream_fclose(fp)); +} + +/* logwriter_one_shot - one-shot logwriter */ + +int logwriter_one_shot(const char *path, const char *buf, ssize_t len) +{ + VSTREAM *fp; + int err; + + fp = logwriter_open_or_die(path); + err = logwriter_write(fp, buf, len); + err |= logwriter_close(fp); + return (err ? VSTREAM_EOF : 0); +} diff --git a/src/util/logwriter.h b/src/util/logwriter.h new file mode 100644 index 0000000..f5266e4 --- /dev/null +++ b/src/util/logwriter.h @@ -0,0 +1,38 @@ +#ifndef _LOGWRITER_H_INCLUDED_ +#define _LOGWRITER_H_INCLUDED_ + +/*++ +/* NAME +/* logwriter 3h +/* SUMMARY +/* logfile writer +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern VSTREAM *logwriter_open_or_die(const char *); +extern int logwriter_write(VSTREAM *, const char *, ssize_t); +extern int logwriter_close(VSTREAM *); +extern int logwriter_one_shot(const char *, const char *, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/lowercase.c b/src/util/lowercase.c new file mode 100644 index 0000000..f7388b4 --- /dev/null +++ b/src/util/lowercase.c @@ -0,0 +1,43 @@ +/*++ +/* NAME +/* lowercase 3 +/* SUMMARY +/* map uppercase characters to lowercase +/* SYNOPSIS +/* #include +/* +/* char *lowercase(buf) +/* char *buf; +/* DESCRIPTION +/* lowercase() replaces uppercase characters in its null-terminated +/* input by their lowercase equivalent. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include + +/* Utility library. */ + +#include "stringops.h" + +char *lowercase(char *string) +{ + char *cp; + int ch; + + for (cp = string; (ch = *cp) != 0; cp++) + if (ISUPPER(ch)) + *cp = TOLOWER(ch); + return (string); +} diff --git a/src/util/lstat_as.c b/src/util/lstat_as.c new file mode 100644 index 0000000..18e0f9f --- /dev/null +++ b/src/util/lstat_as.c @@ -0,0 +1,73 @@ +/*++ +/* NAME +/* lstat_as 3 +/* SUMMARY +/* lstat file as user +/* SYNOPSIS +/* #include +/* #include +/* +/* int lstat_as(path, st, euid, egid) +/* const char *path; +/* struct stat *st; +/* uid_t euid; +/* gid_t egid; +/* DESCRIPTION +/* lstat_as() looks up the file status of the named \fIpath\fR, +/* using the effective rights specified by \fIeuid\fR +/* and \fIegid\fR, and stores the result into the structure pointed +/* to by \fIst\fR. A -1 result means the lookup failed. +/* This call does not follow symbolic links. +/* DIAGNOSTICS +/* Fatal error: no permission to change privilege level. +/* SEE ALSO +/* set_eugid(3) switch effective rights +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "set_eugid.h" +#include "lstat_as.h" +#include "warn_stat.h" + +/* lstat_as - lstat file as user */ + +int lstat_as(const char *path, struct stat * st, uid_t euid, gid_t egid) +{ + uid_t saved_euid = geteuid(); + gid_t saved_egid = getegid(); + int status; + + /* + * Switch to the target user privileges. + */ + set_eugid(euid, egid); + + /* + * Lstat that file. + */ + status = lstat(path, st); + + /* + * Restore saved privileges. + */ + set_eugid(saved_euid, saved_egid); + + return (status); +} diff --git a/src/util/lstat_as.h b/src/util/lstat_as.h new file mode 100644 index 0000000..2195a1e --- /dev/null +++ b/src/util/lstat_as.h @@ -0,0 +1,35 @@ +#ifndef _LSTAT_AS_H_INCLUDED_ +#define _LSTAT_AS_H_INCLUDED_ + +/*++ +/* NAME +/* lstat_as 3h +/* SUMMARY +/* lstat file as user +/* SYNOPSIS +/* #include +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int WARN_UNUSED_RESULT lstat_as(const char *, struct stat *, uid_t, gid_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/mac_expand.c b/src/util/mac_expand.c new file mode 100644 index 0000000..f40819e --- /dev/null +++ b/src/util/mac_expand.c @@ -0,0 +1,848 @@ +/*++ +/* NAME +/* mac_expand 3 +/* SUMMARY +/* attribute expansion +/* SYNOPSIS +/* #include +/* +/* int mac_expand(result, pattern, flags, filter, lookup, context) +/* VSTRING *result; +/* const char *pattern; +/* int flags; +/* const char *filter; +/* const char *lookup(const char *key, int mode, void *context) +/* void *context; +/* AUXILIARY FUNCTIONS +/* typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) ( +/* const char *left, +/* int tok_val, +/* const char *rite) +/* +/* void mac_expand_add_relop( +/* int *tok_list, +/* const char *suffix, +/* MAC_EXPAND_RELOP_FN relop_eval) +/* +/* MAC_EXP_OP_RES mac_exp_op_res_bool[2]; +/* DESCRIPTION +/* This module implements parameter-less named attribute +/* expansions, both conditional and unconditional. As of Postfix +/* 3.0 this code supports relational expression evaluation. +/* +/* In this text, an attribute is considered "undefined" when its value +/* is a null pointer. Otherwise, the attribute is considered "defined" +/* and is expected to have as value a null-terminated string. +/* +/* In the text below, the legacy form $(...) is equivalent to +/* ${...}. The legacy form $(...) may eventually disappear +/* from documentation. In the text below, the name in $name +/* and ${name...} must contain only characters from the set +/* [a-zA-Z0-9_]. +/* +/* The following substitutions are supported: +/* .IP "$name, ${name}" +/* Unconditional attribute-based substitution. The result is the +/* named attribute value (empty if the attribute is not defined) +/* after optional further named attribute substitution. +/* .IP "${name?text}, ${name?{text}}" +/* Conditional attribute-based substitution. If the named attribute +/* value is non-empty, the result is the given text, after +/* named attribute expansion and relational expression evaluation. +/* Otherwise, the result is empty. Whitespace before or after +/* {text} is ignored. +/* .IP "${name:text}, ${name:{text}}" +/* Conditional attribute-based substitution. If the attribute +/* value is empty or undefined, the expansion is the given +/* text, after named attribute expansion and relational expression +/* evaluation. Otherwise, the result is empty. Whitespace +/* before or after {text} is ignored. +/* .IP "${name?{text1}:{text2}}, ${name?{text1}:text2}" +/* Conditional attribute-based substitution. If the named attribute +/* value is non-empty, the result is text1. Otherwise, the +/* result is text2. In both cases the result is subject to +/* named attribute expansion and relational expression evaluation. +/* Whitespace before or after {text1} or {text2} is ignored. +/* .IP "${{text1} == ${text2} ? {text3} : {text4}}" +/* Relational expression-based substitution. First, the content +/* of {text1} and ${text2} is subjected to named attribute and +/* relational expression-based substitution. Next, the relational +/* expression is evaluated. If it evaluates to "true", the +/* result is the content of {text3}, otherwise it is the content +/* of {text4}, after named attribute and relational expression-based +/* substitution. In addition to ==, this supports !=, <, <=, +/* >=, and >. Comparisons are numerical when both operands are +/* all digits, otherwise the comparisons are lexicographical. +/* +/* Arguments: +/* .IP result +/* Storage for the result of expansion. By default, the result +/* is truncated upon entry. +/* .IP pattern +/* The string to be expanded. +/* .IP flags +/* Bit-wise OR of zero or more of the following: +/* .RS +/* .IP MAC_EXP_FLAG_RECURSE +/* Expand attributes in lookup results. This should never be +/* done with data whose origin is untrusted. +/* .IP MAC_EXP_FLAG_APPEND +/* Append text to the result buffer without truncating it. +/* .IP MAC_EXP_FLAG_SCAN +/* Scan the input for named attributes, including named +/* attributes in all conditional result values. Do not expand +/* named attributes, and do not truncate or write to the result +/* argument. +/* .IP MAC_EXP_FLAG_PRINTABLE +/* Use the printable() function instead of \fIfilter\fR. +/* .PP +/* The constant MAC_EXP_FLAG_NONE specifies a manifest null value. +/* .RE +/* .IP filter +/* A null pointer, or a null-terminated array of characters that +/* are allowed to appear in an expansion. Illegal characters are +/* replaced by underscores. +/* .IP lookup +/* The attribute lookup routine. Arguments are: the attribute name, +/* MAC_EXP_MODE_TEST to test the existence of the named attribute +/* or MAC_EXP_MODE_USE to use the value of the named attribute, +/* and the caller context that was given to mac_expand(). A null +/* result value means that the requested attribute was not defined. +/* .IP context +/* Caller context that is passed on to the attribute lookup routine. +/* .PP +/* mac_expand_add_relop() registers a function that implements +/* support for custom relational operators. Custom operator names +/* such as "==xxx" have two parts: a prefix that is identical to +/* a built-in operator such as "==", and an application-specified +/* suffix such as "xxx". +/* +/* Arguments: +/* .IP tok_list +/* A null-terminated list of MAC_EXP_OP_TOK_* values that support +/* the custom operator suffix. +/* .IP suffix +/* A null-terminated alphanumeric string that specifies the custom +/* operator suffix. +/* .IP relop_eval +/* A function that compares two strings according to the +/* MAC_EXP_OP_TOK_* value specified with the tok_val argument, +/* and that returns non-zero if the custom operator evaluates to +/* true, zero otherwise. +/* +/* mac_exp_op_res_bool provides an array that converts a boolean +/* value (0 or 1) to the corresponding MAX_EXP_OP_RES_TRUE or +/* MAX_EXP_OP_RES_FALSE value. +/* DIAGNOSTICS +/* Fatal errors: out of memory. Warnings: syntax errors, unreasonable +/* recursion depth. +/* +/* The result value is the binary OR of zero or more of the following: +/* .IP MAC_PARSE_ERROR +/* A syntax error was found in \fBpattern\fR, or some attribute had +/* an unreasonable nesting depth. +/* .IP MAC_PARSE_UNDEF +/* An attribute was expanded but its value was not defined. +/* SEE ALSO +/* mac_parse(3) locate macro references in string. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * Simplifies the return of common relational operator results. + */ +MAC_EXP_OP_RES mac_exp_op_res_bool[2] = { + MAC_EXP_OP_RES_FALSE, + MAC_EXP_OP_RES_TRUE +}; + + /* + * Little helper structure. + */ +typedef struct { + VSTRING *result; /* result buffer */ + int flags; /* features */ + const char *filter; /* character filter */ + MAC_EXP_LOOKUP_FN lookup; /* lookup routine */ + void *context; /* caller context */ + int status; /* findings */ + int level; /* nesting level */ +} MAC_EXP_CONTEXT; + + /* + * Support for relational expressions. + * + * As of Postfix 2.2, ${attr-name?result} or ${attr-name:result} return the + * result respectively when the parameter value is non-empty, or when the + * parameter value is undefined or empty; support for the ternary ?: + * operator was anticipated, but not implemented for 10 years. + * + * To make ${relational-expr?result} and ${relational-expr:result} work as + * expected without breaking the way that ? and : work, relational + * expressions evaluate to a non-empty or empty value. It does not matter + * what non-empty value we use for TRUE. However we must not use the + * undefined (null pointer) value for FALSE - that would raise the + * MAC_PARSE_UNDEF flag. + * + * The value of a relational expression can be exposed with ${relational-expr}, + * i.e. a relational expression that is not followed by ? or : conditional + * expansion. + */ +#define MAC_EXP_BVAL_TRUE "true" +#define MAC_EXP_BVAL_FALSE "" + + /* + * Relational operators. The MAC_EXP_OP_TOK_* are defined in the header + * file. + */ +#define MAC_EXP_OP_STR_EQ "==" +#define MAC_EXP_OP_STR_NE "!=" +#define MAC_EXP_OP_STR_LT "<" +#define MAC_EXP_OP_STR_LE "<=" +#define MAC_EXP_OP_STR_GE ">=" +#define MAC_EXP_OP_STR_GT ">" +#define MAC_EXP_OP_STR_ANY "\"" MAC_EXP_OP_STR_EQ \ + "\" or \"" MAC_EXP_OP_STR_NE "\"" \ + "\" or \"" MAC_EXP_OP_STR_LT "\"" \ + "\" or \"" MAC_EXP_OP_STR_LE "\"" \ + "\" or \"" MAC_EXP_OP_STR_GE "\"" \ + "\" or \"" MAC_EXP_OP_STR_GT "\"" + +static const NAME_CODE mac_exp_op_table[] = +{ + MAC_EXP_OP_STR_EQ, MAC_EXP_OP_TOK_EQ, + MAC_EXP_OP_STR_NE, MAC_EXP_OP_TOK_NE, + MAC_EXP_OP_STR_LT, MAC_EXP_OP_TOK_LT, + MAC_EXP_OP_STR_LE, MAC_EXP_OP_TOK_LE, + MAC_EXP_OP_STR_GE, MAC_EXP_OP_TOK_GE, + MAC_EXP_OP_STR_GT, MAC_EXP_OP_TOK_GT, + 0, MAC_EXP_OP_TOK_NONE, +}; + + /* + * The whitespace separator set. + */ +#define MAC_EXP_WHITESPACE CHARS_SPACE + + /* + * Support for operator extensions. + */ +static HTABLE *mac_exp_ext_table; +static VSTRING *mac_exp_ext_key; + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) + +/* atol_or_die - convert or die */ + +static long atol_or_die(const char *strval) +{ + long result; + char *remainder; + + result = sane_strtol(strval, &remainder, 10); + if (*strval == 0 /* can't happen */ || *remainder != 0 || errno == ERANGE) + msg_fatal("mac_exp_eval: bad conversion: %s", strval); + return (result); +} + +/* mac_exp_eval - evaluate binary expression */ + +static MAC_EXP_OP_RES mac_exp_eval(const char *left, int tok_val, + const char *rite) +{ + static const char myname[] = "mac_exp_eval"; + long delta; + + /* + * Numerical or string comparison. + */ + if (alldig(left) && alldig(rite)) { + delta = atol_or_die(left) - atol_or_die(rite); + } else { + delta = strcmp(left, rite); + } + switch (tok_val) { + case MAC_EXP_OP_TOK_EQ: + return (mac_exp_op_res_bool[delta == 0]); + case MAC_EXP_OP_TOK_NE: + return (mac_exp_op_res_bool[delta != 0]); + case MAC_EXP_OP_TOK_LT: + return (mac_exp_op_res_bool[delta < 0]); + case MAC_EXP_OP_TOK_LE: + return (mac_exp_op_res_bool[delta <= 0]); + case MAC_EXP_OP_TOK_GE: + return (mac_exp_op_res_bool[delta >= 0]); + case MAC_EXP_OP_TOK_GT: + return (mac_exp_op_res_bool[delta > 0]); + default: + msg_panic("%s: unknown operator: %d", + myname, tok_val); + } +} + +/* mac_exp_parse_error - report parse error, set error flag, return status */ + +static int PRINTFLIKE(2, 3) mac_exp_parse_error(MAC_EXP_CONTEXT *mc, + const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_warn(fmt, ap); + va_end(ap); + return (mc->status |= MAC_PARSE_ERROR); +}; + +/* MAC_EXP_ERR_RETURN - report parse error, set error flag, return status */ + +#define MAC_EXP_ERR_RETURN(mc, fmt, ...) do { \ + return (mac_exp_parse_error(mc, fmt, __VA_ARGS__)); \ + } while (0) + + /* + * Postfix 3.0 introduces support for {text} operands. Only with these do we + * support the ternary ?: operator and relational operators. + * + * We cannot support operators in random text, because that would break Postfix + * 2.11 compatibility. For example, with the expression "${name?value}", the + * value is random text that may contain ':', '?', '{' and '}' characters. + * In particular, with Postfix 2.2 .. 2.11, "${name??foo:{b}ar}" evaluates + * to "?foo:{b}ar" or empty. There are explicit tests in this directory and + * the postconf directory to ensure that Postfix 2.11 compatibility is + * maintained. + * + * Ideally, future Postfix configurations enclose random text operands inside + * {} braces. These allow whitespace around operands, which improves + * readability. + */ + +/* MAC_EXP_FIND_LEFT_CURLY - skip over whitespace to '{', advance read ptr */ + +#define MAC_EXP_FIND_LEFT_CURLY(len, cp) \ + ((cp[len = strspn(cp, MAC_EXP_WHITESPACE)] == '{') ? \ + (cp += len) : 0) + +/* mac_exp_extract_curly_payload - balance {}, skip whitespace, return payload */ + +static char *mac_exp_extract_curly_payload(MAC_EXP_CONTEXT *mc, char **bp) +{ + char *payload; + char *cp; + int level; + int ch; + + /* + * Extract the payload and balance the {}. The caller is expected to skip + * leading whitespace before the {. See MAC_EXP_FIND_LEFT_CURLY(). + */ + for (level = 1, cp = *bp, payload = ++cp; /* see below */ ; cp++) { + if ((ch = *cp) == 0) { + mac_exp_parse_error(mc, "unbalanced {} in attribute expression: " + "\"%s\"", + *bp); + return (0); + } else if (ch == '{') { + level++; + } else if (ch == '}') { + if (--level <= 0) + break; + } + } + *cp++ = 0; + + /* + * Skip trailing whitespace after }. + */ + *bp = cp + strspn(cp, MAC_EXP_WHITESPACE); + return (payload); +} + +/* mac_exp_parse_relational - parse relational expression, advance read ptr */ + +static int mac_exp_parse_relational(MAC_EXP_CONTEXT *mc, const char **lookup, + char **bp) +{ + char *cp = *bp; + VSTRING *left_op_buf; + VSTRING *rite_op_buf; + const char *left_op_strval; + const char *rite_op_strval; + char *op_pos; + char *op_strval; + size_t op_len; + int op_tokval; + int op_result; + size_t tmp_len; + char *type_pos; + size_t type_len; + MAC_EXPAND_RELOP_FN relop_eval; + + /* + * Left operand. The caller is expected to skip leading whitespace before + * the {. See MAC_EXP_FIND_LEFT_CURLY(). + */ + if ((left_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0) + return (mc->status); + + /* + * Operator. Todo: regexp operator. + */ + op_pos = cp; + op_len = strspn(cp, "<>!=?+-*/~&|%"); /* for better diagnostics. */ + op_strval = mystrndup(cp, op_len); + op_tokval = name_code(mac_exp_op_table, NAME_CODE_FLAG_NONE, op_strval); + myfree(op_strval); + if (op_tokval == MAC_EXP_OP_TOK_NONE) + MAC_EXP_ERR_RETURN(mc, "%s expected at: \"...%s}>>>%.20s\"", + MAC_EXP_OP_STR_ANY, left_op_strval, cp); + cp += op_len; + + /* + * Custom operator suffix. + */ + if (mac_exp_ext_table && ISALNUM(*cp)) { + type_pos = cp; + for (type_len = 1; ISALNUM(cp[type_len]); type_len++) + /* void */ ; + cp += type_len; + vstring_sprintf(mac_exp_ext_key, "%.*s", + (int) (op_len + type_len), op_pos); + if ((relop_eval = (MAC_EXPAND_RELOP_FN) htable_find(mac_exp_ext_table, + STR(mac_exp_ext_key))) == 0) + MAC_EXP_ERR_RETURN(mc, "bad operator suffix at: \"...%.*s>>>%.*s\"", + (int) op_len, op_pos, (int) type_len, type_pos); + } else { + relop_eval = mac_exp_eval; + } + + /* + * Right operand. Todo: syntax may depend on operator. + */ + if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp) == 0) + MAC_EXP_ERR_RETURN(mc, "\"{expression}\" expected at: " + "\"...{%s} %.*s>>>%.20s\"", + left_op_strval, (int) op_len, op_pos, cp); + if ((rite_op_strval = mac_exp_extract_curly_payload(mc, &cp)) == 0) + return (mc->status); + + /* + * Evaluate the relational expression. Todo: regexp support. + */ + mc->status |= + mac_expand(left_op_buf = vstring_alloc(100), left_op_strval, + mc->flags, mc->filter, mc->lookup, mc->context); + mc->status |= + mac_expand(rite_op_buf = vstring_alloc(100), rite_op_strval, + mc->flags, mc->filter, mc->lookup, mc->context); + if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0 + && (op_result = relop_eval(vstring_str(left_op_buf), op_tokval, + vstring_str(rite_op_buf))) == MAC_EXP_OP_RES_ERROR) + mc->status |= MAC_PARSE_ERROR; + vstring_free(left_op_buf); + vstring_free(rite_op_buf); + if (mc->status & MAC_PARSE_ERROR) + return (mc->status); + + /* + * Here, we fake up a non-empty or empty parameter value lookup result, + * for compatibility with the historical code that looks named parameter + * values. + */ + if (mc->flags & MAC_EXP_FLAG_SCAN) { + *lookup = 0; + } else { + switch (op_result) { + case MAC_EXP_OP_RES_TRUE: + *lookup = MAC_EXP_BVAL_TRUE; + break; + case MAC_EXP_OP_RES_FALSE: + *lookup = MAC_EXP_BVAL_FALSE; + break; + default: + msg_panic("mac_expand: unexpected operator result: %d", op_result); + } + } + *bp = cp; + return (0); +} + +/* mac_expand_add_relop - register operator extensions */ + +void mac_expand_add_relop(int *tok_list, const char *suffix, + MAC_EXPAND_RELOP_FN relop_eval) +{ + const char myname[] = "mac_expand_add_relop"; + const char *tok_name; + int *tp; + + /* + * Sanity checks. + */ + if (!allalnum(suffix)) + msg_panic("%s: bad operator suffix: %s", myname, suffix); + + /* + * One-time initialization. + */ + if (mac_exp_ext_table == 0) { + mac_exp_ext_table = htable_create(10); + mac_exp_ext_key = vstring_alloc(10); + } + for (tp = tok_list; *tp; tp++) { + if ((tok_name = str_name_code(mac_exp_op_table, *tp)) == 0) + msg_panic("%s: unknown token code: %d", myname, *tp); + vstring_sprintf(mac_exp_ext_key, "%s%s", tok_name, suffix); + if (htable_locate(mac_exp_ext_table, STR(mac_exp_ext_key)) != 0) + msg_panic("%s: duplicate key: %s", myname, STR(mac_exp_ext_key)); + (void) htable_enter(mac_exp_ext_table, + STR(mac_exp_ext_key), (void *) relop_eval); + } +} + +/* mac_expand_callback - callback for mac_parse */ + +static int mac_expand_callback(int type, VSTRING *buf, void *ptr) +{ + static const char myname[] = "mac_expand_callback"; + MAC_EXP_CONTEXT *mc = (MAC_EXP_CONTEXT *) ptr; + int lookup_mode; + const char *lookup; + char *cp; + int ch; + ssize_t res_len; + ssize_t tmp_len; + const char *res_iftrue; + const char *res_iffalse; + + /* + * Sanity check. + */ + if (mc->level++ > 100) + mac_exp_parse_error(mc, "unreasonable macro call nesting: \"%s\"", + vstring_str(buf)); + if (mc->status & MAC_PARSE_ERROR) + return (mc->status); + + /* + * Named parameter or relational expression. In case of a syntax error, + * return without doing damage, and issue a warning instead. + */ + if (type == MAC_PARSE_EXPR) { + + cp = vstring_str(buf); + + /* + * Relational expression. If recursion is disabled, perform only one + * level of $name expansion. + */ + if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { + if (mac_exp_parse_relational(mc, &lookup, &cp) != 0) + return (mc->status); + + /* + * Look for the ? or : operator. + */ + if ((ch = *cp) != 0) { + if (ch != '?' && ch != ':') + MAC_EXP_ERR_RETURN(mc, "\"?\" or \":\" expected at: " + "\"...}>>>%.20s\"", cp); + cp++; + } + } + + /* + * Named parameter. + */ + else { + char *start; + + /* + * Look for the ? or : operator. In case of a syntax error, + * return without doing damage, and issue a warning instead. + */ + start = (cp += strspn(cp, MAC_EXP_WHITESPACE)); + for ( /* void */ ; /* void */ ; cp++) { + if ((ch = cp[tmp_len = strspn(cp, MAC_EXP_WHITESPACE)]) == 0) { + *cp = 0; + lookup_mode = MAC_EXP_MODE_USE; + break; + } + if (ch == '?' || ch == ':') { + *cp++ = 0; + cp += tmp_len; + lookup_mode = MAC_EXP_MODE_TEST; + break; + } + ch = *cp; + if (!ISALNUM(ch) && ch != '_') { + MAC_EXP_ERR_RETURN(mc, "attribute name syntax error at: " + "\"...%.*s>>>%.20s\"", + (int) (cp - vstring_str(buf)), + vstring_str(buf), cp); + } + } + + /* + * Look up the named parameter. Todo: allow the lookup function + * to specify if the result is safe for $name expansion. + */ + lookup = mc->lookup(start, lookup_mode, mc->context); + } + + /* + * Return the requested result. After parsing the result operand + * following ?, we fall through to parse the result operand following + * :. This is necessary with the ternary ?: operator: first, with + * MAC_EXP_FLAG_SCAN to parse both result operands with mac_parse(), + * and second, to find garbage after any result operand. Without + * MAC_EXP_FLAG_SCAN the content of only one of the ?: result + * operands will be parsed with mac_parse(); syntax errors in the + * other operand will be missed. + */ + switch (ch) { + case '?': + if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { + if ((res_iftrue = mac_exp_extract_curly_payload(mc, &cp)) == 0) + return (mc->status); + } else { + res_iftrue = cp; + cp = ""; /* no left-over text */ + } + if ((lookup != 0 && *lookup != 0) || (mc->flags & MAC_EXP_FLAG_SCAN)) + mc->status |= mac_parse(res_iftrue, mac_expand_callback, + (void *) mc); + if (*cp == 0) /* end of input, OK */ + break; + if (*cp != ':') /* garbage */ + MAC_EXP_ERR_RETURN(mc, "\":\" expected at: " + "\"...%s}>>>%.20s\"", res_iftrue, cp); + cp += 1; + /* FALLTHROUGH: do not remove, see comment above. */ + case ':': + if (MAC_EXP_FIND_LEFT_CURLY(tmp_len, cp)) { + if ((res_iffalse = mac_exp_extract_curly_payload(mc, &cp)) == 0) + return (mc->status); + } else { + res_iffalse = cp; + cp = ""; /* no left-over text */ + } + if (lookup == 0 || *lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) + mc->status |= mac_parse(res_iffalse, mac_expand_callback, + (void *) mc); + if (*cp != 0) /* garbage */ + MAC_EXP_ERR_RETURN(mc, "unexpected input at: " + "\"...%s}>>>%.20s\"", res_iffalse, cp); + break; + case 0: + if (lookup == 0) { + mc->status |= MAC_PARSE_UNDEF; + } else if (*lookup == 0 || (mc->flags & MAC_EXP_FLAG_SCAN)) { + /* void */ ; + } else if (mc->flags & MAC_EXP_FLAG_RECURSE) { + vstring_strcpy(buf, lookup); + mc->status |= mac_parse(vstring_str(buf), mac_expand_callback, + (void *) mc); + } else { + res_len = VSTRING_LEN(mc->result); + vstring_strcat(mc->result, lookup); + if (mc->flags & MAC_EXP_FLAG_PRINTABLE) { + printable(vstring_str(mc->result) + res_len, '_'); + } else if (mc->filter) { + cp = vstring_str(mc->result) + res_len; + while (*(cp += strspn(cp, mc->filter))) + *cp++ = '_'; + } + } + break; + default: + msg_panic("%s: unknown operator code %d", myname, ch); + } + } + + /* + * Literal text. + */ + else if ((mc->flags & MAC_EXP_FLAG_SCAN) == 0) { + vstring_strcat(mc->result, vstring_str(buf)); + } + mc->level--; + + return (mc->status); +} + +/* mac_expand - expand $name instances */ + +int mac_expand(VSTRING *result, const char *pattern, int flags, + const char *filter, + MAC_EXP_LOOKUP_FN lookup, void *context) +{ + MAC_EXP_CONTEXT mc; + int status; + + /* + * Bundle up the request and do the substitutions. + */ + mc.result = result; + mc.flags = flags; + mc.filter = filter; + mc.lookup = lookup; + mc.context = context; + mc.status = 0; + mc.level = 0; + if ((flags & (MAC_EXP_FLAG_APPEND | MAC_EXP_FLAG_SCAN)) == 0) + VSTRING_RESET(result); + status = mac_parse(pattern, mac_expand_callback, (void *) &mc); + if ((flags & MAC_EXP_FLAG_SCAN) == 0) + VSTRING_TERMINATE(result); + + return (status); +} + +#ifdef TEST + + /* + * This code certainly deserves a stand-alone test program. + */ +#include +#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 MAC_EXP_OP_RES length_relop_eval(const char *left, int relop, + const char *rite) +{ + const char myname[] = "length_relop_eval"; + ssize_t delta = strlen(left) - strlen(rite); + + switch (relop) { + case MAC_EXP_OP_TOK_EQ: + return (mac_exp_op_res_bool[delta == 0]); + case MAC_EXP_OP_TOK_NE: + return (mac_exp_op_res_bool[delta != 0]); + case MAC_EXP_OP_TOK_LT: + return (mac_exp_op_res_bool[delta < 0]); + case MAC_EXP_OP_TOK_LE: + return (mac_exp_op_res_bool[delta <= 0]); + case MAC_EXP_OP_TOK_GE: + return (mac_exp_op_res_bool[delta >= 0]); + case MAC_EXP_OP_TOK_GT: + return (mac_exp_op_res_bool[delta > 0]); + default: + msg_panic("%s: unknown operator: %d", + myname, relop); + } +} + +int main(int unused_argc, char **argv) +{ + VSTRING *buf = vstring_alloc(100); + VSTRING *result = vstring_alloc(100); + char *cp; + char *name; + char *value; + HTABLE *table; + int stat; + int length_relops[] = { + MAC_EXP_OP_TOK_EQ, MAC_EXP_OP_TOK_NE, + MAC_EXP_OP_TOK_GT, MAC_EXP_OP_TOK_GE, + MAC_EXP_OP_TOK_LT, MAC_EXP_OP_TOK_LE, + 0, + }; + + /* + * Add relops that compare string lengths instead of content. + */ + mac_expand_add_relop(length_relops, "length", length_relop_eval); + + /* + * Loop over the inputs. + */ + while (!vstream_feof(VSTREAM_IN)) { + + table = htable_create(0); + + /* + * Read a block of definitions, terminated with an empty line. + */ + while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { + vstream_printf("<< %s\n", vstring_str(buf)); + vstream_fflush(VSTREAM_OUT); + if (VSTRING_LEN(buf) == 0) + break; + cp = vstring_str(buf); + name = mystrtok(&cp, CHARS_SPACE "="); + value = mystrtok(&cp, CHARS_SPACE "="); + htable_enter(table, name, value ? mystrdup(value) : 0); + } + + /* + * Read a block of patterns, terminated with an empty line or EOF. + */ + while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { + vstream_printf("<< %s\n", vstring_str(buf)); + vstream_fflush(VSTREAM_OUT); + if (VSTRING_LEN(buf) == 0) + break; + cp = vstring_str(buf); + VSTRING_RESET(result); + stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE, + (char *) 0, lookup, (void *) table); + vstream_printf("stat=%d result=%s\n", stat, vstring_str(result)); + vstream_fflush(VSTREAM_OUT); + } + htable_free(table, myfree); + vstream_printf("\n"); + } + + /* + * Clean up. + */ + vstring_free(buf); + vstring_free(result); + exit(0); +} + +#endif diff --git a/src/util/mac_expand.h b/src/util/mac_expand.h new file mode 100644 index 0000000..fbe6347 --- /dev/null +++ b/src/util/mac_expand.h @@ -0,0 +1,81 @@ +#ifndef _MAC_EXPAND_H_INCLUDED_ +#define _MAC_EXPAND_H_INCLUDED_ + +/*++ +/* NAME +/* mac_expand 3h +/* SUMMARY +/* expand macro references in string +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * Features. + */ +#define MAC_EXP_FLAG_NONE (0) +#define MAC_EXP_FLAG_RECURSE (1<<0) +#define MAC_EXP_FLAG_APPEND (1<<1) +#define MAC_EXP_FLAG_SCAN (1<<2) +#define MAC_EXP_FLAG_PRINTABLE (1<<3) + + /* + * Token codes, public so tha they are available to mac_expand_add_relop() + */ +#define MAC_EXP_OP_TOK_NONE 0 /* Sentinel */ +#define MAC_EXP_OP_TOK_EQ 1 /* == */ +#define MAC_EXP_OP_TOK_NE 2 /* != */ +#define MAC_EXP_OP_TOK_LT 3 /* < */ +#define MAC_EXP_OP_TOK_LE 4 /* <= */ +#define MAC_EXP_OP_TOK_GE 5 /* >= */ +#define MAC_EXP_OP_TOK_GT 6 /* > */ + + /* + * Relational operator results. An enum to discourage assuming that 0 is + * false, !0 is true. + */ +typedef enum MAC_EXP_OP_RES { + MAC_EXP_OP_RES_TRUE, + MAC_EXP_OP_RES_FALSE, + MAC_EXP_OP_RES_ERROR, +} MAC_EXP_OP_RES; + + +extern MAC_EXP_OP_RES mac_exp_op_res_bool[2]; + + /* + * Real lookup or just a test? + */ +#define MAC_EXP_MODE_TEST (0) +#define MAC_EXP_MODE_USE (1) + +typedef const char *(*MAC_EXP_LOOKUP_FN) (const char *, int, void *); +typedef MAC_EXP_OP_RES (*MAC_EXPAND_RELOP_FN) (const char *, int, const char *); + +extern int mac_expand(VSTRING *, const char *, int, const char *, MAC_EXP_LOOKUP_FN, void *); +void mac_expand_add_relop(int *, const char *, MAC_EXPAND_RELOP_FN); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/mac_expand.in b/src/util/mac_expand.in new file mode 100644 index 0000000..1d61906 --- /dev/null +++ b/src/util/mac_expand.in @@ -0,0 +1,105 @@ +name1 = name1-value + +$name1 +$(name1 +$(name1) +$( name1) +$(name1 ) +$(na me1) +${na me1} +${${name1} != {}?name 1 defined, |$name1|$name2|} +${ ${name1} != {}?name 1 defined, |$name1|$name2|} +${ ${name1} ?name 1 defined, |$name1|$name2|} +${{$name1} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|} } +${x{$name1} != {}?{name 1 defined, |$name1|$name2|}} +${{$name1}x?{name 1 defined, |$name1|$name2|}} +${{$name1} != {}x{name 1 defined, |$name1|$name2|}} +${{$name1} != {}?x{name 1 defined, |$name1|$name2|}} +${{$name2} != {}?x{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +${{$name1} != {}?{name 1 defined, |$name1|$name2|}x} +${{$name1} != {}?{name 1 defined, |$name1|$name2|}x:{name 1 undefined, |$name1|$name2|}} +${{$name1} != {}?{name 1 defined, |$name1|$name2|}:x{name 1 undefined, |$name1|$name2|}} +${{$name2} != {}?{name 2 defined, |$name1|$name2|}:x{name 2 undefined, |$name1|$name2|}} +${{text}} +${{text}?{non-empty}:{empty}} +${{text} = {}} +${{${ name1}} == {}} +${name1?{${ name1}}:{${name2}}} +${name2?{${ name1}}:{${name2}}} +${name2?{${name1}}:{${ name2}}} +${name2:{${name1}}:{${name2}}} +${name2?{${name1}}?{${name2}}} +${{${name1?bug:test}} != {bug:test}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +${{${name1??bug}} != {?bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +${{${name2::bug}} != {:bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +${{xx}==(yy)?{oops}:{phew}} + +name1 = name1-value + +${name1?name 1 defined, |$name1|$name2|} +${name1:name 1 undefined, |$name1|$name2|} +${name2?name 2 defined, |$name1|$name2|} +${name2:name 2 undefined, |$name1|$name2|} +|$name1|$name2| +${{$name1} != {}?{name 1 defined, |$name1|$name2|}} +${{$name1} != {}:{name 1 undefined, |$name1|$name2|}} +${{$name1} == {}?{name 1 undefined, |$name1|$name2|}} +${{$name1} == {}:{name 1 defined, |$name1|$name2|}} +${name1?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}} +${{$name1} != {}?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}} +${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|}} +${{$name1} != {}?{name 1 defined, |$name1|$name2|}:name 1 undefined, |$name1|$name2|} +${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : name 1 undefined, |$name1|$name2|} +${{$name1} != {}} +${{$name1} == {}} +${{$name2} != {}?{name 2 defined, |$name1|$name2|}} +${{$name2} != {}:{name 2 undefined, |$name1|$name2|}} +${{$name2} == {}?{name 2 undefined, |$name1|$name2|}} +${{$name2} == {}:{name 2 defined, |$name1|$name2|}} +${name2?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +${{$name2} != {}?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : {name 2 undefined, |$name1|$name2|}} +${{$name2} != {}?{name 2 defined, |$name1|$name2|}:name 2 undefined, |$name1|$name2|} +${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : name 2 undefined, |$name1|$name2|} +${{$name2} != {}} +${{$name2} == {}} + + +${{1} == {1}} +${{1} < {1}} +${{1} <= {1}} +${{1} >= {1}} +${{1} > {1}} +${{1} == {2}} +${{1} < {2}} +${{1} <= {2}} +${{1} >= {2}} +${{1} > {2}} +${{a} == {a}} +${{a} < {a}} +${{a} <= {a}} +${{a} >= {a}} +${{a} > {a}} +${{a} == {b}} +${{a} < {b}} +${{a} <= {b}} +${{a} >= {b}} +${{a} > {b}} + +name1 = foo + +${{$name1} >=blah {bar}} +${{aaa} == {bbb}} +${{aaa} ==length {bbb}} +${{aaa} <=length {bbb}} +${{aaa} >=length {bbb}} +${{aaa} != {bbb}} +${{aaa} !=length {bbb}} +${{aaa} > {bb}} +${{aaa} >length {bb}} +${{aaa} >= {bb}} +${{aaa} >=length {bb}} +${{aaa} < {bb}} +${{aaa} >> me1" +stat=1 result= +<< ${na me1} +unknown: warning: attribute name syntax error at: "...na>>> me1" +stat=1 result= +<< ${${name1} != {}?name 1 defined, |$name1|$name2|} +unknown: warning: attribute name syntax error at: "...>>>${name1} != {}?name " +stat=1 result= +<< ${ ${name1} != {}?name 1 defined, |$name1|$name2|} +unknown: warning: attribute name syntax error at: "... >>>${name1} != {}?name " +stat=1 result= +<< ${ ${name1} ?name 1 defined, |$name1|$name2|} +unknown: warning: attribute name syntax error at: "... >>>${name1} ?name 1 def" +stat=1 result= +<< ${{$name1} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|} } +unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...$name1}>>>? {name 1 defined, |" +stat=1 result= +<< ${x{$name1} != {}?{name 1 defined, |$name1|$name2|}} +unknown: warning: attribute name syntax error at: "...x>>>{$name1} != {}?{name" +stat=1 result= +<< ${{$name1}x?{name 1 defined, |$name1|$name2|}} +unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...$name1}>>>x?{name 1 defined, |" +stat=1 result= +<< ${{$name1} != {}x{name 1 defined, |$name1|$name2|}} +unknown: warning: "?" or ":" expected at: "...}>>>x{name 1 defined, |$" +stat=1 result= +<< ${{$name1} != {}?x{name 1 defined, |$name1|$name2|}} +stat=2 result=x{name 1 defined, |name1-value||} +<< ${{$name2} != {}?x{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +stat=2 result= +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}x} +unknown: warning: ":" expected at: "...name 1 defined, |$name1|$name2|}>>>x" +stat=3 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}x:{name 1 undefined, |$name1|$name2|}} +unknown: warning: ":" expected at: "...name 1 defined, |$name1|$name2|}>>>x:{name 1 undefined," +stat=3 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:x{name 1 undefined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:x{name 2 undefined, |$name1|$name2|}} +stat=2 result=x{name 2 undefined, |name1-value||} +<< ${{text}} +unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>" +stat=1 result= +<< ${{text}?{non-empty}:{empty}} +unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>?{non-empty}:{empty}" +stat=1 result= +<< ${{text} = {}} +unknown: warning: "==" or "!="" or "<"" or "<="" or ">="" or ">" expected at: "...text}>>>= {}" +stat=1 result= +<< ${{${ name1}} == {}} +stat=0 result= +<< ${name1?{${ name1}}:{${name2}}} +stat=0 result=name1-value +<< ${name2?{${ name1}}:{${name2}}} +stat=2 result= +<< ${name2?{${name1}}:{${ name2}}} +stat=2 result= +<< ${name2:{${name1}}:{${name2}}} +unknown: warning: unexpected input at: "...${name1}}>>>:{${name2}}" +stat=1 result=name1-value +<< ${name2?{${name1}}?{${name2}}} +unknown: warning: ":" expected at: "...${name1}}>>>?{${name2}}" +stat=1 result= +<< ${{${name1?bug:test}} != {bug:test}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +stat=0 result=Good: Postfix 2.11 compatible +<< ${{${name1??bug}} != {?bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +stat=0 result=Good: Postfix 2.11 compatible +<< ${{${name2::bug}} != {:bug}?{Error: NOT}:{Good:}} Postfix 2.11 compatible +stat=0 result=Good: Postfix 2.11 compatible +<< ${{xx}==(yy)?{oops}:{phew}} +unknown: warning: "{expression}" expected at: "...{xx} ==>>>(yy)?{oops}:{phew}" +stat=1 result= +<< + +<< name1 = name1-value +<< +<< ${name1?name 1 defined, |$name1|$name2|} +stat=2 result=name 1 defined, |name1-value|| +<< ${name1:name 1 undefined, |$name1|$name2|} +stat=0 result= +<< ${name2?name 2 defined, |$name1|$name2|} +stat=0 result= +<< ${name2:name 2 undefined, |$name1|$name2|} +stat=2 result=name 2 undefined, |name1-value|| +<< |$name1|$name2| +stat=2 result=|name1-value|| +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}:{name 1 undefined, |$name1|$name2|}} +stat=0 result= +<< ${{$name1} == {}?{name 1 undefined, |$name1|$name2|}} +stat=0 result= +<< ${{$name1} == {}:{name 1 defined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${name1?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:{name 1 undefined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : {name 1 undefined, |$name1|$name2|}} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}?{name 1 defined, |$name1|$name2|}:name 1 undefined, |$name1|$name2|} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {} ? {name 1 defined, |$name1|$name2|} : name 1 undefined, |$name1|$name2|} +stat=2 result=name 1 defined, |name1-value|| +<< ${{$name1} != {}} +stat=0 result=true +<< ${{$name1} == {}} +stat=0 result= +<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}} +stat=2 result= +<< ${{$name2} != {}:{name 2 undefined, |$name1|$name2|}} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} == {}?{name 2 undefined, |$name1|$name2|}} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} == {}:{name 2 defined, |$name1|$name2|}} +stat=2 result= +<< ${name2?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:{name 2 undefined, |$name1|$name2|}} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : {name 2 undefined, |$name1|$name2|}} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} != {}?{name 2 defined, |$name1|$name2|}:name 2 undefined, |$name1|$name2|} +stat=2 result=name 2 undefined, |name1-value|| +<< ${{$name2} != {} ? {name 2 defined, |$name1|$name2|} : name 2 undefined, |$name1|$name2|} +stat=2 result= name 2 undefined, |name1-value|| +<< ${{$name2} != {}} +stat=2 result= +<< ${{$name2} == {}} +stat=2 result=true +<< + +<< +<< ${{1} == {1}} +stat=0 result=true +<< ${{1} < {1}} +stat=0 result= +<< ${{1} <= {1}} +stat=0 result=true +<< ${{1} >= {1}} +stat=0 result=true +<< ${{1} > {1}} +stat=0 result= +<< ${{1} == {2}} +stat=0 result= +<< ${{1} < {2}} +stat=0 result=true +<< ${{1} <= {2}} +stat=0 result=true +<< ${{1} >= {2}} +stat=0 result= +<< ${{1} > {2}} +stat=0 result= +<< ${{a} == {a}} +stat=0 result=true +<< ${{a} < {a}} +stat=0 result= +<< ${{a} <= {a}} +stat=0 result=true +<< ${{a} >= {a}} +stat=0 result=true +<< ${{a} > {a}} +stat=0 result= +<< ${{a} == {b}} +stat=0 result= +<< ${{a} < {b}} +stat=0 result=true +<< ${{a} <= {b}} +stat=0 result=true +<< ${{a} >= {b}} +stat=0 result= +<< ${{a} > {b}} +stat=0 result= +<< + +<< name1 = foo +<< +<< ${{$name1} >=blah {bar}} +unknown: warning: bad operator suffix at: "...>=>>>blah" +stat=1 result= +<< ${{aaa} == {bbb}} +stat=0 result= +<< ${{aaa} ==length {bbb}} +stat=0 result=true +<< ${{aaa} <=length {bbb}} +stat=0 result=true +<< ${{aaa} >=length {bbb}} +stat=0 result=true +<< ${{aaa} != {bbb}} +stat=0 result=true +<< ${{aaa} !=length {bbb}} +stat=0 result= +<< ${{aaa} > {bb}} +stat=0 result= +<< ${{aaa} >length {bb}} +stat=0 result=true +<< ${{aaa} >= {bb}} +stat=0 result= +<< ${{aaa} >=length {bb}} +stat=0 result=true +<< ${{aaa} < {bb}} +stat=0 result=true +<< ${{aaa} +/* +/* int mac_parse(string, action, context) +/* const char *string; +/* int (*action)(int type, VSTRING *buf, void *context); +/* DESCRIPTION +/* This module recognizes macro expressions in null-terminated +/* strings. Macro expressions have the form $name, $(text) or +/* ${text}. A macro name consists of alphanumerics and/or +/* underscore. Text other than macro expressions is treated +/* as literal text. +/* +/* mac_parse() breaks up its string argument into macro references +/* and other text, and invokes the \fIaction\fR routine for each item +/* found. With each action routine call, the \fItype\fR argument +/* indicates what was found, \fIbuf\fR contains a copy of the text +/* found, and \fIcontext\fR is passed on unmodified from the caller. +/* The application is at liberty to clobber \fIbuf\fR. +/* .IP MAC_PARSE_LITERAL +/* The content of \fIbuf\fR is literal text. +/* .IP MAC_PARSE_EXPR +/* The content of \fIbuf\fR is a macro expression: either a +/* bare macro name without the preceding "$", or all the text +/* inside $() or ${}. +/* .PP +/* The action routine result value is the bit-wise OR of zero or more +/* of the following: +/* .IP MAC_PARSE_ERROR +/* A parsing error was detected. +/* .IP MAC_PARSE_UNDEF +/* A macro was expanded but not defined. +/* .PP +/* Use the constant MAC_PARSE_OK when no error was detected. +/* SEE ALSO +/* dict(3) dictionary interface. +/* DIAGNOSTICS +/* Fatal errors: out of memory. malformed macro name. +/* +/* The result value is the bit-wise OR of zero or more of the +/* following: +/* .IP MAC_PARSE_ERROR +/* A parsing error was detected. +/* .IP MAC_PARSE_UNDEF +/* A macro was expanded but not defined. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include + + /* + * Helper macro for consistency. Null-terminate the temporary buffer, + * execute the action, and reset the temporary buffer for re-use. + */ +#define MAC_PARSE_ACTION(status, type, buf, context) \ + do { \ + VSTRING_TERMINATE(buf); \ + status |= action((type), (buf), (context)); \ + VSTRING_RESET(buf); \ + } while(0) + +/* mac_parse - split string into literal text and macro references */ + +int mac_parse(const char *value, MAC_PARSE_FN action, void *context) +{ + const char *myname = "mac_parse"; + VSTRING *buf = vstring_alloc(1); /* result buffer */ + const char *vp; /* value pointer */ + const char *pp; /* open_paren pointer */ + const char *ep; /* string end pointer */ + static char open_paren[] = "({"; + static char close_paren[] = ")}"; + int level; + int status = 0; + +#define SKIP(start, var, cond) do { \ + for (var = start; *var && (cond); var++) \ + /* void */; \ + } while (0) + + if (msg_verbose > 1) + msg_info("%s: %s", myname, value); + + for (vp = value; *vp;) { + if (*vp != '$') { /* ordinary character */ + VSTRING_ADDCH(buf, *vp); + vp += 1; + } else if (vp[1] == '$') { /* $$ becomes $ */ + VSTRING_ADDCH(buf, *vp); + vp += 2; + } else { /* found bare $ */ + if (VSTRING_LEN(buf) > 0) + MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context); + vp += 1; + pp = open_paren; + if (*vp == *pp || *vp == *++pp) { /* ${x} or $(x) */ + level = 1; + vp += 1; + for (ep = vp; level > 0; ep++) { + if (*ep == 0) { + msg_warn("truncated macro reference: \"%s\"", value); + status |= MAC_PARSE_ERROR; + break; + } + if (*ep == *pp) + level++; + if (*ep == close_paren[pp - open_paren]) + level--; + } + if (status & MAC_PARSE_ERROR) + break; + vstring_strncat(buf, vp, level > 0 ? ep - vp : ep - vp - 1); + vp = ep; + } else { /* plain $x */ + SKIP(vp, ep, ISALNUM(*ep) || *ep == '_'); + vstring_strncat(buf, vp, ep - vp); + vp = ep; + } + if (VSTRING_LEN(buf) == 0) { + status |= MAC_PARSE_ERROR; + msg_warn("empty macro name: \"%s\"", value); + break; + } + MAC_PARSE_ACTION(status, MAC_PARSE_EXPR, buf, context); + } + } + if (VSTRING_LEN(buf) > 0 && (status & MAC_PARSE_ERROR) == 0) + MAC_PARSE_ACTION(status, MAC_PARSE_LITERAL, buf, context); + + /* + * Cleanup. + */ + vstring_free(buf); + + return (status); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. Read strings from stdin, print parsed + * result to stdout. + */ +#include + +/* mac_parse_print - print parse tree */ + +static int mac_parse_print(int type, VSTRING *buf, void *unused_context) +{ + char *type_name; + + switch (type) { + case MAC_PARSE_EXPR: + type_name = "MAC_PARSE_EXPR"; + break; + case MAC_PARSE_LITERAL: + type_name = "MAC_PARSE_LITERAL"; + break; + default: + msg_panic("unknown token type %d", type); + } + vstream_printf("%s \"%s\"\n", type_name, vstring_str(buf)); + return (0); +} + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *buf = vstring_alloc(1); + + while (vstring_fgets_nonl(buf, VSTREAM_IN)) { + mac_parse(vstring_str(buf), mac_parse_print, (void *) 0); + vstream_fflush(VSTREAM_OUT); + } + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/mac_parse.h b/src/util/mac_parse.h new file mode 100644 index 0000000..5ed0dc1 --- /dev/null +++ b/src/util/mac_parse.h @@ -0,0 +1,51 @@ +#ifndef _MAC_PARSE_H_INCLUDED_ +#define _MAC_PARSE_H_INCLUDED_ + +/*++ +/* NAME +/* mac_parse 3h +/* SUMMARY +/* locate macro references in string +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#define MAC_PARSE_LITERAL 1 +#define MAC_PARSE_EXPR 2 +#define MAC_PARSE_VARNAME MAC_PARSE_EXPR /* 2.1 compatibility */ + +#define MAC_PARSE_OK 0 +#define MAC_PARSE_ERROR (1<<0) +#define MAC_PARSE_UNDEF (1<<1) +#define MAC_PARSE_USER 2 /* start user definitions */ + +typedef int (*MAC_PARSE_FN) (int, VSTRING *, void *); + +extern int WARN_UNUSED_RESULT mac_parse(const char *, MAC_PARSE_FN, void *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/make_dirs.c b/src/util/make_dirs.c new file mode 100644 index 0000000..2e37f8f --- /dev/null +++ b/src/util/make_dirs.c @@ -0,0 +1,173 @@ +/*++ +/* NAME +/* make_dirs 3 +/* SUMMARY +/* create directory hierarchy +/* SYNOPSIS +/* #include +/* +/* int make_dirs(path, perms) +/* const char *path; +/* int perms; +/* DESCRIPTION +/* make_dirs() creates the directory specified in \fIpath\fR, and +/* creates any missing intermediate directories as well. Directories +/* are created with the permissions specified in \fIperms\fR, as +/* modified by the process umask. +/* DIAGNOSTICS: +/* Fatal: out of memory. make_dirs() returns 0 in case of success. +/* In case of problems. make_dirs() returns -1 and \fIerrno\fR +/* reflects the nature of the problem. +/* SEE ALSO +/* mkdir(2) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "stringops.h" +#include "make_dirs.h" +#include "warn_stat.h" + +/* make_dirs - create directory hierarchy */ + +int make_dirs(const char *path, int perms) +{ + const char *myname = "make_dirs"; + char *saved_path; + unsigned char *cp; + int saved_ch; + struct stat st; + int ret; + mode_t saved_mode = 0; + gid_t egid = -1; + + /* + * Initialize. Make a copy of the path that we can safely clobber. + */ + cp = (unsigned char *) (saved_path = mystrdup(path)); + + /* + * I didn't like the 4.4BSD "mkdir -p" implementation, but coming up with + * my own took a day, spread out over several days. + */ +#define SKIP_WHILE(cond, ptr) { while(*ptr && (cond)) ptr++; } + + SKIP_WHILE(*cp == '/', cp); + + for (;;) { + SKIP_WHILE(*cp != '/', cp); + if ((saved_ch = *cp) != 0) + *cp = 0; + if ((ret = stat(saved_path, &st)) >= 0) { + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + ret = -1; + break; + } + saved_mode = st.st_mode; + } else { + if (errno != ENOENT) + break; + + /* + * mkdir(foo) fails with EEXIST if foo is a symlink. + */ +#if 0 + + /* + * Create a new directory. Unfortunately, mkdir(2) has no + * equivalent of open(2)'s O_CREAT|O_EXCL safety net, so we must + * require that the parent directory is not world writable. + * Detecting a lost race condition after the fact is not + * sufficient, as an attacker could repeat the attack and add one + * directory level at a time. + */ + if (saved_mode & S_IWOTH) { + msg_warn("refusing to mkdir %s: parent directory is writable by everyone", + saved_path); + errno = EPERM; + ret = -1; + break; + } +#endif + if ((ret = mkdir(saved_path, perms)) < 0) { + if (errno != EEXIST) + break; + /* Race condition? */ + if ((ret = stat(saved_path, &st)) < 0) + break; + if (!S_ISDIR(st.st_mode)) { + errno = ENOTDIR; + ret = -1; + break; + } + } + + /* + * Fix directory ownership when mkdir() ignores the effective + * GID. Don't change the effective UID for doing this. + */ + if ((ret = stat(saved_path, &st)) < 0) { + msg_warn("%s: stat %s: %m", myname, saved_path); + break; + } + if (egid == -1) + egid = getegid(); + if (st.st_gid != egid && (ret = chown(saved_path, -1, egid)) < 0) { + msg_warn("%s: chgrp %s: %m", myname, saved_path); + break; + } + } + if (saved_ch != 0) + *cp = saved_ch; + SKIP_WHILE(*cp == '/', cp); + if (*cp == 0) + break; + } + + /* + * Cleanup. + */ + myfree(saved_path); + return (ret); +} + +#ifdef TEST + + /* + * Test program. Usage: make_dirs path... + */ +#include +#include + +int main(int argc, char **argv) +{ + msg_vstream_init(argv[0], VSTREAM_ERR); + if (argc < 2) + msg_fatal("usage: %s path...", argv[0]); + while (--argc > 0 && *++argv != 0) + if (make_dirs(*argv, 0755)) + msg_fatal("%s: %m", *argv); + exit(0); +} + +#endif diff --git a/src/util/make_dirs.h b/src/util/make_dirs.h new file mode 100644 index 0000000..0df6117 --- /dev/null +++ b/src/util/make_dirs.h @@ -0,0 +1,30 @@ +#ifndef MAKE_DIRS_H_INCLUDED_ +#define MAKE_DIRS_H_INCLUDED_ + +/*++ +/* NAME +/* make_dirs 3h +/* SUMMARY +/* create directory hierarchy +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern int make_dirs(const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/mask_addr.c b/src/util/mask_addr.c new file mode 100644 index 0000000..5ddd0ca --- /dev/null +++ b/src/util/mask_addr.c @@ -0,0 +1,68 @@ +/*++ +/* NAME +/* mask_addr 3 +/* SUMMARY +/* address bit banging +/* SYNOPSIS +/* #include +/* +/* void mask_addr(addr_bytes, addr_byte_count, network_bits) +/* unsigned char *addr_bytes; +/* unsigned addr_byte_count; +/* unsigned network_bits; +/* DESCRIPTION +/* mask_addr() clears all the host bits in the specified +/* address. The code can handle addresses of any length, +/* and bytes of any width. +/* +/* Arguments: +/* .IP addr_bytes +/* The network address in network byte order. +/* .IP addr_byte_count +/* The network address length in bytes. +/* .IP network_bits +/* The number of initial bits that will not be cleared. +/* DIAGNOSTICS +/* Fatal errors: the number of network bits exceeds the address size. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include /* CHAR_BIT */ + +/* Utility library. */ + +#include +#include + +/* mask_addr - mask off a variable-length address */ + +void mask_addr(unsigned char *addr_bytes, + unsigned addr_byte_count, + unsigned network_bits) +{ + unsigned char *p; + + if (network_bits > addr_byte_count * CHAR_BIT) + msg_panic("mask_addr: address byte count %d too small for bit count %d", + addr_byte_count, network_bits); + + p = addr_bytes + network_bits / CHAR_BIT; + network_bits %= CHAR_BIT; + + if (network_bits != 0) + *p++ &= ~0U << (CHAR_BIT - network_bits); + + while (p < addr_bytes + addr_byte_count) + *p++ = 0; +} diff --git a/src/util/mask_addr.h b/src/util/mask_addr.h new file mode 100644 index 0000000..0e70a41 --- /dev/null +++ b/src/util/mask_addr.h @@ -0,0 +1,30 @@ +#ifndef _MASK_ADDR_H_INCLUDED_ +#define _MASK_ADDR_H_INCLUDED_ + +/*++ +/* NAME +/* mask_addr 3h +/* SUMMARY +/* address bit banging +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void mask_addr(unsigned char *, unsigned, unsigned); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/match_list.c b/src/util/match_list.c new file mode 100644 index 0000000..520485d --- /dev/null +++ b/src/util/match_list.c @@ -0,0 +1,281 @@ +/*++ +/* NAME +/* match_list 3 +/* SUMMARY +/* generic list-based pattern matching +/* SYNOPSIS +/* #include +/* +/* MATCH_LIST *match_list_init(pname, flags, pattern_list, count, func,...) +/* const char *pname; +/* int flags; +/* const char *pattern_list; +/* int count; +/* int (*func)(int flags, const char *string, const char *pattern); +/* +/* int match_list_match(list, string,...) +/* MATCH_LIST *list; +/* const char *string; +/* +/* void match_list_free(list) +/* MATCH_LIST *list; +/* DESCRIPTION +/* This module implements a framework for tests for list +/* membership. The actual tests are done by user-supplied +/* functions. +/* +/* Patterns are separated by whitespace and/or commas. A pattern +/* is either a string, a file name (in which case the contents +/* of the file are substituted for the file name) or a type:name +/* lookup table specification. In order to reverse the result +/* of a pattern match, precede a pattern with an exclamation +/* point (!). +/* +/* match_list_init() performs initializations. When the global +/* util_utf8_enable variable is non-zero, and when the code +/* is compiled with EAI support, string comparison will use +/* caseless UTF-8 mode. Otherwise, only ASCII characters will +/* be casefolded. +/* +/* match_list_match() matches strings against the specified +/* pattern list, passing the first string to the first function +/* given to match_list_init(), the second string to the second +/* function, and so on. +/* +/* match_list_free() releases storage allocated by match_list_init(). +/* +/* Arguments: +/* .IP pname +/* Parameter name or other identifying information that is +/* prepended to error messages. +/* .IP flags +/* Specifies the bit-wise OR of zero or more of the following: +/* .RS +/* .IP MATCH_FLAG_PARENT +/* The hostname pattern foo.com matches any name within the +/* domain foo.com. If this flag is cleared, foo.com matches +/* itself only, and .foo.com matches any name below the domain +/* foo.com. +/* .IP MATCH_FLAG_RETURN +/* Request that match_list_match() logs a warning and returns +/* zero (with list->error set to a non-zero dictionary error +/* code) instead of raising a fatal run-time error. +/* .RE +/* Specify MATCH_FLAG_NONE to request none of the above. +/* .IP pattern_list +/* A list of patterns. +/* .IP count +/* Specifies how many match functions follow. +/* .IP list +/* Pattern list produced by match_list_init(). +/* .IP string +/* Search string. +/* DIAGNOSTICS +/* Fatal error: unable to open or read a match_list file; invalid +/* match_list pattern; casefold error (UTF-8 mode only). +/* SEE ALSO +/* host_match(3) match hosts by name or by address +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific */ + +#define MATCH_DICTIONARY(pattern) \ + ((pattern)[0] != '[' && strchr((pattern), ':') != 0) + +/* match_list_parse - parse buffer, destroy buffer */ + +static ARGV *match_list_parse(MATCH_LIST *match_list, ARGV *pat_list, + char *string, int init_match) +{ + const char *myname = "match_list_parse"; + VSTRING *buf = vstring_alloc(10); + VSTREAM *fp; + const char *delim = CHARS_COMMA_SP; + char *bp = string; + char *start; + char *item; + char *map_type_name_flags; + int match; + + /* + * We do not use DICT_FLAG_FOLD_FIX, because we casefold the search + * string at the beginning of a search, and we use strcmp() for string + * comparison. This works because string patterns are casefolded during + * match_list initialization, and databases are supposed to fold case + * upon creation. + */ +#define OPEN_FLAGS O_RDONLY +#define DICT_FLAGS (DICT_FLAG_LOCK | DICT_FLAG_UTF8_REQUEST) +#define STR(x) vstring_str(x) + + /* + * /filename contents are expanded in-line. To support !/filename we + * prepend the negation operator to each item from the file. + * + * If there is an error, implement graceful degradation by inserting a + * pseudo table whose lookups fail with a warning message. + */ + while ((start = mystrtokq(&bp, delim, CHARS_BRACE)) != 0) { + if (*start == '#') { + msg_warn("%s: comment at end of line is not supported: %s %s", + match_list->pname, start, bp); + break; + } + for (match = init_match, item = start; *item == '!'; item++) + match = !match; + if (*item == 0) + /* No graceful degradation for this... */ + msg_fatal("%s: no pattern after '!'", match_list->pname); + if (*item == '/') { /* /file/name */ + if ((fp = vstream_fopen(item, O_RDONLY, 0)) == 0) { + /* Replace unusable pattern with pseudo table. */ + vstring_sprintf(buf, "%s:%s", DICT_TYPE_NOFILE, item); + if (dict_handle(STR(buf)) == 0) + dict_register(STR(buf), + dict_surrogate(DICT_TYPE_NOFILE, item, + OPEN_FLAGS, DICT_FLAGS, + "open file %s: %m", item)); + argv_add(pat_list, STR(buf), (char *) 0); + } else { + while (vstring_fgets(buf, fp)) + if (vstring_str(buf)[0] != '#') + pat_list = match_list_parse(match_list, pat_list, + vstring_str(buf), match); + if (vstream_fclose(fp)) + msg_fatal("%s: read file %s: %m", myname, item); + } + } else if (MATCH_DICTIONARY(item)) { /* type:table */ + vstring_sprintf(buf, "%s%s(%o,%s)", match ? "" : "!", + item, OPEN_FLAGS, dict_flags_str(DICT_FLAGS)); + map_type_name_flags = STR(buf) + (match == 0); + if (dict_handle(map_type_name_flags) == 0) + dict_register(map_type_name_flags, + dict_open(item, OPEN_FLAGS, DICT_FLAGS)); + argv_add(pat_list, STR(buf), (char *) 0); + } else { /* other pattern */ + casefold(match_list->fold_buf, match ? + item : STR(vstring_sprintf(buf, "!%s", item))); + argv_add(pat_list, STR(match_list->fold_buf), (char *) 0); + } + } + vstring_free(buf); + return (pat_list); +} + +/* match_list_init - initialize pattern list */ + +MATCH_LIST *match_list_init(const char *pname, int flags, + const char *patterns, int match_count,...) +{ + MATCH_LIST *list; + char *saved_patterns; + va_list ap; + int i; + + if (flags & ~MATCH_FLAG_ALL) + msg_panic("match_list_init: bad flags 0x%x", flags); + + list = (MATCH_LIST *) mymalloc(sizeof(*list)); + list->pname = mystrdup(pname); + list->flags = flags; + list->match_count = match_count; + list->match_func = + (MATCH_LIST_FN *) mymalloc(match_count * sizeof(MATCH_LIST_FN)); + list->match_args = + (const char **) mymalloc(match_count * sizeof(const char *)); + va_start(ap, match_count); + for (i = 0; i < match_count; i++) + list->match_func[i] = va_arg(ap, MATCH_LIST_FN); + va_end(ap); + list->error = 0; + list->fold_buf = vstring_alloc(20); + +#define DO_MATCH 1 + + saved_patterns = mystrdup(patterns); + list->patterns = match_list_parse(list, argv_alloc(1), saved_patterns, + DO_MATCH); + argv_terminate(list->patterns); + myfree(saved_patterns); + return (list); +} + +/* match_list_match - match strings against pattern list */ + +int match_list_match(MATCH_LIST *list,...) +{ + const char *myname = "match_list_match"; + char **cpp; + char *pat; + int match; + int i; + va_list ap; + + /* + * Iterate over all patterns in the list, stop at the first match. + */ + va_start(ap, list); + for (i = 0; i < list->match_count; i++) + list->match_args[i] = va_arg(ap, const char *); + va_end(ap); + + list->error = 0; + for (cpp = list->patterns->argv; (pat = *cpp) != 0; cpp++) { + for (match = 1; *pat == '!'; pat++) + match = !match; + for (i = 0; i < list->match_count; i++) { + casefold(list->fold_buf, list->match_args[i]); + if (list->match_func[i] (list, STR(list->fold_buf), pat)) + return (match); + else if (list->error != 0) + return (0); + } + } + if (msg_verbose) + for (i = 0; i < list->match_count; i++) + msg_info("%s: %s: no match", myname, list->match_args[i]); + return (0); +} + +/* match_list_free - release storage */ + +void match_list_free(MATCH_LIST *list) +{ + /* XXX Should decrement map refcounts. */ + myfree(list->pname); + argv_free(list->patterns); + myfree((void *) list->match_func); + myfree((void *) list->match_args); + vstring_free(list->fold_buf); + myfree((void *) list); +} diff --git a/src/util/match_list.h b/src/util/match_list.h new file mode 100644 index 0000000..d8b7794 --- /dev/null +++ b/src/util/match_list.h @@ -0,0 +1,66 @@ +#ifndef _MATCH_LIST_H_INCLUDED_ +#define _MATCH_LIST_H_INCLUDED_ + +/*++ +/* NAME +/* match_list 3h +/* SUMMARY +/* generic list-based pattern matching +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * External interface. + */ +typedef struct MATCH_LIST MATCH_LIST; + +typedef int (*MATCH_LIST_FN) (MATCH_LIST *, const char *, const char *); + +struct MATCH_LIST { + char *pname; /* used in error messages */ + int flags; /* processing options */ + ARGV *patterns; /* one pattern each */ + int match_count; /* match function/argument count */ + MATCH_LIST_FN *match_func; /* match functions */ + const char **match_args; /* match arguments */ + VSTRING *fold_buf; /* case-folded pattern string */ + int error; /* last operation */ +}; + +#define MATCH_FLAG_NONE 0 +#define MATCH_FLAG_PARENT (1<<0) +#define MATCH_FLAG_RETURN (1<<1) +#define MATCH_FLAG_ALL (MATCH_FLAG_PARENT | MATCH_FLAG_RETURN) + +extern MATCH_LIST *match_list_init(const char *, int, const char *, int,...); +extern int match_list_match(MATCH_LIST *,...); +extern void match_list_free(MATCH_LIST *); + + /* + * The following functions are not part of the public interface. These + * functions may be called only through match_list_match(). + */ +extern int match_string(MATCH_LIST *, const char *, const char *); +extern int match_hostname(MATCH_LIST *, const char *, const char *); +extern int match_hostaddr(MATCH_LIST *, const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/match_ops.c b/src/util/match_ops.c new file mode 100644 index 0000000..e0b7779 --- /dev/null +++ b/src/util/match_ops.c @@ -0,0 +1,312 @@ +/*++ +/* NAME +/* match_ops 3 +/* SUMMARY +/* simple string or host pattern matching +/* SYNOPSIS +/* #include +/* +/* int match_string(list, string, pattern) +/* MATCH_LIST *list; +/* const char *string; +/* const char *pattern; +/* +/* int match_hostname(list, name, pattern) +/* MATCH_LIST *list; +/* const char *name; +/* const char *pattern; +/* +/* int match_hostaddr(list, addr, pattern) +/* MATCH_LIST *list; +/* const char *addr; +/* const char *pattern; +/* DESCRIPTION +/* This module implements simple string and host name or address +/* matching. The matching process is case insensitive. If a pattern +/* has the form type:name, table lookup is used instead of string +/* or address comparison. +/* +/* match_string() matches the string against the pattern, requiring +/* an exact (case-insensitive) match. The flags argument is not used. +/* +/* match_hostname() matches the host name when the hostname matches +/* the pattern exactly, or when the pattern matches a parent domain +/* of the named host. The flags argument specifies the bit-wise OR +/* of zero or more of the following: +/* .IP MATCH_FLAG_PARENT +/* The hostname pattern foo.com matches itself and any name below +/* the domain foo.com. If this flag is cleared, foo.com matches itself +/* only, and .foo.com matches any name below the domain foo.com. +/* .IP MATCH_FLAG_RETURN +/* Log a warning, return "not found", and set list->error to +/* a non-zero dictionary error code, instead of raising a fatal +/* run-time error. +/* .RE +/* Specify MATCH_FLAG_NONE to request none of the above. +/* +/* match_hostaddr() matches a host address when the pattern is +/* identical to the host address, or when the pattern is a net/mask +/* that contains the address. The mask specifies the number of +/* bits in the network part of the pattern. The flags argument is +/* not used. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +#define MATCH_DICTIONARY(pattern) \ + ((pattern)[0] != '[' && strchr((pattern), ':') != 0) + +/* match_error - return or raise fatal error */ + +static int match_error(MATCH_LIST *list, const char *fmt,...) +{ + VSTRING *buf = vstring_alloc(100); + va_list ap; + + /* + * Report, and maybe return. + */ + va_start(ap, fmt); + vstring_vsprintf(buf, fmt, ap); + va_end(ap); + if (list->flags & MATCH_FLAG_RETURN) { + msg_warn("%s: %s", list->pname, vstring_str(buf)); + } else { + msg_fatal("%s: %s", list->pname, vstring_str(buf)); + } + vstring_free(buf); + return (0); +} + +/* match_string - match a string literal */ + +int match_string(MATCH_LIST *list, const char *string, const char *pattern) +{ + const char *myname = "match_string"; + DICT *dict; + + if (msg_verbose) + msg_info("%s: %s: %s ~? %s", myname, list->pname, string, pattern); + + /* + * Try dictionary lookup: exact match. + */ + if (MATCH_DICTIONARY(pattern)) { + if ((dict = dict_handle(pattern)) == 0) + msg_panic("%s: unknown dictionary: %s", myname, pattern); + if (dict_get(dict, string) != 0) + return (1); + if ((list->error = dict->error) != 0) + return (match_error(list, "%s:%s: table lookup problem", + dict->type, dict->name)); + return (0); + } + + /* + * Try an exact string match. Note that the string and pattern are + * already casefolded. + */ + if (strcmp(string, pattern) == 0) { + return (1); + } + + /* + * No match found. + */ + return (0); +} + +/* match_hostname - match a host by name */ + +int match_hostname(MATCH_LIST *list, const char *name, const char *pattern) +{ + const char *myname = "match_hostname"; + const char *pd; + const char *entry; + const char *next; + int match; + DICT *dict; + + if (msg_verbose) + msg_info("%s: %s: %s ~? %s", myname, list->pname, name, pattern); + + /* + * Try dictionary lookup: exact match and parent domains. + * + * Don't look up parent domain substrings with regexp maps etc. + */ + if (MATCH_DICTIONARY(pattern)) { + if ((dict = dict_handle(pattern)) == 0) + msg_panic("%s: unknown dictionary: %s", myname, pattern); + match = 0; + for (entry = name; *entry != 0; entry = next) { + if (entry == name || (dict->flags & DICT_FLAG_FIXED)) { + match = (dict_get(dict, entry) != 0); + if (msg_verbose > 1) + msg_info("%s: %s: lookup %s:%s %s: %s", + myname, list->pname, dict->type, dict->name, + entry, match ? "found" : "notfound"); + if (match != 0) + break; + if ((list->error = dict->error) != 0) + return (match_error(list, "%s:%s: table lookup problem", + dict->type, dict->name)); + } + if ((next = strchr(entry + 1, '.')) == 0) + break; + if (list->flags & MATCH_FLAG_PARENT) + next += 1; + } + return (match); + } + + /* + * Try an exact match with the host name. Note that the name and the + * pattern are already casefolded. + */ + if (strcmp(name, pattern) == 0) { + return (1); + } + + /* + * See if the pattern is a parent domain of the hostname. Note that the + * name and the pattern are already casefolded. + */ + else { + if (list->flags & MATCH_FLAG_PARENT) { + pd = name + strlen(name) - strlen(pattern); + if (pd > name && pd[-1] == '.' && strcmp(pd, pattern) == 0) + return (1); + } else if (pattern[0] == '.') { + pd = name + strlen(name) - strlen(pattern); + if (pd > name && strcmp(pd, pattern) == 0) + return (1); + } + } + return (0); +} + +/* match_hostaddr - match host by address */ + +int match_hostaddr(MATCH_LIST *list, const char *addr, const char *pattern) +{ + const char *myname = "match_hostaddr"; + char *saved_patt; + CIDR_MATCH match_info; + DICT *dict; + VSTRING *err; + int rc; + + if (msg_verbose) + msg_info("%s: %s: %s ~? %s", myname, list->pname, addr, pattern); + +#define V4_ADDR_STRING_CHARS "01234567890." +#define V6_ADDR_STRING_CHARS V4_ADDR_STRING_CHARS "abcdefABCDEF:" + + if (addr[strspn(addr, V6_ADDR_STRING_CHARS)] != 0) + return (0); + + /* + * Try dictionary lookup. This can be case insensitive. + */ + if (MATCH_DICTIONARY(pattern)) { + if ((dict = dict_handle(pattern)) == 0) + msg_panic("%s: unknown dictionary: %s", myname, pattern); + if (dict_get(dict, addr) != 0) + return (1); + if ((list->error = dict->error) != 0) + return (match_error(list, "%s:%s: table lookup problem", + dict->type, dict->name)); + return (0); + } + + /* + * Try an exact match with the host address. Note that the address and + * pattern are already casefolded. + */ + if (pattern[0] != '[') { + if (strcmp(addr, pattern) == 0) + return (1); + } else { + size_t addr_len = strlen(addr); + + if (strncmp(addr, pattern + 1, addr_len) == 0 + && strcmp(pattern + 1 + addr_len, "]") == 0) + return (1); + } + + /* + * Light-weight tests before we get into expensive operations. + * + * - Don't bother matching IPv4 against IPv6. Postfix transforms + * IPv4-in-IPv6 to native IPv4 form when IPv4 support is enabled in + * Postfix; if not, then Postfix has no business dealing with IPv4 + * addresses anyway. + * + * - Don't bother unless the pattern is either an IPv6 address or net/mask. + * + * We can safely skip IPv4 address patterns because their form is + * unambiguous and they did not match in the strcmp() calls above. + * + * XXX We MUST skip (parent) domain names, which may appear in NAMADR_LIST + * input, to avoid triggering false cidr_match_parse() errors. + * + * The last two conditions below are for backwards compatibility with + * earlier Postfix versions: don't abort with fatal errors on junk that + * was silently ignored (principle of least astonishment). + */ + if (!strchr(addr, ':') != !strchr(pattern, ':') + || pattern[strcspn(pattern, ":/")] == 0 + || pattern[strspn(pattern, V4_ADDR_STRING_CHARS)] == 0 + || pattern[strspn(pattern, V6_ADDR_STRING_CHARS "[]/")] != 0) + return (0); + + /* + * No escape from expensive operations: either we have a net/mask + * pattern, or we have an address that can have multiple valid + * representations (e.g., 0:0:0:0:0:0:0:1 versus ::1, etc.). The only way + * to find out if the address matches the pattern is to transform + * everything into to binary form, and to do the comparison there. + */ + saved_patt = mystrdup(pattern); + err = cidr_match_parse(&match_info, saved_patt, CIDR_MATCH_TRUE, + (VSTRING *) 0); + myfree(saved_patt); + if (err != 0) { + list->error = DICT_ERR_RETRY; + rc = match_error(list, "%s", vstring_str(err)); + vstring_free(err); + return (rc); + } + return (cidr_match_execute(&match_info, addr) != 0); +} diff --git a/src/util/midna_domain.c b/src/util/midna_domain.c new file mode 100644 index 0000000..333a5c9 --- /dev/null +++ b/src/util/midna_domain.c @@ -0,0 +1,419 @@ +/*++ +/* NAME +/* midna_domain 3 +/* SUMMARY +/* ASCII/UTF-8 domain name conversion +/* SYNOPSIS +/* #include +/* +/* int midna_domain_cache_size; +/* int midna_domain_transitional; +/* +/* const char *midna_domain_to_ascii( +/* const char *name) +/* +/* const char *midna_domain_to_utf8( +/* const char *name) +/* +/* const char *midna_domain_suffix_to_ascii( +/* const char *name) +/* +/* const char *midna_domain_suffix_to_utf8( +/* const char *name) +/* AUXILIARY FUNCTIONS +/* void midna_domain_pre_chroot(void) +/* DESCRIPTION +/* The functions in this module transform domain names from/to +/* ASCII and UTF-8 form. The result is cached to avoid repeated +/* conversion. +/* +/* This module builds on the ICU library implementation of the +/* UTS #46 specification, using default ICU library options +/* because those are likely best tested: with transitional +/* processing, with case mapping, with normalization, with +/* limited IDNA2003 compatibility, without STD3 ASCII rules. +/* +/* midna_domain_to_ascii() converts an UTF-8 or ASCII domain +/* name to ASCII. The result is a null pointer in case of +/* error. This function verifies that the result passes +/* valid_hostname(). +/* +/* midna_domain_to_utf8() converts an UTF-8 or ASCII domain +/* name to UTF-8. The result is a null pointer in case of +/* error. This function verifies that the result, after +/* conversion to ASCII, passes valid_hostname(). +/* +/* midna_domain_suffix_to_ascii() and midna_domain_suffix_to_utf8() +/* take a name that starts with '.' and otherwise perform the +/* same operations as midna_domain_to_ascii() and +/* midna_domain_to_utf8(). +/* +/* midna_domain_cache_size specifies the size of the conversion +/* result cache. This value is used only once, upon the first +/* lookup request. +/* +/* midna_domain_transitional enables transitional conversion +/* between UTF8 and ASCII labels. +/* +/* midna_domain_pre_chroot() does some pre-chroot initialization. +/* SEE ALSO +/* http://unicode.org/reports/tr46/ Unicode IDNA Compatibility processing +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* Warnings: conversion error or result validation error. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Arnt Gulbrandsen +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include + +#ifndef NO_EAI +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include +#include + + /* + * Application-specific. + */ +#define DEF_MIDNA_CACHE_SIZE 256 + +int midna_domain_cache_size = DEF_MIDNA_CACHE_SIZE; +int midna_domain_transitional = 0; +static VSTRING *midna_domain_buf; /* x.suffix */ + +#define STR(x) vstring_str(x) + +/* midna_domain_strerror - pick one for error reporting */ + +static const char *midna_domain_strerror(UErrorCode error, int info_errors) +{ + + /* + * XXX The UIDNA_ERROR_EMPTY_LABEL etc. names are defined in an ENUM, so + * we can't use #ifdef to dynamically determine which names exist. + */ + static LONG_NAME_MASK uidna_errors[] = { + "UIDNA_ERROR_EMPTY_LABEL", UIDNA_ERROR_EMPTY_LABEL, + "UIDNA_ERROR_LABEL_TOO_LONG", UIDNA_ERROR_LABEL_TOO_LONG, + "UIDNA_ERROR_DOMAIN_NAME_TOO_LONG", UIDNA_ERROR_DOMAIN_NAME_TOO_LONG, + "UIDNA_ERROR_LEADING_HYPHEN", UIDNA_ERROR_LEADING_HYPHEN, + "UIDNA_ERROR_TRAILING_HYPHEN", UIDNA_ERROR_TRAILING_HYPHEN, + "UIDNA_ERROR_HYPHEN_3_4", UIDNA_ERROR_HYPHEN_3_4, + "UIDNA_ERROR_LEADING_COMBINING_MARK", UIDNA_ERROR_LEADING_COMBINING_MARK, + "UIDNA_ERROR_DISALLOWED", UIDNA_ERROR_DISALLOWED, + "UIDNA_ERROR_PUNYCODE", UIDNA_ERROR_PUNYCODE, + "UIDNA_ERROR_LABEL_HAS_DOT", UIDNA_ERROR_LABEL_HAS_DOT, + "UIDNA_ERROR_INVALID_ACE_LABEL", UIDNA_ERROR_INVALID_ACE_LABEL, + "UIDNA_ERROR_BIDI", UIDNA_ERROR_BIDI, + "UIDNA_ERROR_CONTEXTJ", UIDNA_ERROR_CONTEXTJ, + /* The above errors are defined with ICU 46 and later. */ + 0, + }; + + if (info_errors) { + return (str_long_name_mask_opt((VSTRING *) 0, "idna error", + uidna_errors, info_errors, + NAME_MASK_NUMBER | NAME_MASK_COMMA)); + } else { + return u_errorName(error); + } +} + +/* midna_domain_pre_chroot - pre-chroot initialization */ + +void midna_domain_pre_chroot(void) +{ + UErrorCode error = U_ZERO_ERROR; + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + UIDNA *idna; + + idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT + : UIDNA_NONTRANSITIONAL_TO_ASCII, &error); + if (U_FAILURE(error)) + msg_warn("ICU library initialization failed: %s", + midna_domain_strerror(error, info.errors)); + uidna_close(idna); +} + +/* midna_domain_to_ascii_create - convert domain to ASCII */ + +static void *midna_domain_to_ascii_create(const char *name, void *unused_context) +{ + static const char myname[] = "midna_domain_to_ascii_create"; + char buf[1024]; /* XXX */ + UErrorCode error = U_ZERO_ERROR; + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + UIDNA *idna; + int anl; + + /* + * Paranoia: do not expose uidna_*() to unfiltered network data. + */ + if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) { + msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s", + myname, name, "malformed UTF-8"); + return (0); + } + + /* + * Perform the requested conversion. + */ + idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT + : UIDNA_NONTRANSITIONAL_TO_ASCII, &error); + anl = uidna_nameToASCII_UTF8(idna, + name, strlen(name), + buf, sizeof(buf) - 1, + &info, + &error); + uidna_close(idna); + + /* + * Paranoia: verify that the result passes valid_hostname(). A quick + * check shows that UTS46 ToASCII by default rejects inputs with labels + * that start or end in '-', with names or labels that are over-long, or + * "fake" A-labels, as required by UTS 46 section 4.1, but we rely on + * valid_hostname() on the output side just to be sure. + */ + if (U_SUCCESS(error) && info.errors == 0 && anl > 0) { + buf[anl] = 0; /* XXX */ + if (!valid_hostname(buf, DONT_GRIPE)) { + msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s", + myname, name, "malformed ASCII label(s)"); + return (0); + } + return (mystrndup(buf, anl)); + } else { + msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s", + myname, name, midna_domain_strerror(error, info.errors)); + return (0); + } +} + +/* midna_domain_to_utf8_create - convert domain to UTF8 */ + +static void *midna_domain_to_utf8_create(const char *name, void *unused_context) +{ + static const char myname[] = "midna_domain_to_utf8_create"; + char buf[1024]; /* XXX */ + UErrorCode error = U_ZERO_ERROR; + UIDNAInfo info = UIDNA_INFO_INITIALIZER; + UIDNA *idna; + int anl; + + /* + * Paranoia: do not expose uidna_*() to unfiltered network data. + */ + if (allascii(name) == 0 && valid_utf8_string(name, strlen(name)) == 0) { + msg_warn("%s: Problem translating domain \"%.100s\" to UTF-8 form: %s", + myname, name, "malformed UTF-8"); + return (0); + } + + /* + * Perform the requested conversion. + */ + idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT + : UIDNA_NONTRANSITIONAL_TO_UNICODE, &error); + anl = uidna_nameToUnicodeUTF8(idna, + name, strlen(name), + buf, sizeof(buf) - 1, + &info, + &error); + uidna_close(idna); + + /* + * Paranoia: UTS46 toUTF8 by default accepts and produces an over-long + * name or a name that contains an over-long NR-LDH label (and perhaps + * other invalid forms that are not covered in UTS 46, section 4.1). We + * rely on midna_domain_to_ascii() to validate the output. + */ + if (U_SUCCESS(error) && info.errors == 0 && anl > 0) { + buf[anl] = 0; /* XXX */ + if (midna_domain_to_ascii(buf) == 0) + return (0); + return (mystrndup(buf, anl)); + } else { + msg_warn("%s: Problem translating domain \"%.100s\" to UTF8 form: %s", + myname, name, midna_domain_strerror(error, info.errors)); + return (0); + } +} + +/* midna_domain_cache_free - cache element destructor */ + +static void midna_domain_cache_free(void *value, void *unused_context) +{ + if (value) + myfree(value); +} + +/* midna_domain_to_ascii - convert name to ASCII */ + +const char *midna_domain_to_ascii(const char *name) +{ + static CTABLE *midna_domain_to_ascii_cache = 0; + + if (midna_domain_to_ascii_cache == 0) + midna_domain_to_ascii_cache = ctable_create(midna_domain_cache_size, + midna_domain_to_ascii_create, + midna_domain_cache_free, + (void *) 0); + return (ctable_locate(midna_domain_to_ascii_cache, name)); +} + +/* midna_domain_to_utf8 - convert name to UTF8 */ + +const char *midna_domain_to_utf8(const char *name) +{ + static CTABLE *midna_domain_to_utf8_cache = 0; + + if (midna_domain_to_utf8_cache == 0) + midna_domain_to_utf8_cache = ctable_create(midna_domain_cache_size, + midna_domain_to_utf8_create, + midna_domain_cache_free, + (void *) 0); + return (ctable_locate(midna_domain_to_utf8_cache, name)); +} + +/* midna_domain_suffix_to_ascii - convert .name to ASCII */ + +const char *midna_domain_suffix_to_ascii(const char *suffix) +{ + const char *cache_res; + + /* + * If prepending x to .name causes the result to become too long, then + * the suffix is bad. + */ + if (midna_domain_buf == 0) + midna_domain_buf = vstring_alloc(100); + vstring_sprintf(midna_domain_buf, "x%s", suffix); + if ((cache_res = midna_domain_to_ascii(STR(midna_domain_buf))) == 0) + return (0); + else + return (cache_res + 1); +} + +/* midna_domain_suffix_to_utf8 - convert .name to UTF8 */ + +const char *midna_domain_suffix_to_utf8(const char *name) +{ + const char *cache_res; + + /* + * If prepending x to .name causes the result to become too long, then + * the suffix is bad. + */ + if (midna_domain_buf == 0) + midna_domain_buf = vstring_alloc(100); + vstring_sprintf(midna_domain_buf, "x%s", name); + if ((cache_res = midna_domain_to_utf8(STR(midna_domain_buf))) == 0) + return (0); + else + return (cache_res + 1); +} + +#ifdef TEST + + /* + * Test program - reads names from stdin, reports invalid names to stderr. + */ +#include +#include +#include + +#include /* XXX util_utf8_enable */ +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(1); + const char *bp; + const char *ascii; + const char *utf8; + + if (setlocale(LC_ALL, "C") == 0) + msg_fatal("setlocale(LC_ALL, C) failed: %m"); + + msg_vstream_init(argv[0], VSTREAM_ERR); + /* msg_verbose = 1; */ + util_utf8_enable = 1; + + if (geteuid() == 0) { + midna_domain_pre_chroot(); + if (chroot(".") != 0) + msg_fatal("chroot(\".\"): %m"); + } + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + bp = STR(buffer); + msg_info("> %s", bp); + while (ISSPACE(*bp)) + bp++; + if (*bp == '#' || *bp == 0) + continue; + msg_info("unconditional conversions:"); + utf8 = midna_domain_to_utf8(bp); + msg_info("\"%s\" ->utf8 \"%s\"", bp, utf8 ? utf8 : "(error)"); + ascii = midna_domain_to_ascii(bp); + msg_info("\"%s\" ->ascii \"%s\"", bp, ascii ? ascii : "(error)"); + msg_info("conditional conversions:"); + if (!allascii(bp)) { + if (ascii != 0) { + utf8 = midna_domain_to_utf8(ascii); + msg_info("\"%s\" ->ascii \"%s\" ->utf8 \"%s\"", + bp, ascii, utf8 ? utf8 : "(error)"); + if (utf8 != 0) { + if (strcmp(utf8, bp) != 0) + msg_warn("\"%s\" != \"%s\"", bp, utf8); + } + } + } else { + if (utf8 != 0) { + ascii = midna_domain_to_ascii(utf8); + msg_info("\"%s\" ->utf8 \"%s\" ->ascii \"%s\"", + bp, utf8, ascii ? ascii : "(error)"); + if (ascii != 0) { + if (strcmp(ascii, bp) != 0) + msg_warn("\"%s\" != \"%s\"", bp, ascii); + } + } + } + } + exit(0); +} + +#endif /* TEST */ + +#endif /* NO_EAI */ diff --git a/src/util/midna_domain.h b/src/util/midna_domain.h new file mode 100644 index 0000000..1abe2a1 --- /dev/null +++ b/src/util/midna_domain.h @@ -0,0 +1,43 @@ +#ifndef _MIDNA_H_INCLUDED_ +#define _MIDNA_H_INCLUDED_ + +/*++ +/* NAME +/* midna_domain 3h +/* SUMMARY +/* ASCII/UTF-8 domain name conversion +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern const char *midna_domain_to_ascii(const char *); +extern const char *midna_domain_to_utf8(const char *); +extern const char *midna_domain_suffix_to_ascii(const char *); +extern const char *midna_domain_suffix_to_utf8(const char *); +extern void midna_domain_pre_chroot(void); + +extern int midna_domain_cache_size; +extern int midna_domain_transitional; +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Arnt Gulbrandsen +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/midna_domain_test.in b/src/util/midna_domain_test.in new file mode 100644 index 0000000..55491e4 --- /dev/null +++ b/src/util/midna_domain_test.in @@ -0,0 +1,21 @@ +# Upper-case greek -> lower-case greek. +Δημοσθένους.example.com +# Upper-case ASCII -> lower-case ASCII. +Hello.example.com +# Invalid LDH label('-' at begin or end). +bad-.example.com +-bad.example.com +# Invalid LDH (label > 63 bytes). +abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com +# Valid LDH label (label <= 63 bytes). +abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com +# Invalid name (length > 255 bytes). +abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com +# Aliases for '.' -> '.'. +x。example.com +x.example.com +x。example.com +# Good a-label. +xn--mumble.example.com +# Bad a-label. +xn--123456.example.com diff --git a/src/util/midna_domain_test.ref b/src/util/midna_domain_test.ref new file mode 100644 index 0000000..c1db0bd --- /dev/null +++ b/src/util/midna_domain_test.ref @@ -0,0 +1,89 @@ +./midna_domain: > # Upper-case greek -> lower-case greek. +./midna_domain: > Δημοσθένους.example.com +./midna_domain: unconditional conversions: +./midna_domain: "Δημοσθένους.example.com" ->utf8 "δημοσθένους.example.com" +./midna_domain: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild1af0a.example.com" +./midna_domain: conditional conversions: +./midna_domain: "Δημοσθένους.example.com" ->ascii "xn--ixanjetild1af0a.example.com" ->utf8 "δημοσθένους.example.com" +./midna_domain: warning: "Δημοσθένους.example.com" != "δημοσθένους.example.com" +./midna_domain: > # Upper-case ASCII -> lower-case ASCII. +./midna_domain: > Hello.example.com +./midna_domain: unconditional conversions: +./midna_domain: "Hello.example.com" ->utf8 "hello.example.com" +./midna_domain: "Hello.example.com" ->ascii "hello.example.com" +./midna_domain: conditional conversions: +./midna_domain: "Hello.example.com" ->utf8 "hello.example.com" ->ascii "hello.example.com" +./midna_domain: warning: "Hello.example.com" != "hello.example.com" +./midna_domain: > # Invalid LDH label('-' at begin or end). +./midna_domain: > bad-.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "bad-.example.com" to UTF8 form: UIDNA_ERROR_TRAILING_HYPHEN +./midna_domain: "bad-.example.com" ->utf8 "(error)" +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "bad-.example.com" to ASCII form: UIDNA_ERROR_TRAILING_HYPHEN +./midna_domain: "bad-.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > -bad.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "-bad.example.com" to UTF8 form: UIDNA_ERROR_LEADING_HYPHEN +./midna_domain: "-bad.example.com" ->utf8 "(error)" +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "-bad.example.com" to ASCII form: UIDNA_ERROR_LEADING_HYPHEN +./midna_domain: "-bad.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > # Invalid LDH (label > 63 bytes). +./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" to ASCII form: UIDNA_ERROR_LABEL_TOO_LONG +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" ->utf8 "(error)" +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > # Valid LDH label (label <= 63 bytes). +./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com +./midna_domain: unconditional conversions: +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->utf8 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->ascii "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" +./midna_domain: conditional conversions: +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->utf8 "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" ->ascii "abcdef0123456789abcdef0123456789abcdef0123456789abcdef012345678.example.com" +./midna_domain: > # Invalid name (length > 255 bytes). +./midna_domain: > abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcde" to ASCII form: UIDNA_ERROR_DOMAIN_NAME_TOO_LONG +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com" ->utf8 "(error)" +./midna_domain: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.abcdef0123456789abcdef0123456789abcdef0123456789abcdef01234567.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: +./midna_domain: > # Aliases for '.' -> '.'. +./midna_domain: > x。example.com +./midna_domain: unconditional conversions: +./midna_domain: "x。example.com" ->utf8 "x.example.com" +./midna_domain: "x。example.com" ->ascii "x.example.com" +./midna_domain: conditional conversions: +./midna_domain: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com" +./midna_domain: warning: "x。example.com" != "x.example.com" +./midna_domain: > x.example.com +./midna_domain: unconditional conversions: +./midna_domain: "x.example.com" ->utf8 "x.example.com" +./midna_domain: "x.example.com" ->ascii "x.example.com" +./midna_domain: conditional conversions: +./midna_domain: "x.example.com" ->ascii "x.example.com" ->utf8 "x.example.com" +./midna_domain: warning: "x.example.com" != "x.example.com" +./midna_domain: > x。example.com +./midna_domain: unconditional conversions: +./midna_domain: "x。example.com" ->utf8 "x.example.com" +./midna_domain: "x。example.com" ->ascii "x.example.com" +./midna_domain: conditional conversions: +./midna_domain: "x。example.com" ->ascii "x.example.com" ->utf8 "x.example.com" +./midna_domain: warning: "x。example.com" != "x.example.com" +./midna_domain: > # Good a-label. +./midna_domain: > xn--mumble.example.com +./midna_domain: unconditional conversions: +./midna_domain: "xn--mumble.example.com" ->utf8 "㲹㲺㲵㲴.example.com" +./midna_domain: "xn--mumble.example.com" ->ascii "xn--mumble.example.com" +./midna_domain: conditional conversions: +./midna_domain: "xn--mumble.example.com" ->utf8 "㲹㲺㲵㲴.example.com" ->ascii "xn--mumble.example.com" +./midna_domain: > # Bad a-label. +./midna_domain: > xn--123456.example.com +./midna_domain: unconditional conversions: +./midna_domain: warning: midna_domain_to_utf8_create: Problem translating domain "xn--123456.example.com" to UTF8 form: UIDNA_ERROR_PUNYCODE +./midna_domain: "xn--123456.example.com" ->utf8 "(error)" +./midna_domain: warning: midna_domain_to_ascii_create: Problem translating domain "xn--123456.example.com" to ASCII form: UIDNA_ERROR_PUNYCODE +./midna_domain: "xn--123456.example.com" ->ascii "(error)" +./midna_domain: conditional conversions: diff --git a/src/util/miss_endif_cidr.map b/src/util/miss_endif_cidr.map new file mode 100644 index 0000000..7c88208 --- /dev/null +++ b/src/util/miss_endif_cidr.map @@ -0,0 +1 @@ +if 1.2.3.4 diff --git a/src/util/miss_endif_cidr.ref b/src/util/miss_endif_cidr.ref new file mode 100644 index 0000000..df5dc6c --- /dev/null +++ b/src/util/miss_endif_cidr.ref @@ -0,0 +1,4 @@ +./dict_open: warning: cidr map miss_endif_cidr.map, line 1: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get 1.2.3.5 +1.2.3.5: not found diff --git a/src/util/miss_endif_pcre.ref b/src/util/miss_endif_pcre.ref new file mode 100644 index 0000000..21d7402 --- /dev/null +++ b/src/util/miss_endif_pcre.ref @@ -0,0 +1,4 @@ +./dict_open: warning: pcre map miss_endif_re.map, line 1: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get 1.2.3.5 +1.2.3.5: not found diff --git a/src/util/miss_endif_re.map b/src/util/miss_endif_re.map new file mode 100644 index 0000000..b085ecb --- /dev/null +++ b/src/util/miss_endif_re.map @@ -0,0 +1 @@ +if /foo/ diff --git a/src/util/miss_endif_regexp.ref b/src/util/miss_endif_regexp.ref new file mode 100644 index 0000000..f77f95a --- /dev/null +++ b/src/util/miss_endif_regexp.ref @@ -0,0 +1,4 @@ +./dict_open: warning: regexp map miss_endif_re.map, line 1: IF has no matching ENDIF +owner=untrusted (uid=USER) +> get 1.2.3.5 +1.2.3.5: not found diff --git a/src/util/msg.c b/src/util/msg.c new file mode 100644 index 0000000..70c6eab --- /dev/null +++ b/src/util/msg.c @@ -0,0 +1,340 @@ +/*++ +/* NAME +/* msg 3 +/* SUMMARY +/* diagnostic interface +/* SYNOPSIS +/* #include +/* +/* int msg_verbose; +/* +/* void msg_info(format, ...) +/* const char *format; +/* +/* void vmsg_info(format, ap) +/* const char *format; +/* va_list ap; +/* +/* void msg_warn(format, ...) +/* const char *format; +/* +/* void vmsg_warn(format, ap) +/* const char *format; +/* va_list ap; +/* +/* void msg_error(format, ...) +/* const char *format; +/* +/* void vmsg_error(format, ap) +/* const char *format; +/* va_list ap; +/* +/* NORETURN msg_fatal(format, ...) +/* const char *format; +/* +/* NORETURN vmsg_fatal(format, ap) +/* const char *format; +/* va_list ap; +/* +/* NORETURN msg_fatal_status(status, format, ...) +/* int status; +/* const char *format; +/* +/* NORETURN vmsg_fatal_status(status, format, ap) +/* int status; +/* const char *format; +/* va_list ap; +/* +/* NORETURN msg_panic(format, ...) +/* const char *format; +/* +/* NORETURN vmsg_panic(format, ap) +/* const char *format; +/* va_list ap; +/* +/* MSG_CLEANUP_FN msg_cleanup(cleanup) +/* void (*cleanup)(void); +/* AUXILIARY FUNCTIONS +/* int msg_error_limit(count) +/* int count; +/* +/* void msg_error_clear() +/* DESCRIPTION +/* This module reports diagnostics. By default, diagnostics are sent +/* to the standard error stream, but the disposition can be changed +/* by the user. See the hints below in the SEE ALSO section. +/* +/* msg_info(), msg_warn(), msg_error(), msg_fatal*() and msg_panic() +/* produce a one-line record with the program name, a severity code +/* (except for msg_info()), and an informative message. The program +/* name must have been set by calling one of the msg_XXX_init() +/* functions (see the SEE ALSO section). +/* +/* msg_error() reports a recoverable error and increments the error +/* counter. When the error count exceeds a pre-set limit (default: 13) +/* the program terminates by calling msg_fatal(). +/* +/* msg_fatal() reports an unrecoverable error and terminates the program +/* with a non-zero exit status. +/* +/* msg_fatal_status() reports an unrecoverable error and terminates the +/* program with the specified exit status. +/* +/* msg_panic() reports an internal inconsistency, terminates the +/* program immediately (i.e. without calling the optional user-specified +/* cleanup routine), and forces a core dump when possible. +/* +/* msg_cleanup() specifies a function that msg_fatal[_status]() should +/* invoke before terminating the program, and returns the +/* current function pointer. Specify a null argument to disable +/* this feature. +/* +/* msg_error_limit() sets the error message count limit, and returns. +/* the old limit. +/* +/* msg_error_clear() sets the error message count to zero. +/* +/* msg_verbose is a global flag that can be set to make software +/* more verbose about what it is doing. By default the flag is zero. +/* By convention, a larger value means more noise. +/* REENTRANCY +/* .ad +/* .fi +/* The msg_info() etc. output routines are protected against +/* ordinary recursive calls and against re-entry by signal +/* handlers. +/* +/* Protection against re-entry by signal handlers is subject +/* to the following limitations: +/* .IP \(bu +/* The signal handlers must never return. In other words, the +/* signal handlers must do one or more of the following: call +/* _exit(), kill the process with a signal, and permanently block +/* the process. +/* .IP \(bu +/* The signal handlers must invoke msg_info() etc. not until +/* after the msg_XXX_init() functions complete initialization, +/* and not until after the first formatted output to a VSTRING +/* or VSTREAM. +/* .IP \(bu +/* Each msg_cleanup() call-back function, and each Postfix or +/* system function invoked by that call-back function, either +/* protects itself against recursive calls and re-entry by a +/* terminating signal handler, or is called exclusively by the +/* msg(3) module. +/* .PP +/* When re-entrancy is detected, the requested output and +/* optional cleanup operations are skipped. Skipping the output +/* operations prevents memory corruption of VSTREAM_ERR data +/* structures, and prevents deadlock on Linux releases that +/* use mutexes within system library routines such as syslog(). +/* This protection exists under the condition that these +/* specific resources are accessed exclusively via the msg_info() +/* etc. functions. +/* SEE ALSO +/* msg_output(3) specify diagnostics disposition +/* msg_stdio(3) direct diagnostics to standard I/O stream +/* msg_vstream(3) direct diagnostics to VSTREAM. +/* msg_syslog(3) direct diagnostics to syslog daemon +/* BUGS +/* Some output functions may suffer from intentional or accidental +/* record length restrictions that are imposed by library routines +/* and/or by the runtime environment. +/* +/* Code that spawns a child process should almost always reset +/* the cleanup handler. The exception is when the parent exits +/* immediately and the child continues. +/* +/* msg_cleanup() may be unsafe in code that changes process +/* privileges, because the call-back routine may run with the +/* wrong privileges. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include +#include + +/* Application-specific. */ + +#include "msg.h" +#include "msg_output.h" + + /* + * Default is verbose logging off. + */ +int msg_verbose = 0; + + /* + * Private state. + */ +static MSG_CLEANUP_FN msg_cleanup_fn = 0; +static int msg_error_count = 0; +static int msg_error_bound = 13; + + /* + * The msg_exiting flag prevents us from recursively reporting an error with + * msg_fatal*() or msg_panic(), and provides a first-level safety net for + * optional cleanup actions against signal handler re-entry problems. Note + * that msg_vprintf() implements its own guard against re-entry. + * + * XXX We specify global scope, to discourage the compiler from doing smart + * things. + */ +volatile int msg_exiting = 0; + +/* msg_info - report informative message */ + +void msg_info(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_info(fmt, ap); + va_end(ap); +} + +void vmsg_info(const char *fmt, va_list ap) +{ + msg_vprintf(MSG_INFO, fmt, ap); +} + +/* msg_warn - report warning message */ + +void msg_warn(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_warn(fmt, ap); + va_end(ap); +} + +void vmsg_warn(const char *fmt, va_list ap) +{ + msg_vprintf(MSG_WARN, fmt, ap); +} + +/* msg_error - report recoverable error */ + +void msg_error(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_error(fmt, ap); + va_end(ap); +} + +void vmsg_error(const char *fmt, va_list ap) +{ + msg_vprintf(MSG_ERROR, fmt, ap); + if (++msg_error_count >= msg_error_bound) + msg_fatal("too many errors - program terminated"); +} + +/* msg_fatal - report error and terminate gracefully */ + +NORETURN msg_fatal(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_fatal(fmt, ap); + /* NOTREACHED */ +} + +NORETURN vmsg_fatal(const char *fmt, va_list ap) +{ + if (msg_exiting++ == 0) { + msg_vprintf(MSG_FATAL, fmt, ap); + if (msg_cleanup_fn) + msg_cleanup_fn(); + } + sleep(1); + /* In case we're running as a signal handler. */ + _exit(1); +} + +/* msg_fatal_status - report error and terminate gracefully */ + +NORETURN msg_fatal_status(int status, const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_fatal_status(status, fmt, ap); + /* NOTREACHED */ +} + +NORETURN vmsg_fatal_status(int status, const char *fmt, va_list ap) +{ + if (msg_exiting++ == 0) { + msg_vprintf(MSG_FATAL, fmt, ap); + if (msg_cleanup_fn) + msg_cleanup_fn(); + } + sleep(1); + /* In case we're running as a signal handler. */ + _exit(status); +} + +/* msg_panic - report error and dump core */ + +NORETURN msg_panic(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vmsg_panic(fmt, ap); + /* NOTREACHED */ +} + +NORETURN vmsg_panic(const char *fmt, va_list ap) +{ + if (msg_exiting++ == 0) { + msg_vprintf(MSG_PANIC, fmt, ap); + } + sleep(1); + abort(); /* Die! */ + /* In case we're running as a signal handler. */ + _exit(1); /* DIE!! */ +} + +/* msg_cleanup - specify cleanup routine */ + +MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN cleanup_fn) +{ + MSG_CLEANUP_FN old_fn = msg_cleanup_fn; + + msg_cleanup_fn = cleanup_fn; + return (old_fn); +} + +/* msg_error_limit - set error message counter limit */ + +int msg_error_limit(int limit) +{ + int old = msg_error_bound; + + msg_error_bound = limit; + return (old); +} + +/* msg_error_clear - reset error message counter */ + +void msg_error_clear(void) +{ + msg_error_count = 0; +} diff --git a/src/util/msg.h b/src/util/msg.h new file mode 100644 index 0000000..6c75baf --- /dev/null +++ b/src/util/msg.h @@ -0,0 +1,60 @@ +#ifndef _MSG_H_INCLUDED_ +#define _MSG_H_INCLUDED_ + +/*++ +/* NAME +/* msg 3h +/* SUMMARY +/* diagnostics interface +/* SYNOPSIS +/* #include "msg.h" +/* DESCRIPTION +/* .nf + +/* + * System library. + */ +#include +#include + +/* + * External interface. + */ +typedef void (*MSG_CLEANUP_FN) (void); + +extern int msg_verbose; + +extern void PRINTFLIKE(1, 2) msg_info(const char *,...); +extern void PRINTFLIKE(1, 2) msg_warn(const char *,...); +extern void PRINTFLIKE(1, 2) msg_error(const char *,...); +extern NORETURN PRINTFLIKE(1, 2) msg_fatal(const char *,...); +extern NORETURN PRINTFLIKE(2, 3) msg_fatal_status(int, const char *,...); +extern NORETURN PRINTFLIKE(1, 2) msg_panic(const char *,...); + +extern void vmsg_info(const char *, va_list); +extern void vmsg_warn(const char *, va_list); +extern void vmsg_error(const char *, va_list); +extern NORETURN vmsg_fatal(const char *, va_list); +extern NORETURN vmsg_fatal_status(int, const char *, va_list); +extern NORETURN vmsg_panic(const char *, va_list); + +extern int msg_error_limit(int); +extern void msg_error_clear(void); +extern MSG_CLEANUP_FN msg_cleanup(MSG_CLEANUP_FN); + +extern void PRINTFLIKE(4, 5) msg_rate_delay(time_t *, int, + void PRINTFPTRLIKE(1, 2) (*log_fn) (const char *,...), + const char *,...); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/msg_logger.c b/src/util/msg_logger.c new file mode 100644 index 0000000..07c9e92 --- /dev/null +++ b/src/util/msg_logger.c @@ -0,0 +1,371 @@ +/*++ +/* NAME +/* msg_logger 3 +/* SUMMARY +/* direct diagnostics to logger service +/* SYNOPSIS +/* #include +/* +/* void msg_logger_init( +/* const char *progname, +/* const char *hostname, +/* const char *unix_path, +/* void (*fallback)(const char *)) +/* +/* void msg_logger_control( +/* int key,...) +/* DESCRIPTION +/* This module implements support to report msg(3) diagnostics +/* through a logger daemon, with an optional fallback mechanism. +/* The log record format is like traditional syslog: +/* +/* .nf +/* Mmm dd host progname[pid]: text... +/* .fi +/* +/* msg_logger_init() arranges that subsequent msg(3) calls +/* will write to an internal logging service. This function +/* may also be used to update msg_logger settings. +/* +/* Arguments: +/* .IP progname +/* The program name that is prepended to a log record. +/* .IP hostname +/* The host name that is prepended to a log record. Only the +/* first hostname label will be used. +/* .IP unix_path +/* Pathname of a unix-domain datagram service endpoint. A +/* typical use case is the pathname of the postlog socket. +/* .IP fallback +/* Null pointer, or pointer to function that will be called +/* with a formatted message when the logger service is not +/* (yet) available. A typical use case is to pass the record +/* to the logwriter(3) module. +/* .PP +/* msg_logger_control() makes adjustments to the msg_logger +/* client. These adjustments remain in effect until the next +/* msg_logger_init() or msg_logger_control() call. The arguments +/* are a list of macros with zero or more arguments, terminated +/* with CA_MSG_LOGGER_CTL_END which has none. The following +/* lists the names and the types of the corresponding value +/* arguments. +/* +/* Arguments: +/* .IP CA_MSG_LOGGER_CTL_FALLBACK_ONLY +/* Disable the logging socket, and use the fallback function +/* only. This remains in effect until the next msg_logger_init() +/* call. +/* .IP CA_MSG_LOGGER_CTL_FALLBACK(void (*)(const char *)) +/* Override the fallback setting (see above) with the specified +/* function pointer. This remains in effect until the next +/* msg_logger_init() or msg_logger_control() call. +/* .IP CA_MSG_LOGGER_CTL_DISABLE +/* Disable the msg_logger. This remains in effect until the +/* next msg_logger_init() call. +/* .IP CA_MSG_LOGGER_CTL_CONNECT_NOW +/* Close the logging socket if it was already open, and open +/* the logging socket now, if permitted by current settings. +/* Otherwise, the open is delayed until a logging request. +/* SEE ALSO +/* msg(3) diagnostics module +/* BUGS +/* Output records are truncated to ~2000 characters, because +/* unlimited logging is a liability. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System libraries. + */ +#include +#include +#include +#include +#include +#include + + /* + * Application-specific. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + + /* + * Saved state from msg_logger_init(). + */ +static char *msg_logger_progname; +static char *msg_logger_hostname; +static char *msg_logger_unix_path; +static void (*msg_logger_fallback_fn) (const char *); +static int msg_logger_fallback_only_override = 0; +static int msg_logger_enable = 0; + +#define MSG_LOGGER_NEED_SOCKET() (msg_logger_fallback_only_override == 0) + + /* + * Other state. + */ +#define MSG_LOGGER_SOCK_NONE (-1) + +static VSTRING *msg_logger_buf; +static int msg_logger_sock = MSG_LOGGER_SOCK_NONE; + + /* + * Safety limit. + */ +#define MSG_LOGGER_RECLEN 2000 + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* msg_logger_connect - connect to logger service */ + +static void msg_logger_connect(void) +{ + if (msg_logger_sock == MSG_LOGGER_SOCK_NONE) { + msg_logger_sock = unix_dgram_connect(msg_logger_unix_path, BLOCKING); + if (msg_logger_sock >= 0) + close_on_exec(msg_logger_sock, CLOSE_ON_EXEC); + } +} + +/* msg_logger_disconnect - disconnect from logger service */ + +static void msg_logger_disconnect(void) +{ + if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) { + (void) close(msg_logger_sock); + msg_logger_sock = MSG_LOGGER_SOCK_NONE; + } +} + +/* msg_logger_print - log info to service or file */ + +static void msg_logger_print(int level, const char *text) +{ + time_t now; + struct tm *lt; + ssize_t len; + + /* + * TODO: this should be a reusable NAME_CODE table plus lookup function. + */ + static int log_level[] = { + MSG_INFO, MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC, + }; + static char *severity_name[] = { + "info", "warning", "error", "fatal", "panic", + }; + + /* + * This test is simple enough that we don't bother with unregistering the + * msg_logger_print() function. + */ + if (msg_logger_enable == 0) + return; + + /* + * Note: there is code in postlogd(8) that attempts to strip off + * information that is prepended here. If the formatting below is + * changed, then postlogd needs to be updated as well. + */ + + /* + * Format the time stamp. + */ + if (time(&now) < 0) + msg_fatal("no time: %m"); + lt = localtime(&now); + VSTRING_RESET(msg_logger_buf); + if ((len = strftime(vstring_str(msg_logger_buf), + vstring_avail(msg_logger_buf), + "%b %d %H:%M:%S ", lt)) == 0) + msg_fatal("strftime: %m"); + vstring_set_payload_size(msg_logger_buf, len); + + /* + * Format the host name (first name label only). + */ + vstring_sprintf_append(msg_logger_buf, "%.*s ", + (int) strcspn(msg_logger_hostname, "."), + msg_logger_hostname); + + /* + * Format the message. + */ + if (level < 0 || level >= (int) (sizeof(log_level) / sizeof(log_level[0]))) + msg_panic("msg_logger_print: invalid severity level: %d", level); + + if (level == MSG_INFO) { + vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %.*s", + msg_logger_progname, (long) getpid(), + (int) MSG_LOGGER_RECLEN, text); + } else { + vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %s: %.*s", + msg_logger_progname, (long) getpid(), + severity_name[level], (int) MSG_LOGGER_RECLEN, text); + } + + /* + * Connect to logging service, or fall back to direct log. Many systems + * will report ENOENT if the endpoint does not exist, ECONNREFUSED if no + * server has opened the endpoint. + */ + if (MSG_LOGGER_NEED_SOCKET()) + msg_logger_connect(); + if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) { + send(msg_logger_sock, STR(msg_logger_buf), LEN(msg_logger_buf), 0); + } else if (msg_logger_fallback_fn) { + msg_logger_fallback_fn(STR(msg_logger_buf)); + } +} + +/* msg_logger_init - initialize */ + +void msg_logger_init(const char *progname, const char *hostname, + const char *unix_path, void (*fallback) (const char *)) +{ + static int first_call = 1; + extern char **environ; + + /* + * XXX If this program is set-gid, then TZ must not be trusted. This + * scrubbing code is in the wrong place. + */ + if (first_call) { + if (unsafe()) + while (getenv("TZ")) /* There may be multiple. */ + if (unsetenv("TZ") < 0) { /* Desperate measures. */ + environ[0] = 0; + msg_fatal("unsetenv: %m"); + } + tzset(); + } + + /* + * Save the request info. Use free-after-update because this data will be + * accessed when mystrdup() runs out of memory. + */ +#define UPDATE_AND_FREE(dst, src) do { \ + if ((dst) == 0 || strcmp((dst), (src)) != 0) { \ + char *_bak = (dst); \ + (dst) = mystrdup(src); \ + if ((_bak)) myfree(_bak); \ + } \ + } while (0) + + UPDATE_AND_FREE(msg_logger_progname, progname); + UPDATE_AND_FREE(msg_logger_hostname, hostname); + UPDATE_AND_FREE(msg_logger_unix_path, unix_path); + msg_logger_fallback_fn = fallback; + + /* + * One-time activity: register the output handler, and allocate a buffer. + */ + if (first_call) { + first_call = 0; + msg_output(msg_logger_print); + msg_logger_buf = vstring_alloc(2048); + } + + /* + * Always. + */ + msg_logger_enable = 1; + msg_logger_fallback_only_override = 0; +} + +/* msg_logger_control - tweak the client */ + +void msg_logger_control(int name,...) +{ + const char *myname = "msg_logger_control"; + va_list ap; + + /* + * Overrides remain in effect until the next msg_logger_init() or + * msg_logger_control() call, + */ + for (va_start(ap, name); name != MSG_LOGGER_CTL_END; name = va_arg(ap, int)) { + switch (name) { + case MSG_LOGGER_CTL_FALLBACK_ONLY: + msg_logger_fallback_only_override = 1; + msg_logger_disconnect(); + break; + case MSG_LOGGER_CTL_FALLBACK_FN: + msg_logger_fallback_fn = va_arg(ap, MSG_LOGGER_FALLBACK_FN); + break; + case MSG_LOGGER_CTL_DISABLE: + msg_logger_enable = 0; + break; + case MSG_LOGGER_CTL_CONNECT_NOW: + msg_logger_disconnect(); + if (MSG_LOGGER_NEED_SOCKET()) + msg_logger_connect(); + break; + default: + msg_panic("%s: bad name %d", myname, name); + } + } + va_end(ap); +} + +#ifdef TEST + + /* + * Proof-of-concept program to test the msg_logger module. + * + * Usage: msg_logger hostname unix_path fallback_path text... + */ +static char *fallback_path; + +static void fallback(const char *msg) +{ + if (logwriter_one_shot(fallback_path, msg) != 0) + msg_fatal("unable to fall back to directly write %s: %m", + fallback_path); +} + +int main(int argc, char **argv) +{ + VSTRING *vp = vstring_alloc(256); + + if (argc < 4) + msg_fatal("usage: %s host port path text to log", argv[0]); + msg_logger_init(argv[0], argv[1], argv[2], fallback); + fallback_path = argv[3]; + argc -= 3; + argv += 3; + while (--argc && *++argv) { + vstring_strcat(vp, *argv); + if (argv[1]) + vstring_strcat(vp, " "); + } + msg_warn("static text"); + msg_warn("dynamic text: >%s<", vstring_str(vp)); + msg_warn("dynamic numeric: >%d<", 42); + msg_warn("error text: >%m<"); + msg_warn("dynamic: >%s<: error: >%m<", vstring_str(vp)); + vstring_free(vp); + return (0); +} + +#endif diff --git a/src/util/msg_logger.h b/src/util/msg_logger.h new file mode 100644 index 0000000..4179f8b --- /dev/null +++ b/src/util/msg_logger.h @@ -0,0 +1,62 @@ +#ifndef _MSG_LOGGER_H_INCLUDED_ +#define _MSG_LOGGER_H_INCLUDED_ + +/*++ +/* NAME +/* msg_logger 3h +/* SUMMARY +/* direct diagnostics to logger service +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +typedef void (*MSG_LOGGER_FALLBACK_FN) (const char *); + +extern void msg_logger_init(const char *, const char *, const char *, + MSG_LOGGER_FALLBACK_FN); +extern void msg_logger_control(int,...); + +/* Internal-only API: type-unchecked arguments. */ +#define MSG_LOGGER_CTL_END 0 +#define MSG_LOGGER_CTL_FALLBACK_ONLY 1 +#define MSG_LOGGER_CTL_FALLBACK_FN 2 +#define MSG_LOGGER_CTL_DISABLE 3 +#define MSG_LOGGER_CTL_CONNECT_NOW 4 + +/* Safer API: type-checked arguments, external use. */ +#define CA_MSG_LOGGER_CTL_END MSG_LOGGER_CTL_END +#define CA_MSG_LOGGER_CTL_FALLBACK_ONLY MSG_LOGGER_CTL_FALLBACK_ONLY +#define CA_MSG_LOGGER_CTL_FALLBACK_FN(v) \ + MSG_LOGGER_CTL_FALLBACK_FN, CHECK_VAL(MSG_LOGGER_CTL, \ + MSG_LOGGER_FALLBACK_FN, (v)) +#define CA_MSG_LOGGER_CTL_DISABLE MSG_LOGGER_CTL_DISABLE +#define CA_MSG_LOGGER_CTL_CONNECT_NOW MSG_LOGGER_CTL_CONNECT_NOW + +CHECK_VAL_HELPER_DCL(MSG_LOGGER_CTL, MSG_LOGGER_FALLBACK_FN); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/msg_output.c b/src/util/msg_output.c new file mode 100644 index 0000000..6663877 --- /dev/null +++ b/src/util/msg_output.c @@ -0,0 +1,174 @@ +/*++ +/* NAME +/* msg_output 3 +/* SUMMARY +/* diagnostics output management +/* SYNOPSIS +/* #include +/* +/* typedef void (*MSG_OUTPUT_FN)(int level, char *text) +/* +/* void msg_output(output_fn) +/* MSG_OUTPUT_FN output_fn; +/* +/* void msg_printf(level, format, ...) +/* int level; +/* const char *format; +/* +/* void msg_vprintf(level, format, ap) +/* int level; +/* const char *format; +/* va_list ap; +/* DESCRIPTION +/* This module implements low-level output management for the +/* msg(3) diagnostics interface. +/* +/* msg_output() registers an output handler for the diagnostics +/* interface. An application can register multiple output handlers. +/* Output handlers are called in the specified order. +/* An output handler takes as arguments a severity level (MSG_INFO, +/* MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC, monotonically increasing +/* integer values ranging from 0 to MSG_LAST) and pre-formatted, +/* sanitized, text in the form of a null-terminated string. +/* +/* msg_printf() and msg_vprintf() format their arguments, sanitize the +/* result, and call the output handlers registered with msg_output(). +/* +/* msg_text() copies a pre-formatted text, sanitizes the result, and +/* calls the output handlers registered with msg_output(). +/* REENTRANCY +/* .ad +/* .fi +/* The above output routines are protected against ordinary +/* recursive calls and against re-entry by signal +/* handlers, with the following limitations: +/* .IP \(bu +/* The signal handlers must never return. In other words, the +/* signal handlers must do one or more of the following: call +/* _exit(), kill the process with a signal, and permanently +/* block the process. +/* .IP \(bu +/* The signal handlers must call the above output routines not +/* until after msg_output() completes initialization, and not +/* until after the first formatted output to a VSTRING or +/* VSTREAM. +/* .IP \(bu +/* Each msg_output() call-back function, and each Postfix or +/* system function called by that call-back function, either +/* must protect itself against recursive calls and re-entry +/* by a terminating signal handler, or it must be called +/* exclusively by functions in the msg_output(3) module. +/* .PP +/* When re-entrancy is detected, the requested output operation +/* is skipped. This prevents memory corruption of VSTREAM_ERR +/* data structures, and prevents deadlock on Linux releases +/* that use mutexes within system library routines such as +/* syslog(). This protection exists under the condition that +/* these specific resources are accessed exclusively via +/* msg_output() call-back functions. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + + /* + * Global scope, to discourage the compiler from doing smart things. + */ +volatile int msg_vprintf_level; + + /* + * Private state. Allow one nested call, so that one logging error can be + * reported to stderr before bailing out. + */ +#define MSG_OUT_NESTING_LIMIT 2 +static MSG_OUTPUT_FN *msg_output_fn = 0; +static int msg_output_fn_count = 0; +static VSTRING *msg_buffers[MSG_OUT_NESTING_LIMIT]; + +/* msg_output - specify output handler */ + +void msg_output(MSG_OUTPUT_FN output_fn) +{ + int i; + + /* + * Allocate all resources during initialization. This may result in a + * recursive call due to memory allocation error. + */ + if (msg_buffers[MSG_OUT_NESTING_LIMIT - 1] == 0) { + for (i = 0; i < MSG_OUT_NESTING_LIMIT; i++) + msg_buffers[i] = vstring_alloc(100); + } + + /* + * We're not doing this often, so avoid complexity and allocate memory + * for an exact fit. + */ + if (msg_output_fn_count == 0) + msg_output_fn = (MSG_OUTPUT_FN *) mymalloc(sizeof(*msg_output_fn)); + else + msg_output_fn = (MSG_OUTPUT_FN *) myrealloc((void *) msg_output_fn, + (msg_output_fn_count + 1) * sizeof(*msg_output_fn)); + msg_output_fn[msg_output_fn_count++] = output_fn; +} + +/* msg_printf - format text and log it */ + +void msg_printf(int level, const char *format,...) +{ + va_list ap; + + va_start(ap, format); + msg_vprintf(level, format, ap); + va_end(ap); +} + +/* msg_vprintf - format text and log it */ + +void msg_vprintf(int level, const char *format, va_list ap) +{ + int saved_errno = errno; + VSTRING *vp; + int i; + + if (msg_vprintf_level < MSG_OUT_NESTING_LIMIT) { + msg_vprintf_level += 1; + /* On-the-fly initialization for test programs and startup errors. */ + if (msg_output_fn_count == 0) + msg_vstream_init("unknown", VSTREAM_ERR); + vp = msg_buffers[msg_vprintf_level - 1]; + /* OK if terminating signal handler hijacks control before next stmt. */ + vstring_vsprintf(vp, format, ap); + printable(vstring_str(vp), '?'); + for (i = 0; i < msg_output_fn_count; i++) + msg_output_fn[i] (level, vstring_str(vp)); + msg_vprintf_level -= 1; + } + errno = saved_errno; +} diff --git a/src/util/msg_output.h b/src/util/msg_output.h new file mode 100644 index 0000000..bd84276 --- /dev/null +++ b/src/util/msg_output.h @@ -0,0 +1,51 @@ +#ifndef _MSG_OUTPUT_FN_ +#define _MSG_OUTPUT_FN_ + +/*++ +/* NAME +/* msg_output 3h +/* SUMMARY +/* diagnostics output management +/* SYNOPSIS +/* #include +/* DESCRIPTION + + /* + * System library. + */ +#include + + /* + * External interface. Severity levels are documented to be monotonically + * increasing from 0 up to MSG_LAST. + */ +typedef void (*MSG_OUTPUT_FN) (int, const char *); +extern void msg_output(MSG_OUTPUT_FN); +extern void PRINTFLIKE(2, 3) msg_printf(int, const char *,...); +extern void msg_vprintf(int, const char *, va_list); + +#define MSG_INFO 0 /* informative */ +#define MSG_WARN 1 /* warning (non-fatal) */ +#define MSG_ERROR 2 /* error (fatal) */ +#define MSG_FATAL 3 /* software error (fatal) */ +#define MSG_PANIC 4 /* software error (fatal) */ + +#define MSG_LAST 4 /* highest-numbered severity level */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/msg_rate_delay.c b/src/util/msg_rate_delay.c new file mode 100644 index 0000000..e21b021 --- /dev/null +++ b/src/util/msg_rate_delay.c @@ -0,0 +1,138 @@ +/*++ +/* NAME +/* msg_rate_delay 3 +/* SUMMARY +/* diagnostic interface +/* SYNOPSIS +/* #include +/* +/* void msg_rate_delay(stamp, delay, log_fn, fmt, ...) +/* time_t *stamp; +/* int delay; +/* void (*log_fn)(const char *fmt, ...); +/* const char *fmt; +/* DESCRIPTION +/* msg_rate_delay() produces log output at a reduced rate: no +/* more than one message per 'delay' seconds. It discards log +/* output that would violate the output rate policy. +/* +/* This is typically used to log errors accessing a cache with +/* high-frequency access but low-value information, to avoid +/* spamming the logfile with the same kind of message. +/* +/* Arguments: +/* .IP stamp +/* Time stamp of last log output; specify a zero time stamp +/* on the first call. This is an input-output parameter. +/* This parameter is ignored when verbose logging is enabled +/* or when the delay value is zero. +/* .IP delay +/* The minimum time between log outputs; specify zero to log +/* all output for debugging purposes. This parameter is ignored +/* when verbose logging is enabled. +/* .IP log_fn +/* The function that produces log output. Typically, this will +/* be msg_info() or msg_warn(). +/* .IP fmt +/* Format string as used with msg(3) routines. +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* SLMs. */ + +#define STR(x) vstring_str(x) + +/* msg_rate_delay - rate-limit message logging */ + +void msg_rate_delay(time_t *stamp, int delay, + void (*log_fn) (const char *,...), + const char *fmt,...) +{ + const char *myname = "msg_rate_delay"; + static time_t saved_event_time; + time_t now; + VSTRING *buf; + va_list ap; + + /* + * Sanity check. + */ + if (delay < 0) + msg_panic("%s: bad message rate delay: %d", myname, delay); + + /* + * This function may be called frequently. Avoid an unnecessary syscall + * if possible. Deal with the possibility that a program does not use the + * events(3) engine, so that event_time() always produces the same + * result. + */ + if (msg_verbose == 0 && delay > 0) { + if (saved_event_time == 0) + now = saved_event_time = event_time(); + else if ((now = event_time()) == saved_event_time) + now = time((time_t *) 0); + + /* + * Don't log if time is too early. + */ + if (*stamp + delay > now) + return; + *stamp = now; + } + + /* + * OK to log. This is a low-rate event, so we can afford some overhead. + */ + buf = vstring_alloc(100); + va_start(ap, fmt); + vstring_vsprintf(buf, fmt, ap); + va_end(ap); + log_fn("%s", STR(buf)); + vstring_free(buf); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: log messages but skip messages during a + * two-second gap. + */ +#include + +int main(int argc, char **argv) +{ + int n; + time_t stamp = 0; + + for (n = 0; n < 6; n++) { + msg_rate_delay(&stamp, 2, msg_info, "text here %d", n); + sleep(1); + } + return (0); +} + +#endif diff --git a/src/util/msg_syslog.c b/src/util/msg_syslog.c new file mode 100644 index 0000000..7c979c6 --- /dev/null +++ b/src/util/msg_syslog.c @@ -0,0 +1,266 @@ +/*++ +/* NAME +/* msg_syslog 3 +/* SUMMARY +/* direct diagnostics to syslog daemon +/* SYNOPSIS +/* #include +/* +/* void msg_syslog_init(progname, log_opt, facility) +/* const char *progname; +/* int log_opt; +/* int facility; +/* +/* int msg_syslog_set_facility(facility_name) +/* const char *facility_name; +/* +/* void msg_syslog_disable(void) +/* DESCRIPTION +/* This module implements support to report msg(3) diagnostics +/* via the syslog daemon. +/* +/* msg_syslog_init() is a wrapper around the openlog(3) routine +/* that directs subsequent msg(3) output to the syslog daemon. +/* This function may also be called to update msg_syslog +/* settings. If the program name appears to contain a process ID +/* then msg_syslog_init will attempt to suppress its own PID. +/* +/* msg_syslog_set_facility() is a helper routine that overrides the +/* logging facility that is specified with msg_syslog_init(). +/* The result is zero in case of an unknown facility name. +/* +/* msg_syslog_disable() turns off the msg_syslog client, +/* until a subsequent msg_syslog_init() call. +/* SEE ALSO +/* syslog(3) syslog library +/* msg(3) diagnostics module +/* BUGS +/* Output records are truncated to 2000 characters. This is done in +/* order to defend against a buffer overflow problem in some +/* implementations of the syslog() library routine. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include +#include /* 44BSD stdarg.h uses abort() */ +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "vstring.h" +#include "stringops.h" +#include "msg.h" +#include "msg_output.h" +#include "msg_syslog.h" +#include "safe.h" +#include + + /* + * Stay a little below the 2048-byte limit of older syslog() + * implementations. + */ +#define MSG_SYSLOG_RECLEN 2000 + +struct facility_list { + const char *name; + int facility; +}; + +static struct facility_list facility_list[] = { +#ifdef LOG_AUTH + "auth", LOG_AUTH, +#endif +#ifdef LOG_AUTHPRIV + "authpriv", LOG_AUTHPRIV, +#endif +#ifdef LOG_CRON + "cron", LOG_CRON, +#endif +#ifdef LOG_DAEMON + "daemon", LOG_DAEMON, +#endif +#ifdef LOG_FTP + "ftp", LOG_FTP, +#endif +#ifdef LOG_KERN + "kern", LOG_KERN, +#endif +#ifdef LOG_LPR + "lpr", LOG_LPR, +#endif +#ifdef LOG_MAIL + "mail", LOG_MAIL, +#endif +#ifdef LOG_NEWS + "news", LOG_NEWS, +#endif +#ifdef LOG_SECURITY + "security", LOG_SECURITY, +#endif +#ifdef LOG_SYSLOG + "syslog", LOG_SYSLOG, +#endif +#ifdef LOG_USER + "user", LOG_USER, +#endif +#ifdef LOG_UUCP + "uucp", LOG_UUCP, +#endif +#ifdef LOG_LOCAL0 + "local0", LOG_LOCAL0, +#endif +#ifdef LOG_LOCAL1 + "local1", LOG_LOCAL1, +#endif +#ifdef LOG_LOCAL2 + "local2", LOG_LOCAL2, +#endif +#ifdef LOG_LOCAL3 + "local3", LOG_LOCAL3, +#endif +#ifdef LOG_LOCAL4 + "local4", LOG_LOCAL4, +#endif +#ifdef LOG_LOCAL5 + "local5", LOG_LOCAL5, +#endif +#ifdef LOG_LOCAL6 + "local6", LOG_LOCAL6, +#endif +#ifdef LOG_LOCAL7 + "local7", LOG_LOCAL7, +#endif + 0, +}; + +static int msg_syslog_facility; +static int msg_syslog_enable; + +/* msg_syslog_print - log info to syslog daemon */ + +static void msg_syslog_print(int level, const char *text) +{ + static int log_level[] = { + LOG_INFO, LOG_WARNING, LOG_ERR, LOG_CRIT, LOG_CRIT, + }; + static char *severity_name[] = { + "info", "warning", "error", "fatal", "panic", + }; + + if (msg_syslog_enable == 0) + return; + + if (level < 0 || level >= (int) (sizeof(log_level) / sizeof(log_level[0]))) + msg_panic("msg_syslog_print: invalid severity level: %d", level); + + if (level == MSG_INFO) { + syslog(msg_syslog_facility | log_level[level], "%.*s", + (int) MSG_SYSLOG_RECLEN, text); + } else { + syslog(msg_syslog_facility | log_level[level], "%s: %.*s", + severity_name[level], (int) MSG_SYSLOG_RECLEN, text); + } +} + +/* msg_syslog_init - initialize */ + +void msg_syslog_init(const char *name, int logopt, int facility) +{ + static int first_call = 1; + extern char **environ; + + /* + * XXX If this program is set-gid, then TZ must not be trusted. This + * scrubbing code is in the wrong place. + */ + if (first_call) { + if (unsafe()) + while (getenv("TZ")) /* There may be multiple. */ + if (unsetenv("TZ") < 0) { /* Desperate measures. */ + environ[0] = 0; + msg_fatal("unsetenv: %m"); + } + tzset(); + } + /* Hack for internal logging forwarding after config change. */ + if (strchr(name, '[') != 0) + logopt &= ~LOG_PID; + openlog(name, LOG_NDELAY | logopt, facility); + if (first_call) { + first_call = 0; + msg_output(msg_syslog_print); + } + msg_syslog_enable = 1; +} + +/* msg_syslog_set_facility - set logging facility by name */ + +int msg_syslog_set_facility(const char *facility_name) +{ + struct facility_list *fnp; + + for (fnp = facility_list; fnp->name; ++fnp) { + if (!strcmp(fnp->name, facility_name)) { + msg_syslog_facility = fnp->facility; + return (1); + } + } + return 0; +} + +/* msg_syslog_disable - disable the msg_syslog client */ + +void msg_syslog_disable(void) +{ + msg_syslog_enable = 0; +} + +#ifdef TEST + + /* + * Proof-of-concept program to test the syslogging diagnostics interface + * + * Usage: msg_syslog_test text... + */ + +int main(int argc, char **argv) +{ + VSTRING *vp = vstring_alloc(256); + + msg_syslog_init(argv[0], LOG_PID, LOG_MAIL); + if (argc < 2) + msg_error("usage: %s text to be logged", argv[0]); + while (--argc && *++argv) { + vstring_strcat(vp, *argv); + if (argv[1]) + vstring_strcat(vp, " "); + } + msg_warn("static text"); + msg_warn("dynamic text: >%s<", vstring_str(vp)); + msg_warn("dynamic numeric: >%d<", 42); + msg_warn("error text: >%m<"); + msg_warn("dynamic: >%s<: error: >%m<", vstring_str(vp)); + vstring_free(vp); + return (0); +} + +#endif diff --git a/src/util/msg_syslog.h b/src/util/msg_syslog.h new file mode 100644 index 0000000..d0441bb --- /dev/null +++ b/src/util/msg_syslog.h @@ -0,0 +1,41 @@ +#ifndef _MSG_SYSLOG_H_INCLUDED_ +#define _MSG_SYSLOG_H_INCLUDED_ + +/*++ +/* NAME +/* msg_syslog 3h +/* SUMMARY +/* direct diagnostics to syslog daemon +/* SYNOPSIS +/* #include +/* DESCRIPTION + + /* + * System library. + */ +#include + + /* + * External interface. + */ +extern void msg_syslog_init(const char *, int, int); +extern int msg_syslog_set_facility(const char *); +extern void msg_syslog_disable(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/msg_vstream.c b/src/util/msg_vstream.c new file mode 100644 index 0000000..b6e24e6 --- /dev/null +++ b/src/util/msg_vstream.c @@ -0,0 +1,87 @@ +/*++ +/* NAME +/* msg_vstream 3 +/* SUMMARY +/* report diagnostics to VSTREAM +/* SYNOPSIS +/* #include +/* +/* void msg_vstream_init(progname, stream) +/* const char *progname; +/* VSTREAM *stream; +/* DESCRIPTION +/* This module implements support to report msg(3) diagnostics +/* to a VSTREAM. +/* +/* msg_vstream_init() sets the program name that appears in each output +/* record, and directs diagnostics (see msg(3)) to the specified +/* VSTREAM. The \fIprogname\fR argument is not copied. +/* SEE ALSO +/* msg(3) +/* BUGS +/* No guarantee that long records are written atomically. +/* Only the last msg_vstream_init() call takes effect. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include /* 44BSD stdarg.h uses abort() */ +#include + +/* Utility library. */ + +#include "vstream.h" +#include "msg.h" +#include "msg_output.h" +#include "msg_vstream.h" + + /* + * Private state. + */ +static const char *msg_tag; +static VSTREAM *msg_stream; + +/* msg_vstream_print - log diagnostic to VSTREAM */ + +static void msg_vstream_print(int level, const char *text) +{ + static const char *level_text[] = { + "info", "warning", "error", "fatal", "panic", + }; + + if (level < 0 || level >= (int) (sizeof(level_text) / sizeof(level_text[0]))) + msg_panic("invalid severity level: %d", level); + if (level == MSG_INFO) { + vstream_fprintf(msg_stream, "%s: %s\n", + msg_tag, text); + } else { + vstream_fprintf(msg_stream, "%s: %s: %s\n", + msg_tag, level_text[level], text); + } + vstream_fflush(msg_stream); +} + +/* msg_vstream_init - initialize */ + +void msg_vstream_init(const char *name, VSTREAM *vp) +{ + static int first_call = 1; + + msg_tag = name; + msg_stream = vp; + if (first_call) { + first_call = 0; + msg_output(msg_vstream_print); + } +} diff --git a/src/util/msg_vstream.h b/src/util/msg_vstream.h new file mode 100644 index 0000000..d0679a0 --- /dev/null +++ b/src/util/msg_vstream.h @@ -0,0 +1,34 @@ +#ifndef _MSG_VSTREAM_H_INCLUDED_ +#define _MSG_VSTREAM_H_INCLUDED_ + +/*++ +/* NAME +/* msg_vstream 3h +/* SUMMARY +/* direct diagnostics to VSTREAM +/* SYNOPSIS +/* #include +/* DESCRIPTION + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern void msg_vstream_init(const char *, VSTREAM *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/mvect.c b/src/util/mvect.c new file mode 100644 index 0000000..cf4b0d5 --- /dev/null +++ b/src/util/mvect.c @@ -0,0 +1,117 @@ +/*++ +/* NAME +/* mvect 3 +/* SUMMARY +/* memory vector management +/* SYNOPSIS +/* #include +/* +/* char *mvect_alloc(vector, elsize, nelm, init_fn, wipe_fn) +/* MVECT *vector; +/* ssize_t elsize; +/* ssize_t nelm; +/* void (*init_fn)(char *ptr, ssize_t count); +/* void (*wipe_fn)(char *ptr, ssize_t count); +/* +/* char *mvect_realloc(vector, nelm) +/* MVECT *vector; +/* ssize_t nelm; +/* +/* char *mvect_free(vector) +/* MVECT *vector; +/* DESCRIPTION +/* This module supports memory management for arrays of arbitrary +/* objects. It is up to the application to provide specific code +/* that initializes and uses object memory. +/* +/* mvect_alloc() initializes memory for a vector with elements +/* of \fIelsize\fR bytes, and with at least \fInelm\fR elements. +/* \fIinit_fn\fR is a null pointer, or a pointer to a function +/* that initializes \fIcount\fR vector elements. +/* \fIwipe_fn\fR is a null pointer, or a pointer to a function +/* that is complementary to \fIinit_fn\fR. This routine is called +/* by mvect_free(). The result of mvect_alloc() is a pointer to +/* the allocated vector. +/* +/* mvect_realloc() guarantees that the specified vector has space +/* for at least \fInelm\fR elements. The result is a pointer to the +/* allocated vector, which may change across calls. +/* +/* mvect_free() releases storage for the named vector. The result +/* is a convenient null pointer. +/* SEE ALSO +/* mymalloc(3) memory management +/* DIAGNOSTICS +/* Problems are reported via the msg(3) diagnostics routines: +/* the requested amount of memory is not available; improper use +/* is detected; other fatal errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include "mymalloc.h" +#include "mvect.h" + +/* mvect_alloc - allocate memory vector */ + +char *mvect_alloc(MVECT *vect, ssize_t elsize, ssize_t nelm, + void (*init_fn) (char *, ssize_t), void (*wipe_fn) (char *, ssize_t)) +{ + vect->init_fn = init_fn; + vect->wipe_fn = wipe_fn; + vect->nelm = 0; + vect->ptr = mymalloc(elsize * nelm); + vect->nelm = nelm; + vect->elsize = elsize; + if (vect->init_fn) + vect->init_fn(vect->ptr, vect->nelm); + return (vect->ptr); +} + +/* mvect_realloc - adjust memory vector allocation */ + +char *mvect_realloc(MVECT *vect, ssize_t nelm) +{ + ssize_t old_len = vect->nelm; + ssize_t incr = nelm - old_len; + ssize_t new_nelm; + + if (incr > 0) { + if (incr < old_len) + incr = old_len; + new_nelm = vect->nelm + incr; + vect->ptr = myrealloc(vect->ptr, vect->elsize * new_nelm); + vect->nelm = new_nelm; + if (vect->init_fn) + vect->init_fn(vect->ptr + old_len * vect->elsize, incr); + } + return (vect->ptr); +} + +/* mvect_free - release memory vector storage */ + +char *mvect_free(MVECT *vect) +{ + if (vect->wipe_fn) + vect->wipe_fn(vect->ptr, vect->nelm); + myfree(vect->ptr); + return (0); +} diff --git a/src/util/mvect.h b/src/util/mvect.h new file mode 100644 index 0000000..bdbb701 --- /dev/null +++ b/src/util/mvect.h @@ -0,0 +1,42 @@ +#ifndef _MVECT_H_INCLUDED_ +#define _MVECT_H_INCLUDED_ + +/*++ +/* NAME +/* mvect 3h +/* SUMMARY +/* memory vector management +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Generic memory vector interface. + */ +typedef void (*MVECT_FN) (char *, ssize_t); + +typedef struct { + char *ptr; + ssize_t elsize; + ssize_t nelm; + MVECT_FN init_fn; + MVECT_FN wipe_fn; +} MVECT; + +extern char *mvect_alloc(MVECT *, ssize_t, ssize_t, MVECT_FN, MVECT_FN); +extern char *mvect_realloc(MVECT *, ssize_t); +extern char *mvect_free(MVECT *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/myaddrinfo.c b/src/util/myaddrinfo.c new file mode 100644 index 0000000..5edafde --- /dev/null +++ b/src/util/myaddrinfo.c @@ -0,0 +1,905 @@ +/*++ +/* NAME +/* myaddrinfo 3 +/* SUMMARY +/* addrinfo encapsulation and emulation +/* SYNOPSIS +/* #include +/* +/* #define MAI_V4ADDR_BITS ... +/* #define MAI_V6ADDR_BITS ... +/* #define MAI_V4ADDR_BYTES ... +/* #define MAI_V6ADDR_BYTES ... +/* +/* typedef struct { char buf[....]; } MAI_HOSTNAME_STR; +/* typedef struct { char buf[....]; } MAI_HOSTADDR_STR; +/* typedef struct { char buf[....]; } MAI_SERVNAME_STR; +/* typedef struct { char buf[....]; } MAI_SERVPORT_STR; +/* +/* int hostname_to_sockaddr(hostname, service, socktype, result) +/* const char *hostname; +/* const char *service; +/* int socktype; +/* struct addrinfo **result; +/* +/* int hostname_to_sockaddr_pf(hostname, pf, service, socktype, result) +/* const char *hostname; +/* int pf; +/* const char *service; +/* int socktype; +/* struct addrinfo **result; +/* +/* int hostaddr_to_sockaddr(hostaddr, service, socktype, result) +/* const char *hostaddr; +/* const char *service; +/* int socktype; +/* struct addrinfo **result; +/* +/* int sockaddr_to_hostaddr(sa, salen, hostaddr, portnum, socktype) +/* const struct sockaddr *sa; +/* SOCKADDR_SIZE salen; +/* MAI_HOSTADDR_STR *hostaddr; +/* MAI_SERVPORT_STR *portnum; +/* int socktype; +/* +/* int sockaddr_to_hostname(sa, salen, hostname, service, socktype) +/* const struct sockaddr *sa; +/* SOCKADDR_SIZE salen; +/* MAI_HOSTNAME_STR *hostname; +/* MAI_SERVNAME_STR *service; +/* int socktype; +/* +/* const char *MAI_STRERROR(error) +/* int error; +/* DESCRIPTION +/* This module provides a simplified user interface to the +/* getaddrinfo(3) and getnameinfo(3) routines (which provide +/* a unified interface to manipulate IPv4 and IPv6 socket +/* address structures). +/* +/* On systems without getaddrinfo(3) and getnameinfo(3) support, +/* emulation for IPv4 only can be enabled by defining +/* EMULATE_IPV4_ADDRINFO. +/* +/* hostname_to_sockaddr() looks up the binary addresses for +/* the specified symbolic hostname or numeric address. The +/* result should be destroyed with freeaddrinfo(). A null host +/* pointer converts to the null host address. +/* +/* hostname_to_sockaddr_pf() is an extended interface that +/* provides a protocol family override. +/* +/* hostaddr_to_sockaddr() converts a printable network address +/* into the corresponding binary form. The result should be +/* destroyed with freeaddrinfo(). A null host pointer converts +/* to the null host address. +/* +/* sockaddr_to_hostaddr() converts a binary network address +/* into printable form. The result buffers should be large +/* enough to hold the printable address or port including the +/* null terminator. +/* This function strips off the IPv6 datalink suffix. +/* +/* sockaddr_to_hostname() converts a binary network address +/* into a hostname or service. The result buffer should be +/* large enough to hold the hostname or service including the +/* null terminator. This routine rejects malformed hostnames +/* or numeric hostnames and pretends that the lookup failed. +/* +/* MAI_STRERROR() is an unsafe macro (it evaluates the argument +/* multiple times) that invokes strerror() or gai_strerror() +/* as appropriate. +/* +/* This module exports the following constants that should be +/* user for storage allocation of name or address information: +/* .IP MAI_V4ADDR_BITS +/* .IP MAI_V6ADDR_BITS +/* .IP MAI_V4ADDR_BYTES +/* .IP MAI_V6ADDR_BYTES +/* The number of bits or bytes needed to store a binary +/* IPv4 or IPv6 network address. +/* .PP +/* The types MAI_HOST{NAME,ADDR}_STR and MAI_SERV{NAME,PORT}_STR +/* implement buffers for the storage of the string representations +/* of symbolic or numerical hosts or services. Do not use +/* buffer types other than the ones that are expected here, +/* or things will blow up with buffer overflow problems. +/* +/* Arguments: +/* .IP hostname +/* On input to hostname_to_sockaddr(), a numeric or symbolic +/* hostname, or a null pointer (meaning the wild-card listen +/* address). On output from sockaddr_to_hostname(), storage +/* for the result hostname, or a null pointer. +/* .IP pf +/* Protocol type: PF_UNSPEC (meaning: use any protocol that is +/* available), PF_INET, or PF_INET6. This argument is ignored +/* in EMULATE_IPV4_ADDRINFO mode. +/* .IP hostaddr +/* On input to hostaddr_to_sockaddr(), a numeric hostname, +/* or a null pointer (meaning the wild-card listen address). +/* On output from sockaddr_to_hostaddr(), storage for the +/* result hostaddress, or a null pointer. +/* .IP service +/* On input to hostname/addr_to_sockaddr(), a numeric or +/* symbolic service name, or a null pointer in which case the +/* socktype argument is ignored. On output from +/* sockaddr_to_hostname/addr(), storage for the result service +/* name, or a null pointer. +/* .IP portnum +/* Storage for the result service port number, or a null pointer. +/* .IP socktype +/* Socket type: SOCK_STREAM, SOCK_DGRAM, etc. This argument is +/* ignored when no service or port are specified. +/* .IP sa +/* Protocol-independent socket address structure. +/* .IP salen +/* Protocol-dependent socket address structure size in bytes. +/* SEE ALSO +/* getaddrinfo(3), getnameinfo(3), freeaddrinfo(3), gai_strerror(3) +/* DIAGNOSTICS +/* All routines either return 0 upon success, or an error code +/* that is compatible with gai_strerror(). +/* +/* On systems where addrinfo support is emulated by Postfix, +/* some out-of-memory errors are not reported to the caller, +/* but are handled by mymalloc(). +/* BUGS +/* The IPv4-only emulation code does not support requests that +/* specify a service but no socket type. It returns an error +/* indication, instead of enumerating all the possible answers. +/* +/* The hostname/addr_to_sockaddr() routines should accept a +/* list of address families that the caller is interested in, +/* and they should return only information of those types. +/* +/* Unfortunately, it is not possible to remove unwanted address +/* family results from hostname_to_sockaddr(), because we +/* don't know how the system library routine getaddrinfo() +/* allocates memory. For example, getaddrinfo() could save +/* space by referencing the same string object from multiple +/* addrinfo structures; or it could allocate a string object +/* and the addrinfo structure as one memory block. +/* +/* We could get around this by copying getaddrinfo() results +/* to our own private data structures, but that would only +/* make an already expensive API even more expensive. +/* +/* A better workaround is to return a vector of addrinfo +/* pointers to the elements that contain only the elements +/* that the caller is interested in. The pointer to the +/* original getaddrinfo() result can be hidden at the end +/* after the null terminator, or before the first element. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* sprintf() */ + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * Use an old trick to save some space: allocate space for two objects in + * one. In Postfix we often use this trick for structures that have an array + * of things at the end. + */ +struct ipv4addrinfo { + struct addrinfo info; + struct sockaddr_in sin; +}; + + /* + * When we're not interested in service ports, we must pick a socket type + * otherwise getaddrinfo() will give us duplicate results: one set for TCP, + * and another set for UDP. For consistency, we'll use the same default + * socket type for the results from emulation mode. + */ +#define MAI_SOCKTYPE SOCK_STREAM /* getaddrinfo() query */ + +#ifdef EMULATE_IPV4_ADDRINFO + +/* clone_ipv4addrinfo - clone ipv4addrinfo structure */ + +static struct ipv4addrinfo *clone_ipv4addrinfo(struct ipv4addrinfo * tp) +{ + struct ipv4addrinfo *ip; + + ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip)); + *ip = *tp; + ip->info.ai_addr = (struct sockaddr *) &(ip->sin); + return (ip); +} + +/* init_ipv4addrinfo - initialize an ipv4addrinfo structure */ + +static void init_ipv4addrinfo(struct ipv4addrinfo * ip, int socktype) +{ + + /* + * Portability: null pointers aren't necessarily all-zero bits, so we + * make explicit assignments to all the pointers that we're aware of. + */ + memset((void *) ip, 0, sizeof(*ip)); + ip->info.ai_family = PF_INET; + ip->info.ai_socktype = socktype; + ip->info.ai_protocol = 0; /* XXX */ + ip->info.ai_addrlen = sizeof(ip->sin); + ip->info.ai_canonname = 0; + ip->info.ai_addr = (struct sockaddr *) &(ip->sin); + ip->info.ai_next = 0; + ip->sin.sin_family = AF_INET; +#ifdef HAS_SA_LEN + ip->sin.sin_len = sizeof(ip->sin); +#endif +} + +/* find_service - translate numeric or symbolic service name */ + +static int find_service(const char *service, int socktype) +{ + struct servent *sp; + const char *proto; + unsigned port; + + service = filter_known_tcp_port(service); + if (alldig(service)) { + port = atoi(service); + return (port < 65536 ? htons(port) : -1); + } + if (socktype == SOCK_STREAM) { + proto = "tcp"; + } else if (socktype == SOCK_DGRAM) { + proto = "udp"; + } else { + return (-1); + } + if ((sp = getservbyname(service, proto)) != 0) { + return (sp->s_port); + } else { + return (-1); + } +} + +#endif + +/* hostname_to_sockaddr_pf - hostname to binary address form */ + +int hostname_to_sockaddr_pf(const char *hostname, int pf, + const char *service, int socktype, + struct addrinfo ** res) +{ +#ifdef EMULATE_IPV4_ADDRINFO + + /* + * Emulated getaddrinfo(3) version. + */ + static struct ipv4addrinfo template; + struct ipv4addrinfo *ip; + struct ipv4addrinfo *prev; + struct in_addr addr; + struct hostent *hp; + char **name_list; + int port; + + /* + * Validate the service. + */ + if (service) { + if ((port = find_service(service, socktype)) < 0) + return (EAI_SERVICE); + } else { + port = 0; + socktype = MAI_SOCKTYPE; + } + + /* + * No host means INADDR_ANY. + */ + if (hostname == 0) { + ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip)); + init_ipv4addrinfo(ip, socktype); + ip->sin.sin_addr.s_addr = INADDR_ANY; + ip->sin.sin_port = port; + *res = &(ip->info); + return (0); + } + + /* + * Numeric host. + */ + if (inet_pton(AF_INET, hostname, (void *) &addr) == 1) { + ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip)); + init_ipv4addrinfo(ip, socktype); + ip->sin.sin_addr = addr; + ip->sin.sin_port = port; + *res = &(ip->info); + return (0); + } + + /* + * Look up the IPv4 address list. + */ + if ((hp = gethostbyname(hostname)) == 0) + return (h_errno == TRY_AGAIN ? EAI_AGAIN : EAI_NODATA); + if (hp->h_addrtype != AF_INET + || hp->h_length != sizeof(template.sin.sin_addr)) + return (EAI_NODATA); + + /* + * Initialize the result template. + */ + if (template.info.ai_addrlen == 0) + init_ipv4addrinfo(&template, socktype); + + /* + * Copy the address information into an addrinfo structure. + */ + prev = &template; + for (name_list = hp->h_addr_list; name_list[0]; name_list++) { + ip = clone_ipv4addrinfo(prev); + ip->sin.sin_addr = IN_ADDR(name_list[0]); + ip->sin.sin_port = port; + if (prev == &template) + *res = &(ip->info); + else + prev->info.ai_next = &(ip->info); + prev = ip; + } + return (0); +#else + + /* + * Native getaddrinfo(3) version. + * + * XXX Wild-card listener issues. + * + * With most IPv4 plus IPv6 systems, an IPv6 wild-card listener also listens + * on the IPv4 wild-card address. Connections from IPv4 clients appear as + * IPv4-in-IPv6 addresses; when Postfix support for IPv4 is turned on, + * Postfix automatically maps these embedded addresses to their original + * IPv4 form. So everything seems to be fine. + * + * However, some applications prefer to use separate listener sockets for + * IPv4 and IPv6. The Postfix IPv6 patch provided such an example. And + * this is where things become tricky. On many systems the IPv6 and IPv4 + * wild-card listeners cannot coexist. When one is already active, the + * other fails with EADDRINUSE. Solaris 9, however, will automagically + * "do the right thing" and allow both listeners to coexist. + * + * Recent systems have the IPV6_V6ONLY feature (RFC 3493), which tells the + * system that we really mean IPv6 when we say IPv6. This allows us to + * set up separate wild-card listener sockets for IPv4 and IPv6. So + * everything seems to be fine again. + * + * The following workaround disables the wild-card IPv4 listener when + * IPV6_V6ONLY is unavailable. This is necessary for some Linux versions, + * but is not needed for Solaris 9 (which allows IPv4 and IPv6 wild-card + * listeners to coexist). Solaris 10 beta already has IPV6_V6ONLY. + * + * XXX This workaround obviously breaks if we want to support protocols in + * addition to IPv6 and IPv4, but it is needed only until IPv6 + * implementations catch up with RFC 3493. A nicer fix is to filter the + * getaddrinfo() result, and to return a vector of addrinfo pointers to + * only those types of elements that the caller has expressed interested + * in. + * + * XXX Vanilla AIX 5.1 getaddrinfo() does not support a null hostname with + * AI_PASSIVE. And since we don't know how getaddrinfo() manages its + * memory we can't bypass it for this special case, or freeaddrinfo() + * might blow up. Instead we turn off IPV6_V6ONLY in inet_listen(), and + * supply a protocol-dependent hard-coded string value to getaddrinfo() + * below, so that it will convert into the appropriate wild-card address. + * + * XXX AIX 5.[1-3] getaddrinfo() may return a non-null port when a null + * service argument is specified. + */ + struct addrinfo hints; + int err; + + memset((void *) &hints, 0, sizeof(hints)); + hints.ai_family = (pf != PF_UNSPEC) ? pf : inet_proto_info()->ai_family; + hints.ai_socktype = service ? socktype : MAI_SOCKTYPE; + if (!hostname) { + hints.ai_flags = AI_PASSIVE; +#if !defined(IPV6_V6ONLY) || defined(BROKEN_AI_PASSIVE_NULL_HOST) + switch (hints.ai_family) { + case PF_UNSPEC: + hints.ai_family = PF_INET6; +#ifdef BROKEN_AI_PASSIVE_NULL_HOST + case PF_INET6: + hostname = "::"; + break; + case PF_INET: + hostname = "0.0.0.0"; + break; +#endif + } +#endif + } + if (service) { + service = filter_known_tcp_port(service); + if (alldig(service)) + hints.ai_flags |= AI_NUMERICSERV; + } + err = getaddrinfo(hostname, service, &hints, res); +#if defined(BROKEN_AI_NULL_SERVICE) + if (service == 0 && err == 0) { + struct addrinfo *r; + unsigned short *portp; + + for (r = *res; r != 0; r = r->ai_next) + if (*(portp = SOCK_ADDR_PORTP(r->ai_addr)) != 0) + *portp = 0; + } +#endif + return (err); +#endif +} + +/* hostaddr_to_sockaddr - printable address to binary address form */ + +int hostaddr_to_sockaddr(const char *hostaddr, const char *service, + int socktype, struct addrinfo ** res) +{ +#ifdef EMULATE_IPV4_ADDRINFO + + /* + * Emulated getaddrinfo(3) version. + */ + struct ipv4addrinfo *ip; + struct in_addr addr; + int port; + + /* + * Validate the service. + */ + if (service) { + if ((port = find_service(service, socktype)) < 0) + return (EAI_SERVICE); + } else { + port = 0; + socktype = MAI_SOCKTYPE; + } + + /* + * No host means INADDR_ANY. + */ + if (hostaddr == 0) { + ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip)); + init_ipv4addrinfo(ip, socktype); + ip->sin.sin_addr.s_addr = INADDR_ANY; + ip->sin.sin_port = port; + *res = &(ip->info); + return (0); + } + + /* + * Deal with bad address forms. + */ + switch (inet_pton(AF_INET, hostaddr, (void *) &addr)) { + case 1: /* Success */ + break; + default: /* Unparsable */ + return (EAI_NONAME); + case -1: /* See errno */ + return (EAI_SYSTEM); + } + + /* + * Initialize the result structure. + */ + ip = (struct ipv4addrinfo *) mymalloc(sizeof(*ip)); + init_ipv4addrinfo(ip, socktype); + + /* + * And copy the result. + */ + ip->sin.sin_addr = addr; + ip->sin.sin_port = port; + *res = &(ip->info); + + return (0); +#else + + /* + * Native getaddrinfo(3) version. See comments in hostname_to_sockaddr(). + * + * XXX Vanilla AIX 5.1 getaddrinfo() returns multiple results when + * converting a printable ipv4 or ipv6 address to socket address with + * ai_family=PF_UNSPEC, ai_flags=AI_NUMERICHOST, ai_socktype=SOCK_STREAM, + * ai_protocol=0 or IPPROTO_TCP, and service=0. The workaround is to + * ignore all but the first result. + * + * XXX AIX 5.[1-3] getaddrinfo() may return a non-null port when a null + * service argument is specified. + */ + struct addrinfo hints; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = inet_proto_info()->ai_family; + hints.ai_socktype = service ? socktype : MAI_SOCKTYPE; + hints.ai_flags = AI_NUMERICHOST; + if (!hostaddr) { + hints.ai_flags |= AI_PASSIVE; +#if !defined(IPV6_V6ONLY) || defined(BROKEN_AI_PASSIVE_NULL_HOST) + switch (hints.ai_family) { + case PF_UNSPEC: + hints.ai_family = PF_INET6; +#ifdef BROKEN_AI_PASSIVE_NULL_HOST + case PF_INET6: + hostaddr = "::"; + break; + case PF_INET: + hostaddr = "0.0.0.0"; + break; +#endif + } +#endif + } + if (service) { + service = filter_known_tcp_port(service); + if (alldig(service)) + hints.ai_flags |= AI_NUMERICSERV; + } + err = getaddrinfo(hostaddr, service, &hints, res); +#if defined(BROKEN_AI_NULL_SERVICE) + if (service == 0 && err == 0) { + struct addrinfo *r; + unsigned short *portp; + + for (r = *res; r != 0; r = r->ai_next) + if (*(portp = SOCK_ADDR_PORTP(r->ai_addr)) != 0) + *portp = 0; + } +#endif + return (err); +#endif +} + +/* sockaddr_to_hostaddr - binary address to printable address form */ + +int sockaddr_to_hostaddr(const struct sockaddr *sa, SOCKADDR_SIZE salen, + MAI_HOSTADDR_STR *hostaddr, + MAI_SERVPORT_STR *portnum, + int unused_socktype) +{ +#ifdef EMULATE_IPV4_ADDRINFO + char portbuf[sizeof("65535")]; + ssize_t len; + + /* + * Emulated getnameinfo(3) version. The buffer length includes the space + * for the null terminator. + */ + if (sa->sa_family != AF_INET) { + errno = EAFNOSUPPORT; + return (EAI_SYSTEM); + } + if (hostaddr != 0) { + if (inet_ntop(AF_INET, (void *) &(SOCK_ADDR_IN_ADDR(sa)), + hostaddr->buf, sizeof(hostaddr->buf)) == 0) + return (EAI_SYSTEM); + } + if (portnum != 0) { + sprintf(portbuf, "%d", ntohs(SOCK_ADDR_IN_PORT(sa)) & 0xffff); + if ((len = strlen(portbuf)) >= sizeof(portnum->buf)) { + errno = ENOSPC; + return (EAI_SYSTEM); + } + memcpy(portnum->buf, portbuf, len + 1); + } + return (0); +#else + int ret; + + /* + * Native getnameinfo(3) version. + */ + ret = getnameinfo(sa, salen, + hostaddr ? hostaddr->buf : (char *) 0, + hostaddr ? sizeof(hostaddr->buf) : 0, + portnum ? portnum->buf : (char *) 0, + portnum ? sizeof(portnum->buf) : 0, + NI_NUMERICHOST | NI_NUMERICSERV); + if (hostaddr != 0 && ret == 0 && sa->sa_family == AF_INET6) + (void) split_at(hostaddr->buf, '%'); + return (ret); +#endif +} + +/* sockaddr_to_hostname - binary address to printable hostname */ + +int sockaddr_to_hostname(const struct sockaddr *sa, SOCKADDR_SIZE salen, + MAI_HOSTNAME_STR *hostname, + MAI_SERVNAME_STR *service, + int socktype) +{ +#ifdef EMULATE_IPV4_ADDRINFO + + /* + * Emulated getnameinfo(3) version. + */ + struct hostent *hp; + struct servent *sp; + size_t len; + + /* + * Sanity check. + */ + if (sa->sa_family != AF_INET) + return (EAI_NODATA); + + /* + * Look up the host name. + */ + if (hostname != 0) { + if ((hp = gethostbyaddr((char *) &(SOCK_ADDR_IN_ADDR(sa)), + sizeof(SOCK_ADDR_IN_ADDR(sa)), + AF_INET)) == 0) + return (h_errno == TRY_AGAIN ? EAI_AGAIN : EAI_NONAME); + + /* + * Save the result. The buffer length includes the space for the null + * terminator. Hostname sanity checks are at the end of this + * function. + */ + if ((len = strlen(hp->h_name)) >= sizeof(hostname->buf)) { + errno = ENOSPC; + return (EAI_SYSTEM); + } + memcpy(hostname->buf, hp->h_name, len + 1); + } + + /* + * Look up the service. + */ + if (service != 0) { + if ((sp = getservbyport(ntohs(SOCK_ADDR_IN_PORT(sa)), + socktype == SOCK_DGRAM ? "udp" : "tcp")) == 0) + return (EAI_NONAME); + + /* + * Save the result. The buffer length includes the space for the null + * terminator. + */ + if ((len = strlen(sp->s_name)) >= sizeof(service->buf)) { + errno = ENOSPC; + return (EAI_SYSTEM); + } + memcpy(service->buf, sp->s_name, len + 1); + } +#else + + /* + * Native getnameinfo(3) version. + */ + int err; + + err = getnameinfo(sa, salen, + hostname ? hostname->buf : (char *) 0, + hostname ? sizeof(hostname->buf) : 0, + service ? service->buf : (char *) 0, + service ? sizeof(service->buf) : 0, + socktype == SOCK_DGRAM ? + NI_NAMEREQD | NI_DGRAM : NI_NAMEREQD); + if (err != 0) + return (err); +#endif + + /* + * Hostname sanity checks. + */ + if (hostname != 0) { + if (valid_hostaddr(hostname->buf, DONT_GRIPE)) { + msg_warn("numeric hostname: %s", hostname->buf); + return (EAI_NONAME); + } + if (!valid_hostname(hostname->buf, DO_GRIPE)) + return (EAI_NONAME); + } + return (0); +} + +/* myaddrinfo_control - fine control */ + +void myaddrinfo_control(int name,...) +{ + const char *myname = "myaddrinfo_control"; + va_list ap; + + for (va_start(ap, name); name != 0; name = va_arg(ap, int)) { + switch (name) { + default: + msg_panic("%s: bad name %d", myname, name); + } + } + va_end(ap); +} + +#ifdef EMULATE_IPV4_ADDRINFO + +/* freeaddrinfo - release storage */ + +void freeaddrinfo(struct addrinfo * ai) +{ + struct addrinfo *ap; + struct addrinfo *next; + + /* + * Artifact of implementation: tolerate a null pointer argument. + */ + for (ap = ai; ap != 0; ap = next) { + next = ap->ai_next; + if (ap->ai_canonname) + myfree(ap->ai_canonname); + /* ap->ai_addr is allocated within this memory block */ + myfree((void *) ap); + } +} + +static char *ai_errlist[] = { + "Success", + "Address family for hostname not supported", /* EAI_ADDRFAMILY */ + "Temporary failure in name resolution", /* EAI_AGAIN */ + "Invalid value for ai_flags", /* EAI_BADFLAGS */ + "Non-recoverable failure in name resolution", /* EAI_FAIL */ + "ai_family not supported", /* EAI_FAMILY */ + "Memory allocation failure", /* EAI_MEMORY */ + "No address associated with hostname", /* EAI_NODATA */ + "hostname nor servname provided, or not known", /* EAI_NONAME */ + "service name not supported for ai_socktype", /* EAI_SERVICE */ + "ai_socktype not supported", /* EAI_SOCKTYPE */ + "System error returned in errno", /* EAI_SYSTEM */ + "Invalid value for hints", /* EAI_BADHINTS */ + "Resolved protocol is unknown", /* EAI_PROTOCOL */ + "Unknown error", /* EAI_MAX */ +}; + +/* gai_strerror - error number to string */ + +char *gai_strerror(int ecode) +{ + + /* + * Note: EAI_SYSTEM errors are not automatically handed over to + * strerror(). The application decides. + */ + if (ecode < 0 || ecode > EAI_MAX) + ecode = EAI_MAX; + return (ai_errlist[ecode]); +} + +#endif + +#ifdef TEST + + /* + * A test program that takes some info from the command line and runs it + * forward and backward through the above conversion routines. + */ +#include +#include +#include +#include + +static int compare_family(const void *a, const void *b) +{ + struct addrinfo *resa = *(struct addrinfo **) a; + struct addrinfo *resb = *(struct addrinfo **) b; + + return (resa->ai_family - resb->ai_family); +} + +int main(int argc, char **argv) +{ + struct addrinfo *info; + struct addrinfo *ip; + struct addrinfo **resv; + MAI_HOSTNAME_STR host; + MAI_HOSTADDR_STR addr; + size_t len, n; + int err; + + msg_vstream_init(argv[0], VSTREAM_ERR); + + if (argc != 4) + msg_fatal("usage: %s protocols hostname hostaddress", argv[0]); + + inet_proto_init(argv[0], argv[1]); + + msg_info("=== hostname %s ===", argv[2]); + + if ((err = hostname_to_sockaddr(argv[2], (char *) 0, 0, &info)) != 0) { + msg_info("hostname_to_sockaddr(%s): %s", + argv[2], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + } else { + for (len = 0, ip = info; ip != 0; ip = ip->ai_next) + len += 1; + resv = (struct addrinfo **) mymalloc(len * sizeof(*resv)); + for (len = 0, ip = info; ip != 0; ip = ip->ai_next) + resv[len++] = ip; + qsort((void *) resv, len, sizeof(*resv), compare_family); + for (n = 0; n < len; n++) { + ip = resv[n]; + if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr, + (MAI_SERVPORT_STR *) 0, 0)) != 0) { + msg_info("sockaddr_to_hostaddr: %s", + err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + continue; + } + msg_info("%s -> family=%d sock=%d proto=%d %s", argv[2], + ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf); + if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host, + (MAI_SERVNAME_STR *) 0, 0)) != 0) { + msg_info("sockaddr_to_hostname: %s", + err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + continue; + } + msg_info("%s -> %s", addr.buf, host.buf); + } + freeaddrinfo(info); + myfree((void *) resv); + } + + msg_info("=== host address %s ===", argv[3]); + + if ((err = hostaddr_to_sockaddr(argv[3], (char *) 0, 0, &ip)) != 0) { + msg_info("hostaddr_to_sockaddr(%s): %s", + argv[3], err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + } else { + if ((err = sockaddr_to_hostaddr(ip->ai_addr, ip->ai_addrlen, &addr, + (MAI_SERVPORT_STR *) 0, 0)) != 0) { + msg_info("sockaddr_to_hostaddr: %s", + err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + } else { + msg_info("%s -> family=%d sock=%d proto=%d %s", argv[3], + ip->ai_family, ip->ai_socktype, ip->ai_protocol, addr.buf); + if ((err = sockaddr_to_hostname(ip->ai_addr, ip->ai_addrlen, &host, + (MAI_SERVNAME_STR *) 0, 0)) != 0) { + msg_info("sockaddr_to_hostname: %s", + err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); + } else + msg_info("%s -> %s", addr.buf, host.buf); + freeaddrinfo(ip); + } + } + exit(0); +} + +#endif diff --git a/src/util/myaddrinfo.h b/src/util/myaddrinfo.h new file mode 100644 index 0000000..94f1e9f --- /dev/null +++ b/src/util/myaddrinfo.h @@ -0,0 +1,229 @@ +#ifndef _MYADDRINFO_H_INCLUDED_ +#define _MYADDRINFO_H_INCLUDED_ + +/*++ +/* NAME +/* myaddrinfo 3h +/* SUMMARY +/* addrinfo encapsulation and emulation +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include +#include +#include +#include +#include /* MAI_STRERROR() */ +#include /* CHAR_BIT */ + + /* + * Backwards compatibility support for IPV4 systems without addrinfo API. + */ +#ifdef EMULATE_IPV4_ADDRINFO + + /* + * Avoid clashes with global symbols, just in case some third-party library + * provides its own addrinfo() implementation. This also allows us to test + * the IPV4 emulation code on an IPV6 enabled system. + */ +#undef freeaddrinfo +#define freeaddrinfo mai_freeaddrinfo +#undef gai_strerror +#define gai_strerror mai_strerror +#undef addrinfo +#define addrinfo mai_addrinfo +#undef sockaddr_storage +#define sockaddr_storage mai_sockaddr_storage + + /* + * Modern systems define this in . + */ +struct addrinfo { + int ai_flags; /* AI_PASSIVE|CANONNAME|NUMERICHOST */ + int ai_family; /* PF_xxx */ + int ai_socktype; /* SOCK_xxx */ + int ai_protocol; /* 0 or IPPROTO_xxx */ + size_t ai_addrlen; /* length of ai_addr */ + char *ai_canonname; /* canonical name for nodename */ + struct sockaddr *ai_addr; /* binary address */ + struct addrinfo *ai_next; /* next structure in linked list */ +}; + + /* + * Modern systems define this in . + */ +struct sockaddr_storage { + struct sockaddr_in dummy; /* alignment!! */ +}; + + /* + * Result codes. See gai_strerror() for text. Undefine already imported + * definitions so that we can test the IPv4-only emulation on a modern + * system without getting a ton of compiler warnings. + */ +#undef EAI_ADDRFAMILY +#define EAI_ADDRFAMILY 1 +#undef EAI_AGAIN +#define EAI_AGAIN 2 +#undef EAI_BADFLAGS +#define EAI_BADFLAGS 3 +#undef EAI_FAIL +#define EAI_FAIL 4 +#undef EAI_FAMILY +#define EAI_FAMILY 5 +#undef EAI_MEMORY +#define EAI_MEMORY 6 +#undef EAI_NODATA +#define EAI_NODATA 7 +#undef EAI_NONAME +#define EAI_NONAME 8 +#undef EAI_SERVICE +#define EAI_SERVICE 9 +#undef EAI_SOCKTYPE +#define EAI_SOCKTYPE 10 +#undef EAI_SYSTEM +#define EAI_SYSTEM 11 +#undef EAI_BADHINTS +#define EAI_BADHINTS 12 +#undef EAI_PROTOCOL +#define EAI_PROTOCOL 13 +#undef EAI_RESNULL +#define EAI_RESNULL 14 +#undef EAI_MAX +#define EAI_MAX 15 + +extern void freeaddrinfo(struct addrinfo *); +extern char *gai_strerror(int); + +#endif + + /* + * Bounds grow in leaps. These macros attempt to keep non-library code free + * from IPV6 #ifdef pollution. Avoid macro names that end in STRLEN because + * they suggest that space for the null terminator is not included. + */ +#ifdef HAS_IPV6 +# define MAI_HOSTADDR_STRSIZE INET6_ADDRSTRLEN +#else +# ifndef INET_ADDRSTRLEN +# define INET_ADDRSTRLEN 16 +# endif +# define MAI_HOSTADDR_STRSIZE INET_ADDRSTRLEN +#endif + +#define MAI_HOSTNAME_STRSIZE 1025 +#define MAI_SERVNAME_STRSIZE 32 +#define MAI_SERVPORT_STRSIZE sizeof("65535") + +#define MAI_V4ADDR_BITS 32 +#define MAI_V6ADDR_BITS 128 +#define MAI_V4ADDR_BYTES ((MAI_V4ADDR_BITS + (CHAR_BIT - 1))/CHAR_BIT) +#define MAI_V6ADDR_BYTES ((MAI_V6ADDR_BITS + (CHAR_BIT - 1))/CHAR_BIT) + + /* + * Routines and data structures to hide some of the complexity of the + * addrinfo API. They still don't hide that we may get results for address + * families that we aren't interested in. + * + * Note: the getnameinfo() and inet_ntop() system library functions use unsafe + * APIs with separate pointer and length arguments. To avoid buffer overflow + * problems with these functions, Postfix uses pointers to structures + * internally. This way the compiler can enforce that callers provide + * buffers with the appropriate length, instead of having to trust that + * callers will never mess up some length calculation. + */ +typedef struct { + char buf[MAI_HOSTNAME_STRSIZE]; +} MAI_HOSTNAME_STR; + +typedef struct { + char buf[MAI_HOSTADDR_STRSIZE]; +} MAI_HOSTADDR_STR; + +typedef struct { + char buf[MAI_SERVNAME_STRSIZE]; +} MAI_SERVNAME_STR; + +typedef struct { + char buf[MAI_SERVPORT_STRSIZE]; +} MAI_SERVPORT_STR; + +extern int WARN_UNUSED_RESULT hostname_to_sockaddr_pf(const char *, + int, const char *, int, struct addrinfo **); +extern int WARN_UNUSED_RESULT hostaddr_to_sockaddr(const char *, + const char *, int, struct addrinfo **); +extern int WARN_UNUSED_RESULT sockaddr_to_hostaddr(const struct sockaddr *, + SOCKADDR_SIZE, MAI_HOSTADDR_STR *, MAI_SERVPORT_STR *, int); +extern int WARN_UNUSED_RESULT sockaddr_to_hostname(const struct sockaddr *, + SOCKADDR_SIZE, MAI_HOSTNAME_STR *, MAI_SERVNAME_STR *, int); +extern void myaddrinfo_control(int,...); + +#define MAI_CTL_END 0 /* list terminator */ + +#define MAI_STRERROR(e) ((e) == EAI_SYSTEM ? strerror(errno) : gai_strerror(e)) + +#define hostname_to_sockaddr(host, serv, sock, res) \ + hostname_to_sockaddr_pf((host), PF_UNSPEC, (serv), (sock), (res)) + + /* + * Macros for the case where we really don't want to be bothered with things + * that may fail. + */ +#define HOSTNAME_TO_SOCKADDR_PF(host, pf, serv, sock, res) \ + do { \ + int _aierr; \ + _aierr = hostname_to_sockaddr_pf((host), (pf), (serv), (sock), (res)); \ + if (_aierr) \ + msg_fatal("hostname_to_sockaddr_pf: %s", MAI_STRERROR(_aierr)); \ + } while (0) + +#define HOSTNAME_TO_SOCKADDR(host, serv, sock, res) \ + HOSTNAME_TO_SOCKADDR_PF((host), PF_UNSPEC, (serv), (sock), (res)) + +#define HOSTADDR_TO_SOCKADDR(host, serv, sock, res) \ + do { \ + int _aierr; \ + _aierr = hostaddr_to_sockaddr((host), (serv), (sock), (res)); \ + if (_aierr) \ + msg_fatal("hostaddr_to_sockaddr: %s", MAI_STRERROR(_aierr)); \ + } while (0) + +#define SOCKADDR_TO_HOSTADDR(sa, salen, host, port, sock) \ + do { \ + int _aierr; \ + _aierr = sockaddr_to_hostaddr((sa), (salen), (host), (port), (sock)); \ + if (_aierr) \ + msg_fatal("sockaddr_to_hostaddr: %s", MAI_STRERROR(_aierr)); \ + } while (0) + +#define SOCKADDR_TO_HOSTNAME(sa, salen, host, service, sock) \ + do { \ + int _aierr; \ + _aierr = sockaddr_to_hostname((sa), (salen), (host), (service), (sock)); \ + if (_aierr) \ + msg_fatal("sockaddr_to_hostname: %s", MAI_STRERROR(_aierr)); \ + } while (0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/myaddrinfo.ref b/src/util/myaddrinfo.ref new file mode 100644 index 0000000..7dccdb0 --- /dev/null +++ b/src/util/myaddrinfo.ref @@ -0,0 +1,8 @@ +./myaddrinfo: === hostname belly.porcupine.org === +./myaddrinfo: belly.porcupine.org -> family=2 sock=1 proto=6 168.100.3.6 +./myaddrinfo: 168.100.3.6 -> belly.porcupine.org +./myaddrinfo: belly.porcupine.org -> family=28 sock=1 proto=6 2604:8d00:189::6 +./myaddrinfo: 2604:8d00:189::6 -> belly.porcupine.org +./myaddrinfo: === host address 168.100.3.2 === +./myaddrinfo: 168.100.3.2 -> family=2 sock=1 proto=6 168.100.3.2 +./myaddrinfo: 168.100.3.2 -> spike.porcupine.org diff --git a/src/util/myaddrinfo.ref2 b/src/util/myaddrinfo.ref2 new file mode 100644 index 0000000..f2305dc --- /dev/null +++ b/src/util/myaddrinfo.ref2 @@ -0,0 +1,5 @@ +./myaddrinfo: === hostname null.porcupine.org === +./myaddrinfo: hostname_to_sockaddr(null.porcupine.org): hostname nor servname provided, or not known +./myaddrinfo: === host address 10.0.0.0 === +./myaddrinfo: 10.0.0.0 -> family=2 sock=1 proto=6 10.0.0.0 +./myaddrinfo: sockaddr_to_hostname: hostname nor servname provided, or not known diff --git a/src/util/myaddrinfo4.ref b/src/util/myaddrinfo4.ref new file mode 100644 index 0000000..33c1284 --- /dev/null +++ b/src/util/myaddrinfo4.ref @@ -0,0 +1,6 @@ +./myaddrinfo4: === hostname belly.porcupine.org === +./myaddrinfo4: belly.porcupine.org -> family=2 sock=1 proto=6 168.100.3.6 +./myaddrinfo4: 168.100.3.6 -> belly.porcupine.org +./myaddrinfo4: === host address 168.100.3.2 === +./myaddrinfo4: 168.100.3.2 -> family=2 sock=1 proto=6 168.100.3.2 +./myaddrinfo4: 168.100.3.2 -> spike.porcupine.org diff --git a/src/util/myaddrinfo4.ref2 b/src/util/myaddrinfo4.ref2 new file mode 100644 index 0000000..f73560b --- /dev/null +++ b/src/util/myaddrinfo4.ref2 @@ -0,0 +1,5 @@ +./myaddrinfo4: === hostname null.porcupine.org === +./myaddrinfo4: hostname2sockaddr(null.porcupine.org): No address associated with hostname +./myaddrinfo4: === host address 10.0.0.0 === +./myaddrinfo4: 10.0.0.0 -> family=2 sock=1 proto=6 10.0.0.0 +./myaddrinfo4: sockaddr2hostname: hostname nor servname provided, or not known diff --git a/src/util/myflock.c b/src/util/myflock.c new file mode 100644 index 0000000..bd903ee --- /dev/null +++ b/src/util/myflock.c @@ -0,0 +1,152 @@ +/*++ +/* NAME +/* myflock 3 +/* SUMMARY +/* lock open file +/* SYNOPSIS +/* #include +/* +/* int myflock(fd, lock_style, operation) +/* int fd; +/* int lock_style; +/* int operation; +/* DESCRIPTION +/* myflock() locks or unlocks an entire open file. +/* +/* In the case of a blocking request, a call that fails due to +/* foreseeable transient problems is retried once per second. +/* +/* Arguments: +/* .IP fd +/* The open file to be locked/unlocked. +/* .IP lock_style +/* One of the following values: +/* .RS +/* .IP MYFLOCK_STYLE_FLOCK +/* Use BSD-style flock() locking. +/* .IP MYFLOCK_STYLE_FCNTL +/* Use POSIX-style fcntl() locking. +/* .RE +/* .IP operation +/* One of the following values: +/* .RS +/* .IP MYFLOCK_OP_NONE +/* Release any locks the process has on the specified open file. +/* .IP MYFLOCK_OP_SHARED +/* Attempt to acquire a shared lock on the specified open file. +/* This is appropriate for read-only access. +/* .IP MYFLOCK_OP_EXCLUSIVE +/* Attempt to acquire an exclusive lock on the specified open +/* file. This is appropriate for write access. +/* .PP +/* In addition, setting the MYFLOCK_OP_NOWAIT bit causes the +/* call to return immediately when the requested lock cannot +/* be acquired. +/* .RE +/* DIAGNOSTICS +/* myflock() returns 0 in case of success, -1 in case of failure. +/* A problem description is returned via the global \fIerrno\fR +/* variable. In the case of a non-blocking lock request the value +/* EAGAIN means that a lock is claimed by someone else. +/* +/* Panic: attempts to use an unsupported file locking method or +/* to implement an unsupported operation. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include + +#ifdef HAS_FCNTL_LOCK +#include +#include +#endif + +#ifdef HAS_FLOCK_LOCK +#include +#endif + +/* Utility library. */ + +#include "msg.h" +#include "myflock.h" + +/* myflock - lock/unlock entire open file */ + +int myflock(int fd, int lock_style, int operation) +{ + int status; + + /* + * Sanity check. + */ + if ((operation & (MYFLOCK_OP_BITS)) != operation) + msg_panic("myflock: improper operation type: 0x%x", operation); + + switch (lock_style) { + + /* + * flock() does exactly what we need. Too bad it is not standard. + */ +#ifdef HAS_FLOCK_LOCK + case MYFLOCK_STYLE_FLOCK: + { + static int lock_ops[] = { + LOCK_UN, LOCK_SH, LOCK_EX, -1, + -1, LOCK_SH | LOCK_NB, LOCK_EX | LOCK_NB, -1 + }; + + while ((status = flock(fd, lock_ops[operation])) < 0 + && errno == EINTR) + sleep(1); + break; + } +#endif + + /* + * fcntl() is standard and does more than we need, but we can handle + * it. + */ +#ifdef HAS_FCNTL_LOCK + case MYFLOCK_STYLE_FCNTL: + { + struct flock lock; + int request; + static int lock_ops[] = { + F_UNLCK, F_RDLCK, F_WRLCK + }; + + memset((void *) &lock, 0, sizeof(lock)); + lock.l_type = lock_ops[operation & ~MYFLOCK_OP_NOWAIT]; + request = (operation & MYFLOCK_OP_NOWAIT) ? F_SETLK : F_SETLKW; + while ((status = fcntl(fd, request, &lock)) < 0 + && errno == EINTR) + sleep(1); + break; + } +#endif + default: + msg_panic("myflock: unsupported lock style: 0x%x", lock_style); + } + + /* + * Return a consistent result. Some systems return EACCES when a lock is + * taken by someone else, and that would complicate error processing. + */ + if (status < 0 && (operation & MYFLOCK_OP_NOWAIT) != 0) + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EACCES) + errno = EAGAIN; + + return (status); +} diff --git a/src/util/myflock.h b/src/util/myflock.h new file mode 100644 index 0000000..ee18bdb --- /dev/null +++ b/src/util/myflock.h @@ -0,0 +1,52 @@ +#ifndef _MYFLOCK_H_INCLUDED_ +#define _MYFLOCK_H_INCLUDED_ + +/*++ +/* NAME +/* myflock 3h +/* SUMMARY +/* lock open file +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern int WARN_UNUSED_RESULT myflock(int, int, int); + + /* + * Lock styles. + */ +#define MYFLOCK_STYLE_FLOCK 1 +#define MYFLOCK_STYLE_FCNTL 2 + + /* + * Lock request types. + */ +#define MYFLOCK_OP_NONE 0 +#define MYFLOCK_OP_SHARED 1 +#define MYFLOCK_OP_EXCLUSIVE 2 +#define MYFLOCK_OP_NOWAIT 4 + +#define MYFLOCK_OP_BITS \ + (MYFLOCK_OP_SHARED | MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/mymalloc.c b/src/util/mymalloc.c new file mode 100644 index 0000000..94f7bb3 --- /dev/null +++ b/src/util/mymalloc.c @@ -0,0 +1,269 @@ +/*++ +/* NAME +/* mymalloc 3 +/* SUMMARY +/* memory management wrappers +/* SYNOPSIS +/* #include +/* +/* void *mymalloc(len) +/* ssize_t len; +/* +/* void *myrealloc(ptr, len) +/* void *ptr; +/* ssize_t len; +/* +/* void myfree(ptr) +/* void *ptr; +/* +/* char *mystrdup(str) +/* const char *str; +/* +/* char *mystrndup(str, len) +/* const char *str; +/* ssize_t len; +/* +/* void *mymemdup(ptr, len) +/* const void *ptr; +/* ssize_t len; +/* DESCRIPTION +/* This module performs low-level memory management with error +/* handling. A call of these functions either succeeds or it does +/* not return at all. +/* +/* To save memory, zero-length strings are shared and read-only. +/* The caller must not attempt to modify the null terminator. +/* This code is enabled unless NO_SHARED_EMPTY_STRINGS is +/* defined at compile time (for example, you have an sscanf() +/* routine that pushes characters back into its input). +/* +/* mymalloc() allocates the requested amount of memory. The memory +/* is not set to zero. +/* +/* myrealloc() resizes memory obtained from mymalloc() or myrealloc() +/* to the requested size. The result pointer value may differ from +/* that given via the \fIptr\fR argument. +/* +/* myfree() takes memory obtained from mymalloc() or myrealloc() +/* and makes it available for other use. +/* +/* mystrdup() returns a dynamic-memory copy of its null-terminated +/* argument. This routine uses mymalloc(). +/* +/* mystrndup() returns a dynamic-memory copy of at most \fIlen\fR +/* leading characters of its null-terminated +/* argument. The result is null-terminated. This routine uses mymalloc(). +/* +/* mymemdup() makes a copy of the memory pointed to by \fIptr\fR +/* with length \fIlen\fR. The result is NOT null-terminated. +/* This routine uses mymalloc(). +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Problems are reported via the msg(3) diagnostics routines: +/* the requested amount of memory is not available; improper use +/* is detected; other fatal errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include +#include +#include + +/* Application-specific. */ + +#include "msg.h" +#include "mymalloc.h" + + /* + * Structure of an annotated memory block. In order to detect spurious + * free() calls we prepend a signature to memory given to the application. + * In order to detect access to free()d blocks, overwrite each block as soon + * as it is passed to myfree(). With the code below, the user data has + * integer alignment or better. + */ +typedef struct MBLOCK { + int signature; /* set when block is active */ + ssize_t length; /* user requested length */ + union { + ALIGN_TYPE align; + char payload[1]; /* actually a bunch of bytes */ + } u; +} MBLOCK; + +#define SIGNATURE 0xdead +#define FILLER 0xff + +#define CHECK_IN_PTR(ptr, real_ptr, len, fname) { \ + if (ptr == 0) \ + msg_panic("%s: null pointer input", fname); \ + real_ptr = (MBLOCK *) (ptr - offsetof(MBLOCK, u.payload[0])); \ + if (real_ptr->signature != SIGNATURE) \ + msg_panic("%s: corrupt or unallocated memory block", fname); \ + real_ptr->signature = 0; \ + if ((len = real_ptr->length) < 1) \ + msg_panic("%s: corrupt memory block length", fname); \ +} + +#define CHECK_OUT_PTR(ptr, real_ptr, len) { \ + real_ptr->signature = SIGNATURE; \ + real_ptr->length = len; \ + ptr = real_ptr->u.payload; \ +} + +#define SPACE_FOR(len) (offsetof(MBLOCK, u.payload[0]) + len) + + /* + * Optimization for short strings. We share one copy with multiple callers. + * This differs from normal heap memory in two ways, because the memory is + * shared: + * + * - It must be read-only to avoid horrible bugs. This is OK because there is + * no legitimate reason to modify the null terminator. + * + * - myfree() cannot overwrite the memory with a filler pattern like it can do + * with heap memory. Therefore, some dangling pointer bugs will be masked. + */ +#ifndef NO_SHARED_EMPTY_STRINGS +static const char empty_string[] = ""; + +#endif + +/* mymalloc - allocate memory or bust */ + +void *mymalloc(ssize_t len) +{ + void *ptr; + MBLOCK *real_ptr; + + /* + * Note: for safety reasons the request length is a signed type. This + * allows us to catch integer overflow problems that weren't already + * caught up-stream. + */ + if (len < 1) + msg_panic("mymalloc: requested length %ld", (long) len); +#ifdef MYMALLOC_FUZZ + len += MYMALLOC_FUZZ; +#endif + if ((real_ptr = (MBLOCK *) malloc(SPACE_FOR(len))) == 0) + msg_fatal("mymalloc: insufficient memory for %ld bytes: %m", + (long) len); + CHECK_OUT_PTR(ptr, real_ptr, len); + memset(ptr, FILLER, len); + return (ptr); +} + +/* myrealloc - reallocate memory or bust */ + +void *myrealloc(void *ptr, ssize_t len) +{ + MBLOCK *real_ptr; + ssize_t old_len; + +#ifndef NO_SHARED_EMPTY_STRINGS + if (ptr == empty_string) + return (mymalloc(len)); +#endif + + /* + * Note: for safety reasons the request length is a signed type. This + * allows us to catch integer overflow problems that weren't already + * caught up-stream. + */ + if (len < 1) + msg_panic("myrealloc: requested length %ld", (long) len); +#ifdef MYMALLOC_FUZZ + len += MYMALLOC_FUZZ; +#endif + CHECK_IN_PTR(ptr, real_ptr, old_len, "myrealloc"); + if ((real_ptr = (MBLOCK *) realloc((void *) real_ptr, SPACE_FOR(len))) == 0) + msg_fatal("myrealloc: insufficient memory for %ld bytes: %m", + (long) len); + CHECK_OUT_PTR(ptr, real_ptr, len); + if (len > old_len) + memset(ptr + old_len, FILLER, len - old_len); + return (ptr); +} + +/* myfree - release memory */ + +void myfree(void *ptr) +{ + MBLOCK *real_ptr; + ssize_t len; + +#ifndef NO_SHARED_EMPTY_STRINGS + if (ptr != empty_string) { +#endif + CHECK_IN_PTR(ptr, real_ptr, len, "myfree"); + memset((void *) real_ptr, FILLER, SPACE_FOR(len)); + free((void *) real_ptr); +#ifndef NO_SHARED_EMPTY_STRINGS + } +#endif +} + +/* mystrdup - save string to heap */ + +char *mystrdup(const char *str) +{ + size_t len; + + if (str == 0) + msg_panic("mystrdup: null pointer argument"); +#ifndef NO_SHARED_EMPTY_STRINGS + if (*str == 0) + return ((char *) empty_string); +#endif + if ((len = strlen(str) + 1) > SSIZE_T_MAX) + msg_panic("mystrdup: string length >= SSIZE_T_MAX"); + return (memcpy(mymalloc(len), str, len)); +} + +/* mystrndup - save substring to heap */ + +char *mystrndup(const char *str, ssize_t len) +{ + char *result; + char *cp; + + if (str == 0) + msg_panic("mystrndup: null pointer argument"); + if (len < 0) + msg_panic("mystrndup: requested length %ld", (long) len); +#ifndef NO_SHARED_EMPTY_STRINGS + if (*str == 0) + return ((char *) empty_string); +#endif + if ((cp = memchr(str, 0, len)) != 0) + len = cp - str; + result = memcpy(mymalloc(len + 1), str, len); + result[len] = 0; + return (result); +} + +/* mymemdup - copy memory */ + +void *mymemdup(const void *ptr, ssize_t len) +{ + if (ptr == 0) + msg_panic("mymemdup: null pointer argument"); + return (memcpy(mymalloc(len), ptr, len)); +} diff --git a/src/util/mymalloc.h b/src/util/mymalloc.h new file mode 100644 index 0000000..e2190f7 --- /dev/null +++ b/src/util/mymalloc.h @@ -0,0 +1,40 @@ +#ifndef _MALLOC_H_INCLUDED_ +#define _MALLOC_H_INCLUDED_ + +/*++ +/* NAME +/* mymalloc 3h +/* SUMMARY +/* memory management wrappers +/* SYNOPSIS +/* #include "mymalloc.h" +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void *mymalloc(ssize_t); +extern void *myrealloc(void *, ssize_t); +extern void myfree(void *); +extern char *mystrdup(const char *); +extern char *mystrndup(const char *, ssize_t); +extern void *mymemdup(const void *, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/myrand.c b/src/util/myrand.c new file mode 100644 index 0000000..39fcbfd --- /dev/null +++ b/src/util/myrand.c @@ -0,0 +1,63 @@ +/*++ +/* NAME +/* myrand 3 +/* SUMMARY +/* rand wrapper +/* SYNOPSIS +/* #include +/* +/* void mysrand(seed) +/* int seed; +/* +/* int myrand() +/* DESCRIPTION +/* This module implements a wrapper for the portable, pseudo-random +/* number generator. The wrapper adds automatic initialization. +/* +/* mysrand() performs initialization. This call may be skipped. +/* +/* myrand() returns a pseudo-random number in the range [0, RAND_MAX]. +/* If mysrand() was not called, it is invoked with the process ID +/* ex-or-ed with the time of day in seconds. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* WARNING +/* Do not use this code for generating unpredictable numbers. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include + +static int myrand_initdone = 0; + +/* mysrand - initialize */ + +void mysrand(int seed) +{ + srand(seed); + myrand_initdone = 1; +} + +/* myrand - pseudo-random number */ + +int myrand(void) +{ + if (myrand_initdone == 0) + mysrand(getpid() ^ time((time_t *) 0)); + return (rand()); +} diff --git a/src/util/myrand.h b/src/util/myrand.h new file mode 100644 index 0000000..4cc88db --- /dev/null +++ b/src/util/myrand.h @@ -0,0 +1,35 @@ +#ifndef _MYRAND_H_INCLUDED_ +#define _MYRAND_H_INCLUDED_ + +/*++ +/* NAME +/* myrand 3h +/* SUMMARY +/* rand wrapper +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +#ifndef RAND_MAX +#define RAND_MAX 0x7fffffff +#endif + +extern void mysrand(int); +extern int myrand(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/mystrtok.c b/src/util/mystrtok.c new file mode 100644 index 0000000..85b15f3 --- /dev/null +++ b/src/util/mystrtok.c @@ -0,0 +1,258 @@ +/*++ +/* NAME +/* mystrtok 3 +/* SUMMARY +/* safe tokenizer +/* SYNOPSIS +/* #include +/* +/* char *mystrtok(bufp, delimiters) +/* char **bufp; +/* const char *delimiters; +/* +/* char *mystrtokq(bufp, delimiters, parens) +/* char **bufp; +/* const char *delimiters; +/* const char *parens; +/* +/* char *mystrtokdq(bufp, delimiters) +/* char **bufp; +/* const char *delimiters; +/* DESCRIPTION +/* mystrtok() splits a buffer on the specified \fIdelimiters\fR. +/* Tokens are delimited by runs of delimiters, so this routine +/* cannot return zero-length tokens. +/* +/* mystrtokq() is like mystrtok() but will not split text +/* between balanced parentheses. \fIparens\fR specifies the +/* opening and closing parenthesis (one of each). The set of +/* \fIparens\fR must be distinct from the set of \fIdelimiters\fR. +/* +/* mystrtokdq() is like mystrtok() but will not split text +/* between double quotes. The backslash character may be used +/* to escape characters. The double quote and backslash +/* character must not appear in the set of \fIdelimiters\fR. +/* +/* The \fIbufp\fR argument specifies the start of the search; it +/* is updated with each call. The input is destroyed. +/* +/* The result value is the next token, or a null pointer when the +/* end of the buffer was reached. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include + +/* Utility library. */ + +#include "stringops.h" + +/* mystrtok - safe tokenizer */ + +char *mystrtok(char **src, const char *sep) +{ + char *start = *src; + char *end; + + /* + * Skip over leading delimiters. + */ + start += strspn(start, sep); + if (*start == 0) { + *src = start; + return (0); + } + + /* + * Separate off one token. + */ + end = start + strcspn(start, sep); + if (*end != 0) + *end++ = 0; + *src = end; + return (start); +} + +/* mystrtokq - safe tokenizer with quoting support */ + +char *mystrtokq(char **src, const char *sep, const char *parens) +{ + char *start = *src; + static char *cp; + int ch; + int level; + + /* + * Skip over leading delimiters. + */ + start += strspn(start, sep); + if (*start == 0) { + *src = start; + return (0); + } + + /* + * Parse out the next token. + */ + for (level = 0, cp = start; (ch = *(unsigned char *) cp) != 0; cp++) { + if (ch == parens[0]) { + level++; + } else if (level > 0 && ch == parens[1]) { + level--; + } else if (level == 0 && strchr(sep, ch) != 0) { + *cp++ = 0; + break; + } + } + *src = cp; + return (start); +} + +/* mystrtokdq - safe tokenizer, double quote and backslash support */ + +char *mystrtokdq(char **src, const char *sep) +{ + char *cp = *src; + char *start; + + /* + * Skip leading delimiters. + */ + cp += strspn(cp, sep); + + /* + * Skip to next unquoted space or comma. + */ + if (*cp == 0) { + start = 0; + } else { + int in_quotes; + + for (in_quotes = 0, start = cp; *cp; cp++) { + if (*cp == '\\') { + if (*++cp == 0) + break; + } else if (*cp == '"') { + in_quotes = !in_quotes; + } else if (!in_quotes && strchr(sep, *(unsigned char *) cp) != 0) { + *cp++ = 0; + break; + } + } + } + *src = cp; + return (start); +} + +#ifdef TEST + + /* + * Test program. + */ +#include "msg.h" +#include "mymalloc.h" + + /* + * The following needs to be large enough to include a null terminator in + * every testcase.expected field. + */ +#define EXPECT_SIZE 5 + +struct testcase { + const char *action; + const char *input; + const char *expected[EXPECT_SIZE]; +}; +static const struct testcase testcases[] = { + {"mystrtok", ""}, + {"mystrtok", " foo ", {"foo"}}, + {"mystrtok", " foo bar ", {"foo", "bar"}}, + {"mystrtokq", ""}, + {"mystrtokq", "foo bar", {"foo", "bar"}}, + {"mystrtokq", "{ bar } ", {"{ bar }"}}, + {"mystrtokq", "foo { bar } baz", {"foo", "{ bar }", "baz"}}, + {"mystrtokq", "foo{ bar } baz", {"foo{ bar }", "baz"}}, + {"mystrtokq", "foo { bar }baz", {"foo", "{ bar }baz"}}, + {"mystrtokdq", ""}, + {"mystrtokdq", " foo ", {"foo"}}, + {"mystrtokdq", " foo bar ", {"foo", "bar"}}, + {"mystrtokdq", " foo\\ bar ", {"foo\\ bar"}}, + {"mystrtokdq", " foo \\\" bar", {"foo", "\\\"", "bar"}}, + {"mystrtokdq", " foo \" bar baz\" ", {"foo", "\" bar baz\""}}, +}; + +int main(void) +{ + const struct testcase *tp; + char *actual; + int pass; + int fail; + int match; + int n; + +#define NUM_TESTS sizeof(testcases)/sizeof(testcases[0]) +#define STR_OR_NULL(s) ((s) ? (s) : "null") + + for (pass = fail = 0, tp = testcases; tp < testcases + NUM_TESTS; tp++) { + char *saved_input = mystrdup(tp->input); + char *cp = saved_input; + + msg_info("RUN test case %ld %s >%s<", + (long) (tp - testcases), tp->action, tp->input); +#if 0 + msg_info("action=%s", tp->action); + msg_info("input=%s", tp->input); + for (n = 0; tp->expected[n]; tp++) + msg_info("expected[%d]=%s", n, tp->expected[n]); +#endif + + for (n = 0; n < EXPECT_SIZE; n++) { + if (strcmp(tp->action, "mystrtok") == 0) { + actual = mystrtok(&cp, CHARS_SPACE); + } else if (strcmp(tp->action, "mystrtokq") == 0) { + actual = mystrtokq(&cp, CHARS_SPACE, CHARS_BRACE); + } else if (strcmp(tp->action, "mystrtokdq") == 0) { + actual = mystrtokdq(&cp, CHARS_SPACE); + } else { + msg_panic("invalid command: %s", tp->action); + } + if ((match = (actual && tp->expected[n]) ? + (strcmp(actual, tp->expected[n]) == 0) : + (actual == tp->expected[n])) != 0) { + if (actual == 0) { + msg_info("PASS test %ld", (long) (tp - testcases)); + pass++; + break; + } + } else { + msg_warn("expected: >%s<, got: >%s<", + STR_OR_NULL(tp->expected[n]), STR_OR_NULL(actual)); + msg_info("FAIL test %ld", (long) (tp - testcases)); + fail++; + break; + } + } + if (n >= EXPECT_SIZE) + msg_panic("need to increase EXPECT_SIZE"); + myfree(saved_input); + } + return (fail > 0); +} + +#endif diff --git a/src/util/mystrtok.ref b/src/util/mystrtok.ref new file mode 100644 index 0000000..4f920f9 --- /dev/null +++ b/src/util/mystrtok.ref @@ -0,0 +1,30 @@ +unknown: RUN test case 0 mystrtok >< +unknown: PASS test 0 +unknown: RUN test case 1 mystrtok > foo < +unknown: PASS test 1 +unknown: RUN test case 2 mystrtok > foo bar < +unknown: PASS test 2 +unknown: RUN test case 3 mystrtokq >< +unknown: PASS test 3 +unknown: RUN test case 4 mystrtokq >foo bar< +unknown: PASS test 4 +unknown: RUN test case 5 mystrtokq >{ bar } < +unknown: PASS test 5 +unknown: RUN test case 6 mystrtokq >foo { bar } baz< +unknown: PASS test 6 +unknown: RUN test case 7 mystrtokq >foo{ bar } baz< +unknown: PASS test 7 +unknown: RUN test case 8 mystrtokq >foo { bar }baz< +unknown: PASS test 8 +unknown: RUN test case 9 mystrtokdq >< +unknown: PASS test 9 +unknown: RUN test case 10 mystrtokdq > foo < +unknown: PASS test 10 +unknown: RUN test case 11 mystrtokdq > foo bar < +unknown: PASS test 11 +unknown: RUN test case 12 mystrtokdq > foo\ bar < +unknown: PASS test 12 +unknown: RUN test case 13 mystrtokdq > foo \" bar< +unknown: PASS test 13 +unknown: RUN test case 14 mystrtokdq > foo " bar baz" < +unknown: PASS test 14 diff --git a/src/util/name_code.c b/src/util/name_code.c new file mode 100644 index 0000000..ca6d94a --- /dev/null +++ b/src/util/name_code.c @@ -0,0 +1,91 @@ +/*++ +/* NAME +/* name_code 3 +/* SUMMARY +/* name to number table mapping +/* SYNOPSIS +/* #include +/* +/* typedef struct { +/* .in +4 +/* const char *name; +/* int code; +/* .in -4 +/* } NAME_CODE; +/* +/* int name_code(table, flags, name) +/* const NAME_CODE *table; +/* int flags; +/* const char *name; +/* +/* const char *str_name_code(table, code) +/* const NAME_CODE *table; +/* int code; +/* DESCRIPTION +/* This module does simple name<->number mapping. The process +/* is controlled by a table of (name, code) values. +/* The table is terminated with a null pointer and a code that +/* corresponds to "name not found". +/* +/* name_code() looks up the code that corresponds with the name. +/* The lookup is case insensitive. The flags argument specifies +/* zero or more of the following: +/* .IP NAME_CODE_FLAG_STRICT_CASE +/* String lookups are case sensitive. +/* .PP +/* For convenience the constant NAME_CODE_FLAG_NONE requests +/* no special processing. +/* +/* str_name_code() translates a number to its equivalent string. +/* DIAGNOSTICS +/* When the search fails, the result is the "name not found" code +/* or the null pointer, respectively. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include + +/* name_code - look up code by name */ + +int name_code(const NAME_CODE *table, int flags, const char *name) +{ + const NAME_CODE *np; + int (*lookup) (const char *, const char *); + + if (flags & NAME_CODE_FLAG_STRICT_CASE) + lookup = strcmp; + else + lookup = strcasecmp; + + for (np = table; np->name; np++) + if (lookup(name, np->name) == 0) + break; + return (np->code); +} + +/* str_name_code - look up name by code */ + +const char *str_name_code(const NAME_CODE *table, int code) +{ + const NAME_CODE *np; + + for (np = table; np->name; np++) + if (code == np->code) + break; + return (np->name); +} diff --git a/src/util/name_code.h b/src/util/name_code.h new file mode 100644 index 0000000..e08618d --- /dev/null +++ b/src/util/name_code.h @@ -0,0 +1,39 @@ +#ifndef _NAME_CODE_H_INCLUDED_ +#define _NAME_CODE_H_INCLUDED_ + +/*++ +/* NAME +/* name_mask 3h +/* SUMMARY +/* name to number table mapping +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +typedef struct { + const char *name; + int code; +} NAME_CODE; + +#define NAME_CODE_FLAG_NONE 0 +#define NAME_CODE_FLAG_STRICT_CASE (1<<0) + +extern int name_code(const NAME_CODE *, int, const char *); +extern const char *str_name_code(const NAME_CODE *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/name_mask.c b/src/util/name_mask.c new file mode 100644 index 0000000..284d4fa --- /dev/null +++ b/src/util/name_mask.c @@ -0,0 +1,511 @@ +/*++ +/* NAME +/* name_mask 3 +/* SUMMARY +/* map names to bit mask +/* SYNOPSIS +/* #include +/* +/* int name_mask(context, table, names) +/* const char *context; +/* const NAME_MASK *table; +/* const char *names; +/* +/* long long_name_mask(context, table, names) +/* const char *context; +/* const LONG_NAME_MASK *table; +/* const char *names; +/* +/* const char *str_name_mask(context, table, mask) +/* const char *context; +/* const NAME_MASK *table; +/* int mask; +/* +/* const char *str_long_name_mask(context, table, mask) +/* const char *context; +/* const LONG_NAME_MASK *table; +/* long mask; +/* +/* int name_mask_opt(context, table, names, flags) +/* const char *context; +/* const NAME_MASK *table; +/* const char *names; +/* int flags; +/* +/* long long_name_mask_opt(context, table, names, flags) +/* const char *context; +/* const LONG_NAME_MASK *table; +/* const char *names; +/* int flags; +/* +/* int name_mask_delim_opt(context, table, names, delim, flags) +/* const char *context; +/* const NAME_MASK *table; +/* const char *names; +/* const char *delim; +/* int flags; +/* +/* long long_name_mask_delim_opt(context, table, names, delim, flags) +/* const char *context; +/* const LONG_NAME_MASK *table; +/* const char *names; +/* const char *delim; +/* int flags; +/* +/* const char *str_name_mask_opt(buf, context, table, mask, flags) +/* VSTRING *buf; +/* const char *context; +/* const NAME_MASK *table; +/* int mask; +/* int flags; +/* +/* const char *str_long_name_mask_opt(buf, context, table, mask, flags) +/* VSTRING *buf; +/* const char *context; +/* const LONG_NAME_MASK *table; +/* long mask; +/* int flags; +/* DESCRIPTION +/* name_mask() takes a null-terminated \fItable\fR with (name, mask) +/* values and computes the bit-wise OR of the masks that correspond +/* to the names listed in the \fInames\fR argument, separated by +/* comma and/or whitespace characters. The "long_" version returns +/* a "long int" bitmask, rather than an "int" bitmask. +/* +/* str_name_mask() translates a mask into its equivalent names. +/* The result is written to a static buffer that is overwritten +/* upon each call. The "long_" version converts a "long int" +/* bitmask, rather than an "int" bitmask. +/* +/* name_mask_opt() and str_name_mask_opt() are extended versions +/* with additional fine control. name_mask_delim_opt() supports +/* non-default delimiter characters. +/* +/* Arguments: +/* .IP buf +/* Null pointer or pointer to buffer storage. +/* .IP context +/* What kind of names and +/* masks are being manipulated, in order to make error messages +/* more understandable. Typically, this would be the name of a +/* user-configurable parameter. +/* .IP table +/* Table with (name, bit mask) pairs. +/* .IP names +/* A list of names that is to be converted into a bit mask. +/* .IP mask +/* A bit mask. +/* .IP delim +/* Delimiter characters to use instead of whitespace and commas. +/* .IP flags +/* Bit-wise OR of one or more of the following. Where features +/* would have conflicting results (e.g., FATAL versus IGNORE), +/* the feature that takes precedence is described first. +/* +/* When converting from string to mask, at least one of the +/* following must be specified: NAME_MASK_FATAL, NAME_MASK_RETURN, +/* NAME_MASK_WARN or NAME_MASK_IGNORE. +/* +/* When converting from mask to string, at least one of the +/* following must be specified: NAME_MASK_NUMBER, NAME_MASK_FATAL, +/* NAME_MASK_RETURN, NAME_MASK_WARN or NAME_MASK_IGNORE. +/* .RS +/* .IP NAME_MASK_NUMBER +/* When converting from string to mask, accept hexadecimal +/* inputs starting with "0x" followed by hexadecimal digits. +/* Each hexadecimal input may specify multiple bits. This +/* feature is ignored for hexadecimal inputs that cannot be +/* converted (malformed, out of range, etc.). +/* +/* When converting from mask to string, represent bits not +/* defined in \fItable\fR as "0x" followed by hexadecimal +/* digits. This conversion always succeeds. +/* .IP NAME_MASK_FATAL +/* Require that all names listed in \fIname\fR exist in +/* \fItable\fR or that they can be parsed as a hexadecimal +/* string, and require that all bits listed in \fImask\fR exist +/* in \fItable\fR or that they can be converted to hexadecimal +/* string. Terminate with a fatal run-time error if this +/* condition is not met. This feature is enabled by default +/* when calling name_mask() or str_name_mask(). +/* .IP NAME_MASK_RETURN +/* Require that all names listed in \fIname\fR exist in +/* \fItable\fR or that they can be parsed as a hexadecimal +/* string, and require that all bits listed in \fImask\fR exist +/* in \fItable\fR or that they can be converted to hexadecimal +/* string. Log a warning, and return 0 (name_mask()) or a +/* null pointer (str_name_mask()) if this condition is not +/* met. This feature is not enabled by default when calling +/* name_mask() or str_name_mask(). +/* .IP NAME_MASK_WARN +/* Require that all names listed in \fIname\fR exist in +/* \fItable\fR or that they can be parsed as a hexadecimal +/* string, and require that all bits listed in \fImask\fR exist +/* in \fItable\fR or that they can be converted to hexadecimal +/* string. Log a warning if this condition is not met, continue +/* processing, and return all valid bits or names. This feature +/* is not enabled by default when calling name_mask() or +/* str_name_mask(). +/* .IP NAME_MASK_IGNORE +/* Silently ignore names listed in \fIname\fR that don't exist +/* in \fItable\fR and that can't be parsed as a hexadecimal +/* string, and silently ignore bits listed in \fImask\fR that +/* don't exist in \fItable\fR and that can't be converted to +/* hexadecimal string. +/* .IP NAME_MASK_ANY_CASE +/* Enable case-insensitive matching. +/* This feature is not enabled by default when calling name_mask(); +/* it has no effect with str_name_mask(). +/* .IP NAME_MASK_COMMA +/* Use comma instead of space when converting a mask to string. +/* .IP NAME_MASK_PIPE +/* Use "|" instead of space when converting a mask to string. +/* .RE +/* The value NAME_MASK_NONE explicitly requests no features, +/* and NAME_MASK_DEFAULT enables the default options. +/* DIAGNOSTICS +/* Fatal: the \fInames\fR argument specifies a name not found in +/* \fItable\fR, or the \fImask\fR specifies a bit not found in +/* \fItable\fR. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include + +static int hex_to_ulong(char *, unsigned long, unsigned long *); + +#define STR(x) vstring_str(x) + +/* name_mask_delim_opt - compute mask corresponding to list of names */ + +int name_mask_delim_opt(const char *context, const NAME_MASK *table, + const char *names, const char *delim, int flags) +{ + const char *myname = "name_mask"; + char *saved_names = mystrdup(names); + char *bp = saved_names; + int result = 0; + const NAME_MASK *np; + char *name; + int (*lookup) (const char *, const char *); + unsigned long ulval; + + if ((flags & NAME_MASK_REQUIRED) == 0) + msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag", + myname); + + if (flags & NAME_MASK_ANY_CASE) + lookup = strcasecmp; + else + lookup = strcmp; + + /* + * Break up the names string, and look up each component in the table. If + * the name is found, merge its mask with the result. + */ + while ((name = mystrtok(&bp, delim)) != 0) { + for (np = table; /* void */ ; np++) { + if (np->name == 0) { + if ((flags & NAME_MASK_NUMBER) + && hex_to_ulong(name, ~0U, &ulval)) { + result |= (unsigned int) ulval; + } else if (flags & NAME_MASK_FATAL) { + msg_fatal("unknown %s value \"%s\" in \"%s\"", + context, name, names); + } else if (flags & NAME_MASK_RETURN) { + msg_warn("unknown %s value \"%s\" in \"%s\"", + context, name, names); + myfree(saved_names); + return (0); + } else if (flags & NAME_MASK_WARN) { + msg_warn("unknown %s value \"%s\" in \"%s\"", + context, name, names); + } + break; + } + if (lookup(name, np->name) == 0) { + if (msg_verbose) + msg_info("%s: %s", myname, name); + result |= np->mask; + break; + } + } + } + myfree(saved_names); + return (result); +} + +/* str_name_mask_opt - mask to string */ + +const char *str_name_mask_opt(VSTRING *buf, const char *context, + const NAME_MASK *table, + int mask, int flags) +{ + const char *myname = "name_mask"; + const NAME_MASK *np; + ssize_t len; + static VSTRING *my_buf = 0; + int delim = (flags & NAME_MASK_COMMA ? ',' : + (flags & NAME_MASK_PIPE ? '|' : ' ')); + + if ((flags & STR_NAME_MASK_REQUIRED) == 0) + msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag", + myname); + + if (buf == 0) { + if (my_buf == 0) + my_buf = vstring_alloc(1); + buf = my_buf; + } + VSTRING_RESET(buf); + + for (np = table; mask != 0; np++) { + if (np->name == 0) { + if (flags & NAME_MASK_NUMBER) { + vstring_sprintf_append(buf, "0x%x%c", mask, delim); + } else if (flags & NAME_MASK_FATAL) { + msg_fatal("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + } else if (flags & NAME_MASK_RETURN) { + msg_warn("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + return (0); + } else if (flags & NAME_MASK_WARN) { + msg_warn("%s: unknown %s bit in mask: 0x%x", + myname, context, mask); + } + break; + } + if (mask & np->mask) { + mask &= ~np->mask; + vstring_sprintf_append(buf, "%s%c", np->name, delim); + } + } + if ((len = VSTRING_LEN(buf)) > 0) + vstring_truncate(buf, len - 1); + VSTRING_TERMINATE(buf); + + return (STR(buf)); +} + +/* long_name_mask_delim_opt - compute mask corresponding to list of names */ + +long long_name_mask_delim_opt(const char *context, + const LONG_NAME_MASK * table, + const char *names, const char *delim, + int flags) +{ + const char *myname = "name_mask"; + char *saved_names = mystrdup(names); + char *bp = saved_names; + long result = 0; + const LONG_NAME_MASK *np; + char *name; + int (*lookup) (const char *, const char *); + unsigned long ulval; + + if ((flags & NAME_MASK_REQUIRED) == 0) + msg_panic("%s: missing NAME_MASK_FATAL/RETURN/WARN/IGNORE flag", + myname); + + if (flags & NAME_MASK_ANY_CASE) + lookup = strcasecmp; + else + lookup = strcmp; + + /* + * Break up the names string, and look up each component in the table. If + * the name is found, merge its mask with the result. + */ + while ((name = mystrtok(&bp, delim)) != 0) { + for (np = table; /* void */ ; np++) { + if (np->name == 0) { + if ((flags & NAME_MASK_NUMBER) + && hex_to_ulong(name, ~0UL, &ulval)) { + result |= ulval; + } else if (flags & NAME_MASK_FATAL) { + msg_fatal("unknown %s value \"%s\" in \"%s\"", + context, name, names); + } else if (flags & NAME_MASK_RETURN) { + msg_warn("unknown %s value \"%s\" in \"%s\"", + context, name, names); + myfree(saved_names); + return (0); + } else if (flags & NAME_MASK_WARN) { + msg_warn("unknown %s value \"%s\" in \"%s\"", + context, name, names); + } + break; + } + if (lookup(name, np->name) == 0) { + if (msg_verbose) + msg_info("%s: %s", myname, name); + result |= np->mask; + break; + } + } + } + + myfree(saved_names); + return (result); +} + +/* str_long_name_mask_opt - mask to string */ + +const char *str_long_name_mask_opt(VSTRING *buf, const char *context, + const LONG_NAME_MASK * table, + long mask, int flags) +{ + const char *myname = "name_mask"; + ssize_t len; + static VSTRING *my_buf = 0; + int delim = (flags & NAME_MASK_COMMA ? ',' : + (flags & NAME_MASK_PIPE ? '|' : ' ')); + const LONG_NAME_MASK *np; + + if ((flags & STR_NAME_MASK_REQUIRED) == 0) + msg_panic("%s: missing NAME_MASK_NUMBER/FATAL/RETURN/WARN/IGNORE flag", + myname); + + if (buf == 0) { + if (my_buf == 0) + my_buf = vstring_alloc(1); + buf = my_buf; + } + VSTRING_RESET(buf); + + for (np = table; mask != 0; np++) { + if (np->name == 0) { + if (flags & NAME_MASK_NUMBER) { + vstring_sprintf_append(buf, "0x%lx%c", mask, delim); + } else if (flags & NAME_MASK_FATAL) { + msg_fatal("%s: unknown %s bit in mask: 0x%lx", + myname, context, mask); + } else if (flags & NAME_MASK_RETURN) { + msg_warn("%s: unknown %s bit in mask: 0x%lx", + myname, context, mask); + return (0); + } else if (flags & NAME_MASK_WARN) { + msg_warn("%s: unknown %s bit in mask: 0x%lx", + myname, context, mask); + } + break; + } + if (mask & np->mask) { + mask &= ~np->mask; + vstring_sprintf_append(buf, "%s%c", np->name, delim); + } + } + if ((len = VSTRING_LEN(buf)) > 0) + vstring_truncate(buf, len - 1); + VSTRING_TERMINATE(buf); + + return (STR(buf)); +} + +/* hex_to_ulong - 0x... to unsigned long or smaller */ + +static int hex_to_ulong(char *value, unsigned long mask, unsigned long *ulp) +{ + unsigned long result; + char *cp; + + if (strncasecmp(value, "0x", 2) != 0) + return (0); + + /* + * Check for valid hex number. Since the value starts with 0x, strtoul() + * will not allow a negative sign before the first nibble. So we don't + * need to worry about explicit +/- signs. + */ + errno = 0; + result = strtoul(value, &cp, 16); + if (*cp != '\0' || errno == ERANGE) + return (0); + + *ulp = (result & mask); + return (*ulp == result); +} + +#ifdef TEST + + /* + * Stand-alone test program. + */ +#include +#include +#include + +int main(int argc, char **argv) +{ + static const NAME_MASK demo_table[] = { + "zero", 1 << 0, + "one", 1 << 1, + "two", 1 << 2, + "three", 1 << 3, + 0, 0, + }; + static const NAME_MASK feature_table[] = { + "DEFAULT", NAME_MASK_DEFAULT, + "FATAL", NAME_MASK_FATAL, + "ANY_CASE", NAME_MASK_ANY_CASE, + "RETURN", NAME_MASK_RETURN, + "COMMA", NAME_MASK_COMMA, + "PIPE", NAME_MASK_PIPE, + "NUMBER", NAME_MASK_NUMBER, + "WARN", NAME_MASK_WARN, + "IGNORE", NAME_MASK_IGNORE, + 0, + }; + int in_feature_mask; + int out_feature_mask; + int demo_mask; + const char *demo_str; + VSTRING *out_buf = vstring_alloc(1); + VSTRING *in_buf = vstring_alloc(1); + + if (argc != 3) + msg_fatal("usage: %s in-feature-mask out-feature-mask", argv[0]); + in_feature_mask = name_mask(argv[1], feature_table, argv[1]); + out_feature_mask = name_mask(argv[2], feature_table, argv[2]); + while (vstring_get_nonl(in_buf, VSTREAM_IN) != VSTREAM_EOF) { + demo_mask = name_mask_opt("name", demo_table, + STR(in_buf), in_feature_mask); + demo_str = str_name_mask_opt(out_buf, "mask", demo_table, + demo_mask, out_feature_mask); + vstream_printf("%s -> 0x%x -> %s\n", + STR(in_buf), demo_mask, + demo_str ? demo_str : "(null)"); + vstream_fflush(VSTREAM_OUT); + } + vstring_free(in_buf); + vstring_free(out_buf); + exit(0); +} + +#endif diff --git a/src/util/name_mask.h b/src/util/name_mask.h new file mode 100644 index 0000000..05e45ec --- /dev/null +++ b/src/util/name_mask.h @@ -0,0 +1,86 @@ +#ifndef _NAME_MASK_H_INCLUDED_ +#define _NAME_MASK_H_INCLUDED_ + +/*++ +/* NAME +/* name_mask 3h +/* SUMMARY +/* map names to bit mask +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +typedef struct { + const char *name; + int mask; +} NAME_MASK; + +#define NAME_MASK_FATAL (1<<0) +#define NAME_MASK_ANY_CASE (1<<1) +#define NAME_MASK_RETURN (1<<2) +#define NAME_MASK_COMMA (1<<3) +#define NAME_MASK_PIPE (1<<4) +#define NAME_MASK_NUMBER (1<<5) +#define NAME_MASK_WARN (1<<6) +#define NAME_MASK_IGNORE (1<<7) + +#define NAME_MASK_REQUIRED \ + (NAME_MASK_FATAL | NAME_MASK_RETURN | NAME_MASK_WARN | NAME_MASK_IGNORE) +#define STR_NAME_MASK_REQUIRED (NAME_MASK_REQUIRED | NAME_MASK_NUMBER) + +#define NAME_MASK_MATCH_REQ NAME_MASK_FATAL + +#define NAME_MASK_NONE 0 +#define NAME_MASK_DEFAULT (NAME_MASK_FATAL) +#define NAME_MASK_DEFAULT_DELIM ", \t\r\n" + +#define name_mask_opt(tag, table, str, flags) \ + name_mask_delim_opt((tag), (table), (str), \ + NAME_MASK_DEFAULT_DELIM, (flags)) +#define name_mask(tag, table, str) \ + name_mask_opt((tag), (table), (str), NAME_MASK_DEFAULT) +#define str_name_mask(tag, table, mask) \ + str_name_mask_opt(((VSTRING *) 0), (tag), (table), (mask), NAME_MASK_DEFAULT) + +extern int name_mask_delim_opt(const char *, const NAME_MASK *, const char *, const char *, int); +extern const char *str_name_mask_opt(VSTRING *, const char *, const NAME_MASK *, int, int); + + /* + * "long" API + */ +typedef struct { + const char *name; + long mask; +} LONG_NAME_MASK; + +#define long_name_mask_opt(tag, table, str, flags) \ + long_name_mask_delim_opt((tag), (table), (str), NAME_MASK_DEFAULT_DELIM, (flags)) +#define long_name_mask(tag, table, str) \ + long_name_mask_opt((tag), (table), (str), NAME_MASK_DEFAULT) +#define str_long_name_mask(tag, table, mask) \ + str_long_name_mask_opt(((VSTRING *) 0), (tag), (table), (mask), NAME_MASK_DEFAULT) + +extern long long_name_mask_delim_opt(const char *, const LONG_NAME_MASK *, const char *, const char *, int); +extern const char *str_long_name_mask_opt(VSTRING *, const char *, const LONG_NAME_MASK *, long, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/name_mask.in b/src/util/name_mask.in new file mode 100644 index 0000000..d4c9b16 --- /dev/null +++ b/src/util/name_mask.in @@ -0,0 +1,9 @@ +zero +one +two +three +four +zero one two three four +0xff +0xffffffff +0xffffffffffffffff diff --git a/src/util/name_mask.ref0 b/src/util/name_mask.ref0 new file mode 100644 index 0000000..603dc26 --- /dev/null +++ b/src/util/name_mask.ref0 @@ -0,0 +1,9 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +four -> 0x0 -> +zero one two three four -> 0xf -> zero one two three +0xff -> 0x0 -> +0xffffffff -> 0x0 -> +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref1 b/src/util/name_mask.ref1 new file mode 100644 index 0000000..1ae0fca --- /dev/null +++ b/src/util/name_mask.ref1 @@ -0,0 +1,12 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero one two three +0xff -> 0xff -> zero one two three 0xf0 +0xffffffff -> 0xffffffff -> zero one two three 0xfffffff0 +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref2 b/src/util/name_mask.ref2 new file mode 100644 index 0000000..1ac716a --- /dev/null +++ b/src/util/name_mask.ref2 @@ -0,0 +1,12 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0x0 -> +0xff -> 0xff -> zero one two three 0xf0 +0xffffffff -> 0xffffffff -> zero one two three 0xfffffff0 +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref3 b/src/util/name_mask.ref3 new file mode 100644 index 0000000..82e8850 --- /dev/null +++ b/src/util/name_mask.ref3 @@ -0,0 +1,14 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero one two three +unknown: warning: unknown name value "0xff" in "0xff" +0xff -> 0x0 -> +unknown: warning: unknown name value "0xffffffff" in "0xffffffff" +0xffffffff -> 0x0 -> +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref4 b/src/util/name_mask.ref4 new file mode 100644 index 0000000..4ec996b --- /dev/null +++ b/src/util/name_mask.ref4 @@ -0,0 +1,14 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0x0 -> +unknown: warning: unknown name value "0xff" in "0xff" +0xff -> 0x0 -> +unknown: warning: unknown name value "0xffffffff" in "0xffffffff" +0xffffffff -> 0x0 -> +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref5 b/src/util/name_mask.ref5 new file mode 100644 index 0000000..68c1c30 --- /dev/null +++ b/src/util/name_mask.ref5 @@ -0,0 +1,14 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero one two three +unknown: warning: name_mask: unknown mask bit in mask: 0xf0 +0xff -> 0xff -> (null) +unknown: warning: name_mask: unknown mask bit in mask: 0xfffffff0 +0xffffffff -> 0xffffffff -> (null) +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref6 b/src/util/name_mask.ref6 new file mode 100644 index 0000000..c86a532 --- /dev/null +++ b/src/util/name_mask.ref6 @@ -0,0 +1,14 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero one two three +unknown: warning: name_mask: unknown mask bit in mask: 0xf0 +0xff -> 0xff -> zero one two three +unknown: warning: name_mask: unknown mask bit in mask: 0xfffffff0 +0xffffffff -> 0xffffffff -> zero one two three +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref7 b/src/util/name_mask.ref7 new file mode 100644 index 0000000..156d7aa --- /dev/null +++ b/src/util/name_mask.ref7 @@ -0,0 +1,12 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero one two three +0xff -> 0xff -> zero one two three +0xffffffff -> 0xffffffff -> zero one two three +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref8 b/src/util/name_mask.ref8 new file mode 100644 index 0000000..7463fb7 --- /dev/null +++ b/src/util/name_mask.ref8 @@ -0,0 +1,12 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero,one,two,three +0xff -> 0xff -> zero,one,two,three,0xf0 +0xffffffff -> 0xffffffff -> zero,one,two,three,0xfffffff0 +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/name_mask.ref9 b/src/util/name_mask.ref9 new file mode 100644 index 0000000..e50a73a --- /dev/null +++ b/src/util/name_mask.ref9 @@ -0,0 +1,12 @@ +zero -> 0x1 -> zero +one -> 0x2 -> one +two -> 0x4 -> two +three -> 0x8 -> three +unknown: warning: unknown name value "four" in "four" +four -> 0x0 -> +unknown: warning: unknown name value "four" in "zero one two three four" +zero one two three four -> 0xf -> zero|one|two|three +0xff -> 0xff -> zero|one|two|three|0xf0 +0xffffffff -> 0xffffffff -> zero|one|two|three|0xfffffff0 +unknown: warning: unknown name value "0xffffffffffffffff" in "0xffffffffffffffff" +0xffffffffffffffff -> 0x0 -> diff --git a/src/util/nbbio.c b/src/util/nbbio.c new file mode 100644 index 0000000..e9ccc38 --- /dev/null +++ b/src/util/nbbio.c @@ -0,0 +1,382 @@ +/*++ +/* NAME +/* nbbio 3 +/* SUMMARY +/* non-blocking buffered I/O +/* SYNOPSIS +/* #include +/* +/* NBBIO *nbbio_create(fd, bufsize, label, action, context) +/* int fd; +/* ssize_t bufsize; +/* const char *label; +/* void (*action)(int event, void *context); +/* char *context; +/* +/* void nbbio_free(np) +/* NBBIO *np; +/* +/* void nbbio_enable_read(np, timeout) +/* NBBIO *np; +/* int timeout; +/* +/* void nbbio_enable_write(np, timeout) +/* NBBIO *np; +/* int timeout; +/* +/* void nbbio_disable_readwrite(np) +/* NBBIO *np; +/* +/* void nbbio_slumber(np, timeout) +/* NBBIO *np; +/* int timeout; +/* +/* int NBBIO_ACTIVE_FLAGS(np) +/* NBBIO *np; +/* +/* int NBBIO_ERROR_FLAGS(np) +/* NBBIO *np; +/* +/* const ssize_t NBBIO_BUFSIZE(np) +/* NBBIO *np; +/* +/* ssize_t NBBIO_READ_PEND(np) +/* NBBIO *np; +/* +/* char *NBBIO_READ_BUF(np) +/* NBBIO *np; +/* +/* const ssize_t NBBIO_WRITE_PEND(np) +/* NBBIO *np; +/* +/* char *NBBIO_WRITE_BUF(np) +/* NBBIO *np; +/* DESCRIPTION +/* This module implements low-level support for event-driven +/* I/O on a full-duplex stream. Read/write events are handled +/* by pseudothreads that run under control by the events(5) +/* module. After each I/O operation, the application is +/* notified via a call-back routine. +/* +/* It is up to the call-back routine to turn on/off read/write +/* events as appropriate. It is an error to leave read events +/* enabled for a buffer that is full, or to leave write events +/* enabled for a buffer that is empty. +/* +/* nbbio_create() creates a pair of buffers of the named size +/* for the named stream. The label specifies the purpose of +/* the stream, and is used for diagnostic messages. The +/* nbbio(3) event handler invokes the application call-back +/* routine with the current event type (EVENT_READ etc.) and +/* with the application-specified context. +/* +/* nbbio_free() terminates any pseudothreads associated with +/* the named buffer pair, closes the stream, and destroys the +/* buffer pair. +/* +/* nbbio_enable_read() enables a read pseudothread (if one +/* does not already exist) for the named buffer pair, and +/* (re)starts the buffer pair's timer. It is an error to enable +/* a read pseudothread while the read buffer is full, or while +/* a write pseudothread is still enabled. +/* +/* nbbio_enable_write() enables a write pseudothread (if one +/* does not already exist) for the named buffer pair, and +/* (re)starts the buffer pair's timer. It is an error to enable +/* a write pseudothread while the write buffer is empty, or +/* while a read pseudothread is still enabled. +/* +/* nbbio_disable_readwrite() disables any read/write pseudothreads +/* for the named buffer pair, including timeouts. To ensure +/* buffer liveness, use nbbio_slumber() instead of +/* nbbio_disable_readwrite(). It is no error to call this +/* function while no read/write pseudothread is enabled. +/* +/* nbbio_slumber() disables any read/write pseudothreads for +/* the named buffer pair, but keeps the timer active to ensure +/* buffer liveness. It is no error to call this function while +/* no read/write pseudothread is enabled. +/* +/* NBBIO_ERROR_FLAGS() returns the error flags for the named buffer +/* pair: zero or more of NBBIO_FLAG_EOF (read EOF), NBBIO_FLAG_ERROR +/* (read/write error) or NBBIO_FLAG_TIMEOUT (time limit +/* exceeded). +/* +/* NBBIO_ACTIVE_FLAGS() returns the pseudothread flags for the +/* named buffer pair: NBBIO_FLAG_READ (read pseudothread is +/* active), NBBIO_FLAG_WRITE (write pseudothread is active), +/* or zero (no pseudothread is active). +/* +/* NBBIO_WRITE_PEND() and NBBIO_WRITE_BUF() evaluate to the +/* number of to-be-written bytes and the write buffer for the +/* named buffer pair. NBBIO_WRITE_PEND() must be updated by +/* the application code that fills the write buffer; no more +/* than NBBIO_BUFSIZE() bytes may be filled. +/* +/* NBBIO_READ_PEND() and NBBIO_READ_BUF() evaluate to the +/* number of unread bytes and the read buffer for the named +/* buffer pair. NBBIO_READ_PEND() and NBBIO_READ_BUF() must +/* be updated by the application code that drains the read +/* buffer. +/* SEE ALSO +/* events(3) event manager +/* DIAGNOSTICS +/* Panic: interface violation. +/* +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include +#include /* memmove() */ + + /* + * Utility library. + */ +#include +#include +#include +#include + +/* nbbio_event - non-blocking event handler */ + +static void nbbio_event(int event, void *context) +{ + const char *myname = "nbbio_event"; + NBBIO *np = (NBBIO *) context; + ssize_t count; + + switch (event) { + + /* + * Read data into the read buffer. Leave it up to the application to + * drain the buffer until it is empty. + */ + case EVENT_READ: + if (np->read_pend == np->bufsize) + msg_panic("%s: socket fd=%d: read buffer is full", + myname, np->fd); + if (np->read_pend < 0 || np->read_pend > np->bufsize) + msg_panic("%s: socket fd=%d: bad pending read count %ld", + myname, np->fd, (long) np->read_pend); + count = read(np->fd, np->read_buf + np->read_pend, + np->bufsize - np->read_pend); + if (count > 0) { + np->read_pend += count; + if (msg_verbose) + msg_info("%s: read %ld on %s fd=%d", + myname, (long) count, np->label, np->fd); + } else if (count == 0) { + np->flags |= NBBIO_FLAG_EOF; + if (msg_verbose) + msg_info("%s: read EOF on %s fd=%d", + myname, np->label, np->fd); + } else { + if (errno == EAGAIN) + msg_warn("%s: read() returns EAGAIN on readable descriptor", + myname); + np->flags |= NBBIO_FLAG_ERROR; + if (msg_verbose) + msg_info("%s: read %s fd=%d: %m", myname, np->label, np->fd); + } + break; + + /* + * Drain data from the output buffer. Notify the application + * whenever some bytes are written. + * + * XXX Enforce a total time limit to ensure liveness when a hostile + * receiver sets a very small TCP window size. + */ + case EVENT_WRITE: + if (np->write_pend == 0) + msg_panic("%s: socket fd=%d: empty write buffer", myname, np->fd); + if (np->write_pend < 0 || np->write_pend > np->bufsize) + msg_panic("%s: socket fd=%d: bad pending write count %ld", + myname, np->fd, (long) np->write_pend); + count = write(np->fd, np->write_buf, np->write_pend); + if (count > 0) { + np->write_pend -= count; + if (np->write_pend > 0) + memmove(np->write_buf, np->write_buf + count, np->write_pend); + } else { + if (errno == EAGAIN) + msg_warn("%s: write() returns EAGAIN on writable descriptor", + myname); + np->flags |= NBBIO_FLAG_ERROR; + if (msg_verbose) + msg_info("%s: write %s fd=%d: %m", myname, np->label, np->fd); + } + break; + + /* + * Something bad happened. + */ + case EVENT_XCPT: + np->flags |= NBBIO_FLAG_ERROR; + if (msg_verbose) + msg_info("%s: error on %s fd=%d: %m", myname, np->label, np->fd); + break; + + /* + * Something good didn't happen. + */ + case EVENT_TIME: + np->flags |= NBBIO_FLAG_TIMEOUT; + if (msg_verbose) + msg_info("%s: %s timeout on %s fd=%d", + myname, NBBIO_OP_NAME(np), np->label, np->fd); + break; + + default: + msg_panic("%s: unknown event %d", myname, event); + } + + /* + * Application notification. The application will check for any error + * flags, copy application data from or to our buffer pair, and decide + * what I/O happens next. + */ + np->action(event, np->context); +} + +/* nbbio_enable_read - enable reading from socket into buffer */ + +void nbbio_enable_read(NBBIO *np, int timeout) +{ + const char *myname = "nbbio_enable_read"; + + /* + * Sanity checks. + */ + if (np->flags & (NBBIO_MASK_ACTIVE & ~NBBIO_FLAG_READ)) + msg_panic("%s: socket fd=%d is enabled for %s", + myname, np->fd, NBBIO_OP_NAME(np)); + if (timeout <= 0) + msg_panic("%s: socket fd=%d: bad timeout %d", + myname, np->fd, timeout); + if (np->read_pend >= np->bufsize) + msg_panic("%s: socket fd=%d: read buffer is full", + myname, np->fd); + + /* + * Enable events. + */ + if ((np->flags & NBBIO_FLAG_READ) == 0) { + event_enable_read(np->fd, nbbio_event, (void *) np); + np->flags |= NBBIO_FLAG_READ; + } + event_request_timer(nbbio_event, (void *) np, timeout); +} + +/* nbbio_enable_write - enable writing from buffer to socket */ + +void nbbio_enable_write(NBBIO *np, int timeout) +{ + const char *myname = "nbbio_enable_write"; + + /* + * Sanity checks. + */ + if (np->flags & (NBBIO_MASK_ACTIVE & ~NBBIO_FLAG_WRITE)) + msg_panic("%s: socket fd=%d is enabled for %s", + myname, np->fd, NBBIO_OP_NAME(np)); + if (timeout <= 0) + msg_panic("%s: socket fd=%d: bad timeout %d", + myname, np->fd, timeout); + if (np->write_pend <= 0) + msg_panic("%s: socket fd=%d: empty write buffer", + myname, np->fd); + + /* + * Enable events. + */ + if ((np->flags & NBBIO_FLAG_WRITE) == 0) { + event_enable_write(np->fd, nbbio_event, (void *) np); + np->flags |= NBBIO_FLAG_WRITE; + } + event_request_timer(nbbio_event, (void *) np, timeout); +} + +/* nbbio_disable_readwrite - disable read/write/timer events */ + +void nbbio_disable_readwrite(NBBIO *np) +{ + np->flags &= ~NBBIO_MASK_ACTIVE; + event_disable_readwrite(np->fd); + event_cancel_timer(nbbio_event, (void *) np); +} + +/* nbbio_slumber - disable read/write events, keep timer */ + +void nbbio_slumber(NBBIO *np, int timeout) +{ + np->flags &= ~NBBIO_MASK_ACTIVE; + event_disable_readwrite(np->fd); + event_request_timer(nbbio_event, (void *) np, timeout); +} + +/* nbbio_create - create socket buffer */ + +NBBIO *nbbio_create(int fd, ssize_t bufsize, const char *label, + NBBIO_ACTION action, void *context) +{ + NBBIO *np; + + /* + * Sanity checks. + */ + if (fd < 0) + msg_panic("nbbio_create: bad file descriptor: %d", fd); + if (bufsize <= 0) + msg_panic("nbbio_create: bad buffer size: %ld", (long) bufsize); + + /* + * Create a new buffer pair. + */ + np = (NBBIO *) mymalloc(sizeof(*np)); + np->fd = fd; + np->bufsize = bufsize; + np->label = mystrdup(label); + np->action = action; + np->context = context; + np->flags = 0; + + np->read_buf = mymalloc(bufsize); + np->read_pend = 0; + + np->write_buf = mymalloc(bufsize); + np->write_pend = 0; + + return (np); +} + +/* nbbio_free - destroy socket buffer */ + +void nbbio_free(NBBIO *np) +{ + nbbio_disable_readwrite(np); + (void) close(np->fd); + myfree(np->label); + myfree(np->read_buf); + myfree(np->write_buf); + myfree((void *) np); +} diff --git a/src/util/nbbio.h b/src/util/nbbio.h new file mode 100644 index 0000000..d1d2b0e --- /dev/null +++ b/src/util/nbbio.h @@ -0,0 +1,85 @@ +#ifndef _NBBIO_H_INCLUDED_ +#define _NBBIO_H_INCLUDED_ + +/*++ +/* NAME +/* nbbio 3h +/* SUMMARY +/* non-blocking buffered I/O +/* SYNOPSIS +/* #include "nbbio.h" +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include /* Needed for EVENT_READ etc. */ + + /* + * External interface. All structure members are private. + */ +typedef void (*NBBIO_ACTION) (int, void *); + +typedef struct { + int fd; /* socket file descriptor */ + ssize_t bufsize; /* read/write buffer size */ + char *label; /* diagnostics */ + NBBIO_ACTION action; /* call-back routine */ + void *context; /* call-back context */ + int flags; /* buffer-pair status */ + + char *read_buf; /* start of buffer */ + ssize_t read_pend; /* nr of unread bytes */ + + char *write_buf; /* start of buffer */ + ssize_t write_pend; /* nr of unwritten bytes */ +} NBBIO; + +#define NBBIO_FLAG_READ (1<<0) +#define NBBIO_FLAG_WRITE (1<<1) +#define NBBIO_FLAG_EOF (1<<2) +#define NBBIO_FLAG_ERROR (1<<3) +#define NBBIO_FLAG_TIMEOUT (1<<4) + +#define NBBIO_OP_NAME(np) \ + (((np)->flags & NBBIO_FLAG_READ) ? "read" : \ + ((np)->flags & NBBIO_FLAG_WRITE) ? "write" : \ + "unknown") + +#define NBBIO_MASK_ACTIVE \ + (NBBIO_FLAG_READ | NBBIO_FLAG_WRITE) + +#define NBBIO_MASK_ERROR \ + (NBBIO_FLAG_EOF | NBBIO_FLAG_ERROR | NBBIO_FLAG_TIMEOUT) + +#define NBBIO_BUFSIZE(np) (((np)->bufsize) + 0) /* Read-only */ + +#define NBBIO_READ_PEND(np) ((np)->read_pend) +#define NBBIO_READ_BUF(np) ((np)->read_buf + 0) /* Read-only */ + +#define NBBIO_WRITE_PEND(np) ((np)->write_pend) +#define NBBIO_WRITE_BUF(np) ((np)->write_buf + 0) /* Read-only */ + +#define NBBIO_ACTIVE_FLAGS(np) ((np)->flags & NBBIO_MASK_ACTIVE) +#define NBBIO_ERROR_FLAGS(np) ((np)->flags & NBBIO_MASK_ERROR) + +extern NBBIO *nbbio_create(int, ssize_t, const char *, NBBIO_ACTION, void *); +extern void nbbio_free(NBBIO *); +extern void nbbio_enable_read(NBBIO *, int); +extern void nbbio_enable_write(NBBIO *, int); +extern void nbbio_disable_readwrite(NBBIO *); +extern void nbbio_slumber(NBBIO *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/netstring.c b/src/util/netstring.c new file mode 100644 index 0000000..0edd80e --- /dev/null +++ b/src/util/netstring.c @@ -0,0 +1,498 @@ +/*++ +/* NAME +/* netstring 3 +/* SUMMARY +/* netstring stream I/O support +/* SYNOPSIS +/* #include +/* +/* void netstring_setup(stream, timeout) +/* VSTREAM *stream; +/* int timeout; +/* +/* void netstring_except(stream, exception) +/* VSTREAM *stream; +/* int exception; +/* +/* const char *netstring_strerror(err) +/* int err; +/* +/* VSTRING *netstring_get(stream, buf, limit) +/* VSTREAM *stream; +/* VSTRING *buf; +/* ssize_t limit; +/* +/* void netstring_put(stream, data, len) +/* VSTREAM *stream; +/* const char *data; +/* ssize_t len; +/* +/* void netstring_put_multi(stream, data, len, data, len, ..., 0) +/* VSTREAM *stream; +/* const char *data; +/* ssize_t len; +/* +/* void NETSTRING_PUT_BUF(stream, buf) +/* VSTREAM *stream; +/* VSTRING *buf; +/* +/* void netstring_fflush(stream) +/* VSTREAM *stream; +/* +/* VSTRING *netstring_memcpy(buf, data, len) +/* VSTRING *buf; +/* const char *data; +/* ssize_t len; +/* +/* VSTRING *netstring_memcat(buf, data, len) +/* VSTRING *buf; +/* const char *src; +/* ssize_t len; +/* AUXILIARY ROUTINES +/* ssize_t netstring_get_length(stream) +/* VSTREAM *stream; +/* +/* VSTRING *netstring_get_data(stream, buf, len) +/* VSTREAM *stream; +/* VSTRING *buf; +/* ssize_t len; +/* +/* void netstring_get_terminator(stream) +/* VSTREAM *stream; +/* DESCRIPTION +/* This module reads and writes netstrings with error detection: +/* timeouts, unexpected end-of-file, or format errors. Netstring +/* is a data format designed by Daniel Bernstein. +/* +/* netstring_setup() arranges for a time limit on the netstring +/* read and write operations described below. +/* This routine alters the behavior of streams as follows: +/* .IP \(bu +/* The read/write timeout is set to the specified value. +/* .IP \(bu +/* The stream is configured to enable exception handling. +/* .PP +/* netstring_except() raises the specified exception on the +/* named stream. See the DIAGNOSTICS section below. +/* +/* netstring_strerror() converts an exception number to string. +/* +/* netstring_get() reads a netstring from the specified stream +/* and extracts its content. The limit specifies a maximal size. +/* Specify zero to disable the size limit. The result is not null +/* terminated. The result value is the buf argument. +/* +/* netstring_put() encapsulates the specified string as a netstring +/* and sends the result to the specified stream. +/* The stream output buffer is not flushed. +/* +/* netstring_put_multi() encapsulates the content of multiple strings +/* as one netstring and sends the result to the specified stream. The +/* argument list must be terminated with a null data pointer. +/* The stream output buffer is not flushed. +/* +/* NETSTRING_PUT_BUF() is a macro that provides a VSTRING-based +/* wrapper for the netstring_put() routine. +/* +/* netstring_fflush() flushes the output buffer of the specified +/* stream and handles any errors. +/* +/* netstring_memcpy() encapsulates the specified data as a netstring +/* and copies the result over the specified buffer. The result +/* value is the buffer. +/* +/* netstring_memcat() encapsulates the specified data as a netstring +/* and appends the result to the specified buffer. The result +/* value is the buffer. +/* +/* The following routines provide low-level access to a netstring +/* stream. +/* +/* netstring_get_length() reads a length field from the specified +/* stream, and absorbs the netstring length field terminator. +/* +/* netstring_get_data() reads the specified number of bytes from the +/* specified stream into the specified buffer, and absorbs the +/* netstring terminator. The result value is the buf argument. +/* +/* netstring_get_terminator() reads the netstring terminator from +/* the specified stream. +/* DIAGNOSTICS +/* .fi +/* .ad +/* In case of error, a vstream_longjmp() call is performed to the +/* caller-provided context specified with vstream_setjmp(). +/* Error codes passed along with vstream_longjmp() are: +/* .IP NETSTRING_ERR_EOF +/* An I/O error happened, or the peer has disconnected unexpectedly. +/* .IP NETSTRING_ERR_TIME +/* The time limit specified to netstring_setup() was exceeded. +/* .IP NETSTRING_ERR_FORMAT +/* The input contains an unexpected character value. +/* .IP NETSTRING_ERR_SIZE +/* The input is larger than acceptable. +/* BUGS +/* The timeout deadline affects all I/O on the named stream, not +/* just the I/O done on behalf of this module. +/* +/* The timeout deadline overwrites any previously set up state on +/* the named stream. +/* +/* netstrings are not null terminated, which makes printing them +/* a bit awkward. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* SEE ALSO +/* http://cr.yp.to/proto/netstrings.txt, netstring definition +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* netstring_setup - initialize netstring stream */ + +void netstring_setup(VSTREAM *stream, int timeout) +{ + vstream_control(stream, + CA_VSTREAM_CTL_TIMEOUT(timeout), + CA_VSTREAM_CTL_EXCEPT, + CA_VSTREAM_CTL_END); +} + +/* netstring_except - process netstring stream exception */ + +void netstring_except(VSTREAM *stream, int exception) +{ + vstream_longjmp(stream, exception); +} + +/* netstring_get_length - read netstring length + terminator */ + +ssize_t netstring_get_length(VSTREAM *stream) +{ + const char *myname = "netstring_get_length"; + ssize_t len = 0; + int ch; + int digit; + + for (;;) { + switch (ch = VSTREAM_GETC(stream)) { + case VSTREAM_EOF: + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + case ':': + if (msg_verbose > 1) + msg_info("%s: read netstring length %ld", myname, (long) len); + return (len); + default: + if (!ISDIGIT(ch)) + netstring_except(stream, NETSTRING_ERR_FORMAT); + digit = ch - '0'; + if (len > SSIZE_T_MAX / 10 + || (len *= 10) > SSIZE_T_MAX - digit) + netstring_except(stream, NETSTRING_ERR_SIZE); + len += digit; + break; + } + } +} + +/* netstring_get_data - read netstring payload + terminator */ + +VSTRING *netstring_get_data(VSTREAM *stream, VSTRING *buf, ssize_t len) +{ + const char *myname = "netstring_get_data"; + + /* + * Read the payload and absorb the terminator. + */ + if (vstream_fread_buf(stream, buf, len) != len) + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + if (msg_verbose > 1) + msg_info("%s: read netstring data %.*s", + myname, (int) (len < 30 ? len : 30), STR(buf)); + netstring_get_terminator(stream); + + /* + * Return the buffer. + */ + return (buf); +} + +/* netstring_get_terminator - absorb netstring terminator */ + +void netstring_get_terminator(VSTREAM *stream) +{ + if (VSTREAM_GETC(stream) != ',') + netstring_except(stream, NETSTRING_ERR_FORMAT); +} + +/* netstring_get - read string from netstring stream */ + +VSTRING *netstring_get(VSTREAM *stream, VSTRING *buf, ssize_t limit) +{ + ssize_t len; + + len = netstring_get_length(stream); + if (ENFORCING_SIZE_LIMIT(limit) && len > limit) + netstring_except(stream, NETSTRING_ERR_SIZE); + netstring_get_data(stream, buf, len); + return (buf); +} + +/* netstring_put - send string as netstring */ + +void netstring_put(VSTREAM *stream, const char *data, ssize_t len) +{ + const char *myname = "netstring_put"; + + if (msg_verbose > 1) + msg_info("%s: write netstring len %ld data %.*s", + myname, (long) len, (int) (len < 30 ? len : 30), data); + vstream_fprintf(stream, "%ld:", (long) len); + vstream_fwrite(stream, data, len); + VSTREAM_PUTC(',', stream); +} + +/* netstring_put_multi - send multiple strings as one netstring */ + +void netstring_put_multi(VSTREAM *stream,...) +{ + const char *myname = "netstring_put_multi"; + ssize_t total; + char *data; + ssize_t data_len; + va_list ap; + va_list ap2; + + /* + * Initialize argument lists. + */ + va_start(ap, stream); + VA_COPY(ap2, ap); + + /* + * Figure out the total result size. + */ + for (total = 0; (data = va_arg(ap, char *)) != 0; total += data_len) + if ((data_len = va_arg(ap, ssize_t)) < 0) + msg_panic("%s: bad data length %ld", myname, (long) data_len); + va_end(ap); + if (total < 0) + msg_panic("%s: bad total length %ld", myname, (long) total); + if (msg_verbose > 1) + msg_info("%s: write total length %ld", myname, (long) total); + + /* + * Send the length, content and terminator. + */ + vstream_fprintf(stream, "%ld:", (long) total); + while ((data = va_arg(ap2, char *)) != 0) { + data_len = va_arg(ap2, ssize_t); + if (msg_verbose > 1) + msg_info("%s: write netstring len %ld data %.*s", + myname, (long) data_len, + (int) (data_len < 30 ? data_len : 30), data); + if (vstream_fwrite(stream, data, data_len) != data_len) + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + } + va_end(ap2); + vstream_fwrite(stream, ",", 1); +} + +/* netstring_fflush - flush netstring stream */ + +void netstring_fflush(VSTREAM *stream) +{ + if (vstream_fflush(stream) == VSTREAM_EOF) + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); +} + +/* netstring_memcpy - copy data as in-memory netstring */ + +VSTRING *netstring_memcpy(VSTRING *buf, const char *src, ssize_t len) +{ + vstring_sprintf(buf, "%ld:", (long) len); + vstring_memcat(buf, src, len); + VSTRING_ADDCH(buf, ','); + return (buf); +} + +/* netstring_memcat - append data as in-memory netstring */ + +VSTRING *netstring_memcat(VSTRING *buf, const char *src, ssize_t len) +{ + vstring_sprintf_append(buf, "%ld:", (long) len); + vstring_memcat(buf, src, len); + VSTRING_ADDCH(buf, ','); + return (buf); +} + +/* netstring_strerror - convert error number to string */ + +const char *netstring_strerror(int err) +{ + switch (err) { + case NETSTRING_ERR_EOF: + return ("unexpected disconnect"); + case NETSTRING_ERR_TIME: + return ("time limit exceeded"); + case NETSTRING_ERR_FORMAT: + return ("input format error"); + case NETSTRING_ERR_SIZE: + return ("input exceeds size limit"); + default: + return ("unknown netstring error"); + } +} + + /* + * Proof-of-concept netstring encoder/decoder. + * + * Usage: netstring command... + * + * Run the command as a child process. Then, convert between plain strings on + * our own stdin/stdout, and netstrings on the child program's stdin/stdout. + * + * Example (socketmap test server): netstring nc -l 9999 + */ +#ifdef TEST +#include +#include +#include + +static VSTRING *stdin_read_buf; /* stdin line buffer */ +static VSTRING *child_read_buf; /* child read buffer */ +static VSTREAM *child_stream; /* child stream (full-duplex) */ + +/* stdin_read_event - line-oriented event handler */ + +static void stdin_read_event(int event, void *context) +{ + int ch; + + /* + * Send a netstring to the child when we have accumulated an entire line + * of input. + * + * Note: the first VSTREAM_GETCHAR() call implicitly fills the VSTREAM + * buffer. We must drain the entire VSTREAM buffer before requesting the + * next read(2) event. + */ + do { + ch = VSTREAM_GETCHAR(); + switch (ch) { + default: + VSTRING_ADDCH(stdin_read_buf, ch); + break; + case '\n': + NETSTRING_PUT_BUF(child_stream, stdin_read_buf); + vstream_fflush(child_stream); + VSTRING_RESET(stdin_read_buf); + break; + case VSTREAM_EOF: + /* Better: wait for child to terminate. */ + sleep(1); + exit(0); + } + } while (vstream_peek(VSTREAM_IN) > 0); +} + +/* child_read_event - netstring-oriented event handler */ + +static void child_read_event(int event, void *context) +{ + + /* + * Read an entire netstring from the child and send the result to stdout. + * + * This is a simplistic implementation that assumes a server will not + * trickle its data. + * + * Note: the first netstring_get() call implicitly fills the VSTREAM buffer. + * We must drain the entire VSTREAM buffer before requesting the next + * read(2) event. + */ + do { + netstring_get(child_stream, child_read_buf, 10000); + vstream_fwrite(VSTREAM_OUT, STR(child_read_buf), LEN(child_read_buf)); + VSTREAM_PUTC('\n', VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + } while (vstream_peek(child_stream) > 0); +} + +int main(int argc, char **argv) +{ + int err; + + /* + * Sanity check. + */ + if (argv[1] == 0) + msg_fatal("usage: %s command...", argv[0]); + + /* + * Run the specified command as a child process with stdin and stdout + * connected to us. + */ + child_stream = vstream_popen(O_RDWR, CA_VSTREAM_POPEN_ARGV(argv + 1), + CA_VSTREAM_POPEN_END); + vstream_control(child_stream, CA_VSTREAM_CTL_DOUBLE, CA_VSTREAM_CTL_END); + netstring_setup(child_stream, 10); + + /* + * Buffer plumbing. + */ + stdin_read_buf = vstring_alloc(100); + child_read_buf = vstring_alloc(100); + + /* + * Monitor both the child's stdout stream and our own stdin stream. If + * there is activity on the child stdout stream, read an entire netstring + * or EOF. If there is activity on stdin, send a netstring to the child + * when we have read an entire line, or terminate in case of EOF. + */ + event_enable_read(vstream_fileno(VSTREAM_IN), stdin_read_event, (void *) 0); + event_enable_read(vstream_fileno(child_stream), child_read_event, + (void *) 0); + + if ((err = vstream_setjmp(child_stream)) == 0) { + for (;;) + event_loop(-1); + } else { + msg_fatal("%s: %s", argv[1], netstring_strerror(err)); + } +} + +#endif diff --git a/src/util/netstring.h b/src/util/netstring.h new file mode 100644 index 0000000..e8f418d --- /dev/null +++ b/src/util/netstring.h @@ -0,0 +1,55 @@ +#ifndef _NETSTRING_H_INCLUDED_ +#define _NETSTRING_H_INCLUDED_ + +/*++ +/* NAME +/* netstring 3h +/* SUMMARY +/* netstring stream I/O support +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * External interface. + */ +#define NETSTRING_ERR_EOF 1 /* unexpected disconnect */ +#define NETSTRING_ERR_TIME 2 /* time out */ +#define NETSTRING_ERR_FORMAT 3 /* format error */ +#define NETSTRING_ERR_SIZE 4 /* netstring too large */ + +extern void netstring_except(VSTREAM *, int); +extern void netstring_setup(VSTREAM *, int); +extern ssize_t netstring_get_length(VSTREAM *); +extern VSTRING *netstring_get_data(VSTREAM *, VSTRING *, ssize_t); +extern void netstring_get_terminator(VSTREAM *); +extern VSTRING *netstring_get(VSTREAM *, VSTRING *, ssize_t); +extern void netstring_put(VSTREAM *, const char *, ssize_t); +extern void netstring_put_multi(VSTREAM *,...); +extern void netstring_fflush(VSTREAM *); +extern VSTRING *netstring_memcpy(VSTRING *, const char *, ssize_t); +extern VSTRING *netstring_memcat(VSTRING *, const char *, ssize_t); +extern const char *netstring_strerror(int); + +#define NETSTRING_PUT_BUF(str, buf) \ + netstring_put((str), vstring_str(buf), VSTRING_LEN(buf)) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/neuter.c b/src/util/neuter.c new file mode 100644 index 0000000..53576fb --- /dev/null +++ b/src/util/neuter.c @@ -0,0 +1,56 @@ +/*++ +/* NAME +/* neuter 3 +/* SUMMARY +/* neutralize characters before they can explode +/* SYNOPSIS +/* #include +/* +/* char *neuter(buffer, bad, replacement) +/* char *buffer; +/* const char *bad; +/* int replacement; +/* DESCRIPTION +/* neuter() replaces bad characters in its input +/* by the given replacement. +/* +/* Arguments: +/* .IP buffer +/* The null-terminated input string. +/* .IP bad +/* The null-terminated bad character string. +/* .IP replacement +/* Replacement value for characters in \fIbuffer\fR that do not +/* pass the bad character test. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include + +/* neuter - neutralize bad characters */ + +char *neuter(char *string, const char *bad, int replacement) +{ + char *cp; + int ch; + + for (cp = string; (ch = *(unsigned char *) cp) != 0; cp++) + if (strchr(bad, ch) != 0) + *cp = replacement; + return (string); +} diff --git a/src/util/non_blocking.c b/src/util/non_blocking.c new file mode 100644 index 0000000..6427cd8 --- /dev/null +++ b/src/util/non_blocking.c @@ -0,0 +1,66 @@ +/*++ +/* NAME +/* non_blocking 3 +/* SUMMARY +/* set/clear non-blocking flag +/* SYNOPSIS +/* #include +/* +/* int non_blocking(int fd, int on) +/* DESCRIPTION +/* the \fInon_blocking\fR() function manipulates the non-blocking +/* flag for the specified open file, and returns the old setting. +/* +/* Arguments: +/* .IP fd +/* A file descriptor. +/* .IP on +/* For non-blocking I/O, specify a non-zero value (or use the +/* NON_BLOCKING constant); for blocking I/O, specify zero +/* (or use the BLOCKING constant). +/* +/* The result is non-zero when the non-blocking flag was enabled. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include "sys_defs.h" +#include + +/* Utility library. */ + +#include "msg.h" +#include "iostuff.h" + +/* Backwards compatibility */ +#ifndef O_NONBLOCK +#define PATTERN FNDELAY +#else +#define PATTERN O_NONBLOCK +#endif + +/* non_blocking - set/clear non-blocking flag */ + +int non_blocking(fd, on) +int fd; +int on; +{ + int flags; + + if ((flags = fcntl(fd, F_GETFL, 0)) < 0) + msg_fatal("fcntl: get flags: %m"); + if (fcntl(fd, F_SETFL, on ? flags | PATTERN : flags & ~PATTERN) < 0) + msg_fatal("fcntl: set non-blocking flag %s: %m", on ? "on" : "off"); + return ((flags & PATTERN) != 0); +} diff --git a/src/util/nvtable.c b/src/util/nvtable.c new file mode 100644 index 0000000..5238e29 --- /dev/null +++ b/src/util/nvtable.c @@ -0,0 +1,122 @@ +/*++ +/* NAME +/* nvtable 3 +/* SUMMARY +/* attribute list manager +/* SYNOPSIS +/* #include +/* +/* typedef struct { +/* .in +4 +/* char *key; +/* char *value; +/* /* private fields... */ +/* .in -4 +/* } NVTABLE_INFO; +/* +/* NVTABLE *nvtable_create(size) +/* int size; +/* +/* NVTABLE_INFO *nvtable_update(table, key, value) +/* NVTABLE *table; +/* const char *key; +/* const char *value; +/* +/* char *nvtable_find(table, key) +/* NVTABLE *table; +/* const char *key; +/* +/* NVTABLE_INFO *nvtable_locate(table, key) +/* NVTABLE *table; +/* const char *key; +/* +/* void nvtable_delete(table, key) +/* NVTABLE *table; +/* const char *key; +/* +/* void nvtable_free(table) +/* NVTABLE *table; +/* +/* void nvtable_walk(table, action, ptr) +/* NVTABLE *table; +/* void (*action)(NVTABLE_INFO *, char *ptr); +/* char *ptr; +/* +/* NVTABLE_INFO **nvtable_list(table) +/* NVTABLE *table; +/* DESCRIPTION +/* This module maintains one or more attribute lists. It provides a +/* more convenient interface than hash tables, although it uses the +/* same underlying implementation. Each attribute list entry consists +/* of a unique string-valued lookup key and a string value. +/* +/* nvtable_create() creates a table of the specified size and returns a +/* pointer to the result. +/* +/* nvtable_update() stores or updates a (key, value) pair in the specified +/* table and returns a pointer to the resulting entry. The key and the +/* value are copied. +/* +/* nvtable_find() returns the value that was stored under the given key, +/* or a null pointer if it was not found. In order to distinguish +/* a null value from a non-existent value, use nvtable_locate(). +/* +/* nvtable_locate() returns a pointer to the entry that was stored +/* for the given key, or a null pointer if it was not found. +/* +/* nvtable_delete() removes one entry that was stored under the given key. +/* +/* nvtable_free() destroys a hash table, including contents. +/* +/* nvtable_walk() invokes the action function for each table entry, with +/* a pointer to the entry as its argument. The ptr argument is passed +/* on to the action function. +/* +/* nvtable_list() returns a null-terminated list of pointers to +/* all elements in the named table. The list should be passed to +/* myfree(). +/* RESTRICTIONS +/* A callback function should not modify the attribute list that is +/* specified to its caller. +/* DIAGNOSTICS +/* The following conditions are reported and cause the program to +/* terminate immediately: memory allocation failure; an attempt +/* to delete a non-existent entry. +/* SEE ALSO +/* mymalloc(3) memory management wrapper +/* htable(3) hash table manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* C library */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* nvtable_update - update or enter (key, value) pair */ + +NVTABLE_INFO *nvtable_update(NVTABLE * table, const char *key, const char *value) +{ + NVTABLE_INFO *ht; + + if ((ht = htable_locate(table, key)) != 0) { + myfree(ht->value); + } else { + ht = htable_enter(table, key, (void *) 0); + } + ht->value = mystrdup(value); + return (ht); +} diff --git a/src/util/nvtable.h b/src/util/nvtable.h new file mode 100644 index 0000000..fed366a --- /dev/null +++ b/src/util/nvtable.h @@ -0,0 +1,44 @@ +#ifndef _NVTABLE_H_INCLUDED_ +#define _NVTABLE_H_INCLUDED_ + +/*++ +/* NAME +/* nvtable 3h +/* SUMMARY +/* attribute list manager +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + +typedef struct HTABLE NVTABLE; +typedef struct HTABLE_INFO NVTABLE_INFO; + +#define nvtable_create(size) htable_create(size) +#define nvtable_locate(table, key) htable_locate((table), (key)) +#define nvtable_walk(table, action, ptr) htable_walk((table), HTABLE_ACTION_FN_CAST(action), (ptr)) +#define nvtable_list(table) htable_list(table) +#define nvtable_find(table, key) htable_find((table), (key)) +#define nvtable_delete(table, key) htable_delete((table), (key), myfree) +#define nvtable_free(table) htable_free((table), myfree) + +extern NVTABLE_INFO *nvtable_update(NVTABLE *, const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/open_as.c b/src/util/open_as.c new file mode 100644 index 0000000..0fa84b7 --- /dev/null +++ b/src/util/open_as.c @@ -0,0 +1,70 @@ +/*++ +/* NAME +/* open_as 3 +/* SUMMARY +/* open file as user +/* SYNOPSIS +/* #include +/* #include +/* +/* int open_as(path, flags, mode, euid, egid) +/* const char *path; +/* int mode; +/* uid_t euid; +/* gid_t egid; +/* DESCRIPTION +/* open_as() opens the named \fIpath\fR with the named \fIflags\fR +/* and \fImode\fR, and with the effective rights specified by \fIeuid\fR +/* and \fIegid\fR. A -1 result means the open failed. +/* DIAGNOSTICS +/* Fatal error: no permission to change privilege level. +/* SEE ALSO +/* set_eugid(3) switch effective rights +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "set_eugid.h" +#include "open_as.h" + +/* open_as - open file as user */ + +int open_as(const char *path, int flags, int mode, uid_t euid, gid_t egid) +{ + uid_t saved_euid = geteuid(); + gid_t saved_egid = getegid(); + int fd; + + /* + * Switch to the target user privileges. + */ + set_eugid(euid, egid); + + /* + * Open that file. + */ + fd = open(path, flags, mode); + + /* + * Restore saved privileges. + */ + set_eugid(saved_euid, saved_egid); + + return (fd); +} diff --git a/src/util/open_as.h b/src/util/open_as.h new file mode 100644 index 0000000..308e009 --- /dev/null +++ b/src/util/open_as.h @@ -0,0 +1,30 @@ +#ifndef _OPEN_H_INCLUDED_ +#define _OPEN_H_INCLUDED_ + +/*++ +/* NAME +/* open_as 3h +/* SUMMARY +/* open file as user +/* SYNOPSIS +/* #include +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int open_as(const char *, int, int, uid_t, gid_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/open_limit.c b/src/util/open_limit.c new file mode 100644 index 0000000..8d49433 --- /dev/null +++ b/src/util/open_limit.c @@ -0,0 +1,100 @@ +/*++ +/* NAME +/* open_limit 3 +/* SUMMARY +/* set/get open file limit +/* SYNOPSIS +/* #include +/* +/* int open_limit(int limit) +/* DESCRIPTION +/* The \fIopen_limit\fR() routine attempts to change the maximum +/* number of open files to the specified limit. Specify a null +/* argument to effect no change. The result is the actual open file +/* limit for the current process. The number can be smaller or larger +/* than the requested limit. +/* DIAGNOSTICS +/* open_limit() returns -1 in case of problems. The errno +/* variable gives hints about the nature of the problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include +#include +#include + +#ifdef USE_MAX_FILES_PER_PROC +#include +#define MAX_FILES_PER_PROC "kern.maxfilesperproc" +#endif + +/* Application-specific. */ + +#include "iostuff.h" + + /* + * 44BSD compatibility. + */ +#ifndef RLIMIT_NOFILE +#ifdef RLIMIT_OFILE +#define RLIMIT_NOFILE RLIMIT_OFILE +#endif +#endif + +/* open_limit - set/query file descriptor limit */ + +int open_limit(int limit) +{ +#ifdef RLIMIT_NOFILE + struct rlimit rl; +#endif + + if (limit < 0) { + errno = EINVAL; + return (-1); + } +#ifdef RLIMIT_NOFILE + if (getrlimit(RLIMIT_NOFILE, &rl) < 0) + return (-1); + if (limit > 0) { + + /* + * MacOSX incorrectly reports rlim_max as RLIM_INFINITY. The true + * hard limit is finite and equals the kern.maxfilesperproc value. + */ +#ifdef USE_MAX_FILES_PER_PROC + int max_files_per_proc; + size_t len = sizeof(max_files_per_proc); + + if (sysctlbyname(MAX_FILES_PER_PROC, &max_files_per_proc, &len, + (void *) 0, (size_t) 0) < 0) + return (-1); + if (limit > max_files_per_proc) + limit = max_files_per_proc; +#endif + if (limit > rl.rlim_max) + rl.rlim_cur = rl.rlim_max; + else + rl.rlim_cur = limit; + if (setrlimit(RLIMIT_NOFILE, &rl) < 0) + return (-1); + } + return (rl.rlim_cur); +#endif + +#ifndef RLIMIT_NOFILE + return (getdtablesize()); +#endif +} + diff --git a/src/util/open_lock.c b/src/util/open_lock.c new file mode 100644 index 0000000..87e852d --- /dev/null +++ b/src/util/open_lock.c @@ -0,0 +1,76 @@ +/*++ +/* NAME +/* open_lock 3 +/* SUMMARY +/* open or create file and lock it for exclusive access +/* SYNOPSIS +/* #include +/* +/* VSTREAM *open_lock(path, flags, mode, why) +/* const char *path; +/* int flags; +/* mode_t mode; +/* VSTRING *why; +/* DESCRIPTION +/* This module opens or creates the named file and attempts to +/* acquire an exclusive lock. The lock is lost when the last +/* process closes the file. +/* +/* Arguments: +/* .IP "path, flags, mode" +/* These are passed on to safe_open(). +/* .IP why +/* storage for diagnostics. +/* SEE ALSO +/* safe_open(3) carefully open or create file +/* myflock(3) get exclusive lock on file +/* DIAGNOSTICS +/* In case of problems the result is a null pointer and a problem +/* description is returned via the global \fIerrno\fR variable. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* open_lock - open file and lock it for exclusive access */ + +VSTREAM *open_lock(const char *path, int flags, mode_t mode, VSTRING *why) +{ + VSTREAM *fp; + + /* + * Carefully create or open the file, and lock it down. Some systems + * don't have the O_LOCK open() flag, or the flag does not do what we + * want, so we roll our own lock. + */ + if ((fp = safe_open(path, flags, mode, (struct stat *) 0, -1, -1, why)) == 0) + return (0); + if (myflock(vstream_fileno(fp), INTERNAL_LOCK, + MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) < 0) { + vstring_sprintf(why, "unable to set exclusive lock: %m"); + vstream_fclose(fp); + return (0); + } + return (fp); +} diff --git a/src/util/open_lock.h b/src/util/open_lock.h new file mode 100644 index 0000000..ada9f4d --- /dev/null +++ b/src/util/open_lock.h @@ -0,0 +1,41 @@ +#ifndef _OPEN_LOCK_H_INCLUDED_ +#define _OPEN_LOCK_H_INCLUDED_ + +/*++ +/* NAME +/* open_lock 3h +/* SUMMARY +/* open or create file and lock it for exclusive access +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include + + /* + * External interface. + */ +extern VSTREAM *open_lock(const char *, int, mode_t, VSTRING *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/pass_accept.c b/src/util/pass_accept.c new file mode 100644 index 0000000..d06926f --- /dev/null +++ b/src/util/pass_accept.c @@ -0,0 +1,106 @@ +/*++ +/* NAME +/* pass_accept 3 +/* SUMMARY +/* start UNIX-domain file descriptor listener +/* SYNOPSIS +/* #include +/* +/* int pass_accept(listen_fd) +/* int listen_fd; +/* +/* int pass_accept_attr(listen_fd, attr) +/* int listen_fd; +/* HTABLE **attr; +/* DESCRIPTION +/* This module implements a listener that receives one attribute list +/* and file descriptor over each a local connection that is made to it. +/* +/* Arguments: +/* .IP attr +/* Pointer to attribute list pointer. In case of error, or +/* no attributes, the attribute list pointer is set to null. +/* .IP listen_fd +/* File descriptor returned by LOCAL_LISTEN(). +/* DIAGNOSTICS +/* Warnings: I/O errors, timeout. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +#define PASS_ACCEPT_TMOUT 100 + +/* pass_accept - accept descriptor */ + +int pass_accept(int listen_fd) +{ + const char *myname = "pass_accept"; + int accept_fd; + int recv_fd = -1; + + accept_fd = LOCAL_ACCEPT(listen_fd); + if (accept_fd < 0) { + if (errno != EAGAIN) + msg_warn("%s: cannot accept connection: %m", myname); + return (-1); + } else { + if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0) + msg_warn("%s: timeout receiving file descriptor: %m", myname); + else if ((recv_fd = LOCAL_RECV_FD(accept_fd)) < 0) + msg_warn("%s: cannot receive file descriptor: %m", myname); + if (close(accept_fd) < 0) + msg_warn("%s: close: %m", myname); + return (recv_fd); + } +} + +/* pass_accept_attr - accept descriptor and attribute list */ + +int pass_accept_attr(int listen_fd, HTABLE **attr) +{ + const char *myname = "pass_accept_attr"; + int accept_fd; + int recv_fd = -1; + + *attr = 0; + accept_fd = LOCAL_ACCEPT(listen_fd); + if (accept_fd < 0) { + if (errno != EAGAIN) + msg_warn("%s: cannot accept connection: %m", myname); + return (-1); + } else { + if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0) + msg_warn("%s: timeout receiving file descriptor: %m", myname); + else if ((recv_fd = LOCAL_RECV_FD(accept_fd)) < 0) + msg_warn("%s: cannot receive file descriptor: %m", myname); + else if (read_wait(accept_fd, PASS_ACCEPT_TMOUT) < 0 + || recv_pass_attr(accept_fd, attr, PASS_ACCEPT_TMOUT, 0) < 0) { + msg_warn("%s: cannot receive connection attributes: %m", myname); + if (close(recv_fd) < 0) + msg_warn("%s: close: %m", myname); + recv_fd = -1; + } + if (close(accept_fd) < 0) + msg_warn("%s: close: %m", myname); + return (recv_fd); + } +} diff --git a/src/util/pass_trigger.c b/src/util/pass_trigger.c new file mode 100644 index 0000000..d7d16f2 --- /dev/null +++ b/src/util/pass_trigger.c @@ -0,0 +1,151 @@ +/*++ +/* NAME +/* pass_trigger 3 +/* SUMMARY +/* trigger file descriptor listener +/* SYNOPSIS +/* #include +/* +/* int pass_trigger(service, buf, len, timeout) +/* const char *service; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* pass_trigger() connects to the named local server by sending +/* a file descriptor to it and writing the named buffer. +/* +/* The connection is closed by a background thread. Some kernels +/* cannot handle client-side disconnect before the server has +/* received the message. +/* +/* Arguments: +/* .IP service +/* Name of the communication endpoint. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Deadline in seconds. Specify a value <= 0 to disable +/* the time limit. +/* DIAGNOSTICS +/* The result is zero in case of success, -1 in case of problems. +/* SEE ALSO +/* unix_connect(3), local client +/* stream_connect(3), streams-based client +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +struct pass_trigger { + int connect_fd; + char *service; + int pass_fd[2]; +}; + +/* pass_trigger_event - disconnect from peer */ + +static void pass_trigger_event(int event, void *context) +{ + struct pass_trigger *pp = (struct pass_trigger *) context; + static const char *myname = "pass_trigger_event"; + + /* + * Disconnect. + */ + if (event == EVENT_TIME) + msg_warn("%s: read timeout for service %s", myname, pp->service); + event_disable_readwrite(pp->connect_fd); + event_cancel_timer(pass_trigger_event, context); + /* Don't combine multiple close() calls into one boolean expression. */ + if (close(pp->connect_fd) < 0) + msg_warn("%s: close %s: %m", myname, pp->service); + if (close(pp->pass_fd[0]) < 0) + msg_warn("%s: close pipe: %m", myname); + if (close(pp->pass_fd[1]) < 0) + msg_warn("%s: close pipe: %m", myname); + myfree(pp->service); + myfree((void *) pp); +} + +/* pass_trigger - wakeup local server */ + +int pass_trigger(const char *service, const char *buf, ssize_t len, int timeout) +{ + const char *myname = "pass_trigger"; + int pass_fd[2]; + struct pass_trigger *pp; + int connect_fd; + + if (msg_verbose > 1) + msg_info("%s: service %s", myname, service); + + /* + * Connect... + */ + if ((connect_fd = LOCAL_CONNECT(service, BLOCKING, timeout)) < 0) { + if (msg_verbose) + msg_warn("%s: connect to %s: %m", myname, service); + return (-1); + } + close_on_exec(connect_fd, CLOSE_ON_EXEC); + + /* + * Create a pipe, and send one pipe end to the server. + */ + if (pipe(pass_fd) < 0) + msg_fatal("%s: pipe: %m", myname); + close_on_exec(pass_fd[0], CLOSE_ON_EXEC); + close_on_exec(pass_fd[1], CLOSE_ON_EXEC); + if (LOCAL_SEND_FD(connect_fd, pass_fd[0]) < 0) + msg_fatal("%s: send file descriptor: %m", myname); + + /* + * Stash away context. + */ + pp = (struct pass_trigger *) mymalloc(sizeof(*pp)); + pp->connect_fd = connect_fd; + pp->service = mystrdup(service); + pp->pass_fd[0] = pass_fd[0]; + pp->pass_fd[1] = pass_fd[1]; + + /* + * Write the request... + */ + if (write_buf(pass_fd[1], buf, len, timeout) < 0 + || write_buf(pass_fd[1], "", 1, timeout) < 0) + if (msg_verbose) + msg_warn("%s: write to %s: %m", myname, service); + + /* + * Wakeup when the peer disconnects, or when we lose patience. + */ + if (timeout > 0) + event_request_timer(pass_trigger_event, (void *) pp, timeout + 100); + event_enable_read(connect_fd, pass_trigger_event, (void *) pp); + return (0); +} diff --git a/src/util/peekfd.c b/src/util/peekfd.c new file mode 100644 index 0000000..e9480a2 --- /dev/null +++ b/src/util/peekfd.c @@ -0,0 +1,89 @@ +/*++ +/* NAME +/* peekfd 3 +/* SUMMARY +/* determine amount of data ready to read +/* SYNOPSIS +/* #include +/* +/* ssize_t peekfd(fd) +/* int fd; +/* DESCRIPTION +/* peekfd() attempts to find out how many bytes are available to +/* be read from the named file descriptor. The result value is +/* the number of available bytes. +/* DIAGNOSTICS +/* peekfd() returns -1 in case of trouble. The global \fIerrno\fR +/* variable reflects the nature of the problem. +/* BUGS +/* On some systems, non-blocking read() may fail even after a +/* positive return from peekfd(). The smtp-sink program works +/* around this by using the readable() function instead. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#ifdef FIONREAD_IN_SYS_FILIO_H +#include +#endif +#ifdef FIONREAD_IN_TERMIOS_H +#include +#endif +#include + +#ifndef SHUT_RDWR +#define SHUT_RDWR 2 +#endif + +/* Utility library. */ + +#include "iostuff.h" + +/* peekfd - return amount of data ready to read */ + +ssize_t peekfd(int fd) +{ + + /* + * Anticipate a series of system-dependent code fragments. + */ +#ifdef FIONREAD + int count; + +#ifdef SUNOS5 + + /* + * With Solaris10, write_wait() hangs in poll() until timeout, when + * invoked after peekfd() has received an ECONNRESET error indication. + * This happens when a client sends QUIT and closes the connection + * immediately. + */ + if (ioctl(fd, FIONREAD, (char *) &count) < 0) { + (void) shutdown(fd, SHUT_RDWR); + return (-1); + } else { + return (count); + } +#else /* SUNOS5 */ + return (ioctl(fd, FIONREAD, (char *) &count) < 0 ? -1 : count); +#endif /* SUNOS5 */ +#else +#error "don't know how to look ahead" +#endif +} diff --git a/src/util/poll_fd.c b/src/util/poll_fd.c new file mode 100644 index 0000000..80cd0f6 --- /dev/null +++ b/src/util/poll_fd.c @@ -0,0 +1,269 @@ +/*++ +/* NAME +/* poll_fd 3 +/* SUMMARY +/* wait until file descriptor becomes readable or writable +/* SYNOPSIS +/* #include +/* +/* int readable(fd) +/* int fd; +/* +/* int writable(fd) +/* int fd; +/* +/* int read_wait(fd, time_limit) +/* int fd; +/* int time_limit; +/* +/* int write_wait(fd, time_limit) +/* int fd; +/* int time_limit; +/* +/* int poll_fd(fd, request, time_limit, true_res, false_res) +/* int fd; +/* int request; +/* int time_limit; +/* int true_res; +/* int false_res; +/* DESCRIPTION +/* The read*() and write*() functions in this module are macros +/* that provide a convenient interface to poll_fd(). +/* +/* readable() asks the kernel if the specified file descriptor +/* is readable, i.e. a read operation would not block. +/* +/* writable() asks the kernel if the specified file descriptor +/* is writable, i.e. a write operation would not block. +/* +/* read_wait() waits until the specified file descriptor becomes +/* readable, or until the time limit is reached. +/* +/* write_wait() waits until the specified file descriptor +/* becomes writable, or until the time limit is reached. +/* +/* poll_fd() waits until the specified file descriptor becomes +/* readable or writable, or until the time limit is reached. +/* +/* Arguments: +/* .IP fd +/* File descriptor. With implementations based on select(), a +/* best effort is made to handle descriptors >=FD_SETSIZE. +/* .IP request +/* POLL_FD_READ (wait until readable) or POLL_FD_WRITE (wait +/* until writable). +/* .IP time_limit +/* A positive value specifies a time limit in seconds. A zero +/* value effects a poll (return immediately). A negative value +/* means wait until the requested POLL_FD_READ or POLL_FD_WRITE +/* condition becomes true. +/* .IP true_res +/* Result value when the requested POLL_FD_READ or POLL_FD_WRITE +/* condition is true. +/* .IP false_res +/* Result value when the requested POLL_FD_READ or POLL_FD_WRITE +/* condition is false. +/* DIAGNOSTICS +/* Panic: interface violation. All system call errors are fatal +/* unless specified otherwise. +/* +/* readable() and writable() return 1 when the requested +/* POLL_FD_READ or POLL_FD_WRITE condition is true, zero when +/* it is false. They never return an error indication. +/* +/* read_wait() and write_wait() return zero when the requested +/* POLL_FD_READ or POLL_FD_WRITE condition is true, -1 (with +/* errno set to ETIMEDOUT) when it is false. +/* +/* poll_fd() returns true_res when the requested POLL_FD_READ +/* or POLL_FD_WRITE condition is true, false_res when it is +/* false. When poll_fd() returns a false_res value < 0, it +/* also sets errno to ETIMEDOUT. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + + /* + * Use poll() with fall-back to select(). MacOSX needs this for devices. + */ +#if defined(USE_SYSV_POLL_THEN_SELECT) +#define poll_fd_sysv poll_fd +#define USE_SYSV_POLL +#define USE_BSD_SELECT +int poll_fd_bsd(int, int, int, int, int); + + /* + * Use select() only. + */ +#elif defined(USE_BSD_SELECT) +#define poll_fd_bsd poll_fd +#undef USE_SYSV_POLL + + /* + * Use poll() only. + */ +#elif defined(USE_SYSV_POLL) +#define poll_fd_sysv poll_fd + + /* + * Sanity check. + */ +#else +#error "specify USE_SYSV_POLL, USE_BSD_SELECT or USE_SYSV_POLL_THEN_SELECT" +#endif + +#ifdef USE_SYSV_POLL +#include +#endif + +#ifdef USE_SYS_SELECT_H +#include +#endif + +/* Utility library. */ + +#include +#include + +#ifdef USE_BSD_SELECT + +/* poll_fd_bsd - block with time_limit until file descriptor is ready */ + +int poll_fd_bsd(int fd, int request, int time_limit, + int true_res, int false_res) +{ + fd_set req_fds; + fd_set *read_fds; + fd_set *write_fds; + fd_set except_fds; + struct timeval tv; + struct timeval *tp; + int temp_fd = -1; + + /* + * Sanity checks. + */ + if (FD_SETSIZE <= fd) { + if ((temp_fd = dup(fd)) < 0 || temp_fd >= FD_SETSIZE) + msg_fatal("descriptor %d does not fit FD_SETSIZE %d", fd, FD_SETSIZE); + fd = temp_fd; + } + + /* + * Use select() so we do not depend on alarm() and on signal() handlers. + * Restart select() when interrupted by some signal. Some select() + * implementations reduce the time to wait when interrupted, which is + * exactly what we want. + */ + FD_ZERO(&req_fds); + FD_SET(fd, &req_fds); + except_fds = req_fds; + if (request == POLL_FD_READ) { + read_fds = &req_fds; + write_fds = 0; + } else if (request == POLL_FD_WRITE) { + read_fds = 0; + write_fds = &req_fds; + } else { + msg_panic("poll_fd: bad request %d", request); + } + + if (time_limit >= 0) { + tv.tv_usec = 0; + tv.tv_sec = time_limit; + tp = &tv; + } else { + tp = 0; + } + + for (;;) { + switch (select(fd + 1, read_fds, write_fds, &except_fds, tp)) { + case -1: + if (errno != EINTR) + msg_fatal("select: %m"); + continue; + case 0: + if (temp_fd != -1) + (void) close(temp_fd); + if (false_res < 0) + errno = ETIMEDOUT; + return (false_res); + default: + if (temp_fd != -1) + (void) close(temp_fd); + return (true_res); + } + } +} + +#endif + +#ifdef USE_SYSV_POLL + +#ifdef USE_SYSV_POLL_THEN_SELECT +#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \ + return (poll_fd_bsd((fd), (req), (time_limit), (true_res), (false_res))) +#else +#define HANDLE_SYSV_POLL_ERROR(fd, req, time_limit, true_res, false_res) \ + msg_fatal("poll: %m") +#endif + +/* poll_fd_sysv - block with time_limit until file descriptor is ready */ + +int poll_fd_sysv(int fd, int request, int time_limit, + int true_res, int false_res) +{ + struct pollfd pollfd; + + /* + * System-V poll() is optimal for polling a few descriptors. + */ +#define WAIT_FOR_EVENT (-1) + + pollfd.fd = fd; + if (request == POLL_FD_READ) { + pollfd.events = POLLIN; + } else if (request == POLL_FD_WRITE) { + pollfd.events = POLLOUT; + } else { + msg_panic("poll_fd: bad request %d", request); + } + + for (;;) { + switch (poll(&pollfd, 1, time_limit < 0 ? + WAIT_FOR_EVENT : time_limit * 1000)) { + case -1: + if (errno != EINTR) + HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, + true_res, false_res); + continue; + case 0: + if (false_res < 0) + errno = ETIMEDOUT; + return (false_res); + default: + if (pollfd.revents & POLLNVAL) + HANDLE_SYSV_POLL_ERROR(fd, request, time_limit, + true_res, false_res); + return (true_res); + } + } +} + +#endif diff --git a/src/util/posix_signals.c b/src/util/posix_signals.c new file mode 100644 index 0000000..8ccddf0 --- /dev/null +++ b/src/util/posix_signals.c @@ -0,0 +1,126 @@ +/*++ +/* NAME +/* posix_signals 3 +/* SUMMARY +/* POSIX signal handling compatibility +/* SYNOPSIS +/* #include +/* +/* int sigemptyset(m) +/* sigset_t *m; +/* +/* int sigaddset(set, signum) +/* sigset_t *set; +/* int signum; +/* +/* int sigprocmask(how, set, old) +/* int how; +/* sigset_t *set; +/* sigset_t *old; +/* +/* int sigaction(sig, act, oact) +/* int sig; +/* struct sigaction *act; +/* struct sigaction *oact; +/* DESCRIPTION +/* These routines emulate the POSIX signal handling interface. +/* AUTHOR(S) +/* Pieter Schoenmakers +/* Eindhoven University of Technology +/* P.O. Box 513 +/* 5600 MB Eindhoven +/* The Netherlands +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include + +/* Utility library.*/ + +#include "posix_signals.h" + +#ifdef MISSING_SIGSET_T + +int sigemptyset(sigset_t *m) +{ + return *m = 0; +} + +int sigaddset(sigset_t *set, int signum) +{ + *set |= sigmask(signum); + return 0; +} + +int sigprocmask(int how, sigset_t *set, sigset_t *old) +{ + int previous; + + if (how == SIG_BLOCK) + previous = sigblock(*set); + else if (how == SIG_SETMASK) + previous = sigsetmask(*set); + else if (how == SIG_UNBLOCK) { + int m = sigblock(0); + + previous = sigsetmask(m & ~*set); + } else { + errno = EINVAL; + return -1; + } + + if (old) + *old = previous; + return 0; +} + +#endif + +#ifdef MISSING_SIGACTION + +static struct sigaction actions[NSIG] = {}; + +static int sighandle(int signum) +{ + if (signum == SIGCHLD) { + /* XXX If the child is just stopped, don't invoke the handler. */ + } + actions[signum].sa_handler(signum); +} + +int sigaction(int sig, struct sigaction *act, struct sigaction *oact) +{ + static int initialized = 0; + + if (!initialized) { + int i; + + for (i = 0; i < NSIG; i++) + actions[i].sa_handler = SIG_DFL; + initialized = 1; + } + if (sig <= 0 || sig >= NSIG) { + errno = EINVAL; + return -1; + } + if (oact) + *oact = actions[sig]; + + { + struct sigvec mine = { + sighandle, act->sa_mask, + act->sa_flags & SA_RESTART ? SV_INTERRUPT : 0 + }; + + if (sigvec(sig, &mine, NULL)) + return -1; + } + + actions[sig] = *act; + return 0; +} + +#endif diff --git a/src/util/posix_signals.h b/src/util/posix_signals.h new file mode 100644 index 0000000..12c1664 --- /dev/null +++ b/src/util/posix_signals.h @@ -0,0 +1,59 @@ +#ifndef _POSIX_SIGNALS_H_INCLUDED_ +#define _POSIX_SIGNALS_H_INCLUDED_ +/*++ +/* NAME +/* posix_signals 3h +/* SUMMARY +/* POSIX signal handling compatibility +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Compatibility interface. + */ + +#ifdef MISSING_SIGSET_T + +typedef int sigset_t; + +enum { + SIG_BLOCK, + SIG_UNBLOCK, + SIG_SETMASK +}; + +extern int sigemptyset(sigset_t *); +extern int sigaddset(sigset_t *, int); +extern int sigprocmask(int, sigset_t *, sigset_t *); + +#endif + +#ifdef MISSING_SIGACTION + +struct sigaction { + void (*sa_handler) (); + sigset_t sa_mask; + int sa_flags; +}; + + /* Possible values for sa_flags. Or them to set multiple. */ +enum { + SA_RESTART, + SA_NOCLDSTOP = 4 /* drop the = 4. */ +}; + +extern int sigaction(int, struct sigaction *, struct sigaction *); + +#endif + +/* AUTHOR(S) +/* Pieter Schoenmakers +/* Eindhoven University of Technology +/* P.O. Box 513 +/* 5600 MB Eindhoven +/* The Netherlands +/*--*/ + +#endif diff --git a/src/util/printable.c b/src/util/printable.c new file mode 100644 index 0000000..6c148fd --- /dev/null +++ b/src/util/printable.c @@ -0,0 +1,100 @@ +/*++ +/* NAME +/* printable 3 +/* SUMMARY +/* mask non-printable characters +/* SYNOPSIS +/* #include +/* +/* int util_utf8_enable; +/* +/* char *printable(buffer, replacement) +/* char *buffer; +/* int replacement; +/* +/* char *printable_except(buffer, replacement, except) +/* char *buffer; +/* int replacement; +/* const char *except; +/* DESCRIPTION +/* printable() replaces non-printable characters +/* in its input with the given replacement. +/* +/* util_utf8_enable controls whether UTF8 is considered printable. +/* With util_utf8_enable equal to zero, non-ASCII text is replaced. +/* +/* Arguments: +/* .IP buffer +/* The null-terminated input string. +/* .IP replacement +/* Replacement value for characters in \fIbuffer\fR that do not +/* pass the ASCII isprint(3) test or that are not valid UTF8. +/* .IP except +/* Null-terminated sequence of non-replaced ASCII characters. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include + +/* Utility library. */ + +#include "stringops.h" + +int util_utf8_enable = 0; + +/* printable - binary compatibility */ + +#undef printable + +char *printable(char *, int); + +char *printable(char *string, int replacement) +{ + return (printable_except(string, replacement, (char *) 0)); +} + +/* printable_except - pass through printable or other preserved characters */ + +char *printable_except(char *string, int replacement, const char *except) +{ + unsigned char *cp; + int ch; + + /* + * XXX Replace invalid UTF8 sequences (too short, over-long encodings, + * out-of-range code points, etc). See valid_utf8_string.c. + */ + cp = (unsigned char *) string; + while ((ch = *cp) != 0) { + if (ISASCII(ch) && (ISPRINT(ch) || (except && strchr(except, ch)))) { + /* ok */ + } else if (util_utf8_enable && ch >= 194 && ch <= 254 + && cp[1] >= 128 && cp[1] < 192) { + /* UTF8; skip the rest of the bytes in the character. */ + while (cp[1] >= 128 && cp[1] < 192) + cp++; + } else { + /* Not ASCII and not UTF8. */ + *cp = replacement; + } + cp++; + } + return (string); +} diff --git a/src/util/rand_sleep.c b/src/util/rand_sleep.c new file mode 100644 index 0000000..69a8cc8 --- /dev/null +++ b/src/util/rand_sleep.c @@ -0,0 +1,89 @@ +/*++ +/* NAME +/* rand_sleep 3 +/* SUMMARY +/* sleep for randomized interval +/* SYNOPSIS +/* #include +/* +/* void rand_sleep(delay, variation) +/* unsigned delay; +/* unsigned variation; +/* DESCRIPTION +/* rand_sleep() blocks the current process for an amount of time +/* pseudo-randomly chosen from the interval (delay +- variation/2). +/* +/* Arguments: +/* .IP delay +/* Time to sleep in microseconds. +/* .IP variation +/* Variation in microseconds; must not be larger than delay. +/* DIAGNOSTICS +/* Panic: interface violation. All system call errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* rand_sleep - block for random time */ + +void rand_sleep(unsigned delay, unsigned variation) +{ + const char *myname = "rand_sleep"; + unsigned usec; + + /* + * Sanity checks. + */ + if (delay == 0) + msg_panic("%s: bad delay %d", myname, delay); + if (variation > delay) + msg_panic("%s: bad variation %d", myname, variation); + + /* + * Use the semi-crappy random number generator. + */ + usec = (delay - variation / 2) + variation * (double) myrand() / RAND_MAX; + doze(usec); +} + +#ifdef TEST + +#include + +int main(int argc, char **argv) +{ + int delay; + int variation; + + msg_vstream_init(argv[0], VSTREAM_ERR); + if (argc != 3) + msg_fatal("usage: %s delay variation", argv[0]); + if ((delay = atoi(argv[1])) <= 0) + msg_fatal("bad delay: %s", argv[1]); + if ((variation = atoi(argv[2])) < 0) + msg_fatal("bad variation: %s", argv[2]); + rand_sleep(delay * 1000000, variation * 1000000); + exit(0); +} + +#endif diff --git a/src/util/readlline.c b/src/util/readlline.c new file mode 100644 index 0000000..015877a --- /dev/null +++ b/src/util/readlline.c @@ -0,0 +1,138 @@ +/*++ +/* NAME +/* readlline 3 +/* SUMMARY +/* read logical line +/* SYNOPSIS +/* #include +/* +/* VSTRING *readllines(buf, fp, lineno, first_line) +/* VSTRING *buf; +/* VSTREAM *fp; +/* int *lineno; +/* int *first_line; +/* +/* VSTRING *readlline(buf, fp, lineno) +/* VSTRING *buf; +/* VSTREAM *fp; +/* int *lineno; +/* DESCRIPTION +/* readllines() reads one logical line from the named stream. +/* .IP "blank lines and comments" +/* Empty lines and whitespace-only lines are ignored, as +/* are lines whose first non-whitespace character is a `#'. +/* .IP "multi-line text" +/* A logical line starts with non-whitespace text. A line that +/* starts with whitespace continues a logical line. +/* .PP +/* The result value is the input buffer argument or a null pointer +/* when no input is found. +/* +/* readlline() is a backwards-compatibility wrapper. +/* +/* Arguments: +/* .IP buf +/* A variable-length buffer for input. The result is null terminated. +/* .IP fp +/* Handle to an open stream. +/* .IP lineno +/* A null pointer, or a pointer to an integer that is incremented +/* after reading a physical line. +/* .IP first_line +/* A null pointer, or a pointer to an integer that will contain +/* the line number of the first non-blank, non-comment line +/* in the result logical line. +/* DIAGNOSTICS +/* Warning: a continuation line that does not continue preceding text. +/* The invalid input is ignored, to avoid complicating caller code. +/* SECURITY +/* .ad +/* .fi +/* readlline() imposes no logical line length limit therefore it +/* should be used for reading trusted information only. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "vstream.h" +#include "vstring.h" +#include "readlline.h" + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) +#define END(x) vstring_end(x) + +/* readllines - read one logical line */ + +VSTRING *readllines(VSTRING *buf, VSTREAM *fp, int *lineno, int *first_line) +{ + int ch; + int next; + ssize_t start; + char *cp; + + VSTRING_RESET(buf); + + /* + * Ignore comment lines, all whitespace lines, and empty lines. Terminate + * at EOF or at the beginning of the next logical line. + */ + for (;;) { + /* Read one line, possibly not newline terminated. */ + start = LEN(buf); + while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n') + VSTRING_ADDCH(buf, ch); + if (lineno != 0 && (ch == '\n' || LEN(buf) > start)) + *lineno += 1; + /* Ignore comment line, all whitespace line, or empty line. */ + for (cp = STR(buf) + start; cp < END(buf) && ISSPACE(*cp); cp++) + /* void */ ; + if (cp == END(buf) || *cp == '#') + vstring_truncate(buf, start); + else if (start == 0 && lineno != 0 && first_line != 0) + *first_line = *lineno; + /* Terminate at EOF or at the beginning of the next logical line. */ + if (ch == VSTREAM_EOF) + break; + if (LEN(buf) > 0) { + if ((next = VSTREAM_GETC(fp)) != VSTREAM_EOF) + vstream_ungetc(fp, next); + if (next != '#' && !ISSPACE(next)) + break; + } + } + VSTRING_TERMINATE(buf); + + /* + * Invalid input: continuing text without preceding text. Allowing this + * would complicate "postconf -e", which implements its own multi-line + * parsing routine. Do not abort, just warn, so that critical programs + * like postmap do not leave behind a truncated table. + */ + if (LEN(buf) > 0 && ISSPACE(*STR(buf))) { + msg_warn("%s: logical line must not start with whitespace: \"%.30s%s\"", + VSTREAM_PATH(fp), STR(buf), + LEN(buf) > 30 ? "..." : ""); + return (readllines(buf, fp, lineno, first_line)); + } + + /* + * Done. + */ + return (LEN(buf) > 0 ? buf : 0); +} diff --git a/src/util/readlline.h b/src/util/readlline.h new file mode 100644 index 0000000..d63cf7d --- /dev/null +++ b/src/util/readlline.h @@ -0,0 +1,38 @@ +#ifndef _READLINE_H_INCLUDED_ +#define _READLINE_H_INCLUDED_ + +/*++ +/* NAME +/* readlline 3h +/* SUMMARY +/* read logical line +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include + + /* + * External interface. + */ +extern VSTRING *readllines(VSTRING *, VSTREAM *, int *, int *); + +#define readlline(bp, fp, lp) readllines((bp), (fp), (lp), (int *) 0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/recv_pass_attr.c b/src/util/recv_pass_attr.c new file mode 100644 index 0000000..0d6c647 --- /dev/null +++ b/src/util/recv_pass_attr.c @@ -0,0 +1,98 @@ +/*++ +/* NAME +/* recv_pass_attr 3 +/* SUMMARY +/* predicate if string is all numerical +/* SYNOPSIS +/* #include +/* +/* int recv_pass_attr(fd, attr, timeout, bufsize) +/* int fd; +/* HTABLE **attr; +/* int timeout; +/* ssize_t bufsize; +/* DESCRIPTION +/* recv_pass_attr() receives named attributes over the specified +/* descriptor. The result value is zero for success, -1 for error. +/* +/* Arguments: +/* .IP fd +/* The file descriptor to read from. +/* .IP attr +/* Pointer to attribute list pointer. The target is set to +/* zero on error or when the received attribute list is empty, +/* otherwise it is assigned a pointer to non-empty attribute +/* list. +/* .IP timeout +/* The deadline for receiving all attributes. +/* .IP bufsize +/* The read buffer size. Specify 1 to avoid reading past the +/* end of the attribute list. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* recv_pass_attr - receive connection attributes */ + +int recv_pass_attr(int fd, HTABLE **attr, int timeout, ssize_t bufsize) +{ + VSTREAM *fp; + int stream_err; + + /* + * Set up a temporary VSTREAM to receive the attributes. + * + * XXX We use one-character reads to simplify the implementation. + */ + fp = vstream_fdopen(fd, O_RDWR); + vstream_control(fp, + CA_VSTREAM_CTL_BUFSIZE(bufsize), + CA_VSTREAM_CTL_TIMEOUT(timeout), + CA_VSTREAM_CTL_START_DEADLINE, + CA_VSTREAM_CTL_END); + stream_err = (attr_scan(fp, ATTR_FLAG_NONE, + ATTR_TYPE_HASH, *attr = htable_create(1), + ATTR_TYPE_END) < 0 + || vstream_feof(fp) || vstream_ferror(fp)); + vstream_fdclose(fp); + + /* + * Error reporting and recovery. + */ + if (stream_err) { + htable_free(*attr, myfree); + *attr = 0; + return (-1); + } else { + if ((*attr)->used == 0) { + htable_free(*attr, myfree); + *attr = 0; + } + return (0); + } +} diff --git a/src/util/ring.c b/src/util/ring.c new file mode 100644 index 0000000..d4c5f82 --- /dev/null +++ b/src/util/ring.c @@ -0,0 +1,121 @@ +/*++ +/* NAME +/* ring 3 +/* SUMMARY +/* circular list management +/* SYNOPSIS +/* #include +/* +/* void ring_init(list) +/* RING *list; +/* +/* void ring_prepend(list, element) +/* RING *list; +/* RING *element; +/* +/* void ring_append(list, element) +/* RING *list; +/* RING *element; +/* +/* RING *ring_pred(element) +/* RING *element; +/* +/* RING *ring_succ(element) +/* RING *element; +/* +/* void ring_detach(element) +/* RING *element; +/* +/* RING_FOREACH(RING *element, RING *head) +/* DESCRIPTION +/* This module manages circular, doubly-linked, lists. It provides +/* operations to initialize a list, to add or remove an element, +/* and to iterate over a list. Although the documentation appears +/* to emphasize the special role of the list head, each operation +/* can be applied to each list member. +/* +/* Examples of applications: any sequence of objects such as queue, +/* unordered list, or stack. Typically, an application embeds a RING +/* structure into its own data structure, and uses the RING primitives +/* to maintain the linkage between application-specific data objects. +/* +/* ring_init() initializes its argument to a list of just one element. +/* +/* ring_append() appends the named element to the named list head. +/* +/* ring_prepend() prepends the named element to the named list head. +/* +/* ring_succ() returns the list element that follows its argument. +/* +/* ring_pred() returns the list element that precedes its argument. +/* +/* ring_detach() disconnects a list element from its neighbors +/* and closes the hole. This routine performs no implicit ring_init() +/* on the removed element. +/* +/* RING_FOREACH() is a macro that expands to a for (... ; ... ; ...) +/* statement that iterates over each list element in forward order. +/* Upon completion, the \fIelement\fR variable is set equal to +/* \fIhead\fR. The list head itself is not treated as a list member. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +/* Application-specific. */ + +#include "ring.h" + +/* ring_init - initialize ring head */ + +void ring_init(ring) +RING *ring; +{ + ring->pred = ring->succ = ring; +} + +/* ring_append - insert entry after ring head */ + +void ring_append(ring, entry) +RING *ring; +RING *entry; +{ + entry->succ = ring->succ; + entry->pred = ring; + ring->succ->pred = entry; + ring->succ = entry; +} + +/* ring_prepend - insert new entry before ring head */ + +void ring_prepend(ring, entry) +RING *ring; +RING *entry; +{ + entry->pred = ring->pred; + entry->succ = ring; + ring->pred->succ = entry; + ring->pred = entry; +} + +/* ring_detach - remove entry from ring */ + +void ring_detach(entry) +RING *entry; +{ + RING *succ = entry->succ; + RING *pred = entry->pred; + + pred->succ = succ; + succ->pred = pred; + + entry->succ = entry->pred = 0; +} diff --git a/src/util/ring.h b/src/util/ring.h new file mode 100644 index 0000000..47749e2 --- /dev/null +++ b/src/util/ring.h @@ -0,0 +1,59 @@ +#ifndef _RING_H_INCLUDED_ +#define _RING_H_INCLUDED_ + +/*++ +/* NAME +/* ring 3h +/* SUMMARY +/* circular list management +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +typedef struct RING RING; + +struct RING { + RING *succ; /* successor */ + RING *pred; /* predecessor */ +}; + +extern void ring_init(RING *); +extern void ring_prepend(RING *, RING *); +extern void ring_append(RING *, RING *); +extern void ring_detach(RING *); + +#define ring_succ(c) ((c)->succ) +#define ring_pred(c) ((c)->pred) + +#define RING_FOREACH(entry, head) \ + for (entry = ring_succ(head); entry != (head); entry = ring_succ(entry)) + + /* + * Typically, an application will embed a RING structure into a larger + * structure that also contains application-specific members. This approach + * gives us the best of both worlds. The application can still use the + * generic RING primitives for manipulating RING structures. The macro below + * transforms a pointer from RING structure to the structure that contains + * it. + */ +#define RING_TO_APPL(ring_ptr,app_type,ring_member) \ + ((app_type *) (((char *) (ring_ptr)) - offsetof(app_type,ring_member))) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* LAST MODIFICATION +/* Tue Jan 28 16:50:20 EST 1997 +/*--*/ + +#endif diff --git a/src/util/safe.h b/src/util/safe.h new file mode 100644 index 0000000..8b75bf4 --- /dev/null +++ b/src/util/safe.h @@ -0,0 +1,30 @@ +#ifndef _SAFE_H_INCLUDED_ +#define _SAFE_H_INCLUDED_ + +/*++ +/* NAME +/* safe 3h +/* SUMMARY +/* miscellaneous taint checks +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int unsafe(void); +extern char *safe_getenv(const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/safe_getenv.c b/src/util/safe_getenv.c new file mode 100644 index 0000000..04ca659 --- /dev/null +++ b/src/util/safe_getenv.c @@ -0,0 +1,41 @@ +/*++ +/* NAME +/* safe_getenv 3 +/* SUMMARY +/* guarded getenv() +/* SYNOPSIS +/* #include +/* +/* char *safe_getenv(const name) +/* char *name; +/* DESCRIPTION +/* The \fBsafe_getenv\fR() routine reads the named variable from the +/* environment, provided that the unsafe() routine agrees. +/* SEE ALSO +/* unsafe(3), detect non-user privileges +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include "safe.h" + +/* safe_getenv - read environment variable with guard */ + +char *safe_getenv(const char *name) +{ + return (unsafe() == 0 ? getenv(name) : 0); +} diff --git a/src/util/safe_open.c b/src/util/safe_open.c new file mode 100644 index 0000000..c7a80cf --- /dev/null +++ b/src/util/safe_open.c @@ -0,0 +1,283 @@ +/*++ +/* NAME +/* safe_open 3 +/* SUMMARY +/* safely open or create regular file +/* SYNOPSIS +/* #include +/* +/* VSTREAM *safe_open(path, flags, mode, st, user, group, why) +/* const char *path; +/* int flags; +/* mode_t mode; +/* struct stat *st; +/* uid_t user; +/* gid_t group; +/* VSTRING *why; +/* DESCRIPTION +/* safe_open() carefully opens or creates a file in a directory +/* that may be writable by untrusted users. If a file is created +/* it is given the specified ownership and permission attributes. +/* If an existing file is opened it must not be a symbolic link, +/* it must not be a directory, and it must have only one hard link. +/* +/* Arguments: +/* .IP "path, flags, mode" +/* These arguments are the same as with open(2). The O_EXCL flag +/* must appear either in combination with O_CREAT, or not at all. +/* .sp +/* No change is made to the permissions of an existing file. +/* .IP st +/* Null pointer, or pointer to storage for the attributes of the +/* opened file. +/* .IP "user, group" +/* File ownership for a file created by safe_open(). Specify -1 +/* in order to disable user and/or group ownership change. +/* .sp +/* No change is made to the ownership of an existing file. +/* .IP why +/* A VSTRING pointer for diagnostics. +/* DIAGNOSTICS +/* Panic: interface violations. +/* +/* A null result means there was a problem. The nature of the +/* problem is returned via the \fIwhy\fR buffer; when an error +/* cannot be reported via \fIerrno\fR, the generic value EPERM +/* (operation not permitted) is used instead. +/* HISTORY +/* .fi +/* .ad +/* A safe open routine was discussed by Casper Dik in article +/* <2rdb0s$568@mail.fwi.uva.nl>, posted to comp.security.unix +/* (May 18, 1994). +/* +/* Olaf Kirch discusses how the lstat()/open()+fstat() test can +/* be fooled by delaying the open() until the inode found with +/* lstat() has been re-used for a sensitive file (article +/* <20000103212443.A5807@monad.swb.de> posted to bugtraq on +/* Jan 3, 2000). This can be a concern for a set-ugid process +/* that runs under the control of a user and that can be +/* manipulated with start/stop signals. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* safe_open_exist - open existing file */ + +static VSTREAM *safe_open_exist(const char *path, int flags, + struct stat * fstat_st, VSTRING *why) +{ + struct stat local_statbuf; + struct stat lstat_st; + int saved_errno; + VSTREAM *fp; + + /* + * Open an existing file. + */ + if ((fp = vstream_fopen(path, flags & ~(O_CREAT | O_EXCL), 0)) == 0) { + saved_errno = errno; + vstring_sprintf(why, "cannot open file: %m"); + errno = saved_errno; + return (0); + } + + /* + * Examine the modes from the open file: it must have exactly one hard + * link (so that someone can't lure us into clobbering a sensitive file + * by making a hard link to it), and it must be a non-symlink file. + */ + if (fstat_st == 0) + fstat_st = &local_statbuf; + if (fstat(vstream_fileno(fp), fstat_st) < 0) { + msg_fatal("%s: bad open file status: %m", path); + } else if (fstat_st->st_nlink != 1) { + vstring_sprintf(why, "file has %d hard links", + (int) fstat_st->st_nlink); + errno = EPERM; + } else if (S_ISDIR(fstat_st->st_mode)) { + vstring_sprintf(why, "file is a directory"); + errno = EISDIR; + } + + /* + * Look up the file again, this time using lstat(). Compare the fstat() + * (open file) modes with the lstat() modes. If there is any difference, + * either we followed a symlink while opening an existing file, someone + * quickly changed the number of hard links, or someone replaced the file + * after the open() call. The link and mode tests aren't really necessary + * in daemon processes. Set-uid programs, on the other hand, can be + * slowed down by arbitrary amounts, and there it would make sense to + * compare even more file attributes, such as the inode generation number + * on systems that have one. + * + * Grr. Solaris /dev/whatever is a symlink. We'll have to make an exception + * for symlinks owned by root. NEVER, NEVER, make exceptions for symlinks + * owned by a non-root user. This would open a security hole when + * delivering mail to a world-writable mailbox directory. + * + * Sebastian Krahmer of SuSE brought to my attention that some systems have + * changed their semantics of link(symlink, newpath), such that the + * result is a hardlink to the symlink. For this reason, we now also + * require that the symlink's parent directory is writable only by root. + */ + else if (lstat(path, &lstat_st) < 0) { + vstring_sprintf(why, "file status changed unexpectedly: %m"); + errno = EPERM; + } else if (S_ISLNK(lstat_st.st_mode)) { + if (lstat_st.st_uid == 0) { + VSTRING *parent_buf = vstring_alloc(100); + const char *parent_path = sane_dirname(parent_buf, path); + struct stat parent_st; + int parent_ok; + + parent_ok = (stat(parent_path, &parent_st) == 0 /* not lstat */ + && parent_st.st_uid == 0 + && (parent_st.st_mode & (S_IWGRP | S_IWOTH)) == 0); + vstring_free(parent_buf); + if (parent_ok) + return (fp); + } + vstring_sprintf(why, "file is a symbolic link"); + errno = EPERM; + } else if (fstat_st->st_dev != lstat_st.st_dev + || fstat_st->st_ino != lstat_st.st_ino +#ifdef HAS_ST_GEN + || fstat_st->st_gen != lstat_st.st_gen +#endif + || fstat_st->st_nlink != lstat_st.st_nlink + || fstat_st->st_mode != lstat_st.st_mode) { + vstring_sprintf(why, "file status changed unexpectedly"); + errno = EPERM; + } + + /* + * We are almost there... + */ + else { + return (fp); + } + + /* + * End up here in case of fstat()/lstat() problems or inconsistencies. + */ + vstream_fclose(fp); + return (0); +} + +/* safe_open_create - create new file */ + +static VSTREAM *safe_open_create(const char *path, int flags, mode_t mode, + struct stat * st, uid_t user, gid_t group, VSTRING *why) +{ + VSTREAM *fp; + + /* + * Create a non-existing file. This relies on O_CREAT | O_EXCL to not + * follow symbolic links. + */ + if ((fp = vstream_fopen(path, flags | (O_CREAT | O_EXCL), mode)) == 0) { + vstring_sprintf(why, "cannot create file exclusively: %m"); + return (0); + } + + /* + * Optionally look up the file attributes. + */ + if (st != 0 && fstat(vstream_fileno(fp), st) < 0) + msg_fatal("%s: bad open file status: %m", path); + + /* + * Optionally change ownership after creating a new file. If there is a + * problem we should not attempt to delete the file. Something else may + * have opened the file in the mean time. + */ +#define CHANGE_OWNER(user, group) (user != (uid_t) -1 || group != (gid_t) -1) + + if (CHANGE_OWNER(user, group) + && fchown(vstream_fileno(fp), user, group) < 0) { + msg_warn("%s: cannot change file ownership: %m", path); + } + + /* + * We are almost there... + */ + else { + return (fp); + } + + /* + * End up here in case of trouble. + */ + vstream_fclose(fp); + return (0); +} + +/* safe_open - safely open or create file */ + +VSTREAM *safe_open(const char *path, int flags, mode_t mode, + struct stat * st, uid_t user, gid_t group, VSTRING *why) +{ + VSTREAM *fp; + + switch (flags & (O_CREAT | O_EXCL)) { + + /* + * Open an existing file, carefully. + */ + case 0: + return (safe_open_exist(path, flags, st, why)); + + /* + * Create a new file, carefully. + */ + case O_CREAT | O_EXCL: + return (safe_open_create(path, flags, mode, st, user, group, why)); + + /* + * Open an existing file or create a new one, carefully. When opening + * an existing file, we are prepared to deal with "no file" errors + * only. When creating a file, we are prepared for "file exists" + * errors only. Any other error means we better give up trying. + */ + case O_CREAT: + fp = safe_open_exist(path, flags, st, why); + if (fp == 0 && errno == ENOENT) { + fp = safe_open_create(path, flags, mode, st, user, group, why); + if (fp == 0 && errno == EEXIST) + fp = safe_open_exist(path, flags, st, why); + } + return (fp); + + /* + * Interface violation. Sorry, but we must be strict. + */ + default: + msg_panic("safe_open: O_EXCL flag without O_CREAT flag"); + } +} diff --git a/src/util/safe_open.h b/src/util/safe_open.h new file mode 100644 index 0000000..88b5344 --- /dev/null +++ b/src/util/safe_open.h @@ -0,0 +1,42 @@ +#ifndef _SAFE_OPEN_H_INCLUDED_ +#define _SAFE_OPEN_H_INCLUDED_ + +/*++ +/* NAME +/* safe_open 3h +/* SUMMARY +/* safely open or create regular file +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * External interface. + */ +extern VSTREAM *safe_open(const char *, int, mode_t, struct stat *, uid_t, gid_t, VSTRING *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/sane_accept.c b/src/util/sane_accept.c new file mode 100644 index 0000000..86e3d34 --- /dev/null +++ b/src/util/sane_accept.c @@ -0,0 +1,125 @@ +/*++ +/* NAME +/* sane_accept 3 +/* SUMMARY +/* sanitize accept() error returns +/* SYNOPSIS +/* #include +/* +/* int sane_accept(sock, buf, len) +/* int sock; +/* struct sockaddr *buf; +/* SOCKADDR_SIZE *len; +/* DESCRIPTION +/* sane_accept() implements the accept(2) socket call, and maps +/* known harmless error results to EAGAIN. +/* +/* If the buf and len arguments are not null, then additional +/* workarounds may be enabled that depend on the socket type. +/* BUGS +/* Bizarre systems may have other harmless error results. Such +/* systems encourage programmers to ignore error results, and +/* penalize programmers who code defensively. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "sane_accept.h" + +/* sane_accept - sanitize accept() error returns */ + +int sane_accept(int sock, struct sockaddr *sa, SOCKADDR_SIZE *len) +{ + static int accept_ok_errors[] = { + EAGAIN, + ECONNREFUSED, + ECONNRESET, + EHOSTDOWN, + EHOSTUNREACH, + EINTR, + ENETDOWN, + ENETUNREACH, + ENOTCONN, + EWOULDBLOCK, + ENOBUFS, /* HPUX11 */ + ECONNABORTED, +#ifdef EPROTO + EPROTO, /* SunOS 5.5.1 */ +#endif + 0, + }; + int count; + int err; + int fd; + + /* + * XXX Solaris 2.4 accept() returns EPIPE when a UNIX-domain client has + * disconnected in the mean time. From then on, UNIX-domain sockets are + * hosed beyond recovery. There is no point treating this as a beneficial + * error result because the program would go into a tight loop. + * + * XXX Solaris 2.5.1 accept() returns EPROTO when a TCP client has + * disconnected in the mean time. Since there is no connection, it is + * safe to map the error code onto EAGAIN. + * + * XXX LINUX < 2.1 accept() wakes up before the three-way handshake is + * complete, so it can fail with ECONNRESET and other "false alarm" + * indications. + * + * XXX FreeBSD 4.2-STABLE accept() returns ECONNABORTED when a UNIX-domain + * client has disconnected in the mean time. The data that was sent with + * connect() write() close() is lost, even though the write() and close() + * reported successful completion. This was fixed shortly before FreeBSD + * 4.3. + * + * XXX HP-UX 11 returns ENOBUFS when the client has disconnected in the mean + * time. + */ + if ((fd = accept(sock, sa, len)) < 0) { + for (count = 0; (err = accept_ok_errors[count]) != 0; count++) { + if (errno == err) { + errno = EAGAIN; + break; + } + } + } + + /* + * XXX Solaris select() produces false read events, so that read() blocks + * forever on a blocking socket, and fails with EAGAIN on a non-blocking + * socket. Turning on keepalives will fix a blocking socket provided that + * the kernel's keepalive timer expires before the Postfix watchdog + * timer. + * + * XXX Work around NAT induced damage by sending a keepalive before an idle + * connection is expired. This requires that the kernel keepalive timer + * is set to a short time, like 100s. + */ + else if (sa && (sa->sa_family == AF_INET +#ifdef HAS_IPV6 + || sa->sa_family == AF_INET6 +#endif + )) { + int on = 1; + + (void) setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, + (void *) &on, sizeof(on)); + } + return (fd); +} diff --git a/src/util/sane_accept.h b/src/util/sane_accept.h new file mode 100644 index 0000000..84cc360 --- /dev/null +++ b/src/util/sane_accept.h @@ -0,0 +1,29 @@ +#ifndef _SANE_ACCEPT_H_ +#define _SANE_ACCEPT_H_ + +/*++ +/* NAME +/* sane_accept 3h +/* SUMMARY +/* sanitize accept() error returns +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int sane_accept(int, struct sockaddr *, SOCKADDR_SIZE *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/sane_basename.c b/src/util/sane_basename.c new file mode 100644 index 0000000..6c3a4c1 --- /dev/null +++ b/src/util/sane_basename.c @@ -0,0 +1,181 @@ +/*++ +/* NAME +/* sane_basename 3 +/* SUMMARY +/* split pathname into last component and parent directory +/* SYNOPSIS +/* #include +/* +/* char *sane_basename(buf, path) +/* VSTRING *buf; +/* const char *path; +/* +/* char *sane_dirname(buf, path) +/* VSTRING *buf; +/* const char *path; +/* DESCRIPTION +/* These functions split a pathname into its last component +/* and its parent directory, excluding any trailing "/" +/* characters from the input. The result is a pointer to "/" +/* when the input is all "/" characters, or a pointer to "." +/* when the input is a null pointer or zero-length string. +/* +/* sane_basename() and sane_dirname() differ as follows +/* from standard basename() and dirname() implementations: +/* .IP \(bu +/* They can use caller-provided storage or private storage. +/* .IP \(bu +/* They never modify their input. +/* .PP +/* sane_basename() returns a pointer to string with the last +/* pathname component. +/* +/* sane_dirname() returns a pointer to string with the parent +/* directory. The result is a pointer to "." when the input +/* contains no '/' character. +/* +/* Arguments: +/* .IP buf +/* Result storage. If a null pointer is specified, each function +/* uses its own private memory that is overwritten upon each call. +/* .IP path +/* The input pathname. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include + +#define STR(x) vstring_str(x) + +/* sane_basename - skip directory prefix */ + +char *sane_basename(VSTRING *bp, const char *path) +{ + static VSTRING *buf; + const char *first; + const char *last; + + /* + * Your buffer or mine? + */ + if (bp == 0) { + bp = buf; + if (bp == 0) + bp = buf = vstring_alloc(10); + } + + /* + * Special case: return "." for null or zero-length input. + */ + if (path == 0 || *path == 0) + return (STR(vstring_strcpy(bp, "."))); + + /* + * Remove trailing '/' characters from input. Return "/" if input is all + * '/' characters. + */ + last = path + strlen(path) - 1; + while (*last == '/') { + if (last == path) + return (STR(vstring_strcpy(bp, "/"))); + last--; + } + + /* + * The pathname does not end in '/'. Skip to last '/' character if any. + */ + first = last - 1; + while (first >= path && *first != '/') + first--; + + return (STR(vstring_strncpy(bp, first + 1, last - first))); +} + +/* sane_dirname - keep directory prefix */ + +char *sane_dirname(VSTRING *bp, const char *path) +{ + static VSTRING *buf; + const char *last; + + /* + * Your buffer or mine? + */ + if (bp == 0) { + bp = buf; + if (bp == 0) + bp = buf = vstring_alloc(10); + } + + /* + * Special case: return "." for null or zero-length input. + */ + if (path == 0 || *path == 0) + return (STR(vstring_strcpy(bp, "."))); + + /* + * Remove trailing '/' characters from input. Return "/" if input is all + * '/' characters. + */ + last = path + strlen(path) - 1; + while (*last == '/') { + if (last == path) + return (STR(vstring_strcpy(bp, "/"))); + last--; + } + + /* + * This pathname does not end in '/'. Skip to last '/' character if any. + */ + while (last >= path && *last != '/') + last--; + if (last < path) /* no '/' */ + return (STR(vstring_strcpy(bp, "."))); + + /* + * Strip trailing '/' characters from dirname (not strictly needed). + */ + while (last > path && *last == '/') + last--; + + return (STR(vstring_strncpy(bp, path, last - path + 1))); +} + +#ifdef TEST +#include + +int main(int argc, char **argv) +{ + VSTRING *buf = vstring_alloc(10); + char *dir; + char *base; + + while (vstring_get_nonl(buf, VSTREAM_IN) > 0) { + dir = sane_dirname((VSTRING *) 0, STR(buf)); + base = sane_basename((VSTRING *) 0, STR(buf)); + vstream_printf("input=\"%s\" dir=\"%s\" base=\"%s\"\n", + STR(buf), dir, base); + } + vstream_fflush(VSTREAM_OUT); + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/sane_basename.in b/src/util/sane_basename.in new file mode 100644 index 0000000..75d613d --- /dev/null +++ b/src/util/sane_basename.in @@ -0,0 +1,18 @@ +/// +/ +fo2/// +fo2/ +fo2 +///fo2 +/fo2 +///fo2///bar/// +fo2///bar/// +fo2///bar/ +fo2/bar + +/usr/lib +/usr/ +usr +/ +. +.. diff --git a/src/util/sane_basename.ref b/src/util/sane_basename.ref new file mode 100644 index 0000000..8edcfcd --- /dev/null +++ b/src/util/sane_basename.ref @@ -0,0 +1,18 @@ +input="///" dir="/" base="/" +input="/" dir="/" base="/" +input="fo2///" dir="." base="fo2" +input="fo2/" dir="." base="fo2" +input="fo2" dir="." base="fo2" +input="///fo2" dir="/" base="fo2" +input="/fo2" dir="/" base="fo2" +input="///fo2///bar///" dir="///fo2" base="bar" +input="fo2///bar///" dir="fo2" base="bar" +input="fo2///bar/" dir="fo2" base="bar" +input="fo2/bar" dir="fo2" base="bar" +input="" dir="." base="." +input="/usr/lib" dir="/usr" base="lib" +input="/usr/" dir="/" base="usr" +input="usr" dir="." base="usr" +input="/" dir="/" base="/" +input="." dir="." base="." +input=".." dir="." base=".." diff --git a/src/util/sane_connect.c b/src/util/sane_connect.c new file mode 100644 index 0000000..a15204b --- /dev/null +++ b/src/util/sane_connect.c @@ -0,0 +1,65 @@ +/*++ +/* NAME +/* sane_connect 3 +/* SUMMARY +/* sanitize connect() results +/* SYNOPSIS +/* #include +/* +/* int sane_connect(sock, buf, len) +/* int sock; +/* struct sockaddr *buf; +/* SOCKADDR_SIZE *len; +/* DESCRIPTION +/* sane_connect() implements the connect(2) socket call, and maps +/* known harmless error results to EAGAIN. +/* BUGS +/* Bizarre systems may have other harmless error results. Such +/* systems encourage programmers to ignore error results, and +/* penalize programmers who code defensively. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "sane_connect.h" + +/* sane_connect - sanitize connect() results */ + +int sane_connect(int sock, struct sockaddr *sa, SOCKADDR_SIZE len) +{ + + /* + * XXX Solaris select() produces false read events, so that read() blocks + * forever on a blocking socket, and fails with EAGAIN on a non-blocking + * socket. Turning on keepalives will fix a blocking socket provided that + * the kernel's keepalive timer expires before the Postfix watchdog + * timer. + * + * XXX Work around NAT induced damage by sending a keepalive before an idle + * connection is expired. This requires that the kernel keepalive timer + * is set to a short time, like 100s. + */ + if (sa->sa_family == AF_INET) { + int on = 1; + + (void) setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, + (void *) &on, sizeof(on)); + } + return (connect(sock, sa, len)); +} diff --git a/src/util/sane_connect.h b/src/util/sane_connect.h new file mode 100644 index 0000000..1f023b0 --- /dev/null +++ b/src/util/sane_connect.h @@ -0,0 +1,29 @@ +#ifndef _SANE_CONNECT_H_ +#define _SANE_CONNECT_H_ + +/*++ +/* NAME +/* sane_connect 3h +/* SUMMARY +/* sanitize connect() results +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int sane_connect(int, struct sockaddr *, SOCKADDR_SIZE); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/sane_fsops.h b/src/util/sane_fsops.h new file mode 100644 index 0000000..28e7f67 --- /dev/null +++ b/src/util/sane_fsops.h @@ -0,0 +1,35 @@ +#ifndef _SANE_FSOPS_H_ +#define _SANE_FSOPS_H_ + +/*++ +/* NAME +/* sane_rename 3h +/* SUMMARY +/* sanitize rename() error returns +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int WARN_UNUSED_RESULT sane_rename(const char *, const char *); +extern int WARN_UNUSED_RESULT sane_link(const char *, const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/sane_link.c b/src/util/sane_link.c new file mode 100644 index 0000000..40fd56d --- /dev/null +++ b/src/util/sane_link.c @@ -0,0 +1,72 @@ +/*++ +/* NAME +/* sane_link 3 +/* SUMMARY +/* sanitize link() error returns +/* SYNOPSIS +/* #include +/* +/* int sane_link(from, to) +/* const char *from; +/* const char *to; +/* DESCRIPTION +/* sane_link() implements the link(2) system call, and works +/* around some errors that are possible with NFS file systems. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "sane_fsops.h" +#include "warn_stat.h" + +/* sane_link - sanitize link() error returns */ + +int sane_link(const char *from, const char *to) +{ + const char *myname = "sane_link"; + int saved_errno; + struct stat from_st; + struct stat to_st; + + /* + * Normal case: link() succeeds. + */ + if (link(from, to) >= 0) + return (0); + + /* + * Woops. Save errno, and see if the error is an NFS artifact. If it is, + * pretend the error never happened. + */ + saved_errno = errno; + if (stat(from, &from_st) >= 0 && stat(to, &to_st) >= 0 + && from_st.st_dev == to_st.st_dev + && from_st.st_ino == to_st.st_ino) { + msg_info("%s(%s,%s): worked around spurious NFS error", + myname, from, to); + return (0); + } + + /* + * Nope, it didn't. Restore errno and report the error. + */ + errno = saved_errno; + return (-1); +} diff --git a/src/util/sane_rename.c b/src/util/sane_rename.c new file mode 100644 index 0000000..3b301bd --- /dev/null +++ b/src/util/sane_rename.c @@ -0,0 +1,69 @@ +/*++ +/* NAME +/* sane_rename 3 +/* SUMMARY +/* sanitize rename() error returns +/* SYNOPSIS +/* #include +/* +/* int sane_rename(old, new) +/* const char *from; +/* const char *to; +/* DESCRIPTION +/* sane_rename() implements the rename(2) system call, and works +/* around some errors that are possible with NFS file systems. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include /* rename(2) syscall in stdio.h? */ + +/* Utility library. */ + +#include "msg.h" +#include "sane_fsops.h" +#include "warn_stat.h" + +/* sane_rename - sanitize rename() error returns */ + +int sane_rename(const char *from, const char *to) +{ + const char *myname = "sane_rename"; + int saved_errno; + struct stat st; + + /* + * Normal case: rename() succeeds. + */ + if (rename(from, to) >= 0) + return (0); + + /* + * Woops. Save errno, and see if the error is an NFS artifact. If it is, + * pretend the error never happened. + */ + saved_errno = errno; + if (stat(from, &st) < 0 && stat(to, &st) >= 0) { + msg_info("%s(%s,%s): worked around spurious NFS error", + myname, from, to); + return (0); + } + + /* + * Nope, it didn't. Restore errno and report the error. + */ + errno = saved_errno; + return (-1); +} diff --git a/src/util/sane_socketpair.c b/src/util/sane_socketpair.c new file mode 100644 index 0000000..a889934 --- /dev/null +++ b/src/util/sane_socketpair.c @@ -0,0 +1,71 @@ +/*++ +/* NAME +/* sane_socketpair 3 +/* SUMMARY +/* sanitize socketpair() error returns +/* SYNOPSIS +/* #include +/* +/* int sane_socketpair(domain, type, protocol, result) +/* int domain; +/* int type; +/* int protocol; +/* int *result; +/* DESCRIPTION +/* sane_socketpair() implements the socketpair(2) socket call, and +/* skips over silly error results such as EINTR. +/* BUGS +/* Bizarre systems may have other harmless error results. Such +/* systems encourage programmers to ignore error results, and +/* penalize programmers who code defensively. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "sane_socketpair.h" + +/* sane_socketpair - sanitize socketpair() error returns */ + +int sane_socketpair(int domain, int type, int protocol, int *result) +{ + static int socketpair_ok_errors[] = { + EINTR, + 0, + }; + int count; + int err; + int ret; + + /* + * Solaris socketpair() can fail with EINTR. + */ + while ((ret = socketpair(domain, type, protocol, result)) < 0) { + for (count = 0; /* void */ ; count++) { + if ((err = socketpair_ok_errors[count]) == 0) + return (ret); + if (errno == err) { + msg_warn("socketpair: %m (trying again)"); + sleep(1); + break; + } + } + } + return (ret); +} diff --git a/src/util/sane_socketpair.h b/src/util/sane_socketpair.h new file mode 100644 index 0000000..d54a73d --- /dev/null +++ b/src/util/sane_socketpair.h @@ -0,0 +1,34 @@ +#ifndef _SANE_SOCKETPAIR_H_ +#define _SANE_SOCKETPAIR_H_ + +/*++ +/* NAME +/* sane_socketpair 3h +/* SUMMARY +/* sanitize socketpair() error returns +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int WARN_UNUSED_RESULT sane_socketpair(int, int, int, int *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/sane_strtol.c b/src/util/sane_strtol.c new file mode 100644 index 0000000..b7435dd --- /dev/null +++ b/src/util/sane_strtol.c @@ -0,0 +1,59 @@ +/*++ +/* NAME +/* sane_strtol 3 +/* SUMMARY +/* strtol() with mandatory errno reset +/* SYNOPSIS +/* #include +/* +/* long sane_strtol( +/* const char *start, +/* char **restrict end, +/* int base) +/* +/* unsigned long sane_strtoul( +/* const char *start, +/* char **restrict end, +/* int base) +/* DESCRIPTION +/* These functions are wrappers around the strtol() and strtoul() +/* standard library functions that reset errno first, so that a +/* prior ERANGE error won't cause false errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library. + */ +#include + +/* sane_strtol - strtol() with mandatory initialization */ + +long sane_strtol(const char *start, char **end, int base) +{ + errno = 0; + return (strtol(start, end, base)); +} + +/* sane_strtoul - strtoul() with mandatory initialization */ + +unsigned long sane_strtoul(const char *start, char **end, int base) +{ + errno = 0; + return (strtoul(start, end, base)); +} diff --git a/src/util/sane_strtol.h b/src/util/sane_strtol.h new file mode 100644 index 0000000..ac08316 --- /dev/null +++ b/src/util/sane_strtol.h @@ -0,0 +1,26 @@ +/*++ +/* NAME +/* sane_strtol 3h +/* SUMMARY +/* strtol() with mandatory errno reset +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External API. + */ +extern long sane_strtol(const char *start, char **end, int); +extern unsigned long sane_strtoul(const char *start, char **end, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ diff --git a/src/util/sane_time.c b/src/util/sane_time.c new file mode 100644 index 0000000..cc86de2 --- /dev/null +++ b/src/util/sane_time.c @@ -0,0 +1,127 @@ +/*++ +/* NAME +/* sane_time 3 +/* SUMMARY +/* time(2) with backward time jump protection. +/* SYNOPSIS +/* #include +/* +/* time_t sane_time(void) +/* +/* DESCRIPTION +/* This module provides time(2) like call for applications +/* which need monotonically increasing time function rather +/* than the real exact time. It eliminates the need for various +/* workarounds all over the application which would handle +/* potential problems if time suddenly jumps backward. +/* Instead we choose to deal with this problem inside this +/* module and let the application focus on its own tasks. +/* +/* sane_time() returns the current timestamp as obtained from +/* time(2) call, at least most of the time. In case this routine +/* detects that time has jumped backward, it keeps returning +/* whatever timestamp it returned before, until this timestamp +/* and the time(2) timestamp become synchronized again. +/* Additionally, the returned timestamp is slowly increased to +/* prevent the faked clock from freezing for too long. +/* SEE ALSO +/* time(2) get current time +/* DIAGNOSTICS +/* Warning message is logged if backward time jump is detected. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include + +/* Application-specific. */ + +#include "sane_time.h" + +/* + * How many times shall we slow down the real clock when recovering from + * time jump. + */ +#define SLEW_FACTOR 2 + +/* sane_time - get current time, protected against time warping */ + +time_t sane_time(void) +{ + time_t now; + static time_t last_time, last_real; + long delta; + static int fraction; + static int warned; + + now = time((time_t *) 0); + + if ((delta = now - last_time) < 0 && last_time != 0) { + if ((delta = now - last_real) < 0) { + msg_warn("%sbackward time jump detected -- slewing clock", + warned++ ? "another " : ""); + } else { + delta += fraction; + last_time += delta / SLEW_FACTOR; + fraction = delta % SLEW_FACTOR; + } + } else { + if (warned) { + warned = 0; + msg_warn("backward time jump recovered -- back to normality"); + fraction = 0; + } + last_time = now; + } + last_real = now; + + return (last_time); +} + +#ifdef TEST + + /* + * Proof-of-concept test program. Repeatedly print current system time and + * time returned by sane_time(). Meanwhile, try stepping your system clock + * back and forth to see what happens. + */ + +#include +#include +#include /* doze() */ + +int main(int argc, char **argv) +{ + int delay = 1000000; + time_t now; + + msg_vstream_init(argv[0], VSTREAM_ERR); + + if (argc == 2 && (delay = atol(argv[1]) * 1000) > 0) + /* void */ ; + else if (argc != 1) + msg_fatal("usage: %s [delay in ms (default 1 second)]", argv[0]); + + for (;;) { + now = time((time_t *) 0); + vstream_printf("real: %s", ctime(&now)); + now = sane_time(); + vstream_printf("fake: %s\n", ctime(&now)); + vstream_fflush(VSTREAM_OUT); + doze(delay); + } +} + +#endif diff --git a/src/util/sane_time.h b/src/util/sane_time.h new file mode 100644 index 0000000..0fe50d9 --- /dev/null +++ b/src/util/sane_time.h @@ -0,0 +1,34 @@ +#ifndef _SANE_TIME_H_ +#define _SANE_TIME_H_ + +/*++ +/* NAME +/* sane_time 3h +/* SUMMARY +/* time(2) with backward time jump protection +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * External interface. + */ +extern time_t sane_time(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +#endif diff --git a/src/util/scan_dir.c b/src/util/scan_dir.c new file mode 100644 index 0000000..d94c674 --- /dev/null +++ b/src/util/scan_dir.c @@ -0,0 +1,216 @@ +/*++ +/* NAME +/* scan_dir 3 +/* SUMMARY +/* directory scanning +/* SYNOPSIS +/* #include +/* +/* SCAN_DIR *scan_dir_open(path) +/* const char *path; +/* +/* char *scan_dir_next(scan) +/* SCAN_DIR *scan; +/* +/* char *scan_dir_path(scan) +/* SCAN_DIR *scan; +/* +/* void scan_push(scan, entry) +/* SCAN_DIR *scan; +/* const char *entry; +/* +/* SCAN_DIR *scan_pop(scan) +/* SCAN_DIR *scan; +/* +/* SCAN_DIR *scan_dir_close(scan) +/* SCAN_DIR *scan; +/* DESCRIPTION +/* These functions scan directories for names. The "." and +/* ".." names are skipped. Essentially, this is +/* extended with error handling and with knowledge of the +/* name of the directory being scanned. +/* +/* scan_dir_open() opens the named directory and +/* returns a handle for subsequent use. +/* +/* scan_dir_close() terminates the directory scan, cleans up +/* and returns a null pointer. +/* +/* scan_dir_next() returns the next requested object in the specified +/* directory. It skips the "." and ".." entries. +/* +/* scan_dir_path() returns the name of the directory being scanned. +/* +/* scan_dir_push() causes the specified directory scan to enter the +/* named subdirectory. +/* +/* scan_dir_pop() leaves the directory being scanned and returns +/* to the previous one. The result is the argument, null if no +/* previous directory information is available. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#ifdef HAVE_DIRENT_H +#include +#else +#define dirent direct +#ifdef HAVE_SYS_NDIR_H +#include +#endif +#ifdef HAVE_SYS_DIR_H +#include +#endif +#ifdef HAVE_NDIR_H +#include +#endif +#endif +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "stringops.h" +#include "vstring.h" +#include "scan_dir.h" + + /* + * The interface is based on an opaque structure, so we don't have to expose + * the user to the guts. Subdirectory info sits in front of parent directory + * info: a simple last-in, first-out list. + */ +typedef struct SCAN_INFO SCAN_INFO; + +struct SCAN_INFO { + char *path; /* directory name */ + DIR *dir; /* directory structure */ + SCAN_INFO *parent; /* linkage */ +}; +struct SCAN_DIR { + SCAN_INFO *current; /* current scan */ +}; + +#define SCAN_DIR_PATH(scan) (scan->current->path) +#define STR(x) vstring_str(x) + +/* scan_dir_path - return the path of the directory being read. */ + +char *scan_dir_path(SCAN_DIR *scan) +{ + return (SCAN_DIR_PATH(scan)); +} + +/* scan_dir_push - enter directory */ + +void scan_dir_push(SCAN_DIR *scan, const char *path) +{ + const char *myname = "scan_dir_push"; + SCAN_INFO *info; + + info = (SCAN_INFO *) mymalloc(sizeof(*info)); + if (scan->current) + info->path = concatenate(SCAN_DIR_PATH(scan), "/", path, (char *) 0); + else + info->path = mystrdup(path); + if ((info->dir = opendir(info->path)) == 0) + msg_fatal("%s: open directory %s: %m", myname, info->path); + if (msg_verbose > 1) + msg_info("%s: open %s", myname, info->path); + info->parent = scan->current; + scan->current = info; +} + +/* scan_dir_pop - leave directory */ + +SCAN_DIR *scan_dir_pop(SCAN_DIR *scan) +{ + const char *myname = "scan_dir_pop"; + SCAN_INFO *info = scan->current; + SCAN_INFO *parent; + + if (info == 0) + return (0); + parent = info->parent; + if (closedir(info->dir)) + msg_fatal("%s: close directory %s: %m", myname, info->path); + if (msg_verbose > 1) + msg_info("%s: close %s", myname, info->path); + myfree(info->path); + myfree((void *) info); + scan->current = parent; + return (parent ? scan : 0); +} + +/* scan_dir_open - start directory scan */ + +SCAN_DIR *scan_dir_open(const char *path) +{ + SCAN_DIR *scan; + + scan = (SCAN_DIR *) mymalloc(sizeof(*scan)); + scan->current = 0; + scan_dir_push(scan, path); + return (scan); +} + +/* scan_dir_next - find next entry */ + +char *scan_dir_next(SCAN_DIR *scan) +{ + const char *myname = "scan_dir_next"; + SCAN_INFO *info = scan->current; + struct dirent *dp; + +#define STREQ(x,y) (strcmp((x),(y)) == 0) + + if (info) { + + /* + * Fix 20150421: readdir() does not reset errno after reaching the + * end-of-directory. This dates back all the way to the initial + * implementation of 19970309. + */ + errno = 0; + while ((dp = readdir(info->dir)) != 0) { + if (STREQ(dp->d_name, ".") || STREQ(dp->d_name, "..")) { + if (msg_verbose > 1) + msg_info("%s: skip %s", myname, dp->d_name); + continue; + } else { + if (msg_verbose > 1) + msg_info("%s: found %s", myname, dp->d_name); + return (dp->d_name); + } + } + } + return (0); +} + +/* scan_dir_close - terminate directory scan */ + +SCAN_DIR *scan_dir_close(SCAN_DIR *scan) +{ + while (scan->current) + scan_dir_pop(scan); + myfree((void *) scan); + return (0); +} diff --git a/src/util/scan_dir.h b/src/util/scan_dir.h new file mode 100644 index 0000000..8f3bf8b --- /dev/null +++ b/src/util/scan_dir.h @@ -0,0 +1,37 @@ +#ifndef _SCAN_DIR_H_INCLUDED_ +#define _SCAN_DIR_H_INCLUDED_ + +/*++ +/* NAME +/* scan_dir 3h +/* SUMMARY +/* directory scanner +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * The directory scanner interface. + */ +typedef struct SCAN_DIR SCAN_DIR; + +extern SCAN_DIR *scan_dir_open(const char *); +extern char *scan_dir_next(SCAN_DIR *); +extern char *scan_dir_path(SCAN_DIR *); +extern void scan_dir_push(SCAN_DIR *, const char *); +extern SCAN_DIR *scan_dir_pop(SCAN_DIR *); +extern SCAN_DIR *scan_dir_close(SCAN_DIR *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/select_bug.c b/src/util/select_bug.c new file mode 100644 index 0000000..9b479ca --- /dev/null +++ b/src/util/select_bug.c @@ -0,0 +1,95 @@ +/*++ +/* NAME +/* select_bug 1 +/* SUMMARY +/* select test program +/* SYNOPSIS +/* select_bug +/* DESCRIPTION +/* select_bug forks child processes that perform select() +/* on a shared socket, and sees if a wakeup affects other +/* processes selecting on a different socket or stdin. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include /* bzero() prototype for 44BSD */ + +/* Utility library. */ + +#include +#include +#include + +static pid_t fork_and_read_select(const char *what, int delay, int fd) +{ + struct timeval tv; + pid_t pid; + fd_set readfds; + + switch (pid = fork()) { + case -1: + msg_fatal("fork: %m"); + case 0: + tv.tv_sec = delay; + tv.tv_usec = 0; + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + switch (select(fd + 1, &readfds, (fd_set *) 0, &readfds, &tv)) { + case -1: + msg_fatal("select: %m"); + case 0: + msg_info("%s select timed out", what); + exit(0); + default: + msg_info("%s select wakeup", what); + exit(0); + } + default: + return (pid); + } +} + +int main(int argc, char **argv) +{ + int pair1[2]; + int pair2[2]; + + msg_vstream_init(argv[0], VSTREAM_ERR); + +#define DELAY 1 + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair1) < 0) + msg_fatal("socketpair: %m"); + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair2) < 0) + msg_fatal("socketpair: %m"); + + vstream_printf("Doing multiple select on socket1, then write to it...\n"); + vstream_fflush(VSTREAM_OUT); + fork_and_read_select("socket1", DELAY, pair1[0]); /* one */ + fork_and_read_select("socket1", DELAY, pair1[0]); /* two */ + fork_and_read_select("socket2", DELAY, pair2[0]); + fork_and_read_select("stdin", DELAY, 0); + if (write(pair1[1], "", 1) != 1) + msg_fatal("write: %m"); + while (wait((int *) 0) >= 0) + /* void */ ; + return (0); +} diff --git a/src/util/set_eugid.c b/src/util/set_eugid.c new file mode 100644 index 0000000..ef35380 --- /dev/null +++ b/src/util/set_eugid.c @@ -0,0 +1,70 @@ +/*++ +/* NAME +/* set_eugid 3 +/* SUMMARY +/* set effective user and group attributes +/* SYNOPSIS +/* #include +/* +/* void set_eugid(euid, egid) +/* uid_t euid; +/* gid_t egid; +/* +/* void SAVE_AND_SET_EUGID(uid, gid) +/* uid_t uid; +/* gid_t gid; +/* +/* void RESTORE_SAVED_EUGID() +/* DESCRIPTION +/* set_eugid() sets the effective user and group process attributes +/* and updates the process group access list to be just the specified +/* effective group id. +/* +/* SAVE_AND_SET_EUGID() opens a block that executes with the +/* specified privilege. RESTORE_SAVED_EUGID() closes the block. +/* DIAGNOSTICS +/* All system call errors are fatal. +/* SEE ALSO +/* seteuid(2), setegid(2), setgroups(2) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "set_eugid.h" + +/* set_eugid - set effective user and group attributes */ + +void set_eugid(uid_t euid, gid_t egid) +{ + int saved_errno = errno; + + if (geteuid() != 0) + if (seteuid(0)) + msg_fatal("set_eugid: seteuid(0): %m"); + if (setegid(egid) < 0) + msg_fatal("set_eugid: setegid(%ld): %m", (long) egid); + if (setgroups(1, &egid) < 0) + msg_fatal("set_eugid: setgroups(%ld): %m", (long) egid); + if (euid != 0 && seteuid(euid) < 0) + msg_fatal("set_eugid: seteuid(%ld): %m", (long) euid); + if (msg_verbose) + msg_info("set_eugid: euid %ld egid %ld", (long) euid, (long) egid); + errno = saved_errno; +} diff --git a/src/util/set_eugid.h b/src/util/set_eugid.h new file mode 100644 index 0000000..97a523e --- /dev/null +++ b/src/util/set_eugid.h @@ -0,0 +1,43 @@ +#ifndef _SET_EUGID_H_INCLUDED_ +#define _SET_EUGID_H_INCLUDED_ + +/*++ +/* NAME +/* set_eugid 3h +/* SUMMARY +/* set effective user and group attributes +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern void set_eugid(uid_t, gid_t); + + /* + * The following macros open and close a block that runs at a different + * privilege level. To make mistakes with stray curly braces less likely, we + * shape the macros below as the head and tail of a do-while loop. + */ +#define SAVE_AND_SET_EUGID(uid, gid) do { \ + uid_t __set_eugid_uid = geteuid(); \ + gid_t __set_eugid_gid = getegid(); \ + set_eugid((uid), (gid)); + +#define RESTORE_SAVED_EUGID() \ + set_eugid(__set_eugid_uid, __set_eugid_gid); \ + } while (0) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/set_ugid.c b/src/util/set_ugid.c new file mode 100644 index 0000000..bbcb901 --- /dev/null +++ b/src/util/set_ugid.c @@ -0,0 +1,61 @@ +/*++ +/* NAME +/* set_ugid 3 +/* SUMMARY +/* set real, effective and saved user and group attributes +/* SYNOPSIS +/* #include +/* +/* void set_ugid(uid, gid) +/* uid_t uid; +/* gid_t gid; +/* DESCRIPTION +/* set_ugid() sets the real, effective and saved user and group process +/* attributes and updates the process group access list to be just the +/* user's primary group. This operation is irreversible. +/* DIAGNOSTICS +/* All system call errors are fatal. +/* SEE ALSO +/* setuid(2), setgid(2), setgroups(2) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "set_ugid.h" + +/* set_ugid - set real, effective and saved user and group attributes */ + +void set_ugid(uid_t uid, gid_t gid) +{ + int saved_errno = errno; + + if (geteuid() != 0) + if (seteuid(0) < 0) + msg_fatal("seteuid(0): %m"); + if (setgid(gid) < 0) + msg_fatal("setgid(%ld): %m", (long) gid); + if (setgroups(1, &gid) < 0) + msg_fatal("setgroups(1, &%ld): %m", (long) gid); + if (setuid(uid) < 0) + msg_fatal("setuid(%ld): %m", (long) uid); + if (msg_verbose > 1) + msg_info("setugid: uid %ld gid %ld", (long) uid, (long) gid); + errno = saved_errno; +} diff --git a/src/util/set_ugid.h b/src/util/set_ugid.h new file mode 100644 index 0000000..e752beb --- /dev/null +++ b/src/util/set_ugid.h @@ -0,0 +1,29 @@ +#ifndef _SET_UGID_H_INCLUDED_ +#define _SET_UGID_H_INCLUDED_ + +/*++ +/* NAME +/* set_ugid 3h +/* SUMMARY +/* set real, effective and saved user and group attributes +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern void set_ugid(uid_t, gid_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/sigdelay.c b/src/util/sigdelay.c new file mode 100644 index 0000000..6f07a22 --- /dev/null +++ b/src/util/sigdelay.c @@ -0,0 +1,118 @@ +/*++ +/* NAME +/* sigdelay 3 +/* SUMMARY +/* delay/resume signal delivery +/* SYNOPSIS +/* #include +/* +/* void sigdelay() +/* +/* void sigresume() +/* DESCRIPTION +/* sigdelay() delays delivery of signals. Signals that +/* arrive in the mean time will be queued. +/* +/* sigresume() resumes delivery of signals. Signals that have +/* arrived in the mean time will be delivered. +/* DIAGNOSTICS +/* All errors are fatal. +/* BUGS +/* The signal queue may be really short (as in: one per signal type). +/* +/* Some signals such as SIGKILL cannot be blocked. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "posix_signals.h" +#include "sigdelay.h" + +/* Application-specific. */ + +static sigset_t saved_sigmask; +static sigset_t block_sigmask; +static int suspending; +static int siginit_done; + +/* siginit - compute signal mask only once */ + +static void siginit(void) +{ + int sig; + + siginit_done = 1; + sigemptyset(&block_sigmask); + for (sig = 1; sig < NSIG; sig++) + sigaddset(&block_sigmask, sig); +} + +/* sigresume - deliver delayed signals and disable signal delay */ + +void sigresume(void) +{ + if (suspending != 0) { + suspending = 0; + if (sigprocmask(SIG_SETMASK, &saved_sigmask, (sigset_t *) 0) < 0) + msg_fatal("sigresume: sigprocmask: %m"); + } +} + +/* sigdelay - save signal mask and block all signals */ + +void sigdelay(void) +{ + if (siginit_done == 0) + siginit(); + if (suspending == 0) { + suspending = 1; + if (sigprocmask(SIG_BLOCK, &block_sigmask, &saved_sigmask) < 0) + msg_fatal("sigdelay: sigprocmask: %m"); + } +} + +#ifdef TEST + + /* + * Test program - press Ctrl-C twice while signal delivery is delayed, and + * see how many signals are delivered when signal delivery is resumed. + */ + +#include +#include +#include + +static void gotsig(int sig) +{ + printf("Got signal %d\n", sig); +} + +int main(int unused_argc, char **unused_argv) +{ + signal(SIGINT, gotsig); + signal(SIGQUIT, gotsig); + + printf("Delaying signal delivery\n"); + sigdelay(); + sleep(5); + printf("Resuming signal delivery\n"); + sigresume(); + exit(0); +} + +#endif diff --git a/src/util/sigdelay.h b/src/util/sigdelay.h new file mode 100644 index 0000000..d3b4ea3 --- /dev/null +++ b/src/util/sigdelay.h @@ -0,0 +1,31 @@ +#ifndef _SIGDELAY_H_INCLUDED_ +#define _SIGDELAY_H_INCLUDED_ + +/*++ +/* NAME +/* sigdelay 3h +/* SUMMARY +/* delay/resume signal delivery +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void sigdelay(void); +extern void sigresume(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/skipblanks.c b/src/util/skipblanks.c new file mode 100644 index 0000000..fc19284 --- /dev/null +++ b/src/util/skipblanks.c @@ -0,0 +1,43 @@ +/*++ +/* NAME +/* skipblanks 3 +/* SUMMARY +/* skip leading whitespace +/* SYNOPSIS +/* #include +/* +/* char *skipblanks(string) +/* const char *string; +/* DESCRIPTION +/* skipblanks() returns a pointer to the first non-whitespace +/* character in the specified string, or a pointer to the string +/* terminator when the string contains all white-space characters. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include + +/* Utility library. */ + +#include "stringops.h" + +char *skipblanks(const char *string) +{ + const char *cp; + + for (cp = string; *cp != 0; cp++) + if (!ISSPACE(*cp)) + break; + return ((char *) cp); +} diff --git a/src/util/slmdb.c b/src/util/slmdb.c new file mode 100644 index 0000000..499589d --- /dev/null +++ b/src/util/slmdb.c @@ -0,0 +1,938 @@ +/*++ +/* NAME +/* slmdb 3 +/* SUMMARY +/* Simplified LMDB API +/* SYNOPSIS +/* #include +/* +/* int slmdb_init(slmdb, curr_limit, size_incr, hard_limit) +/* SLMDB *slmdb; +/* size_t curr_limit; +/* int size_incr; +/* size_t hard_limit; +/* +/* int slmdb_open(slmdb, path, open_flags, lmdb_flags, slmdb_flags) +/* SLMDB *slmdb; +/* const char *path; +/* int open_flags; +/* int lmdb_flags; +/* int slmdb_flags; +/* +/* int slmdb_close(slmdb) +/* SLMDB *slmdb; +/* +/* int slmdb_get(slmdb, mdb_key, mdb_value) +/* SLMDB *slmdb; +/* MDB_val *mdb_key; +/* MDB_val *mdb_value; +/* +/* int slmdb_put(slmdb, mdb_key, mdb_value, flags) +/* SLMDB *slmdb; +/* MDB_val *mdb_key; +/* MDB_val *mdb_value; +/* int flags; +/* +/* int slmdb_del(slmdb, mdb_key) +/* SLMDB *slmdb; +/* MDB_val *mdb_key; +/* +/* int slmdb_cursor_get(slmdb, mdb_key, mdb_value, op) +/* SLMDB *slmdb; +/* MDB_val *mdb_key; +/* MDB_val *mdb_value; +/* MDB_cursor_op op; +/* AUXILIARY FUNCTIONS +/* int slmdb_fd(slmdb) +/* SLMDB *slmdb; +/* +/* size_t slmdb_curr_limit(slmdb) +/* SLMDB *slmdb; +/* +/* int slmdb_control(slmdb, request, ...) +/* SLMDB *slmdb; +/* int request; +/* DESCRIPTION +/* This module simplifies the LMDB API by hiding recoverable +/* errors from the application. Details are given in the +/* section "ERROR RECOVERY". +/* +/* slmdb_init() performs mandatory initialization before opening +/* an LMDB database. The result value is an LMDB status code +/* (zero in case of success). +/* +/* slmdb_open() opens an LMDB database. The result value is +/* an LMDB status code (zero in case of success). +/* +/* slmdb_close() finalizes an optional bulk-mode transaction +/* and closes a successfully-opened LMDB database. The result +/* value is an LMDB status code (zero in case of success). +/* +/* slmdb_get() is an mdb_get() wrapper with automatic error +/* recovery. The result value is an LMDB status code (zero +/* in case of success). +/* +/* slmdb_put() is an mdb_put() wrapper with automatic error +/* recovery. The result value is an LMDB status code (zero +/* in case of success). +/* +/* slmdb_del() is an mdb_del() wrapper with automatic error +/* recovery. The result value is an LMDB status code (zero +/* in case of success). +/* +/* slmdb_cursor_get() is an mdb_cursor_get() wrapper with +/* automatic error recovery. The result value is an LMDB +/* status code (zero in case of success). This wrapper supports +/* only one cursor per database. +/* +/* slmdb_fd() returns the file descriptor for the specified +/* database. This may be used for file status queries or +/* application-controlled locking. +/* +/* slmdb_curr_limit() returns the current database size limit +/* for the specified database. +/* +/* slmdb_control() specifies optional features. The result is +/* an LMDB status code (zero in case of success). +/* +/* Arguments: +/* .IP slmdb +/* Pointer to caller-provided storage. +/* .IP curr_limit +/* The initial memory mapping size limit. This limit is +/* automatically increased when the database becomes full. +/* .IP size_incr +/* An integer factor by which the memory mapping size limit +/* is increased when the database becomes full. +/* .IP hard_limit +/* The upper bound for the memory mapping size limit. +/* .IP path +/* LMDB database pathname. +/* .IP open_flags +/* Flags that control file open operations. Do not specify +/* locking flags here. +/* .IP lmdb_flags +/* Flags that control the LMDB environment. If MDB_NOLOCK is +/* specified, then each slmdb_get() or slmdb_cursor_get() call +/* must be protected with a shared (or exclusive) external lock, +/* and each slmdb_put() or slmdb_del() call must be protected +/* with an exclusive external lock. A lock may be released +/* after the call returns. A writer may atomically downgrade +/* an exclusive lock to shared, but it must obtain an exclusive +/* lock before making another slmdb(3) write request. +/* .sp +/* Note: when a database is opened with MDB_NOLOCK, external +/* locks such as fcntl() do not protect slmdb(3) requests +/* within the same process against each other. If a program +/* cannot avoid making simultaneous slmdb(3) requests, then +/* it must synchronize these requests with in-process locks, +/* in addition to the per-process fcntl(2) locks. +/* .IP slmdb_flags +/* Bit-wise OR of zero or more of the following: +/* .RS +/* .IP SLMDB_FLAG_BULK +/* Open the database and create a "bulk" transaction that is +/* committed when the database is closed. If MDB_NOLOCK is +/* specified, then the entire transaction must be protected +/* with a persistent external lock. All slmdb_get(), slmdb_put() +/* and slmdb_del() requests will be directed to the "bulk" +/* transaction. +/* .RE +/* .IP mdb_key +/* Pointer to caller-provided lookup key storage. +/* .IP mdb_value +/* Pointer to caller-provided value storage. +/* .IP op +/* LMDB cursor operation. +/* .IP request +/* The start of a list of (name, value) pairs, terminated with +/* CA_SLMDB_CTL_END. The following text enumerates the symbolic +/* request names and the corresponding argument types. +/* .RS +/* .IP "CA_SLMDB_CTL_LONGJMP_FN(void (*)(void *, int))" +/* Call-back function pointer. The function is called to repeat +/* a failed bulk-mode transaction from the start. The arguments +/* are the application context and the setjmp() or sigsetjmp() +/* result value. +/* .IP "CA_SLMDB_CTL_NOTIFY_FN(void (*)(void *, int, ...))" +/* Call-back function pointer. The function is called to report +/* successful error recovery. The arguments are the application +/* context, the MDB error code, and additional arguments that +/* depend on the error code. Details are given in the section +/* "ERROR RECOVERY". +/* .IP "CA_SLMDB_CTL_ASSERT_FN(void (*)(void *, const char *))" +/* Call-back function pointer. The function is called to +/* report an LMDB internal assertion failure. The arguments +/* are the application context, and text that describes the +/* problem. +/* .IP "CA_SLMDB_CTL_CB_CONTEXT(void *)" +/* Application context that is passed in call-back function +/* calls. +/* .IP "CA_SLMDB_CTL_API_RETRY_LIMIT(int)" +/* How many times to recover from LMDB errors within the +/* execution of a single slmdb(3) API call before giving up. +/* .IP "CA_SLMDB_CTL_BULK_RETRY_LIMIT(int)" +/* How many times to recover from a bulk-mode transaction +/* before giving up. +/* .RE +/* ERROR RECOVERY +/* .ad +/* .fi +/* This module automatically repeats failed requests after +/* recoverable errors, up to the limits specified with +/* slmdb_control(). +/* +/* Recoverable errors are reported through an optional +/* notification function specified with slmdb_control(). With +/* recoverable MDB_MAP_FULL and MDB_MAP_RESIZED errors, the +/* additional argument is a size_t value with the updated +/* current database size limit; with recoverable MDB_READERS_FULL +/* errors there is no additional argument. +/* BUGS +/* Recovery from MDB_MAP_FULL involves resizing the database +/* memory mapping. According to LMDB documentation this +/* requires that there is no concurrent activity in the same +/* database by other threads in the same memory address space. +/* SEE ALSO +/* lmdb(3) API manpage (currently, non-existent). +/* AUTHOR(S) +/* Howard Chu +/* Symas Corporation +/* +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * DO NOT include other Postfix-specific header files. This LMDB wrapper + * must be usable outside Postfix. + */ + +#ifdef HAS_LMDB + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include + + /* + * Minimum LMDB patchlevel. + * + * LMDB 0.9.11 allows Postfix daemons to log an LMDB error message instead of + * falling out of the sky without any explanation. Without such logging, + * Postfix with LMDB would be too hard to support. + * + * LMDB 0.9.10 fixes an information leak where LMDB wrote chunks of up to 4096 + * bytes of uninitialized heap memory to a database. This was a security + * violation because it made information persistent that was not meant to be + * persisted, or it was sharing information that was not meant to be shared. + * + * LMDB 0.9.9 allows Postfix to use external (fcntl()-based) locks, instead of + * having to use world-writable LMDB lock files. + * + * LMDB 0.9.8 allows Postfix to update the database size limit on-the-fly, so + * that it can recover from an MDB_MAP_FULL error without having to close + * the database. It also allows an application to "pick up" a new database + * size limit on-the-fly, so that it can recover from an MDB_MAP_RESIZED + * error without having to close the database. + * + * The database size limit that remains is imposed by the hardware memory + * address space (31 or 47 bits, typically) or file system. The LMDB + * implementation is supposed to handle databases larger than physical + * memory. However, this is not necessarily guaranteed for (bulk) + * transactions larger than physical memory. + */ +#if MDB_VERSION_FULL < MDB_VERINT(0, 9, 11) +#error "This Postfix version requires LMDB version 0.9.11 or later" +#endif + + /* + * Error recovery. + * + * The purpose of the slmdb(3) API is to hide LMDB quirks (recoverable + * MAP_FULL, MAP_RESIZED, or MDB_READERS_FULL errors). With these out of the + * way, applications can pretend that those quirks don't exist, and focus on + * their own job. + * + * - To recover from a single-transaction LMDB error, each wrapper function + * uses tail recursion instead of goto. Since LMDB errors are rare, code + * clarity is more important than speed. + * + * - To recover from a bulk-transaction LMDB error, the error-recovery code + * triggers a long jump back into the caller to some pre-arranged point (the + * closest thing that C has to exception handling). The application is then + * expected to repeat the bulk transaction from scratch. + * + * When any code aborts a bulk transaction, it must reset slmdb->txn to null + * to avoid a use-after-free problem in slmdb_close(). + */ + + /* + * Our default retry attempt limits. We allow a few retries per slmdb(3) API + * call for non-bulk transactions. We allow a number of bulk-transaction + * retries that is proportional to the memory address space. + */ +#define SLMDB_DEF_API_RETRY_LIMIT 30 /* Retries per slmdb(3) API call */ +#define SLMDB_DEF_BULK_RETRY_LIMIT \ + (2 * sizeof(size_t) * CHAR_BIT) /* Retries per bulk-mode transaction */ + + /* + * We increment the recursion counter each time we try to recover from + * error, and reset the recursion counter when returning to the application + * from the slmdb(3) API. + */ +#define SLMDB_API_RETURN(slmdb, status) do { \ + (slmdb)->api_retry_count = 0; \ + return (status); \ + } while (0) + + /* + * With MDB_NOLOCK, the application uses an external lock for inter-process + * synchronization. Because the caller may release the external lock after + * an SLMDB API call, each SLMDB API function must use a short-lived + * transaction unless the transaction is a bulk-mode transaction. + */ + +/* slmdb_cursor_close - close cursor and its read transaction */ + +static void slmdb_cursor_close(SLMDB *slmdb) +{ + MDB_txn *txn; + + /* + * Close the cursor and its read transaction. We can restore it later + * from the saved key information. + */ + txn = mdb_cursor_txn(slmdb->cursor); + mdb_cursor_close(slmdb->cursor); + slmdb->cursor = 0; + mdb_txn_abort(txn); +} + +/* slmdb_saved_key_init - initialize saved key info */ + +static void slmdb_saved_key_init(SLMDB *slmdb) +{ + slmdb->saved_key.mv_data = 0; + slmdb->saved_key.mv_size = 0; + slmdb->saved_key_size = 0; +} + +/* slmdb_saved_key_free - destroy saved key info */ + +static void slmdb_saved_key_free(SLMDB *slmdb) +{ + free(slmdb->saved_key.mv_data); + slmdb_saved_key_init(slmdb); +} + +#define HAVE_SLMDB_SAVED_KEY(s) ((s)->saved_key.mv_data != 0) + +/* slmdb_saved_key_assign - copy the saved key */ + +static int slmdb_saved_key_assign(SLMDB *slmdb, MDB_val *key_val) +{ + + /* + * Extend the buffer to fit the key, so that we can avoid malloc() + * overhead most of the time. + */ + if (slmdb->saved_key_size < key_val->mv_size) { + if (slmdb->saved_key.mv_data == 0) + slmdb->saved_key.mv_data = malloc(key_val->mv_size); + else + slmdb->saved_key.mv_data = + realloc(slmdb->saved_key.mv_data, key_val->mv_size); + if (slmdb->saved_key.mv_data == 0) { + slmdb_saved_key_init(slmdb); + return (ENOMEM); + } else { + slmdb->saved_key_size = key_val->mv_size; + } + } + + /* + * Copy the key under the cursor. + */ + memcpy(slmdb->saved_key.mv_data, key_val->mv_data, key_val->mv_size); + slmdb->saved_key.mv_size = key_val->mv_size; + return (0); +} + +/* slmdb_prepare - LMDB-specific (re)initialization before actual access */ + +static int slmdb_prepare(SLMDB *slmdb) +{ + int status = 0; + + /* + * This is called before accessing the database, or after recovery from + * an LMDB error. Note: this code cannot recover from errors itself. + * slmdb->txn is either the database open() transaction or a + * freshly-created bulk-mode transaction. When slmdb_prepare() commits or + * aborts commits a transaction, it must set slmdb->txn to null to avoid + * a use-after-free error in slmdb_close(). + * + * - With O_TRUNC we make a "drop" request before updating the database. + * + * - With a bulk-mode transaction we commit when the database is closed. + */ + if (slmdb->open_flags & O_TRUNC) { + if ((status = mdb_drop(slmdb->txn, slmdb->dbi, 0)) != 0) { + mdb_txn_abort(slmdb->txn); + slmdb->txn = 0; + return (status); + } + if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) == 0) { + status = mdb_txn_commit(slmdb->txn); + slmdb->txn = 0; + if (status != 0) + return (status); + } + } else if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) == 0) { + mdb_txn_abort(slmdb->txn); + slmdb->txn = 0; + } + slmdb->api_retry_count = 0; + return (status); +} + +/* slmdb_recover - recover from LMDB errors */ + +static int slmdb_recover(SLMDB *slmdb, int status) +{ + MDB_envinfo info; + int original_status = status; + + /* + * This may be needed in non-MDB_NOLOCK mode. Recovery is rare enough + * that we don't care about a few wasted cycles. + */ + if (slmdb->cursor != 0) + slmdb_cursor_close(slmdb); + + /* + * Limit the number of recovery attempts per slmdb(3) API request. + */ + if ((slmdb->api_retry_count += 1) >= slmdb->api_retry_limit) + return (status); + + /* + * Limit the number of bulk transaction recovery attempts. + */ + if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0 + && (slmdb->bulk_retry_count += 1) > slmdb->bulk_retry_limit) + return (status); + + /* + * Try to clear the error condition. + */ + switch (status) { + + /* + * As of LMDB 0.9.8 when a non-bulk update runs into a "map full" + * error, we can resize the environment's memory map and clear the + * error condition. The caller should retry immediately. + */ + case MDB_MAP_FULL: + /* Can we increase the memory map? Give up if we can't. */ + if (slmdb->curr_limit < slmdb->hard_limit / slmdb->size_incr) { + slmdb->curr_limit = slmdb->curr_limit * slmdb->size_incr; + } else if (slmdb->curr_limit < slmdb->hard_limit) { + slmdb->curr_limit = slmdb->hard_limit; + } else { + /* Sorry, we are already maxed out. */ + break; + } + if (slmdb->notify_fn) + slmdb->notify_fn(slmdb->cb_context, MDB_MAP_FULL, + slmdb->curr_limit); + status = mdb_env_set_mapsize(slmdb->env, slmdb->curr_limit); + break; + + /* + * When a writer resizes the database, read-only applications must + * increase their LMDB memory map size limit, too. Otherwise, they + * won't be able to read a table after it grows. + * + * As of LMDB 0.9.8 we can import the new memory map size limit into the + * database environment by calling mdb_env_set_mapsize() with a zero + * size argument. Then we extract the map size limit for later use. + * The caller should retry immediately. + */ + case MDB_MAP_RESIZED: + if ((status = mdb_env_set_mapsize(slmdb->env, 0)) == 0) { + /* Do not panic. Maps may shrink after bulk update. */ + mdb_env_info(slmdb->env, &info); + slmdb->curr_limit = info.me_mapsize; + if (slmdb->notify_fn) + slmdb->notify_fn(slmdb->cb_context, MDB_MAP_RESIZED, + slmdb->curr_limit); + } + break; + + /* + * What is it with these built-in hard limits that cause systems to + * stop when demand is at its highest? When the system is under + * stress it should slow down and keep making progress. + */ + case MDB_READERS_FULL: + if (slmdb->notify_fn) + slmdb->notify_fn(slmdb->cb_context, MDB_READERS_FULL); + sleep(1); + status = 0; + break; + + /* + * We can't solve this problem. The application should terminate with + * a fatal run-time error and the program should be re-run later. + */ + default: + break; + } + + /* + * If we cleared the error condition for a non-bulk transaction, return a + * success status. The caller should retry the failed operation + * immediately. + */ + if (status == 0 && (slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0) { + + /* + * We cleared the error condition for a bulk transaction. If the + * transaction is not restartable, return the original error. The + * caller should terminate with a fatal run-time error, and the + * program should be re-run later. + */ + if (slmdb->longjmp_fn == 0) + return (original_status); + + /* + * Rebuild a bulk transaction from scratch, by making a long jump + * back into the caller at some pre-arranged point. In MDB_NOLOCK + * mode, there is no need to upgrade a lock to "exclusive", because a + * failed write transaction has no side effects. + */ + if ((status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0, + slmdb->lmdb_flags & MDB_RDONLY, + &slmdb->txn)) == 0 + && (status = slmdb_prepare(slmdb)) == 0) + slmdb->longjmp_fn(slmdb->cb_context, 1); + } + return (status); +} + +/* slmdb_txn_begin - mdb_txn_begin() wrapper with LMDB error recovery */ + +static int slmdb_txn_begin(SLMDB *slmdb, int rdonly, MDB_txn **txn) +{ + int status; + + if ((status = mdb_txn_begin(slmdb->env, (MDB_txn *) 0, rdonly, txn)) != 0 + && (status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_txn_begin(slmdb, rdonly, txn); + + return (status); +} + +/* slmdb_get - mdb_get() wrapper with LMDB error recovery */ + +int slmdb_get(SLMDB *slmdb, MDB_val *mdb_key, MDB_val *mdb_value) +{ + MDB_txn *txn; + int status; + + /* + * Start a read transaction if there's no bulk-mode txn. + */ + if (slmdb->txn) + txn = slmdb->txn; + else if ((status = slmdb_txn_begin(slmdb, MDB_RDONLY, &txn)) != 0) + SLMDB_API_RETURN(slmdb, status); + + /* + * Do the lookup. + */ + if ((status = mdb_get(txn, slmdb->dbi, mdb_key, mdb_value)) != 0 + && status != MDB_NOTFOUND) { + mdb_txn_abort(txn); + if (txn == slmdb->txn) + slmdb->txn = 0; + if ((status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_get(slmdb, mdb_key, mdb_value); + SLMDB_API_RETURN(slmdb, status); + } + + /* + * Close the read txn if it's not the bulk-mode txn. + */ + if (slmdb->txn == 0) + mdb_txn_abort(txn); + + SLMDB_API_RETURN(slmdb, status); +} + +/* slmdb_put - mdb_put() wrapper with LMDB error recovery */ + +int slmdb_put(SLMDB *slmdb, MDB_val *mdb_key, + MDB_val *mdb_value, int flags) +{ + MDB_txn *txn; + int status; + + /* + * Start a write transaction if there's no bulk-mode txn. + */ + if (slmdb->txn) + txn = slmdb->txn; + else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0) + SLMDB_API_RETURN(slmdb, status); + + /* + * Do the update. + */ + if ((status = mdb_put(txn, slmdb->dbi, mdb_key, mdb_value, flags)) != 0) { + if (status != MDB_KEYEXIST) { + mdb_txn_abort(txn); + if (txn == slmdb->txn) + slmdb->txn = 0; + if ((status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_put(slmdb, mdb_key, mdb_value, flags); + SLMDB_API_RETURN(slmdb, status); + } else { + /* Abort non-bulk transaction only. */ + if (slmdb->txn == 0) + mdb_txn_abort(txn); + } + } + + /* + * Commit the transaction if it's not the bulk-mode txn. + */ + if (status == 0 && slmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0 + && (status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_put(slmdb, mdb_key, mdb_value, flags); + + SLMDB_API_RETURN(slmdb, status); +} + +/* slmdb_del - mdb_del() wrapper with LMDB error recovery */ + +int slmdb_del(SLMDB *slmdb, MDB_val *mdb_key) +{ + MDB_txn *txn; + int status; + + /* + * Start a write transaction if there's no bulk-mode txn. + */ + if (slmdb->txn) + txn = slmdb->txn; + else if ((status = slmdb_txn_begin(slmdb, 0, &txn)) != 0) + SLMDB_API_RETURN(slmdb, status); + + /* + * Do the update. + */ + if ((status = mdb_del(txn, slmdb->dbi, mdb_key, (MDB_val *) 0)) != 0) { + if (status != MDB_NOTFOUND) { + mdb_txn_abort(txn); + if (txn == slmdb->txn) + slmdb->txn = 0; + if ((status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_del(slmdb, mdb_key); + SLMDB_API_RETURN(slmdb, status); + } else { + /* Abort non-bulk transaction only. */ + if (slmdb->txn == 0) + mdb_txn_abort(txn); + } + } + + /* + * Commit the transaction if it's not the bulk-mode txn. + */ + if (status == 0 && slmdb->txn == 0 && (status = mdb_txn_commit(txn)) != 0 + && (status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_del(slmdb, mdb_key); + + SLMDB_API_RETURN(slmdb, status); +} + +/* slmdb_cursor_get - mdb_cursor_get() wrapper with LMDB error recovery */ + +int slmdb_cursor_get(SLMDB *slmdb, MDB_val *mdb_key, + MDB_val *mdb_value, MDB_cursor_op op) +{ + MDB_txn *txn; + int status = 0; + + /* + * TODO: figure how we would recover a failing bulk transaction. + */ + if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0) { + if (slmdb->assert_fn) + slmdb->assert_fn(slmdb->cb_context, + "slmdb_cursor_get: bulk transaction is not supported"); + return (MDB_PANIC); + } + + /* + * Open a read transaction and cursor if needed. + */ + if (slmdb->cursor == 0) { + if ((status = slmdb_txn_begin(slmdb, MDB_RDONLY, &txn)) != 0) + SLMDB_API_RETURN(slmdb, status); + if ((status = mdb_cursor_open(txn, slmdb->dbi, &slmdb->cursor)) != 0) { + mdb_txn_abort(txn); + if ((status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op); + SLMDB_API_RETURN(slmdb, status); + } + + /* + * Restore the cursor position from the saved key information. + */ + if (HAVE_SLMDB_SAVED_KEY(slmdb) && op != MDB_FIRST) + status = mdb_cursor_get(slmdb->cursor, &slmdb->saved_key, + (MDB_val *) 0, MDB_SET); + } + + /* + * Database lookup. + */ + if (status == 0) + status = mdb_cursor_get(slmdb->cursor, mdb_key, mdb_value, op); + + /* + * Save the cursor position if successful. This can fail only with + * ENOMEM. + * + * Close the cursor read transaction if in MDB_NOLOCK mode, because the + * caller may release the external lock after we return. + */ + if (status == 0) { + status = slmdb_saved_key_assign(slmdb, mdb_key); + if (slmdb->lmdb_flags & MDB_NOLOCK) + slmdb_cursor_close(slmdb); + } + + /* + * Handle end-of-database or other error. + */ + else { + /* Do not hand-optimize out the slmdb_cursor_close() calls below. */ + if (status == MDB_NOTFOUND) { + slmdb_cursor_close(slmdb); + if (HAVE_SLMDB_SAVED_KEY(slmdb)) + slmdb_saved_key_free(slmdb); + } else { + slmdb_cursor_close(slmdb); + if ((status = slmdb_recover(slmdb, status)) == 0) + status = slmdb_cursor_get(slmdb, mdb_key, mdb_value, op); + SLMDB_API_RETURN(slmdb, status); + /* Do not hand-optimize out the above return statement. */ + } + } + SLMDB_API_RETURN(slmdb, status); +} + +/* slmdb_assert_cb - report LMDB assertion failure */ + +static void slmdb_assert_cb(MDB_env *env, const char *text) +{ + SLMDB *slmdb = (SLMDB *) mdb_env_get_userctx(env); + + if (slmdb->assert_fn) + slmdb->assert_fn(slmdb->cb_context, text); +} + +/* slmdb_control - control optional settings */ + +int slmdb_control(SLMDB *slmdb, int first,...) +{ + va_list ap; + int status = 0; + int reqno; + int rc; + + va_start(ap, first); + for (reqno = first; status == 0 && reqno != SLMDB_CTL_END; reqno = va_arg(ap, int)) { + switch (reqno) { + case SLMDB_CTL_LONGJMP_FN: + slmdb->longjmp_fn = va_arg(ap, SLMDB_LONGJMP_FN); + break; + case SLMDB_CTL_NOTIFY_FN: + slmdb->notify_fn = va_arg(ap, SLMDB_NOTIFY_FN); + break; + case SLMDB_CTL_ASSERT_FN: + slmdb->assert_fn = va_arg(ap, SLMDB_ASSERT_FN); + if ((rc = mdb_env_set_userctx(slmdb->env, (void *) slmdb)) != 0 + || (rc = mdb_env_set_assert(slmdb->env, slmdb_assert_cb)) != 0) + status = rc; + break; + case SLMDB_CTL_CB_CONTEXT: + slmdb->cb_context = va_arg(ap, void *); + break; + case SLMDB_CTL_API_RETRY_LIMIT: + slmdb->api_retry_limit = va_arg(ap, int); + break; + case SLMDB_CTL_BULK_RETRY_LIMIT: + slmdb->bulk_retry_limit = va_arg(ap, int); + break; + default: + status = errno = EINVAL; + break; + } + } + va_end(ap); + return (status); +} + +/* slmdb_close - wrapper with LMDB error recovery */ + +int slmdb_close(SLMDB *slmdb) +{ + int status = 0; + + /* + * Finish an open bulk transaction. If slmdb_recover() returns after a + * bulk-transaction error, then it was unable to clear the error + * condition, or unable to restart the bulk transaction. + */ + if ((slmdb->slmdb_flags & SLMDB_FLAG_BULK) != 0 && slmdb->txn != 0 + && (status = mdb_txn_commit(slmdb->txn)) != 0) + status = slmdb_recover(slmdb, status); + + /* + * Clean up after an unfinished sequence() operation. + */ + if (slmdb->cursor != 0) + slmdb_cursor_close(slmdb); + + mdb_env_close(slmdb->env); + + /* + * Clean up the saved key information. + */ + if (HAVE_SLMDB_SAVED_KEY(slmdb)) + slmdb_saved_key_free(slmdb); + + SLMDB_API_RETURN(slmdb, status); +} + +/* slmdb_init - mandatory initialization */ + +int slmdb_init(SLMDB *slmdb, size_t curr_limit, int size_incr, + size_t hard_limit) +{ + + /* + * This is a separate operation to keep the slmdb_open() API simple. + * Don't allocate resources here. Just store control information, + */ + slmdb->curr_limit = curr_limit; + slmdb->size_incr = size_incr; + slmdb->hard_limit = hard_limit; + + return (MDB_SUCCESS); +} + +/* slmdb_open - open wrapped LMDB database */ + +int slmdb_open(SLMDB *slmdb, const char *path, int open_flags, + int lmdb_flags, int slmdb_flags) +{ + struct stat st; + MDB_env *env; + MDB_txn *txn; + MDB_dbi dbi; + int db_fd; + int status; + + /* + * Create LMDB environment. + */ + if ((status = mdb_env_create(&env)) != 0) + return (status); + + /* + * Make sure that the memory map has room to store and commit an initial + * "drop" transaction as well as fixed database metadata. We have no way + * to recover from errors before the first application-level I/O request. + */ +#define SLMDB_FUDGE 10240 + + if (slmdb->curr_limit < SLMDB_FUDGE) + slmdb->curr_limit = SLMDB_FUDGE; + if (stat(path, &st) == 0 + && st.st_size > slmdb->curr_limit - SLMDB_FUDGE) { + if (st.st_size > slmdb->hard_limit) + slmdb->hard_limit = st.st_size; + if (st.st_size < slmdb->hard_limit - SLMDB_FUDGE) + slmdb->curr_limit = st.st_size + SLMDB_FUDGE; + else + slmdb->curr_limit = slmdb->hard_limit; + } + + /* + * mdb_open() requires a txn, but since the default DB always exists in + * an LMDB environment, we usually don't need to do anything else with + * the txn. It is currently used for truncate and for bulk transactions. + */ + if ((status = mdb_env_set_mapsize(env, slmdb->curr_limit)) != 0 + || (status = mdb_env_open(env, path, lmdb_flags, 0644)) != 0 + || (status = mdb_txn_begin(env, (MDB_txn *) 0, + lmdb_flags & MDB_RDONLY, &txn)) != 0 + || (status = mdb_open(txn, (const char *) 0, 0, &dbi)) != 0 + || (status = mdb_env_get_fd(env, &db_fd)) != 0) { + mdb_env_close(env); + return (status); + } + + /* + * Bundle up. + */ + slmdb->open_flags = open_flags; + slmdb->lmdb_flags = lmdb_flags; + slmdb->slmdb_flags = slmdb_flags; + slmdb->env = env; + slmdb->dbi = dbi; + slmdb->db_fd = db_fd; + slmdb->cursor = 0; + slmdb_saved_key_init(slmdb); + slmdb->api_retry_count = 0; + slmdb->bulk_retry_count = 0; + slmdb->api_retry_limit = SLMDB_DEF_API_RETRY_LIMIT; + slmdb->bulk_retry_limit = SLMDB_DEF_BULK_RETRY_LIMIT; + slmdb->longjmp_fn = 0; + slmdb->notify_fn = 0; + slmdb->assert_fn = 0; + slmdb->cb_context = 0; + slmdb->txn = txn; + + if ((status = slmdb_prepare(slmdb)) != 0) + mdb_env_close(env); + + return (status); +} + +#endif diff --git a/src/util/slmdb.h b/src/util/slmdb.h new file mode 100644 index 0000000..9469c0f --- /dev/null +++ b/src/util/slmdb.h @@ -0,0 +1,117 @@ +#ifndef _SLMDB_H_INCLUDED_ +#define _SLMDB_H_INCLUDED_ + +/*++ +/* NAME +/* slmdb 3h +/* SUMMARY +/* LMDB API wrapper +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + +#ifdef PATH_LMDB_H +#include PATH_LMDB_H +#else +#include +#endif + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +#ifdef NO_SIGSETJMP +#define SLMDB_JMP_BUF jmp_buf +#else +#define SLMDB_JMP_BUF sigjmp_buf +#endif + + /* + * All data structure members are private. + */ +typedef struct { + size_t curr_limit; /* database soft size limit */ + int size_incr; /* database expansion factor */ + size_t hard_limit; /* database hard size limit */ + int open_flags; /* open() flags */ + int lmdb_flags; /* LMDB-specific flags */ + int slmdb_flags; /* bulk-mode flag */ + MDB_env *env; /* database environment */ + MDB_dbi dbi; /* database instance */ + MDB_txn *txn; /* bulk transaction */ + int db_fd; /* database file handle */ + MDB_cursor *cursor; /* iterator */ + MDB_val saved_key; /* saved cursor key buffer */ + size_t saved_key_size; /* saved cursor key buffer size */ + void (*longjmp_fn) (void *, int);/* exception handling */ + void (*notify_fn) (void *, int,...); /* workaround notification */ + void (*assert_fn) (void *, const char *); /* assert notification */ + void *cb_context; /* call-back context */ + int api_retry_count; /* slmdb(3) API call retry count */ + int bulk_retry_count; /* bulk_mode retry count */ + int api_retry_limit; /* slmdb(3) API call retry limit */ + int bulk_retry_limit; /* bulk_mode retry limit */ +} SLMDB; + +#define SLMDB_FLAG_BULK (1 << 0) + +extern int slmdb_init(SLMDB *, size_t, int, size_t); +extern int slmdb_open(SLMDB *, const char *, int, int, int); +extern int slmdb_get(SLMDB *, MDB_val *, MDB_val *); +extern int slmdb_put(SLMDB *, MDB_val *, MDB_val *, int); +extern int slmdb_del(SLMDB *, MDB_val *); +extern int slmdb_cursor_get(SLMDB *, MDB_val *, MDB_val *, MDB_cursor_op); +extern int slmdb_control(SLMDB *, int,...); +extern int slmdb_close(SLMDB *); + +#define slmdb_fd(slmdb) ((slmdb)->db_fd) +#define slmdb_curr_limit(slmdb) ((slmdb)->curr_limit) + +/* Legacy API: type-unchecked arguments, internal use. */ +#define SLMDB_CTL_END 0 +#define SLMDB_CTL_LONGJMP_FN 1 /* exception handling */ +#define SLMDB_CTL_NOTIFY_FN 2 /* debug logging function */ +#define SLMDB_CTL_CB_CONTEXT 3 /* call-back context */ +#define SLMDB_CTL_API_RETRY_LIMIT 5 /* per slmdb(3) API call */ +#define SLMDB_CTL_BULK_RETRY_LIMIT 6 /* per bulk update */ +#define SLMDB_CTL_ASSERT_FN 7 /* report assertion failure */ + +/* Safer API: type-checked arguments, external use. */ +#define CA_SLMDB_CTL_END SLMDB_CTL_END +#define CA_SLMDB_CTL_LONGJMP_FN(v) SLMDB_CTL_LONGJMP_FN, CHECK_VAL(SLMDB_CTL, SLMDB_LONGJMP_FN, (v)) +#define CA_SLMDB_CTL_NOTIFY_FN(v) SLMDB_CTL_NOTIFY_FN, CHECK_VAL(SLMDB_CTL, SLMDB_NOTIFY_FN, (v)) +#define CA_SLMDB_CTL_CB_CONTEXT(v) SLMDB_CTL_CB_CONTEXT, CHECK_PTR(SLMDB_CTL, void, (v)) +#define CA_SLMDB_CTL_API_RETRY_LIMIT(v) SLMDB_CTL_API_RETRY_LIMIT, CHECK_VAL(SLMDB_CTL, int, (v)) +#define CA_SLMDB_CTL_BULK_RETRY_LIMIT(v) SLMDB_CTL_BULK_RETRY_LIMIT, CHECK_VAL(SLMDB_CTL, int, (v)) +#define CA_SLMDB_CTL_ASSERT_FN(v) SLMDB_CTL_ASSERT_FN, CHECK_VAL(SLMDB_CTL, SLMDB_ASSERT_FN, (v)) + +typedef void (*SLMDB_NOTIFY_FN) (void *, int,...); +typedef void (*SLMDB_LONGJMP_FN) (void *, int); +typedef void (*SLMDB_ASSERT_FN) (void *, const char *); + +CHECK_VAL_HELPER_DCL(SLMDB_CTL, int); +CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_NOTIFY_FN); +CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_LONGJMP_FN); +CHECK_VAL_HELPER_DCL(SLMDB_CTL, SLMDB_ASSERT_FN); +CHECK_PTR_HELPER_DCL(SLMDB_CTL, void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Howard Chu +/* Symas Corporation +/*--*/ + +#endif diff --git a/src/util/sock_addr.c b/src/util/sock_addr.c new file mode 100644 index 0000000..dc5c4b1 --- /dev/null +++ b/src/util/sock_addr.c @@ -0,0 +1,173 @@ +/*++ +/* NAME +/* sock_addr 3 +/* SUMMARY +/* sockaddr utilities +/* SYNOPSIS +/* #include +/* +/* int sock_addr_cmp_addr(sa, sb) +/* const struct sockaddr *sa; +/* const struct sockaddr *sb; +/* +/* int sock_addr_cmp_port(sa, sb) +/* const struct sockaddr *sa; +/* const struct sockaddr *sb; +/* +/* int SOCK_ADDR_EQ_ADDR(sa, sb) +/* const struct sockaddr *sa; +/* const struct sockaddr *sb; +/* +/* int SOCK_ADDR_EQ_PORT(sa, sb) +/* const struct sockaddr *sa; +/* const struct sockaddr *sb; +/* +/* int sock_addr_in_loopback(sa) +/* const struct sockaddr *sa; +/* AUXILIARY MACROS +/* struct sockaddr *SOCK_ADDR_PTR(ptr) +/* unsigned char SOCK_ADDR_FAMILY(ptr) +/* unsigned char SOCK_ADDR_LEN(ptr) +/* unsigned short SOCK_ADDR_PORT(ptr) +/* unsigned short *SOCK_ADDR_PORTP(ptr) +/* +/* struct sockaddr_in *SOCK_ADDR_IN_PTR(ptr) +/* unsigned char SOCK_ADDR_IN_FAMILY(ptr) +/* unsigned short SOCK_ADDR_IN_PORT(ptr) +/* struct in_addr SOCK_ADDR_IN_ADDR(ptr) +/* struct in_addr IN_ADDR(ptr) +/* +/* struct sockaddr_in6 *SOCK_ADDR_IN6_PTR(ptr) +/* unsigned char SOCK_ADDR_IN6_FAMILY(ptr) +/* unsigned short SOCK_ADDR_IN6_PORT(ptr) +/* struct in6_addr SOCK_ADDR_IN6_ADDR(ptr) +/* struct in6_addr IN6_ADDR(ptr) +/* DESCRIPTION +/* These utilities take protocol-independent address structures +/* and perform protocol-dependent operations on structure members. +/* Some of the macros described here are called unsafe, +/* because they evaluate one or more arguments multiple times. +/* +/* sock_addr_cmp_addr() or sock_addr_cmp_port() compare the +/* address family and network address or port fields for +/* equality, and return indication of the difference between +/* their arguments: < 0 if the first argument is "smaller", +/* 0 for equality, and > 0 if the first argument is "larger". +/* +/* The unsafe macros SOCK_ADDR_EQ_ADDR() or SOCK_ADDR_EQ_PORT() +/* compare compare the address family and network address or +/* port fields for equality, and return non-zero when their +/* arguments differ. +/* +/* sock_addr_in_loopback() determines if the argument specifies +/* a loopback address. +/* +/* The SOCK_ADDR_PTR() macro casts a generic pointer to (struct +/* sockaddr *). The name is upper case for consistency not +/* safety. SOCK_ADDR_FAMILY() and SOCK_ADDR_LEN() return the +/* address family and length of the real structure that hides +/* inside a generic sockaddr structure. On systems where struct +/* sockaddr has no sa_len member, SOCK_ADDR_LEN() cannot be +/* used as lvalue. SOCK_ADDR_PORT() returns the IPv4 or IPv6 +/* port number, in network byte order; it must not be used as +/* lvalue. SOCK_ADDR_PORTP() returns a pointer to the same. +/* +/* The macros SOCK_ADDR_IN{,6}_{PTR,FAMILY,PORT,ADDR}() cast +/* a generic pointer to a specific socket address structure +/* pointer, or access a specific socket address structure +/* member. These can be used as lvalues. +/* +/* The unsafe INADDR() and IN6_ADDR() macros dereference a +/* generic pointer to a specific address structure. +/* DIAGNOSTICS +/* Panic: unsupported address family. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* sock_addr_cmp_addr - compare addresses for equality */ + +int sock_addr_cmp_addr(const struct sockaddr *sa, + const struct sockaddr *sb) +{ + if (sa->sa_family != sb->sa_family) + return (sa->sa_family - sb->sa_family); + + /* + * With IPv6 address structures, assume a non-hostile implementation that + * stores the address as a contiguous sequence of bits. Any holes in the + * sequence would invalidate the use of memcmp(). + */ + if (sa->sa_family == AF_INET) { + return (SOCK_ADDR_IN_ADDR(sa).s_addr - SOCK_ADDR_IN_ADDR(sb).s_addr); +#ifdef HAS_IPV6 + } else if (sa->sa_family == AF_INET6) { + return (memcmp((void *) &(SOCK_ADDR_IN6_ADDR(sa)), + (void *) &(SOCK_ADDR_IN6_ADDR(sb)), + sizeof(SOCK_ADDR_IN6_ADDR(sa)))); +#endif + } else { + msg_panic("sock_addr_cmp_addr: unsupported address family %d", + sa->sa_family); + } +} + +/* sock_addr_cmp_port - compare ports for equality */ + +int sock_addr_cmp_port(const struct sockaddr *sa, + const struct sockaddr *sb) +{ + if (sa->sa_family != sb->sa_family) + return (sa->sa_family - sb->sa_family); + + if (sa->sa_family == AF_INET) { + return (SOCK_ADDR_IN_PORT(sa) - SOCK_ADDR_IN_PORT(sb)); +#ifdef HAS_IPV6 + } else if (sa->sa_family == AF_INET6) { + return (SOCK_ADDR_IN6_PORT(sa) - SOCK_ADDR_IN6_PORT(sb)); +#endif + } else { + msg_panic("sock_addr_cmp_port: unsupported address family %d", + sa->sa_family); + } +} + +/* sock_addr_in_loopback - determine if address is loopback */ + +int sock_addr_in_loopback(const struct sockaddr *sa) +{ + unsigned long inaddr; + + if (sa->sa_family == AF_INET) { + inaddr = ntohl(SOCK_ADDR_IN_ADDR(sa).s_addr); + return (IN_CLASSA(inaddr) + && ((inaddr & IN_CLASSA_NET) >> IN_CLASSA_NSHIFT) + == IN_LOOPBACKNET); +#ifdef HAS_IPV6 + } else if (sa->sa_family == AF_INET6) { + return (IN6_IS_ADDR_LOOPBACK(&SOCK_ADDR_IN6_ADDR(sa))); +#endif + } else { + msg_panic("sock_addr_in_loopback: unsupported address family %d", + sa->sa_family); + } +} diff --git a/src/util/sock_addr.h b/src/util/sock_addr.h new file mode 100644 index 0000000..1f5407a --- /dev/null +++ b/src/util/sock_addr.h @@ -0,0 +1,105 @@ +#ifndef _SOCK_ADDR_EQ_H_INCLUDED_ +#define _SOCK_ADDR_EQ_H_INCLUDED_ + +/*++ +/* NAME +/* sock_addr 3h +/* SUMMARY +/* socket address utilities +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include +#include + + /* + * External interface. + */ +#define SOCK_ADDR_PTR(ptr) ((struct sockaddr *)(ptr)) +#define SOCK_ADDR_FAMILY(ptr) SOCK_ADDR_PTR(ptr)->sa_family +#ifdef HAS_SA_LEN +#define SOCK_ADDR_LEN(ptr) SOCK_ADDR_PTR(ptr)->sa_len +#endif + +#define SOCK_ADDR_IN_PTR(sa) ((struct sockaddr_in *)(sa)) +#define SOCK_ADDR_IN_FAMILY(sa) SOCK_ADDR_IN_PTR(sa)->sin_family +#define SOCK_ADDR_IN_PORT(sa) SOCK_ADDR_IN_PTR(sa)->sin_port +#define SOCK_ADDR_IN_ADDR(sa) SOCK_ADDR_IN_PTR(sa)->sin_addr +#define IN_ADDR(ia) (*((struct in_addr *) (ia))) + +extern int sock_addr_cmp_addr(const struct sockaddr *, const struct sockaddr *); +extern int sock_addr_cmp_port(const struct sockaddr *, const struct sockaddr *); +extern int sock_addr_in_loopback(const struct sockaddr *); + +#ifdef HAS_IPV6 + +#ifndef HAS_SA_LEN +#define SOCK_ADDR_LEN(sa) \ + (SOCK_ADDR_PTR(sa)->sa_family == AF_INET6 ? \ + sizeof(struct sockaddr_in6) : sizeof(struct sockaddr_in)) +#endif + +#define SOCK_ADDR_PORT(sa) \ + (SOCK_ADDR_PTR(sa)->sa_family == AF_INET6 ? \ + SOCK_ADDR_IN6_PORT(sa) : SOCK_ADDR_IN_PORT(sa)) +#define SOCK_ADDR_PORTP(sa) \ + (SOCK_ADDR_PTR(sa)->sa_family == AF_INET6 ? \ + &SOCK_ADDR_IN6_PORT(sa) : &SOCK_ADDR_IN_PORT(sa)) + +#define SOCK_ADDR_IN6_PTR(sa) ((struct sockaddr_in6 *)(sa)) +#define SOCK_ADDR_IN6_FAMILY(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_family +#define SOCK_ADDR_IN6_PORT(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_port +#define SOCK_ADDR_IN6_ADDR(sa) SOCK_ADDR_IN6_PTR(sa)->sin6_addr +#define IN6_ADDR(ia) (*((struct in6_addr *) (ia))) + +#define SOCK_ADDR_EQ_ADDR(sa, sb) \ + ((SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \ + && SOCK_ADDR_IN_ADDR(sa).s_addr == SOCK_ADDR_IN_ADDR(sb).s_addr) \ + || (SOCK_ADDR_FAMILY(sa) == AF_INET6 && SOCK_ADDR_FAMILY(sb) == AF_INET6 \ + && memcmp((char *) &(SOCK_ADDR_IN6_ADDR(sa)), \ + (char *) &(SOCK_ADDR_IN6_ADDR(sb)), \ + sizeof(SOCK_ADDR_IN6_ADDR(sa))) == 0)) + +#define SOCK_ADDR_EQ_PORT(sa, sb) \ + ((SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \ + && SOCK_ADDR_IN_PORT(sa) == SOCK_ADDR_IN_PORT(sb)) \ + || (SOCK_ADDR_FAMILY(sa) == AF_INET6 && SOCK_ADDR_FAMILY(sb) == AF_INET6 \ + && SOCK_ADDR_IN6_PORT(sa) == SOCK_ADDR_IN6_PORT(sb))) + +#else + +#ifndef HAS_SA_LEN +#define SOCK_ADDR_LEN(sa) sizeof(struct sockaddr_in) +#endif + +#define SOCK_ADDR_PORT(sa) SOCK_ADDR_IN_PORT(sa)) +#define SOCK_ADDR_PORTP(sa) &SOCK_ADDR_IN_PORT(sa)) + +#define SOCK_ADDR_EQ_ADDR(sa, sb) \ + (SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \ + && SOCK_ADDR_IN_ADDR(sa).s_addr == SOCK_ADDR_IN_ADDR(sb).s_addr) + +#define SOCK_ADDR_EQ_PORT(sa, sb) \ + (SOCK_ADDR_FAMILY(sa) == AF_INET && SOCK_ADDR_FAMILY(sb) == AF_INET \ + && SOCK_ADDR_IN_PORT(sa) == SOCK_ADDR_IN_PORT(sb)) + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/spawn_command.c b/src/util/spawn_command.c new file mode 100644 index 0000000..739e012 --- /dev/null +++ b/src/util/spawn_command.c @@ -0,0 +1,313 @@ +/*++ +/* NAME +/* spawn_command 3 +/* SUMMARY +/* run external command +/* SYNOPSIS +/* #include +/* +/* WAIT_STATUS_T spawn_command(key, value, ...) +/* int key; +/* DESCRIPTION +/* spawn_command() runs a command in a child process and returns +/* the command exit status. +/* +/* Arguments: +/* .IP key +/* spawn_command() takes a list of macros with arguments, +/* terminated by CA_SPAWN_CMD_END which has no arguments. The +/* following is a listing of macros and expected argument +/* types. +/* .RS +/* .IP "CA_SPAWN_CMD_COMMAND(const char *)" +/* Specifies the command to execute as a string. The string is +/* passed to the shell when it contains shell meta characters +/* or when it appears to be a shell built-in command, otherwise +/* the command is executed without invoking a shell. +/* One of CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified. +/* See also the SPAWN_CMD_SHELL attribute below. +/* .IP "CA_SPAWN_CMD_ARGV(char **)" +/* The command is specified as an argument vector. This vector is +/* passed without further inspection to the \fIexecvp\fR() routine. +/* One of CA_SPAWN_CMD_COMMAND or CA_SPAWN_CMD_ARGV must be specified. +/* .IP "CA_SPAWN_CMD_ENV(char **)" +/* Additional environment information, in the form of a null-terminated +/* list of name, value, name, value, ... elements. By default only the +/* command search path is initialized to _PATH_DEFPATH. +/* .IP "CA_SPAWN_CMD_EXPORT(char **)" +/* Null-terminated array of names of environment parameters that can +/* be exported. By default, everything is exported. +/* .IP "CA_SPAWN_CMD_STDIN(int)" +/* .IP "CA_SPAWN_CMD_STDOUT(int)" +/* .IP "CA_SPAWN_CMD_STDERR(int)" +/* Each of these specifies I/O redirection of one of the standard file +/* descriptors for the command. +/* .IP "CA_SPAWN_CMD_UID(uid_t)" +/* The user ID to execute the command as. The value -1 is reserved +/* and cannot be specified. +/* .IP "CA_SPAWN_CMD_GID(gid_t)" +/* The group ID to execute the command as. The value -1 is reserved +/* and cannot be specified. +/* .IP "CA_SPAWN_CMD_TIME_LIMIT(int)" +/* The amount of time in seconds the command is allowed to run before +/* it is terminated with SIGKILL. The default is no time limit. +/* .IP "CA_SPAWN_CMD_SHELL(const char *)" +/* The shell to use when executing the command specified with +/* CA_SPAWN_CMD_COMMAND. This shell is invoked regardless of the +/* command content. +/* .RE +/* DIAGNOSTICS +/* Panic: interface violations (for example, a missing command). +/* +/* Fatal error: fork() failure, other system call failures. +/* +/* spawn_command() returns the exit status as defined by wait(2). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* SEE ALSO +/* exec_command(3) execute command +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_PATHS_H +#include +#endif +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +struct spawn_args { + char **argv; /* argument vector */ + char *command; /* or a plain string */ + int stdin_fd; /* read stdin here */ + int stdout_fd; /* write stdout here */ + int stderr_fd; /* write stderr here */ + uid_t uid; /* privileges */ + gid_t gid; /* privileges */ + char **env; /* extra environment */ + char **export; /* exportable environment */ + char *shell; /* command shell */ + int time_limit; /* command time limit */ +}; + +/* get_spawn_args - capture the variadic argument list */ + +static void get_spawn_args(struct spawn_args * args, int init_key, va_list ap) +{ + const char *myname = "get_spawn_args"; + int key; + + /* + * First, set the default values. + */ + args->argv = 0; + args->command = 0; + args->stdin_fd = -1; + args->stdout_fd = -1; + args->stderr_fd = -1; + args->uid = (uid_t) - 1; + args->gid = (gid_t) - 1; + args->env = 0; + args->export = 0; + args->shell = 0; + args->time_limit = 0; + + /* + * Then, override the defaults with user-supplied inputs. + */ + for (key = init_key; key != SPAWN_CMD_END; key = va_arg(ap, int)) { + switch (key) { + case SPAWN_CMD_ARGV: + if (args->command) + msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", + myname); + args->argv = va_arg(ap, char **); + break; + case SPAWN_CMD_COMMAND: + if (args->argv) + msg_panic("%s: specify SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", + myname); + args->command = va_arg(ap, char *); + break; + case SPAWN_CMD_STDIN: + args->stdin_fd = va_arg(ap, int); + break; + case SPAWN_CMD_STDOUT: + args->stdout_fd = va_arg(ap, int); + break; + case SPAWN_CMD_STDERR: + args->stderr_fd = va_arg(ap, int); + break; + case SPAWN_CMD_UID: + args->uid = va_arg(ap, uid_t); + if (args->uid == (uid_t) (-1)) + msg_panic("spawn_command: request with reserved user ID: -1"); + break; + case SPAWN_CMD_GID: + args->gid = va_arg(ap, gid_t); + if (args->gid == (gid_t) (-1)) + msg_panic("spawn_command: request with reserved group ID: -1"); + break; + case SPAWN_CMD_TIME_LIMIT: + args->time_limit = va_arg(ap, int); + break; + case SPAWN_CMD_ENV: + args->env = va_arg(ap, char **); + break; + case SPAWN_CMD_EXPORT: + args->export = va_arg(ap, char **); + break; + case SPAWN_CMD_SHELL: + args->shell = va_arg(ap, char *); + break; + default: + msg_panic("%s: unknown key: %d", myname, key); + } + } + if (args->command == 0 && args->argv == 0) + msg_panic("%s: missing SPAWN_CMD_ARGV or SPAWN_CMD_COMMAND", myname); + if (args->command == 0 && args->shell != 0) + msg_panic("%s: SPAWN_CMD_ARGV cannot be used with SPAWN_CMD_SHELL", + myname); +} + +/* spawn_command - execute command with extreme prejudice */ + +WAIT_STATUS_T spawn_command(int key,...) +{ + const char *myname = "spawn_comand"; + va_list ap; + pid_t pid; + WAIT_STATUS_T wait_status; + struct spawn_args args; + char **cpp; + ARGV *argv; + int err; + + /* + * Process the variadic argument list. This also does sanity checks on + * what data the caller is passing to us. + */ + va_start(ap, key); + get_spawn_args(&args, key, ap); + va_end(ap); + + /* + * For convenience... + */ + if (args.command == 0) + args.command = args.argv[0]; + + /* + * Spawn off a child process and irrevocably change privilege to the + * user. This includes revoking all rights on open files (via the close + * on exec flag). If we cannot run the command now, try again some time + * later. + */ + switch (pid = fork()) { + + /* + * Error. Instead of trying again right now, back off, give the + * system a chance to recover, and try again later. + */ + case -1: + msg_fatal("fork: %m"); + + /* + * Child. Run the child in a separate process group so that the + * parent can kill not just the child but also its offspring. + */ + case 0: + if (args.uid != (uid_t) - 1 || args.gid != (gid_t) - 1) + set_ugid(args.uid, args.gid); + setsid(); + + /* + * Pipe plumbing. + */ + if ((args.stdin_fd >= 0 && DUP2(args.stdin_fd, STDIN_FILENO) < 0) + || (args.stdout_fd >= 0 && DUP2(args.stdout_fd, STDOUT_FILENO) < 0) + || (args.stderr_fd >= 0 && DUP2(args.stderr_fd, STDERR_FILENO) < 0)) + msg_fatal("%s: dup2: %m", myname); + + /* + * Environment plumbing. Always reset the command search path. XXX + * That should probably be done by clean_env(). + */ + if (args.export) + clean_env(args.export); + if (setenv("PATH", _PATH_DEFPATH, 1)) + msg_fatal("%s: setenv: %m", myname); + if (args.env) + for (cpp = args.env; *cpp; cpp += 2) + if (setenv(cpp[0], cpp[1], 1)) + msg_fatal("setenv: %m"); + + /* + * Process plumbing. If possible, avoid running a shell. + */ + closelog(); + if (args.argv) { + execvp(args.argv[0], args.argv); + msg_fatal("%s: execvp %s: %m", myname, args.argv[0]); + } else if (args.shell && *args.shell) { + argv = argv_split(args.shell, CHARS_SPACE); + argv_add(argv, args.command, (char *) 0); + argv_terminate(argv); + execvp(argv->argv[0], argv->argv); + msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]); + } else { + exec_command(args.command); + } + /* NOTREACHED */ + + /* + * Parent. + */ + default: + + /* + * Be prepared for the situation that the child does not terminate. + * Make sure that the child terminates before the parent attempts to + * retrieve its exit status, otherwise the parent could become stuck, + * and the mail system would eventually run out of exec daemons. Do a + * thorough job, and kill not just the child process but also its + * offspring. + */ + if ((err = timed_waitpid(pid, &wait_status, 0, args.time_limit)) < 0 + && errno == ETIMEDOUT) { + msg_warn("%s: process id %lu: command time limit exceeded", + args.command, (unsigned long) pid); + kill(-pid, SIGKILL); + err = waitpid(pid, &wait_status, 0); + } + if (err < 0) + msg_fatal("wait: %m"); + return (wait_status); + } +} diff --git a/src/util/spawn_command.h b/src/util/spawn_command.h new file mode 100644 index 0000000..7d16413 --- /dev/null +++ b/src/util/spawn_command.h @@ -0,0 +1,66 @@ +#ifndef _SPAWN_COMMAND_H_INCLUDED_ +#define _SPAWN_COMMAND_H_INCLUDED_ + +/*++ +/* NAME +/* spawn_command 3h +/* SUMMARY +/* run external command +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + +/* Legacy API: type-unchecked arguments, internal use. */ +#define SPAWN_CMD_END 0 /* terminator */ +#define SPAWN_CMD_ARGV 1 /* command is array */ +#define SPAWN_CMD_COMMAND 2 /* command is string */ +#define SPAWN_CMD_STDIN 3 /* mail_copy() flags */ +#define SPAWN_CMD_STDOUT 4 /* mail_copy() sender */ +#define SPAWN_CMD_STDERR 5 /* mail_copy() recipient */ +#define SPAWN_CMD_UID 6 /* privileges */ +#define SPAWN_CMD_GID 7 /* privileges */ +#define SPAWN_CMD_TIME_LIMIT 8 /* time limit */ +#define SPAWN_CMD_ENV 9 /* extra environment */ +#define SPAWN_CMD_SHELL 10 /* alternative shell */ +#define SPAWN_CMD_EXPORT 11 /* exportable parameters */ + +/* Safer API: type-checked arguments, external use. */ +#define CA_SPAWN_CMD_END SPAWN_CMD_END +#define CA_SPAWN_CMD_ARGV(v) SPAWN_CMD_ARGV, CHECK_PPTR(CA_SPAWN_CMD, char, (v)) +#define CA_SPAWN_CMD_COMMAND(v) SPAWN_CMD_COMMAND, CHECK_CPTR(CA_SPAWN_CMD, char, (v)) +#define CA_SPAWN_CMD_STDIN(v) SPAWN_CMD_STDIN, CHECK_VAL(CA_SPAWN_CMD, int, (v)) +#define CA_SPAWN_CMD_STDOUT(v) SPAWN_CMD_STDOUT, CHECK_VAL(CA_SPAWN_CMD, int, (v)) +#define CA_SPAWN_CMD_STDERR(v) SPAWN_CMD_STDERR, CHECK_VAL(CA_SPAWN_CMD, int, (v)) +#define CA_SPAWN_CMD_UID(v) SPAWN_CMD_UID, CHECK_VAL(CA_SPAWN_CMD, uid_t, (v)) +#define CA_SPAWN_CMD_GID(v) SPAWN_CMD_GID, CHECK_VAL(CA_SPAWN_CMD, gid_t, (v)) +#define CA_SPAWN_CMD_TIME_LIMIT(v) SPAWN_CMD_TIME_LIMIT, CHECK_VAL(CA_SPAWN_CMD, int, (v)) +#define CA_SPAWN_CMD_ENV(v) SPAWN_CMD_ENV, CHECK_PPTR(CA_SPAWN_CMD, char, (v)) +#define CA_SPAWN_CMD_SHELL(v) SPAWN_CMD_SHELL, CHECK_CPTR(CA_SPAWN_CMD, char, (v)) +#define CA_SPAWN_CMD_EXPORT(v) SPAWN_CMD_EXPORT, CHECK_PPTR(CA_SPAWN_CMD, char, (v)) + +CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, uid_t); +CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, int); +CHECK_VAL_HELPER_DCL(CA_SPAWN_CMD, gid_t); +CHECK_PPTR_HELPER_DCL(CA_SPAWN_CMD, char); +CHECK_CPTR_HELPER_DCL(CA_SPAWN_CMD, char); + +extern WAIT_STATUS_T spawn_command(int,...); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/split_at.c b/src/util/split_at.c new file mode 100644 index 0000000..c75e902 --- /dev/null +++ b/src/util/split_at.c @@ -0,0 +1,71 @@ +/*++ +/* NAME +/* split_at 3 +/* SUMMARY +/* trivial token splitter +/* SYNOPSIS +/* #include +/* +/* char *split_at(string, delimiter) +/* char *string; +/* int delimiter +/* +/* char *split_at_right(string, delimiter) +/* char *string; +/* int delimiter +/* DESCRIPTION +/* split_at() null-terminates the \fIstring\fR at the first +/* occurrence of the \fIdelimiter\fR character found, and +/* returns a pointer to the remainder. +/* +/* split_at_right() looks for the rightmost delimiter +/* occurrence, but is otherwise identical to split_at(). +/* DIAGNOSTICS +/* The result is a null pointer when the delimiter character +/* was not found. +/* HISTORY +/* .ad +/* .fi +/* A split_at() routine appears in the TCP Wrapper software +/* by Wietse Venema. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries */ + +#include +#include + +/* Utility library. */ + +#include "split_at.h" + +/* split_at - break string at first delimiter, return remainder */ + +char *split_at(char *string, int delimiter) +{ + char *cp; + + if ((cp = strchr(string, delimiter)) != 0) + *cp++ = 0; + return (cp); +} + +/* split_at_right - break string at last delimiter, return remainder */ + +char *split_at_right(char *string, int delimiter) +{ + char *cp; + + if ((cp = strrchr(string, delimiter)) != 0) + *cp++ = 0; + return (cp); +} diff --git a/src/util/split_at.h b/src/util/split_at.h new file mode 100644 index 0000000..2d03ebb --- /dev/null +++ b/src/util/split_at.h @@ -0,0 +1,35 @@ +#ifndef _SPLIT_AT_H_INCLUDED_ +#define _SPLIT_AT_H_INCLUDED_ + +/*++ +/* NAME +/* split_at 3h +/* SUMMARY +/* trivial token splitter +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern char *split_at(char *, int); +extern char *split_at_right(char *, int); + +/* HISTORY +/* .ad +/* .fi +/* A split_at() routine appears in the TCP Wrapper software +/* by Wietse Venema. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/split_nameval.c b/src/util/split_nameval.c new file mode 100644 index 0000000..da74fcf --- /dev/null +++ b/src/util/split_nameval.c @@ -0,0 +1,97 @@ +/*++ +/* NAME +/* split_nameval 3 +/* SUMMARY +/* name-value splitter +/* SYNOPSIS +/* #include +/* +/* const char *split_nameval(buf, name, value) +/* char *buf; +/* char **name; +/* char **value; +/* DESCRIPTION +/* split_nameval() takes a logical line from readlline() and expects +/* text of the form "name = value" or "name =". The buffer +/* argument is broken up into name and value substrings. +/* +/* Arguments: +/* .IP buf +/* Result from readlline() or equivalent. The buffer is modified. +/* .IP name +/* Upon successful completion, this is set to the name +/* substring. +/* .IP value +/* Upon successful completion, this is set to the value +/* substring. +/* FEATURES +/* SEE ALSO +/* dict(3) mid-level dictionary routines +/* BUGS +/* DIAGNOSTICS +/* Fatal errors: out of memory. +/* +/* The result is a null pointer in case of success, a string +/* describing the error otherwise: missing '=' after attribute +/* name; missing attribute name. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include +#include + +/* Utility library. */ + +#include +#include + +/* split_nameval - split text into name and value */ + +const char *split_nameval(char *buf, char **name, char **value) +{ + char *np; /* name substring */ + char *vp; /* value substring */ + char *cp; + char *ep; + + /* + * Ugly macros to make complex expressions less unreadable. + */ +#define SKIP(start, var, cond) do { \ + for (var = start; *var && (cond); var++) \ + /* void */; \ + } while (0) + +#define TRIM(s) do { \ + char *p; \ + for (p = (s) + strlen(s); p > (s) && ISSPACE(p[-1]); p--) \ + /* void */; \ + *p = 0; \ + } while (0) + + SKIP(buf, np, ISSPACE(*np)); /* find name begin */ + if (*np == 0 || *np == '=') + return ("missing attribute name"); + SKIP(np, ep, !ISSPACE(*ep) && *ep != '='); /* find name end */ + SKIP(ep, cp, ISSPACE(*cp)); /* skip blanks before '=' */ + if (*cp != '=') /* need '=' */ + return ("missing '=' after attribute name"); + *ep = 0; /* terminate name */ + cp++; /* skip over '=' */ + SKIP(cp, vp, ISSPACE(*vp)); /* skip leading blanks */ + TRIM(vp); /* trim trailing blanks */ + *name = np; + *value = vp; + return (0); +} diff --git a/src/util/split_qnameval.c b/src/util/split_qnameval.c new file mode 100644 index 0000000..b166125 --- /dev/null +++ b/src/util/split_qnameval.c @@ -0,0 +1,168 @@ +/*++ +/* NAME +/* split_qnameval 3 +/* SUMMARY +/* name-value splitter +/* SYNOPSIS +/* #include +/* +/* const char *split_qnameval(buf, name, value) +/* char *buf; +/* char **name; +/* char **value; +/* DESCRIPTION +/* split_qnameval() expects text of the form "key = value" +/* or "key =", where the key may be quoted with backslash or +/* double quotes. The buffer argument is broken up into the key +/* and value substrings. +/* +/* Arguments: +/* .IP buf +/* Result from readlline() or equivalent. The buffer is modified. +/* .IP key +/* Upon successful completion, this is set to the key +/* substring. +/* .IP value +/* Upon successful completion, this is set to the value +/* substring. +/* SEE ALSO +/* split_nameval(3) name-value splitter +/* BUGS +/* DIAGNOSTICS +/* The result is a null pointer in case of success, a string +/* describing the error otherwise: missing '=' after attribute +/* name; missing attribute name. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* split_qnameval - split "key = value", support quoted key */ + +const char *split_qnameval(char *buf, char **pkey, char **pvalue) +{ + int in_quotes = 0; + char *key; + char *key_end; + char *value; + + for (key = buf; *key && ISSPACE(*key); key++) + /* void */ ; + if (*key == 0) + return ("no key found; expected format: key = value"); + + for (key_end = key; *key_end; key_end++) { + if (*key_end == '\\') { + if (*++key_end == 0) + break; + } else if (ISSPACE(*key_end) || *key_end == '=') { + if (!in_quotes) + break; + } else if (*key_end == '"') { + in_quotes = !in_quotes; + } + } + if (in_quotes) { + return ("unbalanced '\"\'"); + } + value = key_end; + while (ISSPACE(*value)) + value++; + if (*value != '=') + return ("missing '=' after attribute name"); + *key_end = 0; + do { + value++; + } while (ISSPACE(*value)); + trimblanks(value, 0)[0] = 0; + *pkey = key; + *pvalue = value; + return (0); +} + +#ifdef TEST + +#include +#include +#include + +#include + +static int compare(int test_number, const char *what, + const char *expect, const char *real) +{ + if ((expect == 0 && real == 0) + || (expect != 0 && real != 0 && strcmp(expect, real) == 0)) { + return (0); + } else { + msg_warn("test %d: %s mis-match: expect='%s', real='%s'", + test_number, what, expect ? expect : "(null)", + real ? real : "(null)"); + return (1); + } +} + +int main(int argc, char **argv) +{ + struct test_info { + const char *input; + const char *expect_result; + const char *expect_key; + const char *expect_value; + }; + static const struct test_info test_info[] = { + /* Unquoted keys. */ + {"xx = yy", 0, "xx", "yy"}, + {"xx=yy", 0, "xx", "yy"}, + {"xx =", 0, "xx", ""}, + {"xx=", 0, "xx", ""}, + {"xx", "missing '=' after attribute name", 0, 0}, + /* Quoted keys. */ + {"\"xx \" = yy", 0, "\"xx \"", "yy"}, + {"\"xx \"= yy", 0, "\"xx \"", "yy"}, + {"\"xx \" =", 0, "\"xx \"", ""}, + {"\"xx \"=", 0, "\"xx \"", ""}, + {"\"xx \"", "missing '=' after attribute name", 0, 0}, + {"\"xx ", "unbalanced '\"'", 0, 0}, + /* Backslash escapes. */ + {"\"\\\"xx \" = yy", 0, "\"\\\"xx \"", "yy"}, + {0,}, + }; + + int errs = 0; + const struct test_info *tp; + + for (tp = test_info; tp->input != 0; tp++) { + const char *result; + char *key = 0; + char *value = 0; + char *buf = mystrdup(tp->input); + int test_number = (int) (tp - test_info); + + result = split_qnameval(buf, &key, &value); + errs += compare(test_number, "result", tp->expect_result, result); + errs += compare(test_number, "key", tp->expect_key, key); + errs += compare(test_number, "value", tp->expect_value, value); + myfree(buf); + } + exit(errs); +} + +#endif diff --git a/src/util/stat_as.c b/src/util/stat_as.c new file mode 100644 index 0000000..3e05ff7 --- /dev/null +++ b/src/util/stat_as.c @@ -0,0 +1,73 @@ +/*++ +/* NAME +/* stat_as 3 +/* SUMMARY +/* stat file as user +/* SYNOPSIS +/* #include +/* #include +/* +/* int stat_as(path, st, euid, egid) +/* const char *path; +/* struct stat *st; +/* uid_t euid; +/* gid_t egid; +/* DESCRIPTION +/* stat_as() looks up the file status of the named \fIpath\fR, +/* using the effective rights specified by \fIeuid\fR +/* and \fIegid\fR, and stores the result into the structure pointed +/* to by \fIst\fR. A -1 result means the lookup failed. +/* This call follows symbolic links. +/* DIAGNOSTICS +/* Fatal error: no permission to change privilege level. +/* SEE ALSO +/* set_eugid(3) switch effective rights +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "set_eugid.h" +#include "stat_as.h" +#include "warn_stat.h" + +/* stat_as - stat file as user */ + +int stat_as(const char *path, struct stat * st, uid_t euid, gid_t egid) +{ + uid_t saved_euid = geteuid(); + gid_t saved_egid = getegid(); + int status; + + /* + * Switch to the target user privileges. + */ + set_eugid(euid, egid); + + /* + * Stat that file. + */ + status = stat(path, st); + + /* + * Restore saved privileges. + */ + set_eugid(saved_euid, saved_egid); + + return (status); +} diff --git a/src/util/stat_as.h b/src/util/stat_as.h new file mode 100644 index 0000000..90bc05d --- /dev/null +++ b/src/util/stat_as.h @@ -0,0 +1,35 @@ +#ifndef _STAT_AS_H_INCLUDED_ +#define _STAT_AS_H_INCLUDED_ + +/*++ +/* NAME +/* stat_as 3h +/* SUMMARY +/* stat file as user +/* SYNOPSIS +/* #include +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int WARN_UNUSED_RESULT stat_as(const char *, struct stat *, uid_t, gid_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/strcasecmp.c b/src/util/strcasecmp.c new file mode 100644 index 0000000..07e9381 --- /dev/null +++ b/src/util/strcasecmp.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)strcasecmp.c 8.1 (Berkeley) 6/4/93"; +#endif /* LIBC_SCCS and not lint */ + +#include +#include + +int strcasecmp(const char *s1, const char *s2) +{ + const unsigned char *us1 = (const unsigned char *) s1; + const unsigned char *us2 = (const unsigned char *) s2; + + while (tolower(*us1) == tolower(*us2++)) + if (*us1++ == '\0') + return (0); + return (tolower(*us1) - tolower(*--us2)); +} + +int strncasecmp(const char *s1, const char *s2, size_t n) +{ + if (n != 0) { + const unsigned char *us1 = (const unsigned char *) s1; + const unsigned char *us2 = (const unsigned char *) s2; + + do { + if (tolower(*us1) != tolower(*us2++)) + return (tolower(*us1) - tolower(*--us2)); + if (*us1++ == '\0') + break; + } while (--n != 0); + } + return (0); +} diff --git a/src/util/strcasecmp_utf8.c b/src/util/strcasecmp_utf8.c new file mode 100644 index 0000000..e3f20df --- /dev/null +++ b/src/util/strcasecmp_utf8.c @@ -0,0 +1,216 @@ +/*++ +/* NAME +/* strcasecmp_utf8 3 +/* SUMMARY +/* caseless string comparison +/* SYNOPSIS +/* #include +/* +/* int strcasecmp_utf8( +/* const char *s1, +/* const char *s2) +/* +/* int strncasecmp_utf8( +/* const char *s1, +/* const char *s2, +/* ssize_t len) +/* AUXILIARY FUNCTIONS +/* int strcasecmp_utf8x( +/* int flags, +/* const char *s1, +/* const char *s2) +/* +/* int strncasecmp_utf8x( +/* int flags, +/* const char *s1, +/* const char *s2, +/* ssize_t len) +/* DESCRIPTION +/* strcasecmp_utf8() implements caseless string comparison for +/* UTF-8 text, with an API similar to strcasecmp(). Only ASCII +/* characters are casefolded when the code is compiled without +/* EAI support or when util_utf8_enable is zero. +/* +/* strncasecmp_utf8() implements caseless string comparison +/* for UTF-8 text, with an API similar to strncasecmp(). Only +/* ASCII characters are casefolded when the code is compiled +/* without EAI support or when util_utf8_enable is zero. +/* +/* strcasecmp_utf8x() and strncasecmp_utf8x() implement a more +/* complex API that provides the above functionality and more. +/* +/* Arguments: +/* .IP "s1, s2" +/* Null-terminated strings to be compared. +/* .IP len +/* String length before casefolding. +/* .IP flags +/* Zero or CASEF_FLAG_UTF8. The latter flag enables UTF-8 case +/* folding instead of folding only ASCII characters. This flag +/* is ignored when compiled without EAI support. +/* SEE ALSO +/* casefold(), casefold text for caseless comparison. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + + /* + * Utility library. + */ +#include + +#define STR(x) vstring_str(x) + +static VSTRING *f1; /* casefold result for s1 */ +static VSTRING *f2; /* casefold result for s2 */ + +/* strcasecmp_utf8_init - initialize */ + +static void strcasecmp_utf8_init(void) +{ + f1 = vstring_alloc(100); + f2 = vstring_alloc(100); +} + +/* strcasecmp_utf8x - caseless string comparison */ + +int strcasecmp_utf8x(int flags, const char *s1, const char *s2) +{ + + /* + * Short-circuit optimization for ASCII-only text. This may be slower + * than using a cache for all results. We must not expose strcasecmp(3) + * to non-ASCII text. + */ + if (allascii(s1) && allascii(s2)) + return (strcasecmp(s1, s2)); + + if (f1 == 0) + strcasecmp_utf8_init(); + + /* + * Cross our fingers and hope that strcmp() remains agnostic of + * charactersets and locales. + */ + flags &= CASEF_FLAG_UTF8; + casefoldx(flags, f1, s1, -1); + casefoldx(flags, f2, s2, -1); + return (strcmp(STR(f1), STR(f2))); +} + +/* strncasecmp_utf8x - caseless string comparison */ + +int strncasecmp_utf8x(int flags, const char *s1, const char *s2, + ssize_t len) +{ + + /* + * Consider using a cache for all results. + */ + if (f1 == 0) + strcasecmp_utf8_init(); + + /* + * Short-circuit optimization for ASCII-only text. This may be slower + * than using a cache for all results. See comments above for limitations + * of strcasecmp(). + */ + if (allascii_len(s1, len) && allascii_len(s2, len)) + return (strncasecmp(s1, s2, len)); + + /* + * Caution: casefolding may change the number of bytes. See comments + * above for concerns about strcmp(). + */ + flags &= CASEF_FLAG_UTF8; + casefoldx(flags, f1, s1, len); + casefoldx(flags, f2, s2, len); + return (strcmp(STR(f1), STR(f2))); +} + +#ifdef TEST +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(1); + ARGV *cmd; + char **args; + int len; + int flags; + int res; + + msg_vstream_init(argv[0], VSTREAM_ERR); + flags = CASEF_FLAG_UTF8; + util_utf8_enable = 1; + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + vstream_printf("> %s\n", STR(buffer)); + cmd = argv_split(STR(buffer), CHARS_SPACE); + if (cmd->argc == 0 || cmd->argv[0][0] == '#') + continue; + args = cmd->argv; + + /* + * Compare two strings. + */ + if (strcmp(args[0], "compare") == 0 && cmd->argc == 3) { + res = strcasecmp_utf8x(flags, args[1], args[2]); + vstream_printf("\"%s\" %s \"%s\"\n", + args[1], + res < 0 ? "<" : res == 0 ? "==" : ">", + args[2]); + } + + /* + * Compare two substrings. + */ + else if (strcmp(args[0], "compare-len") == 0 && cmd->argc == 4 + && sscanf(args[3], "%d", &len) == 1 && len >= 0) { + res = strncasecmp_utf8x(flags, args[1], args[2], len); + vstream_printf("\"%.*s\" %s \"%.*s\"\n", + len, args[1], + res < 0 ? "<" : res == 0 ? "==" : ">", + len, args[2]); + } + + /* + * Usage. + */ + else { + vstream_printf("Usage: %s compare | compare-len \n", + argv[0]); + } + vstream_fflush(VSTREAM_OUT); + argv_free(cmd); + } + exit(0); +} + +#endif /* TEST */ diff --git a/src/util/strcasecmp_utf8_test.in b/src/util/strcasecmp_utf8_test.in new file mode 100644 index 0000000..8bafbf0 --- /dev/null +++ b/src/util/strcasecmp_utf8_test.in @@ -0,0 +1,10 @@ +compare Δημοσθένους.example.com δημοσθένουσ.example.com +compare Δημοσθένους.example.com ηδμοσθένουσ.example.com +compare ηδμοσθένουσ.example.com Δημοσθένους.example.com +compare HeLlO.ExAmPlE.CoM hello.example.com +compare HeLlO hellp +compare hellp HeLlO +compare-len HeLlO hellp 4 +compare-len HeLO help 4 +compare abcde abcdf +compare YYY€€€XXX yyy€€€xxx diff --git a/src/util/strcasecmp_utf8_test.ref b/src/util/strcasecmp_utf8_test.ref new file mode 100644 index 0000000..8c0e1a5 --- /dev/null +++ b/src/util/strcasecmp_utf8_test.ref @@ -0,0 +1,20 @@ +> compare Δημοσθένους.example.com δημοσθένουσ.example.com +"Δημοσθένους.example.com" == "δημοσθένουσ.example.com" +> compare Δημοσθένους.example.com ηδμοσθένουσ.example.com +"Δημοσθένους.example.com" < "ηδμοσθένουσ.example.com" +> compare ηδμοσθένουσ.example.com Δημοσθένους.example.com +"ηδμοσθένουσ.example.com" > "Δημοσθένους.example.com" +> compare HeLlO.ExAmPlE.CoM hello.example.com +"HeLlO.ExAmPlE.CoM" == "hello.example.com" +> compare HeLlO hellp +"HeLlO" < "hellp" +> compare hellp HeLlO +"hellp" > "HeLlO" +> compare-len HeLlO hellp 4 +"HeLl" == "hell" +> compare-len HeLO help 4 +"HeLO" < "help" +> compare abcde abcdf +"abcde" < "abcdf" +> compare YYY€€€XXX yyy€€€xxx +"YYY€€€XXX" == "yyy€€€xxx" diff --git a/src/util/stream_connect.c b/src/util/stream_connect.c new file mode 100644 index 0000000..b8cc624 --- /dev/null +++ b/src/util/stream_connect.c @@ -0,0 +1,109 @@ +/*++ +/* NAME +/* stream_connect 3 +/* SUMMARY +/* connect to stream listener +/* SYNOPSIS +/* #include +/* +/* int stream_connect(path, block_mode, timeout) +/* const char *path; +/* int block_mode; +/* int timeout; +/* DESCRIPTION +/* stream_connect() connects to a stream listener for the specified +/* pathname, and returns the resulting file descriptor. +/* +/* Arguments: +/* .IP path +/* Null-terminated string with listener endpoint name. +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking stream, or BLOCKING for +/* blocking mode. However, a stream connection succeeds or fails +/* immediately. +/* .IP timeout +/* This argument is ignored; it is present for compatibility with +/* other interfaces. Stream connections succeed or fail immediately. +/* DIAGNOSTICS +/* The result is -1 in case the connection could not be made. +/* Fatal errors: other system call failures. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +#ifdef STREAM_CONNECTIONS + +#include +#include +#include +#include +#include + +#endif + +/* Utility library. */ + +#include +#include + +/* stream_connect - connect to stream listener */ + +int stream_connect(const char *path, int block_mode, int unused_timeout) +{ +#ifdef STREAM_CONNECTIONS + const char *myname = "stream_connect"; + int pair[2]; + int fifo; + + /* + * The requested file system object must exist, otherwise we can't reach + * the server. + */ + if ((fifo = open(path, O_WRONLY | O_NONBLOCK, 0)) < 0) + return (-1); + + /* + * This is for {unix,inet}_connect() compatibility. + */ + if (block_mode == BLOCKING) + non_blocking(fifo, BLOCKING); + + /* + * Create a pipe, and send one pipe end to the server. + */ + if (pipe(pair) < 0) + msg_fatal("%s: pipe: %m", myname); + if (ioctl(fifo, I_SENDFD, pair[1]) < 0) + msg_fatal("%s: send file descriptor: %m", myname); + close(pair[1]); + + /* + * This is for {unix,inet}_connect() compatibility. + */ + if (block_mode == NON_BLOCKING) + non_blocking(pair[0], NON_BLOCKING); + + /* + * Cleanup. + */ + close(fifo); + + /* + * Keep the other end of the pipe. + */ + return (pair[0]); +#else + msg_fatal("stream connections are not implemented"); +#endif +} diff --git a/src/util/stream_listen.c b/src/util/stream_listen.c new file mode 100644 index 0000000..b522b76 --- /dev/null +++ b/src/util/stream_listen.c @@ -0,0 +1,102 @@ +/*++ +/* NAME +/* stream_listen 3 +/* SUMMARY +/* start stream listener +/* SYNOPSIS +/* #include +/* +/* int stream_listen(path, backlog, block_mode) +/* const char *path; +/* int backlog; +/* int block_mode; +/* +/* int stream_accept(fd) +/* int fd; +/* DESCRIPTION +/* This module implements a substitute local IPC for systems that do +/* not have properly-working UNIX-domain sockets. +/* +/* stream_listen() creates a listener endpoint with the specified +/* permissions, and returns a file descriptor to be used for accepting +/* connections. +/* +/* stream_accept() accepts a connection. +/* +/* Arguments: +/* .IP path +/* Null-terminated string with connection destination. +/* .IP backlog +/* This argument exists for compatibility and is ignored. +/* .IP block_mode +/* Either NON_BLOCKING or BLOCKING. This does not affect the +/* mode of accepted connections. +/* .IP fd +/* File descriptor returned by stream_listen(). +/* DIAGNOSTICS +/* Fatal errors: stream_listen() aborts upon any system call failure. +/* stream_accept() leaves all error handling up to the caller. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include + +#ifdef STREAM_CONNECTIONS + +#include +#include +#include +#include +#include + +#endif + +/* Utility library. */ + +#include "msg.h" +#include "listen.h" + +/* stream_listen - create stream listener */ + +int stream_listen(const char *path, int unused_backlog, int block_mode) +{ +#ifdef STREAM_CONNECTIONS + + /* + * We can't specify a listen backlog, however, sending file descriptors + * across a FIFO gives us a backlog buffer of 460 on Solaris 2.4/SPARC. + */ + return (fifo_listen(path, 0622, block_mode)); +#else + msg_fatal("stream connections are not implemented"); +#endif +} + +/* stream_accept - accept stream connection */ + +int stream_accept(int fd) +{ +#ifdef STREAM_CONNECTIONS + struct strrecvfd fdinfo; + + /* + * This will return EAGAIN on a non-blocking stream when someone else + * snatched the connection from us. + */ + if (ioctl(fd, I_RECVFD, &fdinfo) < 0) + return (-1); + return (fdinfo.fd); +#else + msg_fatal("stream connections are not implemented"); +#endif +} diff --git a/src/util/stream_recv_fd.c b/src/util/stream_recv_fd.c new file mode 100644 index 0000000..5be02e4 --- /dev/null +++ b/src/util/stream_recv_fd.c @@ -0,0 +1,120 @@ +/*++ +/* NAME +/* stream_recv_fd 3 +/* SUMMARY +/* receive file descriptor +/* SYNOPSIS +/* #include +/* +/* int stream_recv_fd(fd) +/* int fd; +/* DESCRIPTION +/* stream_recv_fd() receives a file descriptor via the specified +/* stream. The result value is the received descriptor. +/* +/* Arguments: +/* .IP fd +/* File descriptor that connects the sending and receiving processes. +/* DIAGNOSTICS +/* stream_recv_fd() returns -1 upon failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include /* includes */ + +#ifdef STREAM_CONNECTIONS + +#include +#include +#include +#include +#include + +#endif + +/* Utility library. */ + +#include +#include + +/* stream_recv_fd - receive file descriptor */ + +int stream_recv_fd(int fd) +{ +#ifdef STREAM_CONNECTIONS + struct strrecvfd fdinfo; + + /* + * This will return EAGAIN on a non-blocking stream when someone else + * snatched the connection from us. + */ + if (ioctl(fd, I_RECVFD, &fdinfo) < 0) + return (-1); + return (fdinfo.fd); +#else + msg_fatal("stream connections are not implemented"); +#endif +} + +#ifdef TEST + + /* + * Proof-of-concept program. Receive a descriptor (presumably from the + * stream_send_fd test program) and copy its content until EOF. + */ +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + char *transport; + char *endpoint; + int listen_sock; + int client_sock; + int client_fd; + ssize_t read_count; + char buf[1024]; + + if (argc != 2 + || (endpoint = split_at(transport = argv[1], ':')) == 0 + || *endpoint == 0 || *transport == 0) + msg_fatal("usage: %s transport:endpoint", argv[0]); + + if (strcmp(transport, "stream") == 0) { + listen_sock = stream_listen(endpoint, BLOCKING, 0); + } else { + msg_fatal("invalid transport name: %s", transport); + } + if (listen_sock < 0) + msg_fatal("listen %s:%s: %m", transport, endpoint); + + client_sock = stream_accept(listen_sock); + if (client_sock < 0) + msg_fatal("stream_accept: %m"); + + while ((client_fd = stream_recv_fd(client_sock)) >= 0) { + msg_info("client_fd = %d", client_fd); + while ((read_count = read(client_fd, buf, sizeof(buf))) > 0) + write(1, buf, read_count); + if (read_count < 0) + msg_fatal("read: %m"); + if (close(client_fd) != 0) + msg_fatal("close(%d): %m", client_fd); + } + exit(0); +} + +#endif diff --git a/src/util/stream_send_fd.c b/src/util/stream_send_fd.c new file mode 100644 index 0000000..0a9aebf --- /dev/null +++ b/src/util/stream_send_fd.c @@ -0,0 +1,115 @@ +/*++ +/* NAME +/* stream_send_fd 3 +/* SUMMARY +/* send file descriptor +/* SYNOPSIS +/* #include +/* +/* int stream_send_fd(fd, sendfd) +/* int fd; +/* int sendfd; +/* DESCRIPTION +/* stream_send_fd() sends a file descriptor over the specified +/* stream. +/* +/* Arguments: +/* .IP fd +/* File descriptor that connects the sending and receiving processes. +/* .IP sendfd +/* The file descriptor to be sent. +/* DIAGNOSTICS +/* stream_send_fd() returns -1 upon failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include /* includes */ + +#ifdef STREAM_CONNECTIONS + +#include +#include +#include +#include +#include + +#endif + +/* Utility library. */ + +#include +#include + +/* stream_send_fd - send file descriptor */ + +int stream_send_fd(int fd, int sendfd) +{ +#ifdef STREAM_CONNECTIONS + const char *myname = "stream_send_fd"; + + if (ioctl(fd, I_SENDFD, sendfd) < 0) + msg_fatal("%s: send file descriptor %d: %m", myname, sendfd); + return (0); +#else + msg_fatal("stream connections are not implemented"); +#endif +} + +#ifdef TEST + + /* + * Proof-of-concept program. Open a file and send the descriptor, presumably + * to the stream_recv_fd test program. + */ +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + char *transport; + char *endpoint; + char *path; + int server_sock; + int client_fd; + + if (argc < 3 + || (endpoint = split_at(transport = argv[1], ':')) == 0 + || *endpoint == 0 || *transport == 0) + msg_fatal("usage: %s transport:endpoint file...", argv[0]); + + if (strcmp(transport, "stream") == 0) { + server_sock = stream_connect(endpoint, BLOCKING, 0); + } else { + msg_fatal("invalid transport name: %s", transport); + } + if (server_sock < 0) + msg_fatal("connect %s:%s: %m", transport, endpoint); + + argv += 2; + while ((path = *argv++) != 0) { + if ((client_fd = open(path, O_RDONLY, 0)) < 0) + msg_fatal("open %s: %m", path); + msg_info("path=%s client_fd=%d", path, client_fd); + if (stream_send_fd(server_sock, client_fd) < 0) + msg_fatal("send file descriptor: %m"); + if (close(client_fd) != 0) + msg_fatal("close(%d): %m", client_fd); + } + exit(0); +} + +#endif diff --git a/src/util/stream_test.c b/src/util/stream_test.c new file mode 100644 index 0000000..5c8f82f --- /dev/null +++ b/src/util/stream_test.c @@ -0,0 +1,111 @@ +#include "sys_defs.h" +#include +#include +#include +#include + +#include "iostuff.h" +#include "msg.h" +#include "msg_vstream.h" +#include "listen.h" +#include "connect.h" + +#ifdef SUNOS5 +#include + +#define FIFO "/tmp/test-fifo" + +static const char *progname; + +static void print_fstat(int fd) +{ + struct stat st; + + if (fstat(fd, &st) < 0) + msg_fatal("fstat: %m"); + vstream_printf("fd %d\n", fd); + vstream_printf("dev %ld\n", (long) st.st_dev); + vstream_printf("ino %ld\n", (long) st.st_ino); + vstream_fflush(VSTREAM_OUT); +} + +static NORETURN usage(void) +{ + msg_fatal("usage: %s [-p] [-n count] [-v]", progname); +} + +int main(int argc, char **argv) +{ + int server_fd; + int client_fd; + int fd; + int print_fstats = 0; + int count = 1; + int ch; + int i; + + progname = argv[0]; + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "pn:v")) > 0) { + switch (ch) { + default: + usage(); + case 'p': + print_fstats = 1; + break; + case 'n': + if ((count = atoi(optarg)) < 1) + usage(); + break; + case 'v': + msg_verbose++; + break; + } + } + server_fd = stream_listen(FIFO, 0, 0); + if (readable(server_fd)) + msg_fatal("server fd is readable after create"); + + /* + * Connect in client. + */ + for (i = 0; i < count; i++) { + msg_info("connect attempt %d", i); + if ((client_fd = stream_connect(FIFO, 0, 0)) < 0) + msg_fatal("open %s as client: %m", FIFO); + if (readable(server_fd)) + msg_info("server fd is readable after client open"); + if (close(client_fd) < 0) + msg_fatal("close client fd: %m"); + } + + /* + * Accept in server. + */ + for (i = 0; i < count; i++) { + msg_info("receive attempt %d", i); + if (!readable(server_fd)) { + msg_info("wait for server fd to become readable"); + read_wait(server_fd, -1); + } + if ((fd = stream_accept(server_fd)) < 0) + msg_fatal("receive fd: %m"); + if (print_fstats) + print_fstat(fd); + if (close(fd) < 0) + msg_fatal("close received fd: %m"); + } + if (close(server_fd) < 0) + msg_fatal("close server fd"); + return (0); +} +#else +int main(int argc, char **argv) +{ + return (0); +} +#endif diff --git a/src/util/stream_trigger.c b/src/util/stream_trigger.c new file mode 100644 index 0000000..4feea5f --- /dev/null +++ b/src/util/stream_trigger.c @@ -0,0 +1,130 @@ +/*++ +/* NAME +/* stream_trigger 3 +/* SUMMARY +/* wakeup stream server +/* SYNOPSIS +/* #include +/* +/* int stream_trigger(service, buf, len, timeout) +/* const char *service; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* stream_trigger() wakes up the named stream server by making +/* a brief connection to it and writing the named buffer. +/* +/* The connection is closed by a background thread. Some kernels +/* cannot handle client-side disconnect before the server has +/* received the message. +/* +/* Arguments: +/* .IP service +/* Name of the communication endpoint. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Deadline in seconds. Specify a value <= 0 to disable +/* the time limit. +/* DIAGNOSTICS +/* The result is zero in case of success, -1 in case of problems. +/* SEE ALSO +/* stream_connect(3), stream client +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +struct stream_trigger { + int fd; + char *service; +}; + +/* stream_trigger_event - disconnect from peer */ + +static void stream_trigger_event(int event, void *context) +{ + struct stream_trigger *sp = (struct stream_trigger *) context; + static const char *myname = "stream_trigger_event"; + + /* + * Disconnect. + */ + if (event == EVENT_TIME) + msg_warn("%s: read timeout for service %s", myname, sp->service); + event_disable_readwrite(sp->fd); + event_cancel_timer(stream_trigger_event, context); + if (close(sp->fd) < 0) + msg_warn("%s: close %s: %m", myname, sp->service); + myfree(sp->service); + myfree((void *) sp); +} + +/* stream_trigger - wakeup stream server */ + +int stream_trigger(const char *service, const char *buf, ssize_t len, int timeout) +{ + const char *myname = "stream_trigger"; + struct stream_trigger *sp; + int fd; + + if (msg_verbose > 1) + msg_info("%s: service %s", myname, service); + + /* + * Connect... + */ + if ((fd = stream_connect(service, BLOCKING, timeout)) < 0) { + if (msg_verbose) + msg_warn("%s: connect to %s: %m", myname, service); + return (-1); + } + close_on_exec(fd, CLOSE_ON_EXEC); + + /* + * Stash away context. + */ + sp = (struct stream_trigger *) mymalloc(sizeof(*sp)); + sp->fd = fd; + sp->service = mystrdup(service); + + /* + * Write the request... + */ + if (write_buf(fd, buf, len, timeout) < 0 + || write_buf(fd, "", 1, timeout) < 0) + if (msg_verbose) + msg_warn("%s: write to %s: %m", myname, service); + + /* + * Wakeup when the peer disconnects, or when we lose patience. + */ + if (timeout > 0) + event_request_timer(stream_trigger_event, (void *) sp, timeout + 100); + event_enable_read(fd, stream_trigger_event, (void *) sp); + return (0); +} diff --git a/src/util/stringops.h b/src/util/stringops.h new file mode 100644 index 0000000..8ac177b --- /dev/null +++ b/src/util/stringops.h @@ -0,0 +1,107 @@ +#ifndef _STRINGOPS_H_INCLUDED_ +#define _STRINGOPS_H_INCLUDED_ + +/*++ +/* NAME +/* stringops 3h +/* SUMMARY +/* string operations +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern int util_utf8_enable; +extern char *printable_except(char *, int, const char *); +extern char *neuter(char *, const char *, int); +extern char *lowercase(char *); +extern char *casefoldx(int, VSTRING *, const char *, ssize_t); +extern char *uppercase(char *); +extern char *skipblanks(const char *); +extern char *trimblanks(char *, ssize_t); +extern char *concatenate(const char *,...); +extern char *mystrtok(char **, const char *); +extern char *mystrtokq(char **, const char *, const char *); +extern char *mystrtokdq(char **, const char *); +extern char *translit(char *, const char *, const char *); + +#define printable(string, replacement) \ + printable_except((string), (replacement), (char *) 0) + +#ifndef HAVE_BASENAME +#define basename postfix_basename +extern char *basename(const char *); + +#endif +extern char *sane_basename(VSTRING *, const char *); +extern char *sane_dirname(VSTRING *, const char *); +extern VSTRING *unescape(VSTRING *, const char *); +extern VSTRING *escape(VSTRING *, const char *, ssize_t); +extern int alldig(const char *); +extern int allalnum(const char *); +extern int allprint(const char *); +extern int allspace(const char *); +extern int allascii_len(const char *, ssize_t); +extern const char *WARN_UNUSED_RESULT split_nameval(char *, char **, char **); +extern const char *WARN_UNUSED_RESULT split_qnameval(char *, char **, char **); +extern int valid_utf8_string(const char *, ssize_t); +extern size_t balpar(const char *, const char *); +extern char *WARN_UNUSED_RESULT extpar(char **, const char *, int); +extern int strcasecmp_utf8x(int, const char *, const char *); +extern int strncasecmp_utf8x(int, const char *, const char *, ssize_t); + +#define EXTPAR_FLAG_NONE (0) +#define EXTPAR_FLAG_STRIP (1<<0) /* "{ text }" -> "text" */ +#define EXTPAR_FLAG_EXTRACT (1<<1) /* hint from caller's caller */ + +#define CASEF_FLAG_UTF8 (1<<0) +#define CASEF_FLAG_APPEND (1<<1) + + /* + * Convenience wrappers for most-common use cases. + */ +#define allascii(s) allascii_len((s), -1) +#define casefold(dst, src) \ + casefoldx(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (dst), (src), -1) +#define casefold_len(dst, src, len) \ + casefoldx(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (dst), (src), (len)) +#define casefold_append(dst, src) \ + casefoldx((util_utf8_enable ? CASEF_FLAG_UTF8 : 0) | CASEF_FLAG_APPEND, \ + (dst), (src), -1) + +#define strcasecmp_utf8(s1, s2) \ + strcasecmp_utf8x(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (s1), (s2)) +#define strncasecmp_utf8(s1, s2, l) \ + strncasecmp_utf8x(util_utf8_enable ? CASEF_FLAG_UTF8 : 0, (s1), (s2), (l)) + + /* + * Use STRREF(x) instead of x, to shut up compiler warnings when the operand + * is a string literal. + */ +#define STRREF(x) (&x[0]) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/surrogate.ref b/src/util/surrogate.ref new file mode 100644 index 0000000..b8ecd8e --- /dev/null +++ b/src/util/surrogate.ref @@ -0,0 +1,55 @@ +./dict_open: error: cidr:/xx map requires O_RDONLY access mode +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: cidr:/xx is unavailable. cidr:/xx map requires O_RDONLY access mode +foo: error +./dict_open: error: open /xx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: cidr:/xx is unavailable. open /xx: No such file or directory +foo: error +./dict_open: error: pcre:/xx map requires O_RDONLY access mode +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: pcre:/xx is unavailable. pcre:/xx map requires O_RDONLY access mode +foo: error +./dict_open: error: open /xx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: pcre:/xx is unavailable. open /xx: No such file or directory +foo: error +./dict_open: error: regexp:/xx map requires O_RDONLY access mode +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: regexp:/xx is unavailable. regexp:/xx map requires O_RDONLY access mode +foo: error +./dict_open: error: open /xx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: regexp:/xx is unavailable. open /xx: No such file or directory +foo: error +./dict_open: error: unix:xx map requires O_RDONLY access mode +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: unix:xx is unavailable. unix:xx map requires O_RDONLY access mode +foo: error +./dict_open: error: unknown table: unix:xx +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: unix:xx is unavailable. unknown table: unix:xx +foo: error +./dict_open: error: texthash:/xx map requires O_RDONLY access mode +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: texthash:/xx is unavailable. texthash:/xx map requires O_RDONLY access mode +foo: error +./dict_open: error: open database /xx: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: texthash:/xx is unavailable. open database /xx: No such file or directory +foo: error +./dict_open: error: open database /xx.db: No such file or directory +owner=trusted (uid=2147483647) +> get foo +./dict_open: warning: hash:/xx is unavailable. open database /xx.db: No such file or directory +foo: error diff --git a/src/util/sys_compat.c b/src/util/sys_compat.c new file mode 100644 index 0000000..8bf8e58 --- /dev/null +++ b/src/util/sys_compat.c @@ -0,0 +1,389 @@ +/*++ +/* NAME +/* sys_compat 3 +/* SUMMARY +/* compatibility routines +/* SYNOPSIS +/* #include +/* +/* void closefrom(int lowfd) +/* int lowfd; +/* +/* const char *strerror(err) +/* int err; +/* +/* int setenv(name, value, clobber) +/* const char *name; +/* const char *value; +/* int clobber; +/* +/* int unsetenv(name) +/* const char *name; +/* +/* int seteuid(euid) +/* uid_t euid; +/* +/* int setegid(egid) +/* gid_t euid; +/* +/* int mkfifo(path, mode) +/* char *path; +/* int mode; +/* +/* int waitpid(pid, statusp, options) +/* int pid; +/* WAIT_STATUS_T *statusp; +/* int options; +/* +/* int setsid() +/* +/* void dup2_pass_on_exec(int oldd, int newd) +/* +/* char *inet_ntop(af, src, dst, size) +/* int af; +/* const void *src; +/* char *dst; +/* SOCKADDR_SIZE size; +/* +/* int inet_pton(af, src, dst) +/* int af; +/* const char *src; +/* void *dst; +/* DESCRIPTION +/* These routines are compiled for platforms that lack the functionality +/* or that have broken versions that we prefer to stay away from. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" + + /* + * ANSI strerror() emulation + */ +#ifdef MISSING_STRERROR + +extern int errno; +extern char *sys_errlist[]; +extern int sys_nerr; + +#include + +/* strerror - print text corresponding to error */ + +const char *strerror(int err) +{ + static VSTRING *buf; + + if (err < 0 || err >= sys_nerr) { + if (buf == 0) + buf = vstring_alloc(10); + vstring_sprintf(buf, "Unknown error %d", err); + return (vstring_str(buf)); + } else { + return (sys_errlist[errno]); + } +} + +#endif + + /* + * setenv() emulation on top of putenv(). + */ +#ifdef MISSING_SETENV + +#include +#include +#include + +/* setenv - update or insert environment (name,value) pair */ + +int setenv(const char *name, const char *value, int clobber) +{ + char *cp; + + if (clobber == 0 && getenv(name) != 0) + return (0); + if ((cp = malloc(strlen(name) + strlen(value) + 2)) == 0) + return (1); + sprintf(cp, "%s=%s", name, value); + return (putenv(cp)); +} + +/* unsetenv - remove all instances of the name */ + +int unsetenv(const char *name) +{ + extern char **environ; + ssize_t name_len = strlen(name); + char **src_pp; + char **dst_pp; + + for (dst_pp = src_pp = environ; *src_pp; src_pp++, dst_pp++) { + if (strncmp(*src_pp, name, name_len) == 0 + && *(*src_pp + name_len) == '=') { + dst_pp--; + } else if (dst_pp != src_pp) { + *dst_pp = *src_pp; + } + } + *dst_pp = 0; + return (0); +} + +#endif + + /* + * seteuid() and setegid() emulation, the HP-UX way + */ +#ifdef MISSING_SETEUID +#ifdef HAVE_SETRESUID +#include + +int seteuid(uid_t euid) +{ + return setresuid(-1, euid, -1); +} + +#else +#error MISSING_SETEUID +#endif + +#endif + +#ifdef MISSING_SETEGID +#ifdef HAVE_SETRESGID +#include + +int setegid(gid_t egid) +{ + return setresgid(-1, egid, -1); +} + +#else +#error MISSING_SETEGID +#endif + +#endif + + /* + * mkfifo() emulation - requires superuser privileges + */ +#ifdef MISSING_MKFIFO + +#include + +int mkfifo(char *path, int mode) +{ + return mknod(path, (mode & ~_S_IFMT) | _S_IFIFO, 0); +} + +#endif + + /* + * waitpid() emulation on top of Berkeley UNIX wait4() + */ +#ifdef MISSING_WAITPID +#ifdef HAS_WAIT4 + +#include +#include + +int waitpid(int pid, WAIT_STATUS_T *status, int options) +{ + if (pid == -1) + pid = 0; + return wait4(pid, status, options, (struct rusage *) 0); +} + +#else +#error MISSING_WAITPID +#endif + +#endif + + /* + * setsid() emulation, the Berkeley UNIX way + */ +#ifdef MISSING_SETSID + +#include +#include +#include +#include + +#ifdef TIOCNOTTY + +#include + +int setsid(void) +{ + int p = getpid(); + int fd; + + if (setpgrp(p, p)) + return -1; + + fd = open("/dev/tty", O_RDONLY, 0); + if (fd >= 0 || errno != ENXIO) { + if (fd < 0) { + msg_warn("open /dev/tty: %m"); + return -1; + } + if (ioctl(fd, TIOCNOTTY, 0)) { + msg_warn("ioctl TIOCNOTTY: %m"); + return -1; + } + close(fd); + } + return 0; +} + +#else +#error MISSING_SETSID +#endif + +#endif + + /* + * dup2_pass_on_exec() - dup2() and clear close-on-exec flag on the result + */ +#ifdef DUP2_DUPS_CLOSE_ON_EXEC + +#include "iostuff.h" + +int dup2_pass_on_exec(int oldd, int newd) +{ + int res; + + if ((res = dup2(oldd, newd)) >= 0) + close_on_exec(newd, PASS_ON_EXEC); + + return res; +} + +#endif + +#ifndef HAS_CLOSEFROM + +#include +#include +#include + +/* closefrom() - closes all file descriptors from the given one up */ + +int closefrom(int lowfd) +{ + int fd_limit = open_limit(0); + int fd; + + /* + * lowfrom does not have an easy to determine upper limit. A process may + * have files open that were inherited from a parent process with a less + * restrictive resource limit. + */ + if (lowfd < 0) { + errno = EBADF; + return (-1); + } + if (fd_limit > 500) + fd_limit = 500; + for (fd = lowfd; fd < fd_limit; fd++) + (void) close(fd); + + return (0); +} + +#endif + +#ifdef MISSING_INET_NTOP + +#include +#include +#include +#include +#include +#include +#include + +/* inet_ntop - convert binary address to printable address */ + +const char *inet_ntop(int af, const void *src, char *dst, SOCKADDR_SIZE size) +{ + const unsigned char *addr; + char buffer[sizeof("255.255.255.255")]; + int len; + + if (af != AF_INET) { + errno = EAFNOSUPPORT; + return (0); + } + addr = (const unsigned char *) src; +#if (CHAR_BIT > 8) + sprintf(buffer, "%d.%d.%d.%d", addr[0] & 0xff, + addr[1] & 0xff, addr[2] & 0xff, addr[3] & 0xff); +#else + sprintf(buffer, "%d.%d.%d.%d", addr[0], addr[1], addr[2], addr[3]); +#endif + if ((len = strlen(buffer)) >= size) { + errno = ENOSPC; + return (0); + } else { + memcpy(dst, buffer, len + 1); + return (dst); + } +} + +#endif + +#ifdef MISSING_INET_PTON + +#include +#include +#include +#include +#include +#include + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +/* inet_pton - convert printable address to binary address */ + +int inet_pton(int af, const char *src, void *dst) +{ + struct in_addr addr; + + /* + * inet_addr() accepts a wider range of input formats than inet_pton(); + * the former accepts 1-, 2-, or 3-part dotted addresses, while the + * latter requires dotted quad form. + */ + if (af != AF_INET) { + errno = EAFNOSUPPORT; + return (-1); + } else if ((addr.s_addr = inet_addr(src)) == INADDR_NONE + && strcmp(src, "255.255.255.255") != 0) { + return (0); + } else { + memcpy(dst, (void *) &addr, sizeof(addr)); + return (1); + } +} + +#endif diff --git a/src/util/sys_defs.h b/src/util/sys_defs.h new file mode 100644 index 0000000..37e460f --- /dev/null +++ b/src/util/sys_defs.h @@ -0,0 +1,1797 @@ +#ifndef _SYS_DEFS_H_INCLUDED_ +#define _SYS_DEFS_H_INCLUDED_ + +/*++ +/* NAME +/* sys_defs 3h +/* SUMMARY +/* portability header +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Specific platforms. Major release numbers differ for a good reason. So be + * a good girl, plan for the future, and at least include the major release + * number in the system type (for example, SUNOS5 or FREEBSD2). The system + * type is determined by the makedefs shell script in the top-level + * directory. Adding support for a new system type means updating the + * makedefs script, and adding a section below for the new system. + */ +#ifdef SUNOS5 +#define _SVID_GETTOD /* Solaris 2.5, XSH4.2 versus SVID */ +#endif +#include + + /* + * 4.4BSD and close derivatives. + */ +#if defined(FREEBSD2) || defined(FREEBSD3) || defined(FREEBSD4) \ + || defined(FREEBSD5) || defined(FREEBSD6) || defined(FREEBSD7) \ + || defined(FREEBSD8) || defined(FREEBSD9) || defined(FREEBSD10) \ + || defined(FREEBSD11) || defined(FREEBSD12) || defined(FREEBSD13) \ + || defined(FREEBSD14) \ + || defined(BSDI2) || defined(BSDI3) || defined(BSDI4) \ + || defined(OPENBSD2) || defined(OPENBSD3) || defined(OPENBSD4) \ + || defined(OPENBSD5) || defined(OPENBSD6) || defined(OPENBSD7) \ + || defined(NETBSD1) || defined(NETBSD2) || defined(NETBSD3) \ + || defined(NETBSD4) || defined(NETBSD5) || defined(NETBSD6) \ + || defined(NETBSD7) | defined(NETBSD8) || defined(NETBSD9) \ + || defined(NETBSD10) \ + || defined(EKKOBSD1) || defined(DRAGONFLY) +#define SUPPORTED +#include +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define USE_PATHS_H +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define HAS_SUN_LEN +#define HAS_FSYNC +#define HAS_DB +#define HAS_SA_LEN +#define NATIVE_DB_TYPE "hash" +#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 104250000) +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" /* sendmail 8.10 */ +#endif +#if (defined(OpenBSD) && OpenBSD >= 200006) +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" /* OpenBSD 2.7 */ +#endif +#ifndef ALIAS_DB_MAP +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" +#if (defined(__NetBSD_Version__) && __NetBSD_Version__ > 299000900) +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#else +#define USE_STATFS +#define STATFS_IN_SYS_MOUNT_H +#endif +#define HAS_POSIX_REGEXP +#define HAS_ST_GEN /* struct stat contains inode + * generation number */ +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#define HAS_DLOPEN +#endif + +#ifdef FREEBSD2 +#define getsid(p) getpgrp() +#ifndef CMSG_SPACE +#define CMSG_SPACE(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + CMSG_ALIGN(len)) +#endif +#ifndef CMSG_LEN +#define CMSG_LEN(len) (CMSG_ALIGN(sizeof(struct cmsghdr)) + (len)) +#endif +#ifndef CMSG_ALIGN +#define CMSG_ALIGN(n) ALIGN(n) +#endif +#endif /* FREEBSD2 */ + +#ifdef BSDI4 +/* #define HAS_IPV6 find out interface lookup method */ +#endif + +/* __FreeBSD_version version is major+minor */ + +#if __FreeBSD_version >= 220000 +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* introduced 2.1.5 */ +#endif + +#if __FreeBSD_version >= 300000 +#define HAS_ISSETUGID +#define HAS_FUTIMES +#endif + +#if __FreeBSD_version >= 400000 +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#endif + +#if __FreeBSD_version >= 420000 +#define HAS_DUPLEX_PIPE /* 4.1 breaks with kqueue(2) */ +#endif + +#if (__FreeBSD_version >= 702104 && __FreeBSD_version <= 800000) \ + || __FreeBSD_version >= 800100 +#define HAS_CLOSEFROM +#endif + +/* OpenBSD version is year+month */ + +#if OpenBSD >= 199805 /* XXX */ +#define HAS_FUTIMES /* XXX maybe earlier */ +#endif + +#if (defined(OpenBSD) && OpenBSD >= 199608 && OpenBSD < 201105) +#define PREFERRED_RAND_SOURCE "dev:/dev/arandom" /* XXX earlier */ +#endif + +#if OpenBSD >= 200000 /* XXX */ +#define HAS_ISSETUGID +#endif + +#if OpenBSD >= 200200 /* XXX */ +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#endif + +#if OpenBSD >= 200405 /* 3.5 */ +#define HAS_CLOSEFROM +#endif + +/* __NetBSD_Version__ is major+minor */ + +#if __NetBSD_Version__ >= 103000000 /* XXX maybe earlier */ +#undef DEF_MAILBOX_LOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* XXX maybe earlier */ +#endif + +#if __NetBSD_Version__ >= 105000000 +#define HAS_ISSETUGID /* XXX maybe earlier */ +#endif + +#if __NetBSD_Version__ >= 106000000 /* XXX maybe earlier */ +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#endif + +#if __NetBSD_Version__ >= 299000900 /* 2.99.9 */ +#define HAS_CLOSEFROM +#endif + +#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 102000000) +#define HAS_FUTIMES +#endif + +#if defined(__DragonFly__) +#define HAS_DEV_URANDOM +#define HAS_ISSETUGID +#define HAS_FUTIMES +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#define HAS_DUPLEX_PIPE +#endif + +#if (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 105000000) \ + || (defined(__FreeBSD__) && __FreeBSD__ >= 4) \ + || (defined(OpenBSD) && OpenBSD >= 200003) \ + || defined(__DragonFly__) \ + || defined(USAGI_LIBINET6) +#ifndef NO_IPV6 +#define HAS_IPV6 +#define HAVE_GETIFADDRS +#endif + +#if (defined(__FreeBSD_version) && __FreeBSD_version >= 300000) \ + || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 103000000) \ + || (defined(OpenBSD) && OpenBSD >= 199700) /* OpenBSD 2.0?? */ \ + || defined(__DragonFly__) +#define USE_SYSV_POLL +#endif + +#ifndef NO_KQUEUE +#if (defined(__FreeBSD_version) && __FreeBSD_version >= 410000) \ + || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 200000000) \ + || (defined(OpenBSD) && OpenBSD >= 200105) /* OpenBSD 2.9 */ \ + || defined(__DragonFly__) +#define EVENTS_STYLE EVENTS_STYLE_KQUEUE +#endif +#endif + +#ifndef NO_POSIX_GETPW_R +#if (defined(__FreeBSD_version) && __FreeBSD_version >= 510000) \ + || (defined(__NetBSD_Version__) && __NetBSD_Version__ >= 300000000) \ + || (defined(OpenBSD) && OpenBSD >= 200811) /* OpenBSD 4.4 */ +#define HAVE_POSIX_GETPW_R +#endif +#endif + +#endif + + /* + * UNIX on MAC. + */ +#if defined(RHAPSODY5) || defined(MACOSX) +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define USE_PATHS_H +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define HAS_SUN_LEN +#define HAS_FSYNC +#define HAS_DB +#define HAS_SA_LEN +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" +#define USE_STATFS +#define STATFS_IN_SYS_MOUNT_H +#define HAS_POSIX_REGEXP +#ifndef NO_NETINFO +#define HAS_NETINFO +#endif +#ifndef NO_IPV6 +#define HAS_IPV6 +#define HAVE_GETIFADDRS +#endif +#define HAS_FUTIMES /* XXX Guessing */ +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#ifndef NO_KQUEUE +#define EVENTS_STYLE EVENTS_STYLE_KQUEUE +#define USE_SYSV_POLL_THEN_SELECT +#endif +#define USE_MAX_FILES_PER_PROC +#ifndef NO_POSIX_GETPW_R +#define HAVE_POSIX_GETPW_R +#endif +#define HAS_DLOPEN +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" +#endif + + /* + * Ultrix 4.x, a sort of 4.[1-2] BSD system with System V.2 compatibility + * and POSIX. + */ +#ifdef ULTRIX4 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +/* Ultrix by default has only 64 descriptors per process */ +#ifndef FD_SETSIZE +#define FD_SETSIZE 96 +#endif +#define _PATH_MAILDIR "/var/spool/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/bin:/usr/bin:/usr/etc:/usr/ucb" +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define HAS_FSYNC +/* might be set by makedef */ +#ifdef HAS_DB +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#else +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#endif +extern int optind; +extern char *optarg; +extern int opterr; +extern int h_errno; + +#define MISSING_STRFTIME_E +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/etc:/usr/etc:/usr/ucb" +#define USE_STATFS +#define USE_STRUCT_FS_DATA +#define STATFS_IN_SYS_MOUNT_H +/* Ultrix misses just S_ISSOCK, the others are there */ +#define S_ISSOCK(mode) (((mode) & (S_IFMT)) == (S_IFSOCK)) +#define DUP2_DUPS_CLOSE_ON_EXEC +#define MISSING_USLEEP +#define NO_HERRNO +#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail" +#define NATIVE_COMMAND_DIR "/usr/etc" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#endif + + /* + * OSF, then Digital UNIX, then Compaq. A BSD-flavored hybrid. + */ +#ifdef OSF1 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define USE_PATHS_H +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define HAS_FSYNC +#define HAVE_BASENAME +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/var/adm/sendmail/aliases" +extern int optind; /* XXX use */ +extern char *optarg; /* XXX use */ +extern int opterr; /* XXX use */ + +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define USE_STATFS +#define STATFS_IN_SYS_MOUNT_H +#define HAS_POSIX_REGEXP +#define BROKEN_WRITE_SELECT_ON_NON_BLOCKING_PIPE +#define NO_MSGHDR_MSG_CONTROL +#ifndef NO_IPV6 +#define HAS_IPV6 +#endif + +#endif + + /* + * SunOS 4.x, a mostly 4.[2-3] BSD system with System V.2 compatibility and + * POSIX support. + */ +#ifdef SUNOS4 +#define SUPPORTED +#include +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define UNSAFE_CTYPE +#define fpos_t long +#define MISSING_SETENV +#define MISSING_STRERROR +#define MISSING_STRTOUL +#define _PATH_MAILDIR "/var/spool/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/etc:/usr/ucb" +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +extern int optind; +extern char *optarg; +extern int opterr; + +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/etc:/usr/etc:/usr/ucb" +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define memmove(d,s,l) bcopy(s,d,l) +#define NO_HERRNO +#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail" +#define NATIVE_MAILQ_PATH "/usr/ucb/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/ucb/newaliases" +#define NATIVE_COMMAND_DIR "/usr/etc" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#define STRCASECMP_IN_STRINGS_H +#define OCTAL_TO_UNSIGNED(res, str) sscanf((str), "%o", &(res)) +#define size_t unsigned +#define ssize_t int +#define getsid getpgrp +#define NO_SNPRINTF +#endif + + /* + * SunOS 5.x, mostly System V Release 4. + */ +#ifdef SUNOS5 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define _PATH_MAILDIR "/var/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#ifndef NO_NIS +#define HAS_NIS +#ifndef NO_NISPLUS +#define HAS_NISPLUS +#endif /* NO_NISPLUS */ +#endif +#define USE_SYS_SOCKIO_H /* Solaris 2.5, changed sys/ioctl.h */ +#define GETTIMEOFDAY(t) gettimeofday(t) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define FIONREAD_IN_SYS_FILIO_H +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define INT_MAX_IN_LIMITS_H +#ifdef STREAM_CONNECTIONS /* avoid UNIX-domain sockets */ +#define LOCAL_LISTEN stream_listen +#define LOCAL_ACCEPT stream_accept +#define LOCAL_CONNECT stream_connect +#define LOCAL_TRIGGER stream_trigger +#define LOCAL_SEND_FD stream_send_fd +#define LOCAL_RECV_FD stream_recv_fd +#endif +#define HAS_VOLATILE_LOCKS +#define BROKEN_READ_SELECT_ON_TCP_SOCKET +#define CANT_WRITE_BEFORE_SENDING_FD +#ifndef NO_POSIX_REGEXP +#define HAS_POSIX_REGEXP +#endif +#ifndef NO_IPV6 +#define HAS_IPV6 +#define HAS_SIOCGLIF +#endif +#ifndef NO_CLOSEFROM +#define HAS_CLOSEFROM +#endif +#ifndef NO_DEV_URANDOM +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" +#endif +#ifndef NO_FUTIMESAT +#define HAS_FUTIMESAT +#endif +#define USE_SYSV_POLL +#ifndef NO_DEVPOLL +#define EVENTS_STYLE EVENTS_STYLE_DEVPOLL +#endif +#ifndef NO_POSIX_GETPW_R +#define HAVE_POSIX_GETPW_R +#define GETPW_R_NEEDS_POSIX_PTHREAD_SEMANTICS +#endif + +/* + * Allow build environment to override paths. + */ +#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" + +#define HAS_DLOPEN +#endif + + /* + * UnixWare, System Release 4. + */ +#ifdef UW7 /* UnixWare 7 */ +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define _PATH_MAILDIR "/var/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define MISSING_SETENV +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define USE_SYS_SOCKIO_H +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define FIONREAD_IN_SYS_FILIO_H +#define DBM_NO_TRAILING_NULL +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define STRCASECMP_IN_STRINGS_H +#define USE_SET_H_ERRNO +#endif + +#ifdef UW21 /* UnixWare 2.1.x */ +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define _PATH_MAILDIR "/var/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define MISSING_SETENV +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#ifndef NO_NIS +#define HAS_NIS */ +#endif +#define USE_SYS_SOCKIO_H +#define GETTIMEOFDAY(t) gettimeofday(t,NULL) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define FIONREAD_IN_SYS_FILIO_H +#define DBM_NO_TRAILING_NULL +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#endif + + /* + * AIX: a SYSV-flavored hybrid. NB: fcntl() and flock() access the same + * underlying locking primitives. + */ +#if defined(AIX5) || defined(AIX6) +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define USE_PATHS_H +#ifndef _PATH_BSHELL +#define _PATH_BSHELL "/bin/sh" +#endif +#ifndef _PATH_MAILDIR +#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */ +#endif +#ifndef _PATH_DEFPATH +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#endif +#ifndef _PATH_STDPATH +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#endif +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define USE_SYS_SELECT_H +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define HAS_SA_LEN +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/sbin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/sbin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" + + /* + * XXX Need CMSG_SPACE() and CMSG_LEN() but don't want to drag in everything + * that comes with _LINUX_SOURCE_COMPAT. + */ +#include +#ifndef CMSG_SPACE +#define CMSG_SPACE(len) (_CMSG_ALIGN(sizeof(struct cmsghdr)) + _CMSG_ALIGN(len)) +#endif +#ifndef CMSG_LEN +#define CMSG_LEN(len) (_CMSG_ALIGN(sizeof(struct cmsghdr)) + (len)) +#endif +#ifndef NO_IPV6 +#define HAS_IPV6 +#endif +#define BROKEN_AI_PASSIVE_NULL_HOST +#define BROKEN_AI_NULL_SERVICE +#define USE_SYSV_POLL +#define MYMALLOC_FUZZ 1 +#endif + +#ifdef AIX4 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */ +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define USE_SYS_SELECT_H +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define HAS_SA_LEN +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define RESOLVE_H_NEEDS_STDIO_H +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define SOCKADDR_SIZE size_t +#define SOCKOPT_SIZE size_t +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define STRCASECMP_IN_STRINGS_H +#if 0 +extern time_t time(time_t *); +extern int seteuid(uid_t); +extern int setegid(gid_t); +extern int initgroups(const char *, int); + +#endif +#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail" +#define NATIVE_MAILQ_PATH "/usr/sbin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/sbin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" + +#define CANT_USE_SEND_RECV_MSG +#endif + +#ifdef AIX3 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/var/spool/mail" /* paths.h lies */ +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define USE_SYS_SELECT_H +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define HAS_SA_LEN +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define RESOLVE_H_NEEDS_STDIO_H +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define SOCKADDR_SIZE size_t +#define SOCKOPT_SIZE size_t +#define USE_STATFS +#define STATFS_IN_SYS_STATFS_H +#define STRCASECMP_IN_STRINGS_H +extern time_t time(time_t *); +extern int seteuid(uid_t); +extern int setegid(gid_t); +extern int initgroups(const char *, int); + +#define NATIVE_SENDMAIL_PATH "/usr/lib/sendmail" + +#define CANT_USE_SEND_RECV_MSG +#endif + + /* + * IRIX, a mix of System V Releases. + */ +#if defined(IRIX5) || defined(IRIX6) +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define _PATH_MAILDIR "/var/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/bsd" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/bsd" +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define USE_SYS_SOCKIO_H /* XXX check */ +#define GETTIMEOFDAY(t) gettimeofday(t) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/bsd" +#define FIONREAD_IN_SYS_FILIO_H /* XXX check */ +#define DBM_NO_TRAILING_NULL /* XXX check */ +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define BROKEN_WRITE_SELECT_ON_NON_BLOCKING_PIPE +#define CANT_USE_SEND_RECV_MSG +#endif + +#if defined(IRIX5) +#define MISSING_USLEEP +#endif + +#if defined(IRIX6) +#ifndef NO_IPV6 +#define HAS_IPV6 +#endif +#define HAS_POSIX_REGEXP +#define PIPES_CANT_FIONREAD +#endif + + /* + * LINUX. + */ +#if defined(LINUX2) || defined(LINUX3) || defined(LINUX4) || defined(LINUX5) \ + || defined(LINUX6) +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#include +#define USE_PATHS_H +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "fcntl, dotlock" /* RedHat >= 4.x */ +#define HAS_FSYNC +#define HAS_DB +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" +#define FIONREAD_IN_TERMIOS_H +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define PREPEND_PLUS_TO_OPTSTRING +#define HAS_POSIX_REGEXP +#define HAS_DLOPEN +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#ifdef __GLIBC_PREREQ +#define HAVE_GLIBC_API_VERSION_SUPPORT(maj, min) __GLIBC_PREREQ(maj, min) +#else +#define HAVE_GLIBC_API_VERSION_SUPPORT(maj, min) \ + (defined(__GLIBC__) && \ + ((__GLIBC__ << 16) + __GLIBC_MINOR__ >= ((maj) << 16) + (min))) +#endif +#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 1) +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#else +#define NO_SNPRINTF +#endif +#ifndef NO_IPV6 +#define HAS_IPV6 +#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 4) +/* Really 2.3.3 or later, but there's no __GLIBC_MICRO version macro. */ +#define HAVE_GETIFADDRS +#else +#define HAS_PROCNET_IFINET6 +#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6" +#endif +#endif +#include +#if !defined(KERNEL_VERSION) +#define KERNEL_VERSION(a,b,c) (LINUX_VERSION_CODE + 1) +#endif +#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,2,0)) \ + || (defined(__GLIBC__) && __GLIBC__ < 2) +#define CANT_USE_SEND_RECV_MSG +#define DEF_SMTP_CACHE_DEMAND 0 +#else +#define CANT_WRITE_BEFORE_SENDING_FD +#endif +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" /* introduced in 1.1 */ +#ifndef NO_EPOLL +#define EVENTS_STYLE EVENTS_STYLE_EPOLL /* introduced in 2.5 */ +#endif +#define USE_SYSV_POLL +#ifndef NO_POSIX_GETPW_R +#if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) \ + || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 1) \ + || (defined(_BSD_SOURCE) && _BSD_SOURCE >= 1) \ + || (defined(_SVID_SOURCE) && _SVID_SOURCE >= 1) \ + || (defined(_POSIX_SOURCE) && _POSIX_SOURCE >= 1) +#define HAVE_POSIX_GETPW_R +#endif +#endif +#if HAVE_GLIBC_API_VERSION_SUPPORT(2, 34) +#define HAS_CLOSEFROM +#endif + +#endif + +#ifdef LINUX1 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define USE_PATHS_H +#define HAS_FLOCK_LOCK +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "dotlock" /* verified RedHat 3.03 */ +#define HAS_FSYNC +#define HAS_DB +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" +#define FIONREAD_IN_TERMIOS_H /* maybe unnecessary */ +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define PREPEND_PLUS_TO_OPTSTRING +#define HAS_POSIX_REGEXP +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#define CANT_USE_SEND_RECV_MSG +#define DEF_SMTP_CACHE_DEMAND 0 +#endif + + /* + * GNU. + */ +#ifdef GNU0 +#define SUPPORTED +#include +#define USE_PATHS_H +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" /* RedHat >= 4.x */ +#define HAS_FSYNC +#define HAS_DB +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin" +#define FIONREAD_IN_TERMIOS_H +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define UNIX_DOMAIN_CONNECT_BLOCKS_FOR_ACCEPT +#define PREPEND_PLUS_TO_OPTSTRING +#define HAS_POSIX_REGEXP +#define HAS_DLOPEN +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#ifdef DEBIAN +#define NATIVE_DAEMON_DIR "/usr/lib/postfix" +#ifndef DEF_MANPAGE_DIR +#define DEF_MANPAGE_DIR "/usr/share/man" +#endif +#ifndef DEF_SAMPLE_DIR +#define DEF_SAMPLE_DIR "/usr/share/doc/postfix/examples" +#endif +#ifndef DEF_README_DIR +#define DEF_README_DIR "/usr/share/doc/postfix" +#endif +#else +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#endif +#define SOCKADDR_SIZE socklen_t +#define SOCKOPT_SIZE socklen_t +#ifdef __FreeBSD_kernel__ +#define HAS_DUPLEX_PIPE +#define HAS_ISSETUGID +#endif +#ifndef NO_IPV6 +#define HAS_IPV6 +#ifdef __FreeBSD_kernel__ +#define HAVE_GETIFADDRS +#else +#define HAS_PROCNET_IFINET6 +#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6" +#endif +#endif +#define CANT_USE_SEND_RECV_MSG +#define DEF_SMTP_CACHE_DEMAND 0 +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" +#endif + + /* + * HPUX11 was copied from HPUX10, but can perhaps be trimmed down a bit. + */ +#ifdef HPUX11 +#define SUPPORTED +#define USE_SIG_RETURN +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define HAS_DBM +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#define ROOT_PATH "/usr/bin:/sbin:/usr/sbin" +#define MISSING_SETENV +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/var/mail" +#define _PATH_DEFPATH "/usr/bin" +#define _PATH_STDPATH "/usr/bin:/sbin:/usr/sbin" +#define MISSING_SETEUID +#define HAVE_SETRESUID +#define MISSING_SETEGID +#define HAVE_SETRESGID +extern int h_errno; /* imports too much stuff */ + +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define HAS_POSIX_REGEXP +#define HAS_DLOPEN +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#endif + +#ifdef HPUX10 +#define SUPPORTED +#define USE_SIG_RETURN +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define HAS_DBM +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#define ROOT_PATH "/usr/bin:/sbin:/usr/sbin" +#define MISSING_SETENV +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/var/mail" +#define _PATH_DEFPATH "/usr/bin" +#define _PATH_STDPATH "/usr/bin:/sbin:/usr/sbin" +#define MISSING_SETEUID +#define HAVE_SETRESUID +#define MISSING_SETEGID +#define HAVE_SETRESGID +extern int h_errno; /* imports too much stuff */ + +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define HAS_POSIX_REGEXP +#define HAS_SHL_LOAD +#define NATIVE_SENDMAIL_PATH "/usr/sbin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_COMMAND_DIR "/usr/sbin" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#endif + +#ifdef HPUX9 +#define SUPPORTED +#define USE_SIG_RETURN +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define HAS_DBM +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#ifndef NO_NIS +#define HAS_NIS +#endif +#define MISSING_SETENV +#define MISSING_RLIMIT_FSIZE +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/usr/lib/aliases" +#define ROOT_PATH "/bin:/usr/bin:/etc" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/usr/mail" +#define _PATH_DEFPATH "/bin:/usr/bin" +#define _PATH_STDPATH "/bin:/usr/bin:/etc" +#define MISSING_SETEUID +#define HAVE_SETRESUID +#define MISSING_SETEGID +#define HAVE_SETRESGID +extern int h_errno; + +#define USE_ULIMIT /* no setrlimit() */ +#define USE_STATFS +#define STATFS_IN_SYS_VFS_H +#define HAS_POSIX_REGEXP +#define HAS_SHL_LOAD +#define NATIVE_SENDMAIL_PATH "/usr/bin/sendmail" +#define NATIVE_MAILQ_PATH "/usr/bin/mailq" +#define NATIVE_NEWALIAS_PATH "/usr/bin/newaliases" +#define NATIVE_DAEMON_DIR "/usr/libexec/postfix" +#endif + + /* + * NEXTSTEP3, without -lposix, because its naming service is broken. + */ +#ifdef NEXTSTEP3 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define HAS_DBM +#define HAS_FLOCK_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define USE_STATFS +#define HAVE_SYS_DIR_H +#define STATFS_IN_SYS_VFS_H +#define HAS_FSYNC +#ifndef NO_NIS +#define HAS_NIS +#endif +#define HAS_NETINFO +#define MISSING_SETENV_PUTENV +#define MISSING_MKFIFO +#define MISSING_SIGSET_T +#define MISSING_SIGACTION +#define MISSING_STD_FILENOS +#define MISSING_SETSID +#define MISSING_WAITPID +#define MISSING_UTIMBUF +#define HAS_WAIT4 +#define WAIT_STATUS_T union wait +#define NORMAL_EXIT_STATUS(x) (WIFEXITED(x) && !WEXITSTATUS (x)) +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define _PATH_MAILDIR "/usr/spool/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/bin:/usr/bin:/usr/ucb" +#define ROOT_PATH "/bin:/usr/bin:/usr/etc:/usr/ucb" +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP "netinfo:/aliases" +#include +#define MISSING_POSIX_S_IS +#define MISSING_POSIX_S_MODES +/* It's amazing what is all missing... */ +#define isascii(c) ((unsigned)(c)<=0177) +extern int opterr; +typedef unsigned short mode_t; + +#define MISSING_PID_T +#define MISSING_STRFTIME_E +#define FD_CLOEXEC 1 +#define O_NONBLOCK O_NDELAY +#define WEXITSTATUS(x) ((x).w_retcode) +#define WTERMSIG(x) ((x).w_termsig) +#endif + + /* + * OPENSTEP does not have posix (some fix...) + */ +#ifdef OPENSTEP4 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define HAS_DBM +#define HAS_FLOCK_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FLOCK +#define DEF_MAILBOX_LOCK "flock, dotlock" +#define USE_STATFS +#define HAVE_SYS_DIR_H +#define STATFS_IN_SYS_VFS_H +#define HAS_FSYNC +#ifndef NO_NIS +#define HAS_NIS +#endif +#define HAS_NETINFO +#define MISSING_SETENV_PUTENV +#define MISSING_MKFIFO +#define MISSING_SIGSET_T +#define MISSING_SIGACTION +#define MISSING_STD_FILENOS +#define MISSING_SETSID +#define MISSING_WAITPID +#define MISSING_UTIMBUF +#define HAS_WAIT4 +#define WAIT_STATUS_T union wait +#define NORMAL_EXIT_STATUS(x) (WIFEXITED(x) && !WEXITSTATUS (x)) +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define _PATH_MAILDIR "/usr/spool/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/bin:/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/bin:/usr/bin:/usr/ucb" +#define ROOT_PATH "/bin:/usr/bin:/usr/etc:/usr/ucb" +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP "netinfo:/aliases" +#include +#define MISSING_POSIX_S_IS +#define MISSING_POSIX_S_MODES +/* It's amazing what is all missing... */ +#define isascii(c) ((unsigned)(c)<=0177) +extern int opterr; +typedef unsigned short mode_t; + +#define MISSING_PID_T +#define MISSING_STRFTIME_E +#define FD_CLOEXEC 1 +#define O_NONBLOCK O_NDELAY +#define WEXITSTATUS(x) ((x).w_retcode) +#define WTERMSIG(x) ((x).w_termsig) +#endif + +#ifdef ReliantUnix543 +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define MISSING_SETENV +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_MAILDIR "/var/spool/mail" +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define FIONREAD_IN_SYS_FILIO_H +#define USE_SYS_SOCKIO_H +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/var/adm/sendmail/aliases" +extern int optind; /* XXX use */ +extern char *optarg; /* XXX use */ +extern int opterr; /* XXX use */ + +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define MISSING_USLEEP +#endif + +#ifdef DCOSX1 /* Siemens Pyramid */ +#define SUPPORTED +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define _PATH_MAILDIR "/var/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/usr/bin:/usr/ucb" +#define _PATH_STDPATH "/usr/bin:/usr/sbin:/usr/ucb" +#define MISSING_SETENV +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define NATIVE_DB_TYPE "hash" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/aliases" +/* Uncomment the following line if you have NIS package installed */ +/* #define HAS_NIS */ +#define USE_SYS_SOCKIO_H +#define GETTIMEOFDAY(t) gettimeofday(t,NULL) +#define ROOT_PATH "/bin:/usr/bin:/sbin:/usr/sbin:/usr/ucb" +#define FIONREAD_IN_SYS_FILIO_H +#define DBM_NO_TRAILING_NULL +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#ifndef S_ISSOCK +#define S_ISSOCK(mode) ((mode&0xF000) == 0xC000) +#endif +#endif + +#ifdef SCO5 +#define SUPPORTED +#include +extern int h_errno; + +#define UINT32_TYPE unsigned int +#define UINT16_TYPE unsigned short +#define _PATH_MAILDIR "/usr/spool/mail" +#define _PATH_BSHELL "/bin/sh" +#define _PATH_DEFPATH "/bin:/usr/bin" +#define USE_PATHS_H +#define HAS_FCNTL_LOCK +#define INTERNAL_LOCK MYFLOCK_STYLE_FCNTL +#define DEF_MAILBOX_LOCK "fcntl, dotlock" +#define HAS_FSYNC +#define HAS_DBM +#define NATIVE_DB_TYPE "dbm" +#define ALIAS_DB_MAP DEF_DB_TYPE ":/etc/mail/aliases" +#define DBM_NO_TRAILING_NULL +#ifndef NO_NIS +#define HAS_NIS +#endif +#define GETTIMEOFDAY(t) gettimeofday(t,(struct timezone *) 0) +#define ROOT_PATH "/bin:/etc:/usr/bin:/tcb/bin" +#define USE_STATVFS +#define STATVFS_IN_SYS_STATVFS_H +#define MISSING_SETENV +#define STRCASECMP_IN_STRINGS_H +/* SCO5 misses just S_ISSOCK, the others are there + * Use C_ISSOCK definition from cpio.h. + */ +#include +#define S_ISSOCK(mode) (((mode) & (S_IFMT)) == (C_ISSOCK)) +#define CANT_USE_SEND_RECV_MSG +#define DEF_SMTP_CACHE_DEMAND 0 +#endif + + /* + * We're not going to try to guess like configure does. + */ +#ifndef SUPPORTED +#error "unsupported platform" +#endif + + /* + * Allow command line flags to override native settings + */ +#ifndef DEF_COMMAND_DIR +#ifdef NATIVE_COMMAND_DIR +#define DEF_COMMAND_DIR NATIVE_COMMAND_DIR +#endif +#endif + +#ifndef DEF_DAEMON_DIR +#ifdef NATIVE_DAEMON_DIR +#define DEF_DAEMON_DIR NATIVE_DAEMON_DIR +#endif +#endif + +#ifndef DEF_SENDMAIL_PATH +#ifdef NATIVE_SENDMAIL_PATH +#define DEF_SENDMAIL_PATH NATIVE_SENDMAIL_PATH +#endif +#endif + +#ifndef DEF_MAILQ_PATH +#ifdef NATIVE_MAILQ_PATH +#define DEF_MAILQ_PATH NATIVE_MAILQ_PATH +#endif +#endif + +#ifndef DEF_NEWALIAS_PATH +#ifdef NATIVE_NEWALIAS_PATH +#define DEF_NEWALIAS_PATH NATIVE_NEWALIAS_PATH +#endif +#endif + +#ifndef DEF_DB_TYPE +#define DEF_DB_TYPE NATIVE_DB_TYPE +#endif + +#define CAST_ANY_PTR_TO_INT(cptr) ((int) (long) (cptr)) +#define CAST_INT_TO_VOID_PTR(ival) ((void *) (long) (ival)) + +#ifdef DUP2_DUPS_CLOSE_ON_EXEC +/* dup2_pass_on_exec() can be found in util/sys_compat.c */ +extern int dup2_pass_on_exec(int oldd, int newd); + +#define DUP2 dup2_pass_on_exec +#else +#define DUP2 dup2 +#endif + +#ifdef PREPEND_PLUS_TO_OPTSTRING +#define GETOPT(argc, argv, str) getopt((argc), (argv), "+" str) +#else +#define GETOPT(argc, argv, str) getopt((argc), (argv), (str)) +#endif +#define OPTIND (optind > 0 ? optind : 1) + +#if !defined(__UCLIBC__) && !defined(NO_RES_SEND) +#define HAVE_RES_SEND +#else +#undef HAVE_RES_SEND +#endif + + /* + * Check for required but missing definitions. + */ +#if !defined(HAS_FCNTL_LOCK) && !defined(HAS_FLOCK_LOCK) +#error "define HAS_FCNTL_LOCK and/or HAS_FLOCK_LOCK" +#endif + +#if !defined(DEF_MAILBOX_LOCK) +#error "define DEF_MAILBOX_LOCK" +#endif + +#if !defined(INTERNAL_LOCK) +#error "define INTERNAL_LOCK" +#endif + +#if defined(USE_STATFS) && defined(USE_STATVFS) +#error "define USE_STATFS or USE_STATVFS, not both" +#endif + +#if !defined(USE_STATFS) && !defined(USE_STATVFS) +#error "define USE_STATFS or USE_STATVFS" +#endif + + /* + * Defaults for systems that pre-date IPv6 support. + */ +#ifndef HAS_IPV6 +#include +#define EMULATE_IPV4_ADDRINFO +#define MISSING_INET_PTON +#define MISSING_INET_NTOP +extern const char *inet_ntop(int, const void *, char *, SOCKADDR_SIZE); +extern int inet_pton(int, const char *, void *); + +#endif + + /* + * Workaround: after a watchdog alarm signal, wake up from select/poll/etc. + * by writing to a pipe. Solaris needs this, and HP-UX apparently, too. The + * run-time cost is negligible so we just turn it on for all systems. As a + * side benefit, making this code system-independent will simplify the + * detection of bit-rot problems. + */ +#ifndef NO_WATCHDOG_PIPE +#define USE_WATCHDOG_PIPE +#endif + + /* + * If we don't have defined a preferred random device above, but the system + * has /dev/urandom, then we use that. + */ +#if !defined(PREFERRED_RAND_SOURCE) && defined(HAS_DEV_URANDOM) +#define PREFERRED_RAND_SOURCE "dev:/dev/urandom" +#endif + + /* + * Defaults for systems without kqueue, /dev/poll or epoll support. + * master/multi-server.c and *qmgr/qmgr_transport.c depend on this. + */ +#if !defined(EVENTS_STYLE) +#define EVENTS_STYLE EVENTS_STYLE_SELECT +#endif + +#define EVENTS_STYLE_SELECT 1 /* Traditional BSD select */ +#define EVENTS_STYLE_KQUEUE 2 /* FreeBSD kqueue */ +#define EVENTS_STYLE_DEVPOLL 3 /* Solaris /dev/poll */ +#define EVENTS_STYLE_EPOLL 4 /* Linux epoll */ + + /* + * We use poll() for read/write time limit enforcement on modern systems. We + * use select() on historical systems without poll() support. And on systems + * where poll() is not implemented for some file handle types, we try to use + * select() as a fall-back solution (MacOS X needs this). + */ +#if !defined(USE_SYSV_POLL) && !defined(USE_SYSV_POLL_THEN_SELECT) +#define USE_BSD_SELECT +#endif + + /* + * The Postfix 2.9 post-install workaround assumes that the inet_protocols + * default value is "ipv4" when Postfix is compiled without IPv6 support. + */ +#ifndef DEF_INET_PROTOCOLS +#ifdef HAS_IPV6 +#define DEF_INET_PROTOCOLS INET_PROTO_NAME_ALL +#else +#define DEF_INET_PROTOCOLS INET_PROTO_NAME_IPV4 +#endif +#endif + + /* + * Defaults for systems that pre-date POSIX socklen_t. + */ +#ifndef SOCKADDR_SIZE +#define SOCKADDR_SIZE int +#endif + +#ifndef SOCKOPT_SIZE +#define SOCKOPT_SIZE int +#endif + + /* + * Defaults for normal systems. + */ +#ifndef LOCAL_LISTEN +#define LOCAL_LISTEN unix_listen +#define LOCAL_ACCEPT unix_accept +#define LOCAL_CONNECT unix_connect +#define LOCAL_TRIGGER unix_trigger +#define LOCAL_SEND_FD unix_send_fd +#define LOCAL_RECV_FD unix_recv_fd +#endif + +#if !defined (HAVE_SYS_NDIR_H) && !defined (HAVE_SYS_DIR_H) \ + && !defined (HAVE_NDIR_H) +#define HAVE_DIRENT_H +#endif + +#ifndef WAIT_STATUS_T +typedef int WAIT_STATUS_T; + +#define NORMAL_EXIT_STATUS(status) ((status) == 0) +#endif + +#ifdef NO_POSIX_GETPW_R +#undef HAVE_POSIX_GETPW_R +#endif + +#ifdef NO_DB +#undef HAS_DB +#endif + +#ifndef OCTAL_TO_UNSIGNED +#define OCTAL_TO_UNSIGNED(res, str) ((res) = strtoul((str), (char **) 0, 8)) +#endif + + /* + * Avoid useless type mis-matches when using sizeof in an integer context. + */ +#define INT_SIZEOF(foo) ((int) sizeof(foo)) + + /* + * Turn on the compatibility stuff. + */ +#ifdef MISSING_UTIMBUF +struct utimbuf { + time_t actime; + time_t modtime; +}; + +#endif + +#ifdef MISSING_STRERROR +extern const char *strerror(int); + +#endif + +#if defined (MISSING_SETENV) || defined (MISSING_SETENV_PUTENV) +extern int setenv(const char *, const char *, int); + +#endif + +#ifdef MISSING_SETEUID +extern int seteuid(uid_t euid); + +#endif + +#ifdef MISSING_SETEGID +extern int setegid(gid_t egid); + +#endif + +#ifdef MISSING_MKFIFO +extern int mkfifo(char *, int); + +#endif + +#ifdef MISSING_WAITPID +extern int waitpid(int, WAIT_STATUS_T *status, int options); + +#endif + +#ifdef MISSING_SETSID +extern int setsid(void); + +#endif + +#ifndef HAS_CLOSEFROM +extern int closefrom(int); + +#endif + +#ifdef MISSING_STD_FILENOS +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 +#endif + +#ifdef MISSING_PID_T +typedef int pid_t; + +#endif + +#ifdef MISSING_POSIX_S_IS +#define S_ISBLK(mode) (((mode) & (_S_IFMT)) == (_S_IFBLK)) +#define S_ISCHR(mode) (((mode) & (_S_IFMT)) == (_S_IFCHR)) +#define S_ISDIR(mode) (((mode) & (_S_IFMT)) == (_S_IFDIR)) +#define S_ISSOCK(mode) (((mode) & (_S_IFMT)) == (_S_IFSOCK)) +#define S_ISFIFO(mode) (((mode) & (_S_IFMT)) == (_S_IFIFO)) +#define S_ISREG(mode) (((mode) & (_S_IFMT)) == (_S_IFREG)) +#define S_ISLNK(mode) (((mode) & (_S_IFMT)) == (_S_IFLNK)) +#endif + +#ifdef MISSING_POSIX_S_MODES +#define S_IRUSR _S_IRUSR +#define S_IRGRP 0000040 +#define S_IROTH 0000004 +#define S_IWUSR _S_IWUSR +#define S_IWGRP 0000020 +#define S_IWOTH 0000002 +#define S_IXUSR _S_IXUSR +#define S_IXGRP 0000010 +#define S_IXOTH 0000001 +#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) +#endif + + /* + * Memory alignment of memory allocator results. By default we align for + * doubles. + */ +#ifndef ALIGN_TYPE +#if defined(__hpux) && defined(__ia64) +#define ALIGN_TYPE __float80 +#elif defined(__ia64__) +#define ALIGN_TYPE long double +#else +#define ALIGN_TYPE double +#endif +#endif + + /* + * Clang-style attribute tests. + * + * XXX Without the unconditional test below, gcc 4.6 will barf on ``elif + * defined(__clang__) && __has_attribute(__whatever__)'' with error message + * ``missing binary operator before token "("''. + */ +#ifndef __has_attribute +#define __has_attribute(x) 0 +#endif /* __has_attribute */ + + /* + * Need to specify what functions never return, so that the compiler can + * warn for missing initializations and other trouble. However, OPENSTEP4 + * gcc 2.7.x cannot handle this so we define this only if NORETURN isn't + * already defined above. + * + * Data point: gcc 2.7.2 has __attribute__ (Wietse Venema) but gcc 2.6.3 does + * not (Clive Jones). So we'll set the threshold at 2.7. + */ +#ifndef NORETURN +#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3 +#define NORETURN void __attribute__((__noreturn__)) +#elif defined(__clang__) && __has_attribute(__noreturn__) +#define NORETURN void __attribute__((__noreturn__)) +#else +#define NORETURN void +#endif +#endif /* NORETURN */ + + /* + * Turn on format string argument checking. This is more accurate than + * printfck, but it misses #ifdef-ed code. XXX I am just guessing at what + * gcc versions support this. In order to turn this off for some platforms, + * specify #define PRINTFLIKE and #define SCANFLIKE in the system-dependent + * sections above. + */ +#ifndef PRINTFLIKE +#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3 +#define PRINTFLIKE(x,y) __attribute__ ((format (printf, (x), (y)))) +#elif defined(__clang__) && __has_attribute(__format__) +#define PRINTFLIKE(x,y) __attribute__ ((__format__ (__printf__, (x), (y)))) +#else +#define PRINTFLIKE(x,y) +#endif +#endif /* PRINTFLIKE */ + +#ifndef SCANFLIKE +#if (__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ >= 3 +#define SCANFLIKE(x,y) __attribute__ ((format (scanf, (x), (y)))) +#elif defined(__clang__) && __has_attribute(__format__) +#define SCANFLIKE(x,y) __attribute__ ((__format__ (__scanf__, (x), (y)))) +#else +#define SCANFLIKE(x,y) +#endif +#endif /* SCANFLIKE */ + + /* + * Some gcc implementations don't grok these attributes with pointer to + * function. Again, wild guess of what is supported. To override, specify + * #define PRINTFPTRLIKE in the system-dependent sections above. + */ +#ifndef PRINTFPTRLIKE +#if (__GNUC__ >= 3) /* XXX Rough estimate */ +#define PRINTFPTRLIKE(x,y) PRINTFLIKE(x,y) +#elif defined(__clang__) && __has_attribute(__format__) +#define PRINTFPTRLIKE(x,y) __attribute__ ((__format__ (__printf__, (x), (y)))) +#else +#define PRINTFPTRLIKE(x,y) +#endif +#endif + + /* + * Compiler optimization hint. This makes sense only for code in a + * performance-critical loop. + */ +#ifndef EXPECTED +#if defined(__GNUC__) && (__GNUC__ > 2) +#define EXPECTED(x) __builtin_expect(!!(x), 1) +#define UNEXPECTED(x) __builtin_expect(!!(x), 0) +#else +#define EXPECTED(x) (x) +#define UNEXPECTED(x) (x) +#endif +#endif + + /* + * Warn about ignored function result values that must never be ignored. + * Typically, this is for error results from "read" functions that normally + * write to output parameters (for example, stat- or scanf-like functions) + * or from functions that have other useful side effects (for example, + * fseek- or rename-like functions). + * + * DO NOT use this for functions that write to a stream; it is entirely + * legitimate to detect write errors with fflush() or fclose() only. On the + * other hand most (but not all) functions that read from a stream must + * never ignore result values. + * + * XXX Prepending "(void)" won't shut up GCC. Clang behaves as expected. + */ +#if ((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ > 3) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#elif defined(__clang__) && __has_attribute(warn_unused_result) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif + + /* + * ISO C says that the "volatile" qualifier protects against optimizations + * that cause longjmp() to clobber local variables. + */ +#ifndef NOCLOBBER +#define NOCLOBBER volatile +#endif + + /* + * Bit banging!! There is no official constant that defines the INT_MAX + * equivalent for off_t, ssize_t, etc. Wietse came up with the following + * macro that works as long as off_t, ssize_t, etc. use one's or two's + * complement logic (that is, the maximum value is binary 01...1). Don't use + * right-shift for signed types: the result is implementation-defined. + */ +#include +#define __MAXINT__(T) ((T) ~(((T) 1) << ((sizeof(T) * CHAR_BIT) - 1))) +#ifndef OFF_T_MAX +#define OFF_T_MAX __MAXINT__(off_t) +#endif + +#ifndef SSIZE_T_MAX +#define SSIZE_T_MAX __MAXINT__(ssize_t) +#endif + + /* + * Consistent enforcement of size limits. + */ +#define ENFORCING_SIZE_LIMIT(param) ((param) > 0) + + /* + * Don't mix socket message send/receive calls with socket stream read/write + * calls. The fact that you can get away with it only on some stacks implies + * that there is no long-term guarantee. + */ +#ifndef CAN_WRITE_BEFORE_SENDING_FD +#define CANT_WRITE_BEFORE_SENDING_FD +#endif + + /* + * FreeBSD sendmsg(2) says that after sending a file descriptor, the sender + * must not immediately close the descriptor, otherwise it may close the + * descriptor before it is actually sent. + */ +#ifndef DONT_WAIT_AFTER_SENDING_FD +#define MUST_READ_AFTER_SENDING_FD +#endif + + /* + * Hope for the best. + */ +#ifndef UINT32_TYPE +#define UINT32_TYPE uint32_t +#define UINT16_TYPE uint16_t +#endif +#define UINT32_SIZE 4 +#define UINT16_SIZE 2 + + /* + * For the sake of clarity. + */ +#ifndef HAVE_CONST_CHAR_STAR +typedef const char *CONST_CHAR_STAR; + +#endif + + /* + * Safety. On some systems, ctype.h misbehaves with non-ASCII or negative + * characters. More importantly, Postfix uses the ISXXX() macros to ensure + * protocol compliance, so we have to rule out non-ASCII characters. + * + * XXX The (unsigned char) casts in isalnum() etc arguments are unnecessary + * because the ISASCII() guard already ensures that the values are + * non-negative; the casts are done anyway to shut up chatty compilers. + */ +#define ISASCII(c) isascii(_UCHAR_(c)) +#define _UCHAR_(c) ((unsigned char)(c)) +#define ISALNUM(c) (ISASCII(c) && isalnum((unsigned char)(c))) +#define ISALPHA(c) (ISASCII(c) && isalpha((unsigned char)(c))) +#define ISCNTRL(c) (ISASCII(c) && iscntrl((unsigned char)(c))) +#define ISDIGIT(c) (ISASCII(c) && isdigit((unsigned char)(c))) +#define ISGRAPH(c) (ISASCII(c) && isgraph((unsigned char)(c))) +#define ISLOWER(c) (ISASCII(c) && islower((unsigned char)(c))) +#define ISPRINT(c) (ISASCII(c) && isprint((unsigned char)(c))) +#define ISPUNCT(c) (ISASCII(c) && ispunct((unsigned char)(c))) +#define ISSPACE(c) (ISASCII(c) && isspace((unsigned char)(c))) +#define ISUPPER(c) (ISASCII(c) && isupper((unsigned char)(c))) +#define TOLOWER(c) (ISUPPER(c) ? tolower((unsigned char)(c)) : (c)) +#define TOUPPER(c) (ISLOWER(c) ? toupper((unsigned char)(c)) : (c)) + + /* + * Character sets for parsing. + */ +#define CHARS_COMMA_SP ", \t\r\n" /* list separator */ +#define CHARS_SPACE " \t\r\n" /* word separator */ +#define CHARS_BRACE "{}" /* grouping */ + + /* + * Scaffolding. I don't want to lose messages while the program is under + * development. + */ +extern int REMOVE(const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/testdb b/src/util/testdb new file mode 100644 index 0000000..097c6c0 --- /dev/null +++ b/src/util/testdb @@ -0,0 +1,2 @@ +foo fooval +bar barval diff --git a/src/util/timecmp.c b/src/util/timecmp.c new file mode 100644 index 0000000..607a9ae --- /dev/null +++ b/src/util/timecmp.c @@ -0,0 +1,93 @@ +/*++ +/* NAME +/* timecmp 3 +/* SUMMARY +/* compare two time_t values +/* SYNOPSIS +/* #include +/* +/* int timecmp(t1, t2) +/* time_t t1; +/* time_t t2; +/* DESCRIPTION +/* The timecmp() function return an integer greater than, equal to, or +/* less than 0, according as the time t1 is greater than, equal to, or +/* less than the time t2. The comparison is made in a manner that is +/* insensitive to clock wrap-around, provided the underlying times are +/* within half of the time interval between the smallest and largest +/* representable time values. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Viktor Dukhovni +/*--*/ + +#include "timecmp.h" + +/* timecmp - wrap-safe time_t comparison */ + +int timecmp(time_t t1, time_t t2) +{ + time_t delta = t1 - t2; + + if (delta == 0) + return 0; + +#define UNSIGNED(type) ( ((type)-1) > ((type)0) ) + + /* + * With a constant switch value, the compiler will emit only the code for + * the correct case, so the signed/unsigned test happens at compile time. + */ + switch (UNSIGNED(time_t) ? 0 : 1) { + case 0: + return ((2 * delta > delta) ? 1 : -1); + case 1: + return ((delta > (time_t) 0) ? 1 : -1); + } +} + +#ifdef TEST +#include + + /* + * Bit banging!! There is no official constant that defines the INT_MAX + * equivalent of the off_t type. Wietse came up with the following macro + * that works as long as off_t is some two's complement number. + * + * Note, however, that C99 permits signed integer representations other than + * two's complement. + */ +#include +#define __MAXINT__(T) ((T) (((((T) 1) << ((sizeof(T) * CHAR_BIT) - 1)) ^ ((T) -1)))) + +int main(void) +{ + time_t now = time((time_t *) 0); + + /* Test that it works for normal times */ + assert(timecmp(now + 10, now) > 0); + assert(timecmp(now, now) == 0); + assert(timecmp(now - 10, now) < 0); + + /* Test that it works at a boundary time */ + if (UNSIGNED(time_t)) + now = (time_t) -1; + else + now = __MAXINT__(time_t); + + assert(timecmp(now + 10, now) > 0); + assert(timecmp(now, now) == 0); + assert(timecmp(now - 10, now) < 0); + + return (0); +} + +#endif diff --git a/src/util/timecmp.h b/src/util/timecmp.h new file mode 100644 index 0000000..b6efeab --- /dev/null +++ b/src/util/timecmp.h @@ -0,0 +1,37 @@ +#ifndef _TIMECMP_H_INCLUDED_ +#define _TIMECMP_H_INCLUDED_ + +#include + +/*++ +/* NAME +/* timecmp 3h +/* SUMMARY +/* compare two time_t values +/* SYNOPSIS +/* #include +/* +/* int timecmp(t1, t2) +/* time_t t1; +/* time_t t2; +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern int timecmp(time_t, time_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Viktor Dukhovni +/*--*/ + +#endif diff --git a/src/util/timed_connect.c b/src/util/timed_connect.c new file mode 100644 index 0000000..962545f --- /dev/null +++ b/src/util/timed_connect.c @@ -0,0 +1,111 @@ +/*++ +/* NAME +/* timed_connect 3 +/* SUMMARY +/* connect operation with timeout +/* SYNOPSIS +/* #include +/* #include +/* +/* int timed_connect(fd, buf, buf_len, timeout) +/* int fd; +/* struct sockaddr *buf; +/* int buf_len; +/* int timeout; +/* DESCRIPTION +/* timed_connect() implement a BSD socket connect() operation that is +/* bounded in time. +/* +/* Arguments: +/* .IP fd +/* File descriptor in the range 0..FD_SETSIZE. This descriptor +/* must be set to non-blocking mode prior to calling timed_connect(). +/* .IP buf +/* Socket address buffer pointer. +/* .IP buf_len +/* Size of socket address buffer. +/* .IP timeout +/* The deadline in seconds. This must be a number > 0. +/* DIAGNOSTICS +/* Panic: interface violations. +/* When the operation does not complete within the deadline, the +/* result value is -1, and errno is set to ETIMEDOUT. +/* All other returns are identical to those of a blocking connect(2) +/* operation. +/* WARNINGS +/* .ad +/* .fi +/* A common error is to call timed_connect() without enabling +/* non-blocking I/O on the socket. In that case, the \fItimeout\fR +/* parameter takes no effect. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "iostuff.h" +#include "sane_connect.h" +#include "timed_connect.h" + +/* timed_connect - connect with deadline */ + +int timed_connect(int sock, struct sockaddr *sa, int len, int timeout) +{ + int error; + SOCKOPT_SIZE error_len; + + /* + * Sanity check. Just like with timed_wait(), the timeout must be a + * positive number. + */ + if (timeout <= 0) + msg_panic("timed_connect: bad timeout: %d", timeout); + + /* + * Start the connection, and handle all possible results. + */ + if (sane_connect(sock, sa, len) == 0) + return (0); + if (errno != EINPROGRESS) + return (-1); + + /* + * A connection is in progress. Wait for a limited amount of time for + * something to happen. If nothing happens, report an error. + */ + if (write_wait(sock, timeout) < 0) + return (-1); + + /* + * Something happened. Some Solaris 2 versions have getsockopt() itself + * return the error, instead of returning it via the parameter list. + */ + error = 0; + error_len = sizeof(error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (void *) &error, &error_len) < 0) + return (-1); + if (error) { + errno = error; + return (-1); + } + + /* + * No problems. + */ + return (0); +} diff --git a/src/util/timed_connect.h b/src/util/timed_connect.h new file mode 100644 index 0000000..76ac715 --- /dev/null +++ b/src/util/timed_connect.h @@ -0,0 +1,31 @@ +#ifndef _TIMED_CONNECT_H_INCLUDED_ +#define _TIMED_CONNECT_H_INCLUDED_ + +/*++ +/* NAME +/* timed_connect 3h +/* SUMMARY +/* connect operation with timeout +/* SYNOPSIS +/* #include +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern int timed_connect(int, struct sockaddr *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/timed_read.c b/src/util/timed_read.c new file mode 100644 index 0000000..fdae8fa --- /dev/null +++ b/src/util/timed_read.c @@ -0,0 +1,86 @@ +/*++ +/* NAME +/* timed_read 3 +/* SUMMARY +/* read operation with pre-read timeout +/* SYNOPSIS +/* #include +/* +/* ssize_t timed_read(fd, buf, len, timeout, context) +/* int fd; +/* void *buf; +/* size_t len; +/* int timeout; +/* void *context; +/* DESCRIPTION +/* timed_read() performs a read() operation when the specified +/* descriptor becomes readable within a user-specified deadline. +/* +/* Arguments: +/* .IP fd +/* File descriptor in the range 0..FD_SETSIZE. +/* .IP buf +/* Read buffer pointer. +/* .IP len +/* Read buffer size. +/* .IP timeout +/* The deadline in seconds. If this is <= 0, the deadline feature +/* is disabled. +/* .IP context +/* Application context. This parameter is unused. It exists only +/* for the sake of VSTREAM compatibility. +/* DIAGNOSTICS +/* When the operation does not complete within the deadline, the +/* result value is -1, and errno is set to ETIMEDOUT. +/* All other returns are identical to those of a read(2) operation. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* timed_read - read with deadline */ + +ssize_t timed_read(int fd, void *buf, size_t len, + int timeout, void *unused_context) +{ + ssize_t ret; + + /* + * Wait for a limited amount of time for something to happen. If nothing + * happens, report an ETIMEDOUT error. + * + * XXX Solaris 8 read() fails with EAGAIN after read-select() returns + * success. + */ + for (;;) { + if (timeout > 0 && read_wait(fd, timeout) < 0) + return (-1); + if ((ret = read(fd, buf, len)) < 0 && timeout > 0 && errno == EAGAIN) { + msg_warn("read() returns EAGAIN on a readable file descriptor!"); + msg_warn("pausing to avoid going into a tight select/read loop!"); + sleep(1); + continue; + } else if (ret < 0 && errno == EINTR) { + continue; + } else { + return (ret); + } + } +} diff --git a/src/util/timed_wait.c b/src/util/timed_wait.c new file mode 100644 index 0000000..45fbb69 --- /dev/null +++ b/src/util/timed_wait.c @@ -0,0 +1,122 @@ +/*++ +/* NAME +/* timed_wait 3 +/* SUMMARY +/* wait operations with timeout +/* SYNOPSIS +/* #include +/* +/* int timed_waitpid(pid, statusp, options, time_limit) +/* pid_t pid; +/* WAIT_STATUS_T *statusp; +/* int options; +/* int time_limit; +/* DESCRIPTION +/* \fItimed_waitpid\fR() waits at most \fItime_limit\fR seconds +/* for process termination. +/* +/* Arguments: +/* .IP "pid, statusp, options" +/* The process ID, status pointer and options passed to waitpid(3). +/* .IP time_limit +/* The time in seconds that timed_waitpid() will wait. +/* This must be a number > 0. +/* DIAGNOSTICS +/* Panic: interface violation. +/* +/* When the time limit is exceeded, the result is -1 and errno +/* is set to ETIMEDOUT. Otherwise, the result value is the result +/* from the underlying waitpid() routine. +/* BUGS +/* If there were a \fIportable\fR way to select() on process status +/* information, these routines would not have to use a steenkeeng +/* alarm() timer and signal() handler. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include + +/* Application-specific. */ + +static int timed_wait_expired; + +/* timed_wait_alarm - timeout handler */ + +static void timed_wait_alarm(int unused_sig) +{ + + /* + * WARNING WARNING WARNING. + * + * This code runs at unpredictable moments, as a signal handler. This code + * is here only so that we can break out of waitpid(). Don't put any code + * here other than for setting a global flag. + */ + timed_wait_expired = 1; +} + +/* timed_waitpid - waitpid with time limit */ + +int timed_waitpid(pid_t pid, WAIT_STATUS_T *statusp, int options, + int time_limit) +{ + const char *myname = "timed_waitpid"; + struct sigaction action; + struct sigaction old_action; + int time_left; + int wpid; + + /* + * Sanity checks. + */ + if (time_limit <= 0) + msg_panic("%s: bad time limit: %d", myname, time_limit); + + /* + * Set up a timer. + */ + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = timed_wait_alarm; + if (sigaction(SIGALRM, &action, &old_action) < 0) + msg_fatal("%s: sigaction(SIGALRM): %m", myname); + timed_wait_expired = 0; + time_left = alarm(time_limit); + + /* + * Wait for only a limited amount of time. + */ + if ((wpid = waitpid(pid, statusp, options)) < 0 && timed_wait_expired) + errno = ETIMEDOUT; + + /* + * Cleanup. + */ + alarm(0); + if (sigaction(SIGALRM, &old_action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction(SIGALRM): %m", myname); + if (time_left) + alarm(time_left); + + return (wpid); +} diff --git a/src/util/timed_wait.h b/src/util/timed_wait.h new file mode 100644 index 0000000..6a153de --- /dev/null +++ b/src/util/timed_wait.h @@ -0,0 +1,35 @@ +#ifndef _TIMED_WAIT_H_INCLUDED_ +#define _TIMED_WAIT_H_INCLUDED_ + +/*++ +/* NAME +/* timed_wait 3h +/* SUMMARY +/* wait operations with timeout +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern int WARN_UNUSED_RESULT timed_waitpid(pid_t, WAIT_STATUS_T *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/timed_write.c b/src/util/timed_write.c new file mode 100644 index 0000000..220b4c4 --- /dev/null +++ b/src/util/timed_write.c @@ -0,0 +1,92 @@ +/*++ +/* NAME +/* timed_write 3 +/* SUMMARY +/* write operation with pre-write timeout +/* SYNOPSIS +/* #include +/* +/* ssize_t timed_write(fd, buf, len, timeout, context) +/* int fd; +/* const void *buf; +/* size_t len; +/* int timeout; +/* void *context; +/* DESCRIPTION +/* timed_write() performs a write() operation when the specified +/* descriptor becomes writable within a user-specified deadline. +/* +/* Arguments: +/* .IP fd +/* File descriptor in the range 0..FD_SETSIZE. +/* .IP buf +/* Write buffer pointer. +/* .IP len +/* Write buffer size. +/* .IP timeout +/* The deadline in seconds. If this is <= 0, the deadline feature +/* is disabled. +/* .IP context +/* Application context. This parameter is unused. It exists only +/* for the sake of VSTREAM compatibility. +/* DIAGNOSTICS +/* When the operation does not complete within the deadline, the +/* result value is -1, and errno is set to ETIMEDOUT. +/* All other returns are identical to those of a write(2) operation. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* timed_write - write with deadline */ + +ssize_t timed_write(int fd, const void *buf, size_t len, + int timeout, void *unused_context) +{ + ssize_t ret; + + /* + * Wait for a limited amount of time for something to happen. If nothing + * happens, report an ETIMEDOUT error. + * + * XXX Solaris 8 read() fails with EAGAIN after read-select() returns + * success. The code below exists just in case their write implementation + * is equally broken. + * + * This condition may also be found on systems where select() returns + * success on pipes with less than PIPE_BUF bytes of space, and with + * badly designed software where multiple writers are fighting for access + * to the same resource. + */ + for (;;) { + if (timeout > 0 && write_wait(fd, timeout) < 0) + return (-1); + if ((ret = write(fd, buf, len)) < 0 && timeout > 0 && errno == EAGAIN) { + msg_warn("write() returns EAGAIN on a writable file descriptor!"); + msg_warn("pausing to avoid going into a tight select/write loop!"); + sleep(1); + continue; + } else if (ret < 0 && errno == EINTR) { + continue; + } else { + return (ret); + } + } +} diff --git a/src/util/translit.c b/src/util/translit.c new file mode 100644 index 0000000..ba04bf2 --- /dev/null +++ b/src/util/translit.c @@ -0,0 +1,86 @@ +/*++ +/* NAME +/* translit 3 +/* SUMMARY +/* transliterate characters +/* SYNOPSIS +/* #include +/* +/* char *translit(buf, original, replacement) +/* char *buf; +/* char *original; +/* char *replacement; +/* DESCRIPTION +/* translit() takes a null-terminated string, and replaces characters +/* given in its \fIoriginal\fR argument by the corresponding characters +/* in the \fIreplacement\fR string. The result value is the \fIbuf\fR +/* argument. +/* BUGS +/* Cannot replace null characters. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include + +/* Utility library. */ + +#include "stringops.h" + +char *translit(char *string, const char *original, const char *replacement) +{ + char *cp; + const char *op; + + /* + * For large inputs, should use a lookup table. + */ + for (cp = string; *cp != 0; cp++) { + for (op = original; *op != 0; op++) { + if (*cp == *op) { + *cp = replacement[op - original]; + break; + } + } + } + return (string); +} + +#ifdef TEST + + /* + * Usage: translit string1 string2 + * + * test program to perform the most basic operation of the UNIX tr command. + */ +#include +#include +#include +#include + +#define STR vstring_str + +int main(int argc, char **argv) +{ + VSTRING *buf = vstring_alloc(100); + + if (argc != 3) + msg_fatal("usage: %s string1 string2", argv[0]); + while (vstring_fgets(buf, VSTREAM_IN)) + vstream_fputs(translit(STR(buf), argv[1], argv[2]), VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + vstring_free(buf); + return (0); +} + +#endif diff --git a/src/util/trigger.h b/src/util/trigger.h new file mode 100644 index 0000000..e716d53 --- /dev/null +++ b/src/util/trigger.h @@ -0,0 +1,34 @@ +#ifndef _TRIGGER_H_INCLUDED_ +#define _TRIGGER_H_INCLUDED_ + +/*++ +/* NAME +/* trigger 3h +/* SUMMARY +/* client interface file +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern int unix_trigger(const char *, const char *, ssize_t, int); +extern int inet_trigger(const char *, const char *, ssize_t, int); +extern int fifo_trigger(const char *, const char *, ssize_t, int); +extern int stream_trigger(const char *, const char *, ssize_t, int); +extern int pass_trigger(const char *, const char *, ssize_t, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/trimblanks.c b/src/util/trimblanks.c new file mode 100644 index 0000000..a4d9cb2 --- /dev/null +++ b/src/util/trimblanks.c @@ -0,0 +1,50 @@ +/*++ +/* NAME +/* trimblanks 3 +/* SUMMARY +/* skip leading whitespace +/* SYNOPSIS +/* #include +/* +/* char *trimblanks(string, len) +/* char *string; +/* ssize_t len; +/* DESCRIPTION +/* trimblanks() returns a pointer to the beginning of the trailing +/* whitespace in \fIstring\fR, or a pointer to the string terminator +/* when the string contains no trailing whitespace. +/* The \fIlen\fR argument is either zero or the string length. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include + +/* Utility library. */ + +#include "stringops.h" + +char *trimblanks(char *string, ssize_t len) +{ + char *curr; + + if (len) { + curr = string + len; + } else { + for (curr = string; *curr != 0; curr++) + /* void */ ; + } + while (curr > string && ISSPACE(curr[-1])) + curr -= 1; + return (curr); +} diff --git a/src/util/unescape.c b/src/util/unescape.c new file mode 100644 index 0000000..4eacbba --- /dev/null +++ b/src/util/unescape.c @@ -0,0 +1,208 @@ +/*++ +/* NAME +/* unescape 3 +/* SUMMARY +/* translate C-like escape sequences +/* SYNOPSIS +/* #include +/* +/* VSTRING *unescape(result, input) +/* VSTRING *result; +/* const char *input; +/* +/* VSTRING *escape(result, input, len) +/* VSTRING *result; +/* const char *input; +/* ssize_t len; +/* DESCRIPTION +/* unescape() translates C-like escape sequences in the null-terminated +/* string \fIinput\fR and places the result in \fIresult\fR. The result +/* is null-terminated, and is the function result value. +/* +/* escape() does the reverse transformation. +/* +/* Escape sequences and their translations: +/* .IP \ea +/* Bell character. +/* .IP \eb +/* Backspace character. +/* .IP \ef +/* formfeed character. +/* .IP \en +/* newline character +/* .IP \er +/* Carriage-return character. +/* .IP \et +/* Horizontal tab character. +/* .IP \ev +/* Vertical tab character. +/* .IP \e\e +/* Backslash character. +/* .IP \e\fInum\fR +/* 8-bit character whose ASCII value is the 1..3 digit +/* octal number \fInum\fR. +/* .IP \e\fIother\fR +/* The backslash character is discarded. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include + +/* unescape - process escape sequences */ + +VSTRING *unescape(VSTRING *result, const char *data) +{ + int ch; + int oval; + int i; + +#define UCHAR(cp) ((unsigned char *) (cp)) +#define ISOCTAL(ch) (ISDIGIT(ch) && (ch) != '8' && (ch) != '9') + + VSTRING_RESET(result); + + while ((ch = *UCHAR(data++)) != 0) { + if (ch == '\\') { + if ((ch = *UCHAR(data++)) == 0) + break; + switch (ch) { + case 'a': /* \a -> audible bell */ + ch = '\a'; + break; + case 'b': /* \b -> backspace */ + ch = '\b'; + break; + case 'f': /* \f -> formfeed */ + ch = '\f'; + break; + case 'n': /* \n -> newline */ + ch = '\n'; + break; + case 'r': /* \r -> carriagereturn */ + ch = '\r'; + break; + case 't': /* \t -> horizontal tab */ + ch = '\t'; + break; + case 'v': /* \v -> vertical tab */ + ch = '\v'; + break; + case '0': /* \nnn -> ASCII value */ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + for (oval = ch - '0', i = 0; + i < 2 && (ch = *UCHAR(data)) != 0 && ISOCTAL(ch); + i++, data++) { + oval = (oval << 3) | (ch - '0'); + } + ch = oval; + break; + default: /* \any -> any */ + break; + } + } + VSTRING_ADDCH(result, ch); + } + VSTRING_TERMINATE(result); + return (result); +} + +/* escape - reverse transformation */ + +VSTRING *escape(VSTRING *result, const char *data, ssize_t len) +{ + int ch; + + VSTRING_RESET(result); + while (len-- > 0) { + ch = *UCHAR(data++); + if (ISASCII(ch)) { + if (ISPRINT(ch)) { + if (ch == '\\') + VSTRING_ADDCH(result, ch); + VSTRING_ADDCH(result, ch); + continue; + } else if (ch == '\a') { /* \a -> audible bell */ + vstring_strcat(result, "\\a"); + continue; + } else if (ch == '\b') { /* \b -> backspace */ + vstring_strcat(result, "\\b"); + continue; + } else if (ch == '\f') { /* \f -> formfeed */ + vstring_strcat(result, "\\f"); + continue; + } else if (ch == '\n') { /* \n -> newline */ + vstring_strcat(result, "\\n"); + continue; + } else if (ch == '\r') { /* \r -> carriagereturn */ + vstring_strcat(result, "\\r"); + continue; + } else if (ch == '\t') { /* \t -> horizontal tab */ + vstring_strcat(result, "\\t"); + continue; + } else if (ch == '\v') { /* \v -> vertical tab */ + vstring_strcat(result, "\\v"); + continue; + } + } + vstring_sprintf_append(result, "\\%03o", ch); + } + VSTRING_TERMINATE(result); + return (result); +} + +#ifdef TEST + +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + VSTRING *in = vstring_alloc(10); + VSTRING *out = vstring_alloc(10); + int un_escape = 1; + + if (argc > 2 || (argc > 1 && (un_escape = strcmp(argv[1], "-e"))) != 0) + msg_fatal("usage: %s [-e (escape)]", argv[0]); + + if (un_escape) { + while (vstring_fgets_nonl(in, VSTREAM_IN)) { + unescape(out, vstring_str(in)); + vstream_fwrite(VSTREAM_OUT, vstring_str(out), VSTRING_LEN(out)); + VSTREAM_PUTC('\n', VSTREAM_OUT); + } + } else { + while (vstring_fgets_nonl(in, VSTREAM_IN)) { + escape(out, vstring_str(in), VSTRING_LEN(in)); + vstream_fwrite(VSTREAM_OUT, vstring_str(out), VSTRING_LEN(out)); + VSTREAM_PUTC('\n', VSTREAM_OUT); + } + } + vstream_fflush(VSTREAM_OUT); + exit(0); +} + +#endif diff --git a/src/util/unescape.in b/src/util/unescape.in new file mode 100644 index 0000000..41f24a7 --- /dev/null +++ b/src/util/unescape.in @@ -0,0 +1,4 @@ +\a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z +\1\2\3\4\5\6\7\8\9 +\1234\2345\3456\4567 +rcpt to: diff --git a/src/util/unescape.ref b/src/util/unescape.ref new file mode 100644 index 0000000..db16fa8 --- /dev/null +++ b/src/util/unescape.ref @@ -0,0 +1,11 @@ +0000000 \a \b c d e \f g h i j k l m \n o p + 007 010 143 144 145 014 147 150 151 152 153 154 155 012 157 160 +0000020 q \r s \t u \v w x y z \n 001 002 003 004 005 + 161 015 163 011 165 013 167 170 171 172 012 001 002 003 004 005 +0000040 006 \a 8 9 \n S 4 234 5 345 6 . 7 \n r c + 006 007 070 071 012 123 064 234 065 345 066 056 067 012 162 143 +0000060 p t t o : < w i e t s e @ Ï€ ** + 160 164 040 164 157 072 074 167 151 145 164 163 145 100 317 200 +0000100 . p o r c u p i n e . o r g > \n + 056 160 157 162 143 165 160 151 156 145 056 157 162 147 076 012 +0000120 diff --git a/src/util/unix_connect.c b/src/util/unix_connect.c new file mode 100644 index 0000000..cbd8c0d --- /dev/null +++ b/src/util/unix_connect.c @@ -0,0 +1,110 @@ +/*++ +/* NAME +/* unix_connect 3 +/* SUMMARY +/* connect to UNIX-domain listener +/* SYNOPSIS +/* #include +/* +/* int unix_connect(addr, block_mode, timeout) +/* const char *addr; +/* int block_mode; +/* int timeout; +/* DESCRIPTION +/* unix_connect() connects to a listener in the UNIX domain at the +/* specified address, and returns the resulting file descriptor. +/* +/* Arguments: +/* .IP addr +/* Null-terminated string with connection destination. +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* .IP timeout +/* Bounds the number of seconds that the operation may take. Specify +/* a value <= 0 to disable the time limit. +/* DIAGNOSTICS +/* The result is -1 in case the connection could not be made. +/* Fatal errors: other system call failures. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "iostuff.h" +#include "sane_connect.h" +#include "connect.h" +#include "timed_connect.h" + +/* unix_connect - connect to UNIX-domain listener */ + +int unix_connect(const char *addr, int block_mode, int timeout) +{ +#undef sun + struct sockaddr_un sun; + ssize_t len = strlen(addr); + int sock; + + /* + * Translate address information to internal form. + */ + if (len >= sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", addr); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = len + 1; +#endif + memcpy(sun.sun_path, addr, len + 1); + + /* + * Create a client socket. + */ + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + return (-1); + + /* + * Timed connect. + */ + if (timeout > 0) { + non_blocking(sock, NON_BLOCKING); + if (timed_connect(sock, (struct sockaddr *) &sun, sizeof(sun), timeout) < 0) { + close(sock); + return (-1); + } + if (block_mode != NON_BLOCKING) + non_blocking(sock, block_mode); + return (sock); + } + + /* + * Maybe block until connected. + */ + else { + non_blocking(sock, block_mode); + if (sane_connect(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0 + && errno != EINPROGRESS) { + close(sock); + return (-1); + } + return (sock); + } +} diff --git a/src/util/unix_dgram_connect.c b/src/util/unix_dgram_connect.c new file mode 100644 index 0000000..3df8963 --- /dev/null +++ b/src/util/unix_dgram_connect.c @@ -0,0 +1,91 @@ +/*++ +/* NAME +/* unix_dgram_connect 3 +/* SUMMARY +/* connect to UNIX-domain datagram server +/* SYNOPSIS +/* #include +/* +/* int unix_dgram_connect( +/* const char *path, +/* int block_mode) +/* DESCRIPTION +/* unix_dgram_connect() connects to the specified UNIX-domain +/* datagram server, and returns the resulting file descriptor. +/* +/* Arguments: +/* .IP path +/* Null-terminated string with connection destination.` +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* DIAGNOSTICS +/* Fatal errors: path too large, can't create socket. +/* +/* Other errors result in a -1 result value, with errno indicating +/* why the service is unavailable. +/* .sp +/* ENOENT: the named socket does not exist. +/* .sp +/* ECONNREFUSED: the named socket is not open. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include + +/* unix_dgram_connect - connect to UNIX-domain datagram service */ + +int unix_dgram_connect(const char *path, int block_mode) +{ + const char myname[] = "unix_dgram_connect"; +#undef sun + struct sockaddr_un sun; + ssize_t path_len; + int sock; + + /* + * Translate address information to internal form. + */ + if ((path_len = strlen(path)) >= sizeof(sun.sun_path)) + msg_fatal("%s: unix-domain name too long: %s", myname, path); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len + 1); + + /* + * Create a client socket. + */ + if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + if (connect(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0) { + close(sock); + return (-1); + } + non_blocking(sock, block_mode); + return (sock); +} diff --git a/src/util/unix_dgram_listen.c b/src/util/unix_dgram_listen.c new file mode 100644 index 0000000..e73ad4e --- /dev/null +++ b/src/util/unix_dgram_listen.c @@ -0,0 +1,93 @@ +/*++ +/* NAME +/* unix_dgram_listen 3 +/* SUMMARY +/* listen to UNIX-domain datagram server +/* SYNOPSIS +/* #include +/* +/* int unix_dgram_listen( +/* const char *path, +/* int block_mode) +/* DESCRIPTION +/* unix_dgram_listen() binds to the specified UNIX-domain +/* datagram endpoint, and returns the resulting file descriptor. +/* +/* Arguments: +/* .IP path +/* Null-terminated string with connection destination. +/* .IP backlog +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* DIAGNOSTICS +/* Fatal errors: path too large, can't create socket. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include +#include + +/* unix_dgram_listen - bind to UNIX-domain datagram endpoint */ + +int unix_dgram_listen(const char *path, int block_mode) +{ + const char myname[] = "unix_dgram_listen"; +#undef sun + struct sockaddr_un sun; + ssize_t path_len; + int sock; + + /* + * Translate address information to internal form. + */ + if ((path_len = strlen(path)) >= sizeof(sun.sun_path)) + msg_fatal("%s: unix-domain name too long: %s", myname, path); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len + 1); + + /* + * Create a 'server' socket. + */ + if ((sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) + msg_fatal("%s: socket: %m", myname); + if (unlink(path) < 0 && errno != ENOENT) + msg_fatal( "remove %s: %m", path); + if (bind(sock, (struct sockaddr *) & sun, sizeof(sun)) < 0) + msg_fatal( "bind: %s: %m", path); +#ifdef FCHMOD_UNIX_SOCKETS + if (fchmod(sock, 0666) < 0) + msg_fatal("fchmod socket %s: %m", path); +#else + if (chmod(path, 0666) < 0) + msg_fatal("chmod socket %s: %m", path); +#endif + non_blocking(sock, block_mode); + return (sock); +} diff --git a/src/util/unix_listen.c b/src/util/unix_listen.c new file mode 100644 index 0000000..6440406 --- /dev/null +++ b/src/util/unix_listen.c @@ -0,0 +1,113 @@ +/*++ +/* NAME +/* unix_listen 3 +/* SUMMARY +/* start UNIX-domain listener +/* SYNOPSIS +/* #include +/* +/* int unix_listen(addr, backlog, block_mode) +/* const char *addr; +/* int backlog; +/* int block_mode; +/* +/* int unix_accept(fd) +/* int fd; +/* DESCRIPTION +/* The \fBunix_listen\fR() routine starts a listener in the UNIX domain +/* on the specified address, with the specified backlog, and returns +/* the resulting file descriptor. +/* +/* unix_accept() accepts a connection and sanitizes error results. +/* +/* Arguments: +/* .IP addr +/* Null-terminated string with connection destination. +/* .IP backlog +/* This argument is passed on to the \fIlisten(2)\fR routine. +/* .IP block_mode +/* Either NON_BLOCKING for a non-blocking socket, or BLOCKING for +/* blocking mode. +/* .IP fd +/* File descriptor returned by unix_listen(). +/* DIAGNOSTICS +/* Fatal errors: unix_listen() aborts upon any system call failure. +/* unix_accept() leaves all error handling up to the caller. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System interfaces. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "iostuff.h" +#include "listen.h" +#include "sane_accept.h" + +/* unix_listen - create UNIX-domain listener */ + +int unix_listen(const char *addr, int backlog, int block_mode) +{ +#undef sun + struct sockaddr_un sun; + ssize_t len = strlen(addr); + int sock; + + /* + * Translate address information to internal form. + */ + if (len >= sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", addr); + memset((void *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = len + 1; +#endif + memcpy(sun.sun_path, addr, len + 1); + + /* + * Create a listener socket. Do whatever we can so we don't run into + * trouble when this process is restarted after crash. + */ + if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) + msg_fatal("socket: %m"); + if (unlink(addr) < 0 && errno != ENOENT) + msg_fatal("remove %s: %m", addr); + if (bind(sock, (struct sockaddr *) &sun, sizeof(sun)) < 0) + msg_fatal("bind: %s: %m", addr); +#ifdef FCHMOD_UNIX_SOCKETS + if (fchmod(sock, 0666) < 0) + msg_fatal("fchmod socket %s: %m", addr); +#else + if (chmod(addr, 0666) < 0) + msg_fatal("chmod socket %s: %m", addr); +#endif + non_blocking(sock, block_mode); + if (listen(sock, backlog) < 0) + msg_fatal("listen: %m"); + return (sock); +} + +/* unix_accept - accept connection */ + +int unix_accept(int fd) +{ + return (sane_accept(fd, (struct sockaddr *) 0, (SOCKADDR_SIZE *) 0)); +} diff --git a/src/util/unix_pass_fd_fix.c b/src/util/unix_pass_fd_fix.c new file mode 100644 index 0000000..9522e61 --- /dev/null +++ b/src/util/unix_pass_fd_fix.c @@ -0,0 +1,67 @@ +/*++ +/* NAME +/* unix_pass_fd_fix 3 +/* SUMMARY +/* file descriptor passing bug workarounds +/* SYNOPSIS +/* #include +/* +/* void set_unix_pass_fd_fix(workarounds) +/* const char *workarounds; +/* DESCRIPTION +/* This module supports programmatic control over workarounds +/* for sending or receiving file descriptors over UNIX-domain +/* sockets. +/* +/* set_unix_pass_fd_fix() takes a list of workarounds in external +/* form, and stores their internal representation. The result +/* is used by unix_send_fd() and unix_recv_fd(). +/* +/* Arguments: +/* .IP workarounds +/* List of zero or more of the following, separated by comma +/* or whitespace. +/* .RS +/* .IP cmsg_len +/* Send the CMSG_LEN of the file descriptor, instead of +/* the total message buffer length. +/* .RE +/* SEE ALSO +/* unix_send_fd(3) send file descriptor +/* unix_recv_fd(3) receive file descriptor +/* DIAGNOSTICS +/* Fatal errors: non-existent workaround. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +int unix_pass_fd_fix = 0; + +/* set_unix_pass_fd_fix - set workaround programmatically */ + +void set_unix_pass_fd_fix(const char *workarounds) +{ + const static NAME_MASK table[] = { + "cmsg_len", UNIX_PASS_FD_FIX_CMSG_LEN, + 0, + }; + + unix_pass_fd_fix = name_mask("descriptor passing workarounds", + table, workarounds); +} diff --git a/src/util/unix_recv_fd.c b/src/util/unix_recv_fd.c new file mode 100644 index 0000000..3410cff --- /dev/null +++ b/src/util/unix_recv_fd.c @@ -0,0 +1,178 @@ +/*++ +/* NAME +/* unix_recv_fd 3 +/* SUMMARY +/* receive file descriptor +/* SYNOPSIS +/* #include +/* +/* int unix_recv_fd(fd) +/* int fd; +/* DESCRIPTION +/* unix_recv_fd() receives a file descriptor via the specified +/* UNIX-domain socket. The result value is the received descriptor. +/* +/* Arguments: +/* .IP fd +/* File descriptor that connects the sending and receiving processes. +/* DIAGNOSTICS +/* unix_recv_fd() returns -1 upon failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include /* includes */ +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* unix_recv_fd - receive file descriptor */ + +int unix_recv_fd(int fd) +{ + const char *myname = "unix_recv_fd"; + + /* + * This code does not work with version <2.2 Linux kernels, and it does + * not compile with version <2 Linux libraries. + */ +#ifdef CANT_USE_SEND_RECV_MSG + msg_warn("%s: your system has no support for file descriptor passing", + myname); + return (-1); +#else + struct msghdr msg; + int newfd; + struct iovec iov[1]; + char buf[1]; + + /* + * Adapted from: W. Richard Stevens, UNIX Network Programming, Volume 1, + * Second edition. Except that we use CMSG_LEN instead of CMSG_SPACE, for + * portability to some LP64 environments. See also unix_send_fd.c. + */ +#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL) + union { + struct cmsghdr just_for_alignment; + char control[CMSG_SPACE(sizeof(newfd))]; + } control_un; + struct cmsghdr *cmptr; + + memset((void *) &msg, 0, sizeof(msg)); /* Fix 200512 */ + msg.msg_control = control_un.control; + if (unix_pass_fd_fix & UNIX_PASS_FD_FIX_CMSG_LEN) { + msg.msg_controllen = CMSG_LEN(sizeof(newfd)); /* Fix 200506 */ + } else { + msg.msg_controllen = sizeof(control_un.control); /* normal */ + } +#else + msg.msg_accrights = (char *) &newfd; + msg.msg_accrightslen = sizeof(newfd); +#endif + + msg.msg_name = 0; + msg.msg_namelen = 0; + + /* + * XXX We don't want to pass any data, just a file descriptor. However, + * setting msg.msg_iov = 0 and msg.msg_iovlen = 0 causes trouble: we need + * to read_wait() before we can receive the descriptor, and the code + * fails after the first descriptor when we attempt to receive a sequence + * of descriptors. + */ + iov->iov_base = buf; + iov->iov_len = sizeof(buf); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + if (recvmsg(fd, &msg, 0) < 0) + return (-1); + +#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL) + if ((cmptr = CMSG_FIRSTHDR(&msg)) != 0 + && cmptr->cmsg_len == CMSG_LEN(sizeof(newfd))) { + if (cmptr->cmsg_level != SOL_SOCKET) + msg_fatal("%s: control level %d != SOL_SOCKET", + myname, cmptr->cmsg_level); + if (cmptr->cmsg_type != SCM_RIGHTS) + msg_fatal("%s: control type %d != SCM_RIGHTS", + myname, cmptr->cmsg_type); + return (*(int *) CMSG_DATA(cmptr)); + } else + return (-1); +#else + if (msg.msg_accrightslen == sizeof(newfd)) + return (newfd); + else + return (-1); +#endif +#endif +} + +#ifdef TEST + + /* + * Proof-of-concept program. Receive a descriptor (presumably from the + * unix_send_fd test program) and copy its content until EOF. + */ +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + char *transport; + char *endpoint; + int listen_sock; + int client_sock; + int client_fd; + ssize_t read_count; + char buf[1024]; + + if (argc < 2 || argc > 3 + || (endpoint = split_at(transport = argv[1], ':')) == 0 + || *endpoint == 0 || *transport == 0) + msg_fatal("usage: %s transport:endpoint [workaround]", argv[0]); + + if (strcmp(transport, "unix") == 0) { + listen_sock = unix_listen(endpoint, 10, BLOCKING); + } else { + msg_fatal("invalid transport name: %s", transport); + } + if (listen_sock < 0) + msg_fatal("listen %s:%s: %m", transport, endpoint); + + client_sock = accept(listen_sock, (struct sockaddr *) 0, (SOCKADDR_SIZE) 0); + if (client_sock < 0) + msg_fatal("accept: %m"); + + set_unix_pass_fd_fix(argv[2] ? argv[2] : ""); + + while ((client_fd = unix_recv_fd(client_sock)) >= 0) { + msg_info("client_fd = %d, fix=%d", client_fd, unix_pass_fd_fix); + while ((read_count = read(client_fd, buf, sizeof(buf))) > 0) + write(1, buf, read_count); + if (read_count < 0) + msg_fatal("read: %m"); + close(client_fd); + } + exit(0); +} + +#endif diff --git a/src/util/unix_send_fd.c b/src/util/unix_send_fd.c new file mode 100644 index 0000000..1998a7c --- /dev/null +++ b/src/util/unix_send_fd.c @@ -0,0 +1,193 @@ +/*++ +/* NAME +/* unix_send_fd 3 +/* SUMMARY +/* send file descriptor +/* SYNOPSIS +/* #include +/* +/* int unix_send_fd(fd, sendfd) +/* int fd; +/* int sendfd; +/* DESCRIPTION +/* unix_send_fd() sends a file descriptor over the specified +/* UNIX-domain socket. +/* +/* Arguments: +/* .IP fd +/* File descriptor that connects the sending and receiving processes. +/* .IP sendfd +/* The file descriptor to be sent. +/* DIAGNOSTICS +/* unix_send_fd() returns -1 upon failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include /* includes */ +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* unix_send_fd - send file descriptor */ + +int unix_send_fd(int fd, int sendfd) +{ + + /* + * This code does not work with version <2.2 Linux kernels, and it does + * not compile with version <2 Linux libraries. + */ +#ifdef CANT_USE_SEND_RECV_MSG + const char *myname = "unix_send_fd"; + + msg_warn("%s: your system has no support for file descriptor passing", + myname); + return (-1); +#else + struct msghdr msg; + struct iovec iov[1]; + + /* + * Adapted from: W. Richard Stevens, UNIX Network Programming, Volume 1, + * Second edition. Except that we use CMSG_LEN instead of CMSG_SPACE, for + * portability to some LP64 environments. See also unix_recv_fd.c. + */ +#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL) + union { + struct cmsghdr just_for_alignment; + char control[CMSG_SPACE(sizeof(sendfd))]; + } control_un; + struct cmsghdr *cmptr; + + memset((void *) &msg, 0, sizeof(msg)); /* Fix 200512 */ + msg.msg_control = control_un.control; + if (unix_pass_fd_fix & UNIX_PASS_FD_FIX_CMSG_LEN) { + msg.msg_controllen = CMSG_LEN(sizeof(sendfd)); /* Fix 200506 */ + } else { + msg.msg_controllen = sizeof(control_un.control); /* normal */ + } + cmptr = CMSG_FIRSTHDR(&msg); + cmptr->cmsg_len = CMSG_LEN(sizeof(sendfd)); + cmptr->cmsg_level = SOL_SOCKET; + cmptr->cmsg_type = SCM_RIGHTS; + *(int *) CMSG_DATA(cmptr) = sendfd; +#else + msg.msg_accrights = (char *) &sendfd; + msg.msg_accrightslen = sizeof(sendfd); +#endif + + msg.msg_name = 0; + msg.msg_namelen = 0; + + /* + * XXX We don't want to pass any data, just a file descriptor. However, + * setting msg.msg_iov = 0 and msg.msg_iovlen = 0 causes trouble. See the + * comments in the unix_recv_fd() routine. + */ + iov->iov_base = ""; + iov->iov_len = 1; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + /* + * The CMSG_LEN send/receive workaround was originally developed for + * OpenBSD 3.6 on SPARC64. After the workaround was verified to not break + * Solaris 8 on SPARC64, it was hard-coded with Postfix 2.3 for all + * platforms because of increasing pressure to work on other things. The + * workaround does nothing for 32-bit systems. + * + * The investigation was reopened with Postfix 2.7 because the workaround + * broke with NetBSD 5.0 on 64-bit architectures. This time it was found + * that OpenBSD <= 4.3 on AMD64 and SPARC64 needed the workaround for + * sending only. The following platforms worked with and without the + * workaround: OpenBSD 4.5 on AMD64 and SPARC64, FreeBSD 7.2 on AMD64, + * Solaris 8 on SPARC64, and Linux 2.6-11 on x86_64. + * + * As this appears to have been an OpenBSD-specific problem, we revert to + * the Postfix 2.2 behavior. Instead of hard-coding the workaround for + * all platforms, we now detect sendmsg() errors at run time and turn on + * the workaround dynamically. + * + * The workaround was made run-time configurable to investigate the problem + * on multiple platforms. Though set_unix_pass_fd_fix() is over-kill for + * this specific problem, it is left in place so that it can serve as an + * example of how to add run-time configurable workarounds to Postfix. + */ + if (sendmsg(fd, &msg, 0) >= 0) + return (0); + if (unix_pass_fd_fix == 0) { + if (msg_verbose) + msg_info("sendmsg error (%m). Trying CMSG_LEN workaround."); + unix_pass_fd_fix = UNIX_PASS_FD_FIX_CMSG_LEN; + return (unix_send_fd(fd, sendfd)); + } else { + return (-1); + } +#endif +} + +#ifdef TEST + + /* + * Proof-of-concept program. Open a file and send the descriptor, presumably + * to the unix_recv_fd test program. + */ +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + char *transport; + char *endpoint; + char *path; + int server_sock; + int client_fd; + + msg_verbose = 1; + + if (argc < 3 + || (endpoint = split_at(transport = argv[1], ':')) == 0 + || *endpoint == 0 || *transport == 0) + msg_fatal("usage: %s transport:endpoint file...", argv[0]); + + if (strcmp(transport, "unix") == 0) { + server_sock = unix_connect(endpoint, BLOCKING, 0); + } else { + msg_fatal("invalid transport name: %s", transport); + } + if (server_sock < 0) + msg_fatal("connect %s:%s: %m", transport, endpoint); + + argv += 2; + while ((path = *argv++) != 0) { + if ((client_fd = open(path, O_RDONLY, 0)) < 0) + msg_fatal("open %s: %m", path); + msg_info("path=%s fd=%d", path, client_fd); + if (unix_send_fd(server_sock, client_fd) < 0) + msg_fatal("send file descriptor: %m"); + if (close(client_fd) != 0) + msg_fatal("close(%d): %m", client_fd); + } + exit(0); +} + +#endif diff --git a/src/util/unix_trigger.c b/src/util/unix_trigger.c new file mode 100644 index 0000000..59a18ff --- /dev/null +++ b/src/util/unix_trigger.c @@ -0,0 +1,131 @@ +/*++ +/* NAME +/* unix_trigger 3 +/* SUMMARY +/* wakeup UNIX-domain server +/* SYNOPSIS +/* #include +/* +/* int unix_trigger(service, buf, len, timeout) +/* const char *service; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* unix_trigger() wakes up the named UNIX-domain server by making +/* a brief connection to it and writing the named buffer. +/* +/* The connection is closed by a background thread. Some kernels +/* cannot handle client-side disconnect before the server has +/* received the message. +/* +/* Arguments: +/* .IP service +/* Name of the communication endpoint. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Deadline in seconds. Specify a value <= 0 to disable +/* the time limit. +/* DIAGNOSTICS +/* The result is zero in case of success, -1 in case of problems. +/* SEE ALSO +/* unix_connect(3), UNIX-domain client +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +struct unix_trigger { + int fd; + char *service; +}; + +/* unix_trigger_event - disconnect from peer */ + +static void unix_trigger_event(int event, void *context) +{ + struct unix_trigger *up = (struct unix_trigger *) context; + static const char *myname = "unix_trigger_event"; + + /* + * Disconnect. + */ + if (event == EVENT_TIME) + msg_warn("%s: read timeout for service %s", myname, up->service); + event_disable_readwrite(up->fd); + event_cancel_timer(unix_trigger_event, context); + if (close(up->fd) < 0) + msg_warn("%s: close %s: %m", myname, up->service); + myfree(up->service); + myfree((void *) up); +} + +/* unix_trigger - wakeup UNIX-domain server */ + +int unix_trigger(const char *service, const char *buf, ssize_t len, int timeout) +{ + const char *myname = "unix_trigger"; + struct unix_trigger *up; + int fd; + + if (msg_verbose > 1) + msg_info("%s: service %s", myname, service); + + /* + * Connect... + */ + if ((fd = unix_connect(service, BLOCKING, timeout)) < 0) { + if (msg_verbose) + msg_warn("%s: connect to %s: %m", myname, service); + return (-1); + } + close_on_exec(fd, CLOSE_ON_EXEC); + + /* + * Stash away context. + */ + up = (struct unix_trigger *) mymalloc(sizeof(*up)); + up->fd = fd; + up->service = mystrdup(service); + + /* + * Write the request... + */ + if (write_buf(fd, buf, len, timeout) < 0 + || write_buf(fd, "", 1, timeout) < 0) + if (msg_verbose) + msg_warn("%s: write to %s: %m", myname, service); + + /* + * Wakeup when the peer disconnects, or when we lose patience. + */ + if (timeout > 0) + event_request_timer(unix_trigger_event, (void *) up, timeout + 100); + event_enable_read(fd, unix_trigger_event, (void *) up); + return (0); +} diff --git a/src/util/unsafe.c b/src/util/unsafe.c new file mode 100644 index 0000000..5d307c9 --- /dev/null +++ b/src/util/unsafe.c @@ -0,0 +1,77 @@ +/*++ +/* NAME +/* unsafe 3 +/* SUMMARY +/* are we running at non-user privileges +/* SYNOPSIS +/* #include +/* +/* int unsafe() +/* DESCRIPTION +/* The \fBunsafe()\fR routine attempts to determine if the process +/* (runs with privileges or has access to information) that the +/* controlling user has no access to. The purpose is to prevent +/* misuse of privileges, including access to protected information. +/* +/* The result is always false when both of the following conditions +/* are true: +/* .IP \(bu +/* The real UID is zero. +/* .IP \(bu +/* The effective UID is zero. +/* .PP +/* Otherwise, the result is true if any of the following conditions +/* is true: +/* .IP \(bu +/* The issetuid kernel flag is non-zero (on systems that support +/* this concept). +/* .IP \(bu +/* The real and effective user id differ. +/* .IP \(bu +/* The real and effective group id differ. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include "safe.h" + +/* unsafe - can we trust user-provided environment, working directory, etc. */ + +int unsafe(void) +{ + + /* + * The super-user is trusted. + */ + if (getuid() == 0 && geteuid() == 0) + return (0); + + /* + * Danger: don't trust inherited process attributes, and don't leak + * privileged info that the parent has no access to. + */ + return (geteuid() != getuid() +#ifdef HAS_ISSETUGID + || issetugid() +#endif + || getgid() != getegid()); +} diff --git a/src/util/uppercase.c b/src/util/uppercase.c new file mode 100644 index 0000000..9c61622 --- /dev/null +++ b/src/util/uppercase.c @@ -0,0 +1,43 @@ +/*++ +/* NAME +/* uppercase 3 +/* SUMMARY +/* map lowercase characters to uppercase +/* SYNOPSIS +/* #include +/* +/* char *uppercase(buf) +/* char *buf; +/* DESCRIPTION +/* uppercase() replaces lowercase characters in its null-terminated +/* input by their uppercase equivalent. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include + +/* Utility library. */ + +#include "stringops.h" + +char *uppercase(char *string) +{ + char *cp; + int ch; + + for (cp = string; (ch = *cp) != 0; cp++) + if (ISLOWER(ch)) + *cp = TOUPPER(ch); + return (string); +} diff --git a/src/util/username.c b/src/util/username.c new file mode 100644 index 0000000..680161c --- /dev/null +++ b/src/util/username.c @@ -0,0 +1,47 @@ +/*++ +/* NAME +/* username 3 +/* SUMMARY +/* lookup name of real user +/* SYNOPSIS +/* #include +/* +/* const char *username() +/* DESCRIPTION +/* username() jumps whatever system-specific hoops it takes to +/* get the name of the user who started the process. The result +/* is volatile. Make a copy if it is to be used for an appreciable +/* amount of time. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include "username.h" + +/* username - get name of user */ + +const char *username(void) +{ + uid_t uid; + struct passwd *pwd; + + uid = getuid(); + if ((pwd = getpwuid(uid)) == 0) + return (0); + return (pwd->pw_name); +} diff --git a/src/util/username.h b/src/util/username.h new file mode 100644 index 0000000..648be45 --- /dev/null +++ b/src/util/username.h @@ -0,0 +1,29 @@ +#ifndef _USERNAME_H_INCLUDED_ +#define _USERNAME_H_INCLUDED_ + +/*++ +/* NAME +/* username 3h +/* SUMMARY +/* lookup name of real user +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface. */ + +extern const char *username(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/valid_hostname.c b/src/util/valid_hostname.c new file mode 100644 index 0000000..8b234c4 --- /dev/null +++ b/src/util/valid_hostname.c @@ -0,0 +1,412 @@ +/*++ +/* NAME +/* valid_hostname 3 +/* SUMMARY +/* network name validation +/* SYNOPSIS +/* #include +/* +/* int valid_hostname(name, gripe) +/* const char *name; +/* int gripe; +/* +/* int valid_hostaddr(addr, gripe) +/* const char *addr; +/* int gripe; +/* +/* int valid_ipv4_hostaddr(addr, gripe) +/* const char *addr; +/* int gripe; +/* +/* int valid_ipv6_hostaddr(addr, gripe) +/* const char *addr; +/* int gripe; +/* +/* int valid_hostport(port, gripe) +/* const char *port; +/* int gripe; +/* DESCRIPTION +/* valid_hostname() scrutinizes a hostname: the name should +/* be no longer than VALID_HOSTNAME_LEN characters, should +/* contain only letters, digits, dots and hyphens, no adjacent +/* dots, no leading or trailing dots or hyphens, no labels +/* longer than VALID_LABEL_LEN characters, and it should not +/* be all numeric. +/* +/* valid_hostaddr() requires that the input is a valid string +/* representation of an IPv4 or IPv6 network address as +/* described next. +/* +/* valid_ipv4_hostaddr() and valid_ipv6_hostaddr() implement +/* protocol-specific address syntax checks. A valid IPv4 +/* address is in dotted-quad decimal form. A valid IPv6 address +/* has 16-bit hexadecimal fields separated by ":", and does not +/* include the RFC 2821 style "IPv6:" prefix. +/* +/* These routines operate silently unless the gripe parameter +/* specifies a non-zero value. The macros DO_GRIPE and DONT_GRIPE +/* provide suitable constants. +/* +/* valid_hostport() requires that the input is a valid string +/* representation of a TCP or UDP port number. +/* BUGS +/* valid_hostmumble() does not guarantee that string lengths +/* fit the buffer sizes defined in myaddrinfo(3h). +/* DIAGNOSTICS +/* All functions return zero if they disagree with the input. +/* SEE ALSO +/* RFC 952, RFC 1123, RFC 1035, RFC 2373. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include "msg.h" +#include "mymalloc.h" +#include "stringops.h" +#include "valid_hostname.h" + +/* valid_hostname - screen out bad hostnames */ + +int valid_hostname(const char *name, int flags) +{ + const char *myname = "valid_hostname"; + const char *cp; + int label_length = 0; + int label_count = 0; + int non_numeric = 0; + int ch; + int gripe = flags & DO_GRIPE; + + /* + * Trivial cases first. + */ + if (*name == 0) { + if (gripe) + msg_warn("%s: empty hostname", myname); + return (0); + } + + /* + * Find bad characters or label lengths. Find adjacent delimiters. + */ + for (cp = name; (ch = *(unsigned char *) cp) != 0; cp++) { + if (ISALNUM(ch) || ch == '_') { /* grr.. */ + if (label_length == 0) + label_count++; + label_length++; + if (label_length > VALID_LABEL_LEN) { + if (gripe) + msg_warn("%s: hostname label too long: %.100s", myname, name); + return (0); + } + if (!ISDIGIT(ch)) + non_numeric = 1; + } else if ((flags & DO_WILDCARD) && ch == '*') { + if (label_length || label_count || (cp[1] && cp[1] != '.')) { + if (gripe) + msg_warn("%s: '*' can be the first label only: %.100s", myname, name); + return (0); + } + label_count++; + label_length++; + non_numeric = 1; + } else if (ch == '.') { + if (label_length == 0 || cp[1] == 0) { + if (gripe) + msg_warn("%s: misplaced delimiter: %.100s", myname, name); + return (0); + } + label_length = 0; + } else if (ch == '-') { + non_numeric = 1; + label_length++; + if (label_length == 1 || cp[1] == 0 || cp[1] == '.') { + if (gripe) + msg_warn("%s: misplaced hyphen: %.100s", myname, name); + return (0); + } + } +#ifdef SLOPPY_VALID_HOSTNAME + else if (ch == ':' && valid_ipv6_hostaddr(name, DONT_GRIPE)) { + non_numeric = 0; + break; + } +#endif + else { + if (gripe) + msg_warn("%s: invalid character %d(decimal): %.100s", + myname, ch, name); + return (0); + } + } + + if (non_numeric == 0) { + if (gripe) + msg_warn("%s: numeric hostname: %.100s", myname, name); +#ifndef SLOPPY_VALID_HOSTNAME + return (0); +#endif + } + if (cp - name > VALID_HOSTNAME_LEN) { + if (gripe) + msg_warn("%s: bad length %d for %.100s...", + myname, (int) (cp - name), name); + return (0); + } + return (1); +} + +/* valid_hostaddr - verify numerical address syntax */ + +int valid_hostaddr(const char *addr, int gripe) +{ + const char *myname = "valid_hostaddr"; + + /* + * Trivial cases first. + */ + if (*addr == 0) { + if (gripe) + msg_warn("%s: empty address", myname); + return (0); + } + + /* + * Protocol-dependent processing next. + */ + if (strchr(addr, ':') != 0) + return (valid_ipv6_hostaddr(addr, gripe)); + else + return (valid_ipv4_hostaddr(addr, gripe)); +} + +/* valid_ipv4_hostaddr - test dotted quad string for correctness */ + +int valid_ipv4_hostaddr(const char *addr, int gripe) +{ + const char *cp; + const char *myname = "valid_ipv4_hostaddr"; + int in_byte = 0; + int byte_count = 0; + int byte_val = 0; + int ch; + +#define BYTES_NEEDED 4 + + /* + * Scary code to avoid sscanf() overflow nasties. + * + * This routine is called by valid_ipv6_hostaddr(). It must not call that + * routine, to avoid deadly recursion. + */ + for (cp = addr; (ch = *(unsigned const char *) cp) != 0; cp++) { + if (ISDIGIT(ch)) { + if (in_byte == 0) { + in_byte = 1; + byte_val = 0; + byte_count++; + } + byte_val *= 10; + byte_val += ch - '0'; + if (byte_val > 255) { + if (gripe) + msg_warn("%s: invalid octet value: %.100s", myname, addr); + return (0); + } + } else if (ch == '.') { + if (in_byte == 0 || cp[1] == 0) { + if (gripe) + msg_warn("%s: misplaced dot: %.100s", myname, addr); + return (0); + } + /* XXX Allow 0.0.0.0 but not 0.1.2.3 */ + if (byte_count == 1 && byte_val == 0 && addr[strspn(addr, "0.")]) { + if (gripe) + msg_warn("%s: bad initial octet value: %.100s", myname, addr); + return (0); + } + in_byte = 0; + } else { + if (gripe) + msg_warn("%s: invalid character %d(decimal): %.100s", + myname, ch, addr); + return (0); + } + } + + if (byte_count != BYTES_NEEDED) { + if (gripe) + msg_warn("%s: invalid octet count: %.100s", myname, addr); + return (0); + } + return (1); +} + +/* valid_ipv6_hostaddr - validate IPv6 address syntax */ + +int valid_ipv6_hostaddr(const char *addr, int gripe) +{ + const char *myname = "valid_ipv6_hostaddr"; + int null_field = 0; + int field = 0; + unsigned char *cp = (unsigned char *) addr; + int len = 0; + + /* + * FIX 200501 The IPv6 patch validated syntax with getaddrinfo(), but I + * am not confident that everyone's system library routines are robust + * enough, like buffer overflow free. Remember, the valid_hostmumble() + * routines are meant to protect Postfix against malformed information in + * data received from the network. + * + * We require eight-field hex addresses of the form 0:1:2:3:4:5:6:7, + * 0:1:2:3:4:5:6a.6b.7c.7d, or some :: compressed version of the same. + * + * Note: the character position is advanced inside the loop. I have added + * comments to show why we can't get stuck. + */ + for (;;) { + switch (*cp) { + case 0: + /* Terminate the loop. */ + if (field < 2) { + if (gripe) + msg_warn("%s: too few `:' in IPv6 address: %.100s", + myname, addr); + return (0); + } else if (len == 0 && null_field != field - 1) { + if (gripe) + msg_warn("%s: bad null last field in IPv6 address: %.100s", + myname, addr); + return (0); + } else + return (1); + case '.': + /* Terminate the loop. */ + if (field < 2 || field > 6) { + if (gripe) + msg_warn("%s: malformed IPv4-in-IPv6 address: %.100s", + myname, addr); + return (0); + } else + /* NOT: valid_hostaddr(). Avoid recursion. */ + return (valid_ipv4_hostaddr((char *) cp - len, gripe)); + case ':': + /* Advance by exactly 1 character position or terminate. */ + if (field == 0 && len == 0 && ISALNUM(cp[1])) { + if (gripe) + msg_warn("%s: bad null first field in IPv6 address: %.100s", + myname, addr); + return (0); + } + field++; + if (field > 7) { + if (gripe) + msg_warn("%s: too many `:' in IPv6 address: %.100s", + myname, addr); + return (0); + } + cp++; + len = 0; + if (*cp == ':') { + if (null_field > 0) { + if (gripe) + msg_warn("%s: too many `::' in IPv6 address: %.100s", + myname, addr); + return (0); + } + null_field = field; + } + break; + default: + /* Advance by at least 1 character position or terminate. */ + len = strspn((char *) cp, "0123456789abcdefABCDEF"); + if (len /* - strspn((char *) cp, "0") */ > 4) { + if (gripe) + msg_warn("%s: malformed IPv6 address: %.100s", + myname, addr); + return (0); + } + if (len <= 0) { + if (gripe) + msg_warn("%s: invalid character %d(decimal) in IPv6 address: %.100s", + myname, *cp, addr); + return (0); + } + cp += len; + break; + } + } +} + +/* valid_hostport - validate numeric port */ + +int valid_hostport(const char *str, int gripe) +{ + const char *myname = "valid_hostport"; + int port; + + if (str[0] == '0' && str[1] != 0) { + if (gripe) + msg_warn("%s: leading zero in port number: %.100s", myname, str); + return (0); + } + if (alldig(str) == 0) { + if (gripe) + msg_warn("%s: non-numeric port number: %.100s", myname, str); + return (0); + } + if (strlen(str) > strlen("65535") + || (port = atoi(str)) > 65535 || port < 0) { + if (gripe) + msg_warn("%s: out-of-range port number: %.100s", myname, str); + return (0); + } + return (1); +} + +#ifdef TEST + + /* + * Test program - reads hostnames from stdin, reports invalid hostnames to + * stderr. + */ +#include + +#include "vstring.h" +#include "vstream.h" +#include "vstring_vstream.h" +#include "msg_vstream.h" + +int main(int unused_argc, char **argv) +{ + VSTRING *buffer = vstring_alloc(1); + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + + while (vstring_fgets_nonl(buffer, VSTREAM_IN)) { + msg_info("testing: \"%s\"", vstring_str(buffer)); + valid_hostname(vstring_str(buffer), DO_GRIPE); + valid_hostaddr(vstring_str(buffer), DO_GRIPE); + } + exit(0); +} + +#endif diff --git a/src/util/valid_hostname.h b/src/util/valid_hostname.h new file mode 100644 index 0000000..463bc6e --- /dev/null +++ b/src/util/valid_hostname.h @@ -0,0 +1,41 @@ +#ifndef _VALID_HOSTNAME_H_INCLUDED_ +#define _VALID_HOSTNAME_H_INCLUDED_ + +/*++ +/* NAME +/* valid_hostname 3h +/* SUMMARY +/* validate hostname +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* External interface */ + +#define VALID_HOSTNAME_LEN 255 /* RFC 1035 */ +#define VALID_LABEL_LEN 63 /* RFC 1035 */ + +#define DONT_GRIPE 0 +#define DO_GRIPE 1 +#define DONT_WILDCARD 0 +#define DO_WILDCARD (1<<1) + +extern int valid_hostname(const char *, int); +extern int valid_hostaddr(const char *, int); +extern int valid_ipv4_hostaddr(const char *, int); +extern int valid_ipv6_hostaddr(const char *, int); +extern int valid_hostport(const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/valid_hostname.in b/src/util/valid_hostname.in new file mode 100644 index 0000000..608c0d1 --- /dev/null +++ b/src/util/valid_hostname.in @@ -0,0 +1,55 @@ +123456789012345678901234567890123456789012345678901234567890123 +1234567890123456789012345678901234567890123456789012345678901234 +a.123456789012345678901234567890123456789012345678901234567890123.b +a.1234567890123456789012345678901234567890123456789012345678901234.b +1.2.3.4 +321.255.255.255 +0.0.0.0 +255.255.255.255 +0.255.255.255 +1.2.3.321 +1.2.3 +1.2.3.4.5 +1..2.3.4 +.1.2.3.4 +1.2.3.4.5. +1 +. + +321 +f +f.2.3.4 +1f.2.3.4 +f1.2.3.4 +1.2f.3.4 +1.f2.3.4 +1.2.3.4f +1.2.3.f4 +1.2.3.f +-.a.b +a.-.b +a.b.- +-aa.b.b +aa-.b.b +a.-bb.b +a.bb-.b +a.b.-bb +a.b.bb- +a-a.b-b +: +:: +::: +a:: +::a +::1.2.3.4 +a:a:a:a:a:1.2.3.4 +a:a:a:a:a:a:1.2.3.4 +a:a:a:a:a:a:a:1.2.3.4 +a:a:a:a:a:a:1.2.3. +a:a:a:a:a:a:1.2.3 +a:a:a:a:a:a:a:a +a:a:a:a:a:a:a:a:a +g:a:a:a:a:a:a:a +a::b +:a::b +a::b: diff --git a/src/util/valid_hostname.ref b/src/util/valid_hostname.ref new file mode 100644 index 0000000..08b23b8 --- /dev/null +++ b/src/util/valid_hostname.ref @@ -0,0 +1,143 @@ +./valid_hostname: testing: "123456789012345678901234567890123456789012345678901234567890123" +./valid_hostname: warning: valid_hostname: numeric hostname: 123456789012345678901234567890123456789012345678901234567890123 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 123456789012345678901234567890123456789012345678901234567890123 +./valid_hostname: testing: "1234567890123456789012345678901234567890123456789012345678901234" +./valid_hostname: warning: valid_hostname: hostname label too long: 1234567890123456789012345678901234567890123456789012345678901234 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 1234567890123456789012345678901234567890123456789012345678901234 +./valid_hostname: testing: "a.123456789012345678901234567890123456789012345678901234567890123.b" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.123456789012345678901234567890123456789012345678901234567890123.b +./valid_hostname: testing: "a.1234567890123456789012345678901234567890123456789012345678901234.b" +./valid_hostname: warning: valid_hostname: hostname label too long: a.1234567890123456789012345678901234567890123456789012345678901234.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.1234567890123456789012345678901234567890123456789012345678901234.b +./valid_hostname: testing: "1.2.3.4" +./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.4 +./valid_hostname: testing: "321.255.255.255" +./valid_hostname: warning: valid_hostname: numeric hostname: 321.255.255.255 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 321.255.255.255 +./valid_hostname: testing: "0.0.0.0" +./valid_hostname: warning: valid_hostname: numeric hostname: 0.0.0.0 +./valid_hostname: testing: "255.255.255.255" +./valid_hostname: warning: valid_hostname: numeric hostname: 255.255.255.255 +./valid_hostname: testing: "0.255.255.255" +./valid_hostname: warning: valid_hostname: numeric hostname: 0.255.255.255 +./valid_hostname: warning: valid_ipv4_hostaddr: bad initial octet value: 0.255.255.255 +./valid_hostname: testing: "1.2.3.321" +./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.321 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 1.2.3.321 +./valid_hostname: testing: "1.2.3" +./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3 +./valid_hostname: testing: "1.2.3.4.5" +./valid_hostname: warning: valid_hostname: numeric hostname: 1.2.3.4.5 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3.4.5 +./valid_hostname: testing: "1..2.3.4" +./valid_hostname: warning: valid_hostname: misplaced delimiter: 1..2.3.4 +./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1..2.3.4 +./valid_hostname: testing: ".1.2.3.4" +./valid_hostname: warning: valid_hostname: misplaced delimiter: .1.2.3.4 +./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: .1.2.3.4 +./valid_hostname: testing: "1.2.3.4.5." +./valid_hostname: warning: valid_hostname: misplaced delimiter: 1.2.3.4.5. +./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1.2.3.4.5. +./valid_hostname: testing: "1" +./valid_hostname: warning: valid_hostname: numeric hostname: 1 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1 +./valid_hostname: testing: "." +./valid_hostname: warning: valid_hostname: misplaced delimiter: . +./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: . +./valid_hostname: testing: "" +./valid_hostname: warning: valid_hostname: empty hostname +./valid_hostname: warning: valid_hostaddr: empty address +./valid_hostname: testing: "321" +./valid_hostname: warning: valid_hostname: numeric hostname: 321 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet value: 321 +./valid_hostname: testing: "f" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f +./valid_hostname: testing: "f.2.3.4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f.2.3.4 +./valid_hostname: testing: "1f.2.3.4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1f.2.3.4 +./valid_hostname: testing: "f1.2.3.4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): f1.2.3.4 +./valid_hostname: testing: "1.2f.3.4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2f.3.4 +./valid_hostname: testing: "1.f2.3.4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.f2.3.4 +./valid_hostname: testing: "1.2.3.4f" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.4f +./valid_hostname: testing: "1.2.3.f4" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.f4 +./valid_hostname: testing: "1.2.3.f" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 102(decimal): 1.2.3.f +./valid_hostname: testing: "-.a.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: -.a.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 45(decimal): -.a.b +./valid_hostname: testing: "a.-.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.-.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.-.b +./valid_hostname: testing: "a.b.-" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.- +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.- +./valid_hostname: testing: "-aa.b.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: -aa.b.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 45(decimal): -aa.b.b +./valid_hostname: testing: "aa-.b.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: aa-.b.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): aa-.b.b +./valid_hostname: testing: "a.-bb.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.-bb.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.-bb.b +./valid_hostname: testing: "a.bb-.b" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.bb-.b +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.bb-.b +./valid_hostname: testing: "a.b.-bb" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.-bb +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.-bb +./valid_hostname: testing: "a.b.bb-" +./valid_hostname: warning: valid_hostname: misplaced hyphen: a.b.bb- +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a.b.bb- +./valid_hostname: testing: "a-a.b-b" +./valid_hostname: warning: valid_ipv4_hostaddr: invalid character 97(decimal): a-a.b-b +./valid_hostname: testing: ":" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): : +./valid_hostname: warning: valid_ipv6_hostaddr: too few `:' in IPv6 address: : +./valid_hostname: testing: "::" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): :: +./valid_hostname: testing: ":::" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::: +./valid_hostname: warning: valid_ipv6_hostaddr: too many `::' in IPv6 address: ::: +./valid_hostname: testing: "a::" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:: +./valid_hostname: testing: "::a" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::a +./valid_hostname: testing: "::1.2.3.4" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): ::1.2.3.4 +./valid_hostname: testing: "a:a:a:a:a:1.2.3.4" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:1.2.3.4 +./valid_hostname: testing: "a:a:a:a:a:a:1.2.3.4" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3.4 +./valid_hostname: testing: "a:a:a:a:a:a:a:1.2.3.4" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:1.2.3.4 +./valid_hostname: warning: valid_ipv6_hostaddr: malformed IPv4-in-IPv6 address: a:a:a:a:a:a:a:1.2.3.4 +./valid_hostname: testing: "a:a:a:a:a:a:1.2.3." +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3. +./valid_hostname: warning: valid_ipv4_hostaddr: misplaced dot: 1.2.3. +./valid_hostname: testing: "a:a:a:a:a:a:1.2.3" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:1.2.3 +./valid_hostname: warning: valid_ipv4_hostaddr: invalid octet count: 1.2.3 +./valid_hostname: testing: "a:a:a:a:a:a:a:a" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:a +./valid_hostname: testing: "a:a:a:a:a:a:a:a:a" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a:a:a:a:a:a:a:a:a +./valid_hostname: warning: valid_ipv6_hostaddr: too many `:' in IPv6 address: a:a:a:a:a:a:a:a:a +./valid_hostname: testing: "g:a:a:a:a:a:a:a" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): g:a:a:a:a:a:a:a +./valid_hostname: warning: valid_ipv6_hostaddr: invalid character 103(decimal) in IPv6 address: g:a:a:a:a:a:a:a +./valid_hostname: testing: "a::b" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a::b +./valid_hostname: testing: ":a::b" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): :a::b +./valid_hostname: warning: valid_ipv6_hostaddr: bad null first field in IPv6 address: :a::b +./valid_hostname: testing: "a::b:" +./valid_hostname: warning: valid_hostname: invalid character 58(decimal): a::b: +./valid_hostname: warning: valid_ipv6_hostaddr: bad null last field in IPv6 address: a::b: diff --git a/src/util/valid_utf8_hostname.c b/src/util/valid_utf8_hostname.c new file mode 100644 index 0000000..3d6922a --- /dev/null +++ b/src/util/valid_utf8_hostname.c @@ -0,0 +1,87 @@ +/*++ +/* NAME +/* valid_utf8_hostname 3 +/* SUMMARY +/* validate (maybe UTF-8) domain name +/* SYNOPSIS +/* #include +/* +/* int valid_utf8_hostname( +/* int enable_utf8, +/* const char *domain, +/* int gripe) +/* DESCRIPTION +/* valid_utf8_hostname() is a wrapper around valid_hostname(). +/* If EAI support is compiled in, and enable_utf8 is true, the +/* name is converted from UTF-8 to ASCII per IDNA rules, before +/* invoking valid_hostname(). +/* SEE ALSO +/* valid_hostname(3) STD3 hostname validation. +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* Warnings: malformed domain name. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include +#include +#include +#include +#include + +/* valid_utf8_hostname - validate internationalized domain name */ + +int valid_utf8_hostname(int enable_utf8, const char *name, int gripe) +{ + static const char myname[] = "valid_utf8_hostname"; + + /* + * Trivial cases first. + */ + if (*name == 0) { + if (gripe) + msg_warn("%s: empty domain name", myname); + return (0); + } + + /* + * Convert non-ASCII domain name to ASCII and validate the result per + * STD3. midna_domain_to_ascii() applies valid_hostname() to the result. + * Propagate the gripe parameter for better diagnostics (note that + * midna_domain_to_ascii() logs a problem only when the result is not + * cached). + */ +#ifndef NO_EAI + if (enable_utf8 && !allascii(name)) { + if (midna_domain_to_ascii(name) == 0) { + if (gripe) + msg_warn("%s: malformed UTF-8 domain name", myname); + return (0); + } else { + return (1); + } + } +#endif + + /* + * Validate ASCII name per STD3. + */ + return (valid_hostname(name, gripe)); +} diff --git a/src/util/valid_utf8_hostname.h b/src/util/valid_utf8_hostname.h new file mode 100644 index 0000000..0c0b41f --- /dev/null +++ b/src/util/valid_utf8_hostname.h @@ -0,0 +1,35 @@ +#ifndef _VALID_UTF8_HOSTNAME_H_INCLUDED_ +#define _VALID_UTF8_HOSTNAME_H_INCLUDED_ + +/*++ +/* NAME +/* valid_utf8_hostname 3h +/* SUMMARY +/* validate (maybe UTF-8) domain name +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * External interface + */ +extern int valid_utf8_hostname(int, const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/valid_utf8_string.c b/src/util/valid_utf8_string.c new file mode 100644 index 0000000..96b5b4d --- /dev/null +++ b/src/util/valid_utf8_string.c @@ -0,0 +1,139 @@ +/*++ +/* NAME +/* valid_utf8_string 3 +/* SUMMARY +/* predicate if string is valid UTF-8 +/* SYNOPSIS +/* #include +/* +/* int valid_utf8_string(str, len) +/* const char *str; +/* ssize_t len; +/* DESCRIPTION +/* valid_utf8_string() determines if a string satisfies the UTF-8 +/* definition in RFC 3629. That is, it contains proper encodings +/* of code points U+0000..U+10FFFF, excluding over-long encodings +/* and excluding U+D800..U+DFFF surrogates. +/* +/* A zero-length string is considered valid. +/* DIAGNOSTICS +/* The result value is zero when the caller specifies a negative +/* length, or a string that violates RFC 3629, for example a +/* string that is truncated in the middle of a multi-byte +/* sequence. +/* BUGS +/* But wait, there is more. Code points in the range U+FDD0..U+FDEF +/* and ending in FFFE or FFFF are non-characters in UNICODE. This +/* function does not block these. +/* SEE ALSO +/* RFC 3629 +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include + +/* valid_utf8_string - validate string according to RFC 3629 */ + +int valid_utf8_string(const char *str, ssize_t len) +{ + const unsigned char *end = (const unsigned char *) str + len; + const unsigned char *cp; + unsigned char c0, ch; + + if (len < 0) + return (0); + if (len <= 0) + return (1); + + /* + * Optimized for correct input, time, space, and for CPUs that have a + * decent number of registers. + */ + for (cp = (const unsigned char *) str; cp < end; cp++) { + /* Single-byte encodings. */ + if (EXPECTED((c0 = *cp) <= 0x7f) /* we know that c0 >= 0x0 */ ) { + /* void */ ; + } + /* Two-byte encodings. */ + else if (EXPECTED(c0 <= 0xdf) /* we know that c0 >= 0x80 */ ) { + /* Exclude over-long encodings. */ + if (UNEXPECTED(c0 < 0xc2) + || UNEXPECTED(cp + 1 >= end) + /* Require UTF-8 tail byte. */ + || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80)) + return (0); + } + /* Three-byte encodings. */ + else if (EXPECTED(c0 <= 0xef) /* we know that c0 >= 0xe0 */ ) { + if (UNEXPECTED(cp + 2 >= end) + /* Exclude over-long encodings. */ + || UNEXPECTED((ch = *++cp) < (c0 == 0xe0 ? 0xa0 : 0x80)) + /* Exclude U+D800..U+DFFF. */ + || UNEXPECTED(ch > (c0 == 0xed ? 0x9f : 0xbf)) + /* Require UTF-8 tail byte. */ + || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80)) + return (0); + } + /* Four-byte encodings. */ + else if (EXPECTED(c0 <= 0xf4) /* we know that c0 >= 0xf0 */ ) { + if (UNEXPECTED(cp + 3 >= end) + /* Exclude over-long encodings. */ + || UNEXPECTED((ch = *++cp) < (c0 == 0xf0 ? 0x90 : 0x80)) + /* Exclude code points above U+10FFFF. */ + || UNEXPECTED(ch > (c0 == 0xf4 ? 0x8f : 0xbf)) + /* Require UTF-8 tail byte. */ + || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80) + /* Require UTF-8 tail byte. */ + || UNEXPECTED(((ch = *++cp) & 0xc0) != 0x80)) + return (0); + } + /* Invalid: c0 >= 0xf5 */ + else { + return (0); + } + } + return (1); +} + + /* + * Stand-alone test program. Each string is a line without line terminator. + */ +#ifdef TEST +#include +#include +#include +#include + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +int main(void) +{ + VSTRING *buf = vstring_alloc(1); + + while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) { + vstream_printf("%c", (LEN(buf) && !valid_utf8_string(STR(buf), LEN(buf))) ? + '!' : ' '); + vstream_fwrite(VSTREAM_OUT, STR(buf), LEN(buf)); + vstream_printf("\n"); + } + vstream_fflush(VSTREAM_OUT); + vstring_free(buf); + exit(0); +} + +#endif diff --git a/src/util/vbuf.c b/src/util/vbuf.c new file mode 100644 index 0000000..924e230 --- /dev/null +++ b/src/util/vbuf.c @@ -0,0 +1,238 @@ +/*++ +/* NAME +/* vbuf 3 +/* SUMMARY +/* generic buffer package +/* SYNOPSIS +/* #include +/* +/* int VBUF_GET(bp) +/* VBUF *bp; +/* +/* int VBUF_PUT(bp, ch) +/* VBUF *bp; +/* int ch; +/* +/* int VBUF_SPACE(bp, len) +/* VBUF *bp; +/* ssize_t len; +/* +/* int vbuf_unget(bp, ch) +/* VBUF *bp; +/* int ch; +/* +/* ssize_t vbuf_read(bp, buf, len) +/* VBUF *bp; +/* void *buf; +/* ssize_t len; +/* +/* ssize_t vbuf_write(bp, buf, len) +/* VBUF *bp; +/* const void *buf; +/* ssize_t len; +/* +/* int vbuf_err(bp) +/* VBUF *bp; +/* +/* int vbuf_eof(bp) +/* VBUF *bp; +/* +/* int vbuf_timeout(bp) +/* VBUF *bp; +/* +/* int vbuf_clearerr(bp) +/* VBUF *bp; +/* +/* int vbuf_rd_err(bp) +/* VBUF *bp; +/* +/* int vbuf_wr_err(bp) +/* VBUF *bp; +/* +/* int vbuf_rd_timeout(bp) +/* VBUF *bp; +/* +/* int vbuf_wr_timeout(bp) +/* VBUF *bp; +/* DESCRIPTION +/* This module implements a buffer with read/write primitives that +/* automatically handle buffer-empty or buffer-full conditions. +/* The application is expected to provide callback routines that run +/* when the read-write primitives detect a buffer-empty/full condition. +/* +/* VBUF buffers provide primitives to store and retrieve characters, +/* and to look up buffer status information. +/* By design, VBUF buffers provide no explicit primitives for buffer +/* memory management. This is left to the application to avoid any bias +/* toward specific management models. The application is free to use +/* whatever strategy suits best: memory-resident buffer, memory mapped +/* file, or stdio-like window to an open file. +/* +/* VBUF_GET() returns the next character from the specified buffer, +/* or VBUF_EOF when none is available. VBUF_GET() is an unsafe macro +/* that evaluates its argument more than once. +/* +/* VBUF_PUT() stores one character into the specified buffer. The result +/* is the stored character, or VBUF_EOF in case of problems. VBUF_PUT() +/* is an unsafe macro that evaluates its arguments more than once. +/* +/* VBUF_SPACE() requests that the requested amount of buffer space be +/* made available, so that it can be accessed without using VBUF_PUT(). +/* The result value is 0 for success, VBUF_EOF for problems. +/* VBUF_SPACE() is an unsafe macro that evaluates its arguments more +/* than once. VBUF_SPACE() does not support read-only streams. +/* +/* vbuf_unget() provides at least one character of pushback, and returns +/* the pushed back character, or VBUF_EOF in case of problems. It is +/* an error to call vbuf_unget() on a buffer before reading any data +/* from it. vbuf_unget() clears the buffer's end-of-file indicator upon +/* success, and sets the buffer's error indicator when an attempt is +/* made to push back a non-character value. +/* +/* vbuf_read() and vbuf_write() do bulk I/O. The result value is the +/* number of bytes transferred. A short count is returned in case of +/* an error. +/* +/* vbuf_timeout() is a macro that returns non-zero if a timeout error +/* condition was detected while reading or writing the buffer. The +/* error status can be reset by calling vbuf_clearerr(). +/* +/* vbuf_err() is a macro that returns non-zero if a non-EOF error +/* (including timeout) condition was detected while reading or writing +/* the buffer. The error status can be reset by calling vbuf_clearerr(). +/* +/* The vbuf_rd_mumble() and vbuf_wr_mumble() macros report on +/* read and write error conditions, respectively. +/* +/* vbuf_eof() is a macro that returns non-zero if an end-of-file +/* condition was detected while reading or writing the buffer. The error +/* status can be reset by calling vbuf_clearerr(). +/* APPLICATION CALLBACK SYNOPSIS +/* int get_ready(bp) +/* VBUF *bp; +/* +/* int put_ready(bp) +/* VBUF *bp; +/* +/* int space(bp, len) +/* VBUF *bp; +/* ssize_t len; +/* APPLICATION CALLBACK DESCRIPTION +/* .ad +/* .fi +/* get_ready() is called when VBUF_GET() detects a buffer-empty condition. +/* The result is zero when more data could be read, VBUF_EOF otherwise. +/* +/* put_ready() is called when VBUF_PUT() detects a buffer-full condition. +/* The result is zero when the buffer could be flushed, VBUF_EOF otherwise. +/* +/* space() performs whatever magic necessary to make at least \fIlen\fR +/* bytes available for access without using VBUF_PUT(). The result is 0 +/* in case of success, VBUF_EOF otherwise. +/* SEE ALSO +/* vbuf(3h) layout of the VBUF data structure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include + +/* Utility library. */ + +#include "vbuf.h" + +/* vbuf_unget - implement at least one character pushback */ + +int vbuf_unget(VBUF *bp, int ch) +{ + if ((ch & 0xff) != ch || -bp->cnt >= bp->len) { + bp->flags |= VBUF_FLAG_RD_ERR; /* This error affects reads! */ + return (VBUF_EOF); + } else { + bp->cnt--; + bp->flags &= ~VBUF_FLAG_EOF; + return (*--bp->ptr = ch); + } +} + +/* vbuf_get - handle read buffer empty condition */ + +int vbuf_get(VBUF *bp) +{ + return (bp->get_ready(bp) ? + ((bp->flags |= VBUF_FLAG_EOF), VBUF_EOF) : VBUF_GET(bp)); +} + +/* vbuf_put - handle write buffer full condition */ + +int vbuf_put(VBUF *bp, int ch) +{ + return (bp->put_ready(bp) ? VBUF_EOF : VBUF_PUT(bp, ch)); +} + +/* vbuf_read - bulk read from buffer */ + +ssize_t vbuf_read(VBUF *bp, void *buf, ssize_t len) +{ + ssize_t count; + void *cp; + ssize_t n; + +#if 0 + for (count = 0; count < len; count++) + if ((buf[count] = VBUF_GET(bp)) < 0) + break; + return (count); +#else + for (cp = buf, count = len; count > 0; cp += n, count -= n) { + if (bp->cnt >= 0 && bp->get_ready(bp)) + break; + n = (count < -bp->cnt ? count : -bp->cnt); + memcpy(cp, bp->ptr, n); + bp->ptr += n; + bp->cnt += n; + } + return (len - count); +#endif +} + +/* vbuf_write - bulk write to buffer */ + +ssize_t vbuf_write(VBUF *bp, const void *buf, ssize_t len) +{ + ssize_t count; + const void *cp; + ssize_t n; + +#if 0 + for (count = 0; count < len; count++) + if (VBUF_PUT(bp, buf[count]) < 0) + break; + return (count); +#else + for (cp = buf, count = len; count > 0; cp += n, count -= n) { + if (bp->cnt <= 0 && bp->put_ready(bp) != 0) + break; + n = (count < bp->cnt ? count : bp->cnt); + memcpy(bp->ptr, cp, n); + bp->ptr += n; + bp->cnt -= n; + } + return (len - count); +#endif +} diff --git a/src/util/vbuf.h b/src/util/vbuf.h new file mode 100644 index 0000000..149fe0c --- /dev/null +++ b/src/util/vbuf.h @@ -0,0 +1,110 @@ +#ifndef _VBUF_H_INCLUDED_ +#define _VBUF_H_INCLUDED_ + +/*++ +/* NAME +/* vbuf 3h +/* SUMMARY +/* generic buffer +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * The VBUF buffer is defined by 1) its structure, by 2) the VBUF_GET() and + * 3) VBUF_PUT() operations that automatically handle buffer empty and + * buffer full conditions, and 4) by the VBUF_SPACE() operation that allows + * the user to reserve buffer space ahead of time, to allow for situations + * where calling VBUF_PUT() is not possible or desirable. + * + * The VBUF buffer does not specify primitives for memory allocation or + * deallocation. The purpose is to allow different applications to have + * different strategies: a memory-resident buffer; a memory-mapped file; or + * a stdio-like window to an open file. Each application provides its own + * get(), put() and space() methods that perform the necessary magic. + * + * This interface is pretty normal. With one exception: the number of bytes + * left to read is negated. This is done so that we can change direction + * between reading and writing on the fly. The alternative would be to use + * separate read and write counters per buffer. + */ +typedef struct VBUF VBUF; +typedef int (*VBUF_GET_READY_FN) (VBUF *); +typedef int (*VBUF_PUT_READY_FN) (VBUF *); +typedef int (*VBUF_SPACE_FN) (VBUF *, ssize_t); + +struct VBUF { + int flags; /* status, see below */ + unsigned char *data; /* variable-length buffer */ + ssize_t len; /* buffer length */ + ssize_t cnt; /* bytes left to read/write */ + unsigned char *ptr; /* read/write position */ + VBUF_GET_READY_FN get_ready; /* read buffer empty action */ + VBUF_PUT_READY_FN put_ready; /* write buffer full action */ + VBUF_SPACE_FN space; /* request for buffer space */ +}; + + /* + * Typically, an application will embed a VBUF structure into a larger + * structure that also contains application-specific members. This approach + * gives us the best of both worlds. The application can still use the + * generic VBUF primitives for reading and writing VBUFs. The macro below + * transforms a pointer from VBUF structure to the structure that contains + * it. + */ +#define VBUF_TO_APPL(vbuf_ptr,app_type,vbuf_member) \ + ((app_type *) (((char *) (vbuf_ptr)) - offsetof(app_type,vbuf_member))) + + /* + * Buffer status management. + */ +#define VBUF_FLAG_RD_ERR (1<<0) /* read error */ +#define VBUF_FLAG_WR_ERR (1<<1) /* write error */ +#define VBUF_FLAG_ERR (VBUF_FLAG_RD_ERR | VBUF_FLAG_WR_ERR) +#define VBUF_FLAG_EOF (1<<2) /* end of data */ +#define VBUF_FLAG_RD_TIMEOUT (1<<3) /* read timeout */ +#define VBUF_FLAG_WR_TIMEOUT (1<<4) /* write timeout */ +#define VBUF_FLAG_TIMEOUT (VBUF_FLAG_RD_TIMEOUT | VBUF_FLAG_WR_TIMEOUT) +#define VBUF_FLAG_BAD (VBUF_FLAG_ERR | VBUF_FLAG_EOF | VBUF_FLAG_TIMEOUT) +#define VBUF_FLAG_FIXED (1<<5) /* fixed-size buffer */ + +#define vbuf_rd_error(v) ((v)->flags & (VBUF_FLAG_RD_ERR | VBUF_FLAG_RD_TIMEOUT)) +#define vbuf_wr_error(v) ((v)->flags & (VBUF_FLAG_WR_ERR | VBUF_FLAG_WR_TIMEOUT)) +#define vbuf_rd_timeout(v) ((v)->flags & VBUF_FLAG_RD_TIMEOUT) +#define vbuf_wr_timeout(v) ((v)->flags & VBUF_FLAG_WR_TIMEOUT) + +#define vbuf_error(v) ((v)->flags & (VBUF_FLAG_ERR | VBUF_FLAG_TIMEOUT)) +#define vbuf_eof(v) ((v)->flags & VBUF_FLAG_EOF) +#define vbuf_timeout(v) ((v)->flags & VBUF_FLAG_TIMEOUT) +#define vbuf_clearerr(v) ((v)->flags &= ~VBUF_FLAG_BAD) + + /* + * Buffer I/O-like operations and results. + */ +#define VBUF_GET(v) ((v)->cnt < 0 ? ++(v)->cnt, \ + (int) *(v)->ptr++ : vbuf_get(v)) +#define VBUF_PUT(v,c) ((v)->cnt > 0 ? --(v)->cnt, \ + (int) (*(v)->ptr++ = (c)) : vbuf_put((v),(c))) +#define VBUF_SPACE(v,n) ((v)->space((v),(n))) + +#define VBUF_EOF (-1) /* no more space or data */ + +extern int vbuf_get(VBUF *); +extern int vbuf_put(VBUF *, int); +extern int vbuf_unget(VBUF *, int); +extern ssize_t vbuf_read(VBUF *, void *, ssize_t); +extern ssize_t vbuf_write(VBUF *, const void *, ssize_t); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/vbuf_print.c b/src/util/vbuf_print.c new file mode 100644 index 0000000..d7a323f --- /dev/null +++ b/src/util/vbuf_print.c @@ -0,0 +1,385 @@ +/*++ +/* NAME +/* vbuf_print 3 +/* SUMMARY +/* formatted print to generic buffer +/* SYNOPSIS +/* #include +/* #include +/* +/* VBUF *vbuf_print(bp, format, ap) +/* VBUF *bp; +/* const char *format; +/* va_list ap; +/* DESCRIPTION +/* vbuf_print() appends data to the named buffer according to its +/* \fIformat\fR argument. It understands the s, c, d, u, o, x, X, p, e, +/* f and g format types, the l modifier, field width and precision, +/* sign, and padding with zeros or spaces. +/* +/* In addition, vbuf_print() recognizes the %m format specifier +/* and expands it to the error message corresponding to the current +/* value of the global \fIerrno\fR variable. +/* REENTRANCY +/* .ad +/* .fi +/* vbuf_print() allocates a static buffer. After completion +/* of the first vbuf_print() call, this buffer is safe for +/* reentrant vbuf_print() calls by (asynchronous) terminating +/* signal handlers or by (synchronous) terminating error +/* handlers. vbuf_print() initialization typically happens +/* upon the first formatted output to a VSTRING or VSTREAM. +/* +/* However, it is up to the caller to ensure that the destination +/* VSTREAM or VSTRING buffer is protected against reentrant usage. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include /* 44BSD stdarg.h uses abort() */ +#include +#include +#include +#include /* 44bsd stdarg.h uses abort() */ +#include /* sprintf() prototype */ +#include /* range of doubles */ +#include +#include /* CHAR_BIT, INT_MAX */ + +/* Application-specific. */ + +#include "msg.h" +#include "mymalloc.h" +#include "vbuf.h" +#include "vstring.h" +#include "vbuf_print.h" + + /* + * What we need here is a *sprintf() routine that can ask for more room (as + * in 4.4 BSD). However, that functionality is not widely available, and I + * have no plans to maintain a complete 4.4 BSD *sprintf() alternative. + * + * Postfix vbuf_print() was implemented when many mainstream systems had no + * usable snprintf() implementation (usable means: return the length, + * excluding terminator, that the output would have if the buffer were large + * enough). For example, GLIBC before 2.1 (1999) snprintf() did not + * distinguish between formatting error and buffer size error, while SUN had + * no snprintf() implementation before Solaris 2.6 (1997). + * + * For the above reasons, vbuf_print() was implemented with sprintf() and a + * generously-sized output buffer. Current vbuf_print() implementations use + * snprintf(), and report an error if the output does not fit (in that case, + * the old sprintf()-based implementation would have had a buffer overflow + * vulnerability). The old implementation is still available for building + * Postfix on ancient systems. + * + * Guessing the output size of a string (%s) conversion is not hard. The + * problem is with numerical results. Instead of making an accurate guess we + * take a wide margin when reserving space. The INT_SPACE margin should be + * large enough to hold the result from any (octal, hex, decimal) integer + * conversion that has no explicit width or precision specifiers. With + * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP + * just to be sure. + */ +#define INT_SPACE ((CHAR_BIT * sizeof(long)) / 2) +#define DBL_SPACE ((CHAR_BIT * sizeof(double)) / 2 + DBL_MAX_10_EXP) +#define PTR_SPACE ((CHAR_BIT * sizeof(char *)) / 2) + + /* + * Helper macros... Note that there is no need to check the result from + * VSTRING_SPACE() because that always succeeds or never returns. + */ +#ifndef NO_SNPRINTF +#define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \ + ssize_t _ret; \ + if (VBUF_SPACE((bp), (sz)) != 0) \ + return (bp); \ + _ret = snprintf((char *) (bp)->ptr, (bp)->cnt, (fmt), (arg)); \ + if (_ret < 0) \ + msg_panic("%s: output error for '%s'", myname, mystrdup(fmt)); \ + if (_ret >= (bp)->cnt) \ + msg_panic("%s: output for '%s' exceeds space %ld", \ + myname, mystrdup(fmt), (long) (bp)->cnt); \ + VBUF_SKIP(bp); \ + } while (0) +#else +#define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \ + if (VBUF_SPACE((bp), (sz)) != 0) \ + return (bp); \ + sprintf((char *) (bp)->ptr, (fmt), (arg)); \ + VBUF_SKIP(bp); \ + } while (0) +#endif + +#define VBUF_SKIP(bp) do { \ + while ((bp)->cnt > 0 && *(bp)->ptr) \ + (bp)->ptr++, (bp)->cnt--; \ + } while (0) + +#define VSTRING_ADDNUM(vp, n) do { \ + VBUF_SNPRINTF(&(vp)->vbuf, INT_SPACE, "%d", n); \ + } while (0) + +#define VBUF_STRCAT(bp, s) do { \ + unsigned char *_cp = (unsigned char *) (s); \ + int _ch; \ + while ((_ch = *_cp++) != 0) \ + VBUF_PUT((bp), _ch); \ + } while (0) + +/* vbuf_print - format string, vsprintf-like interface */ + +VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap) +{ + const char *myname = "vbuf_print"; + static VSTRING *fmt; /* format specifier */ + unsigned char *cp; + int width; /* width and numerical precision */ + int prec; /* are signed for overflow defense */ + unsigned long_flag; /* long or plain integer */ + int ch; + char *s; + int saved_errno = errno; /* VBUF_SPACE() may clobber it */ + + /* + * Assume that format strings are short. + */ + if (fmt == 0) + fmt = vstring_alloc(INT_SPACE); + + /* + * Iterate over characters in the format string, picking up arguments + * when format specifiers are found. + */ + for (cp = (unsigned char *) format; *cp; cp++) { + if (*cp != '%') { + VBUF_PUT(bp, *cp); /* ordinary character */ + } else if (cp[1] == '%') { + VBUF_PUT(bp, *cp++); /* %% becomes % */ + } else { + + /* + * Handle format specifiers one at a time, since we can only deal + * with arguments one at a time. Try to determine the end of the + * format specifier. We do not attempt to fully parse format + * strings, since we are ging to let sprintf() do the hard work. + * In regular expression notation, we recognize: + * + * %-?+?0?([0-9]+|\*)?(\.([0-9]+|\*))?l?[a-zA-Z] + * + * which includes some combinations that do not make sense. Garbage + * in, garbage out. + */ + VSTRING_RESET(fmt); /* clear format string */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == '-') /* left-adjusted field? */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == '+') /* signed field? */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == '0') /* zero-padded field? */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == '*') { /* dynamic field width */ + width = va_arg(ap, int); + if (width < 0) { + msg_warn("%s: bad width %d in %.50s", + myname, width, format); + width = 0; + } else + VSTRING_ADDNUM(fmt, width); + cp++; + } else { /* hard-coded field width */ + for (width = 0; ch = *cp, ISDIGIT(ch); cp++) { + int digit = ch - '0'; + + if (width > INT_MAX / 10 + || (width *= 10) > INT_MAX - digit) + msg_panic("%s: bad width %d... in %.50s", + myname, width, format); + width += digit; + VSTRING_ADDCH(fmt, ch); + } + } + if (*cp == '.') { /* width/precision separator */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == '*') { /* dynamic precision */ + prec = va_arg(ap, int); + if (prec < 0) { + msg_warn("%s: bad precision %d in %.50s", + myname, prec, format); + prec = -1; + } else + VSTRING_ADDNUM(fmt, prec); + cp++; + } else { /* hard-coded precision */ + for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) { + int digit = ch - '0'; + + if (prec > INT_MAX / 10 + || (prec *= 10) > INT_MAX - digit) + msg_panic("%s: bad precision %d... in %.50s", + myname, prec, format); + prec += digit; + VSTRING_ADDCH(fmt, ch); + } + } + } else { + prec = -1; + } + if ((long_flag = (*cp == 'l')) != 0)/* long whatever */ + VSTRING_ADDCH(fmt, *cp++); + if (*cp == 0) /* premature end, punt */ + break; + VSTRING_ADDCH(fmt, *cp); /* type (checked below) */ + VSTRING_TERMINATE(fmt); /* null terminate */ + + /* + * Execute the format string - let sprintf() do the hard work for + * non-trivial cases only. For simple string conversions and for + * long string conversions, do a direct copy to the output + * buffer. + */ + switch (*cp) { + case 's': /* string-valued argument */ + if (long_flag) + msg_panic("%s: %%l%c is not supported", myname, *cp); + s = va_arg(ap, char *); + if (prec >= 0 || (width > 0 && width > strlen(s))) { + VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE, + vstring_str(fmt), s); + } else { + VBUF_STRCAT(bp, s); + } + break; + case 'c': /* integral-valued argument */ + if (long_flag) + msg_panic("%s: %%l%c is not supported", myname, *cp); + /* FALLTHROUGH */ + case 'd': + case 'u': + case 'o': + case 'x': + case 'X': + if (long_flag) + VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE, + vstring_str(fmt), va_arg(ap, long)); + else + VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE, + vstring_str(fmt), va_arg(ap, int)); + break; + case 'e': /* float-valued argument */ + case 'f': + case 'g': + /* C99 *printf ignore the 'l' modifier. */ + VBUF_SNPRINTF(bp, (width > prec ? width : prec) + DBL_SPACE, + vstring_str(fmt), va_arg(ap, double)); + break; + case 'm': + /* Ignore the 'l' modifier, width and precision. */ + VBUF_STRCAT(bp, saved_errno ? + strerror(saved_errno) : "Application error"); + break; + case 'p': + if (long_flag) + msg_panic("%s: %%l%c is not supported", myname, *cp); + VBUF_SNPRINTF(bp, (width > prec ? width : prec) + PTR_SPACE, + vstring_str(fmt), va_arg(ap, char *)); + break; + default: /* anything else is bad */ + msg_panic("vbuf_print: unknown format type: %c", *cp); + /* NOTREACHED */ + break; + } + } + } + return (bp); +} + +#ifdef TEST +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + VSTRING *ibuf = vstring_alloc(100); + + msg_vstream_init(argv[0], VSTREAM_ERR); + + while (vstring_fgets_nonl(ibuf, VSTREAM_IN)) { + ARGV *args = argv_split(vstring_str(ibuf), CHARS_SPACE); + char *cp; + + if (args->argc == 0 || *(cp = args->argv[0]) == '#') { + /* void */ ; + } else if (args->argc != 2 || *cp != '%') { + msg_warn("usage: format number"); + } else { + char *fmt = cp++; + int lflag; + + /* Determine the vstring_sprintf() argument type. */ + cp += strspn(cp, "+-*0123456789."); + if ((lflag = (*cp == 'l')) != 0) + cp++; + if (cp[1] != 0) { + msg_warn("bad format: \"%s\"", fmt); + } else { + VSTRING *obuf = vstring_alloc(1); + char *val = args->argv[1]; + + /* Test the worst-case memory allocation. */ +#ifdef CA_VSTRING_CTL_EXACT + vstring_ctl(obuf, CA_VSTRING_CTL_EXACT, CA_VSTRING_CTL_END); +#endif + switch (*cp) { + case 'c': + case 'd': + case 'o': + case 'u': + case 'x': + case 'X': + if (lflag) + vstring_sprintf(obuf, fmt, atol(val)); + else + vstring_sprintf(obuf, fmt, atoi(val)); + msg_info("\"%s\"", vstring_str(obuf)); + break; + case 's': + vstring_sprintf(obuf, fmt, val); + msg_info("\"%s\"", vstring_str(obuf)); + break; + case 'f': + case 'g': + vstring_sprintf(obuf, fmt, atof(val)); + msg_info("\"%s\"", vstring_str(obuf)); + break; + default: + msg_warn("bad format: \"%s\"", fmt); + break; + } + vstring_free(obuf); + } + } + argv_free(args); + } + vstring_free(ibuf); + return (0); +} + +#endif diff --git a/src/util/vbuf_print.h b/src/util/vbuf_print.h new file mode 100644 index 0000000..32549c1 --- /dev/null +++ b/src/util/vbuf_print.h @@ -0,0 +1,40 @@ +#ifndef _VBUF_PRINT_H_INCLUDED_ +#define _VBUF_PRINT_H_INCLUDED_ + +/*++ +/* NAME +/* vbuf_print 3h +/* SUMMARY +/* formatted print to generic buffer +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include + + /* + * External interface. + */ +extern VBUF *vbuf_print(VBUF *, const char *, va_list); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/vbuf_print_test.in b/src/util/vbuf_print_test.in new file mode 100644 index 0000000..5ed13dc --- /dev/null +++ b/src/util/vbuf_print_test.in @@ -0,0 +1,33 @@ +# Check width and precision. +%-30.20s 123456789012345678901234567890 +%30.20s 123456789012345678901234567890 + +%d 123456789 +%+10d 123456789 +%-10d 123456789 +%10d 123456789 +%10.10d 123456789 + +%+ld 123456789 +%-ld 123456789 +%ld 123456789 +%10ld 123456789 +%10.10ld 123456789 + +%+lo 123456789 +%-lo 123456789 +%lo 123456789 +%10lo 123456789 +%10.10lo 123456789 + +%f 1e308 +%.100f 1e308 +%g 1e308 +%.309g 1e308 + +%s foo +%0s foo +%.0s foo +%10s foo +%+10s foo +%-10s foo diff --git a/src/util/vbuf_print_test.ref b/src/util/vbuf_print_test.ref new file mode 100644 index 0000000..346c919 --- /dev/null +++ b/src/util/vbuf_print_test.ref @@ -0,0 +1,27 @@ +./vbuf_print: "12345678901234567890 " +./vbuf_print: " 12345678901234567890" +./vbuf_print: "123456789" +./vbuf_print: "+123456789" +./vbuf_print: "123456789 " +./vbuf_print: " 123456789" +./vbuf_print: "0123456789" +./vbuf_print: "+123456789" +./vbuf_print: "123456789" +./vbuf_print: "123456789" +./vbuf_print: " 123456789" +./vbuf_print: "0123456789" +./vbuf_print: "726746425" +./vbuf_print: "726746425" +./vbuf_print: "726746425" +./vbuf_print: " 726746425" +./vbuf_print: "0726746425" +./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336.000000" +./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +./vbuf_print: "1e+308" +./vbuf_print: "100000000000000001097906362944045541740492309677311846336810682903157585404911491537163328978494688899061249669721172515611590283743140088328307009198146046031271664502933027185697489699588559043338384466165001178426897626212945177628091195786707458122783970171784415105291802893207873272974885715430223118336" +./vbuf_print: "foo" +./vbuf_print: "foo" +./vbuf_print: "" +./vbuf_print: " foo" +./vbuf_print: " foo" +./vbuf_print: "foo " diff --git a/src/util/vstream.c b/src/util/vstream.c new file mode 100644 index 0000000..b4f9fbb --- /dev/null +++ b/src/util/vstream.c @@ -0,0 +1,2046 @@ +/*++ +/* NAME +/* vstream 3 +/* SUMMARY +/* light-weight buffered I/O package +/* SYNOPSIS +/* #include +/* +/* VSTREAM *vstream_fopen(path, flags, mode) +/* const char *path; +/* int flags; +/* mode_t mode; +/* +/* VSTREAM *vstream_fdopen(fd, flags) +/* int fd; +/* int flags; +/* +/* VSTREAM *vstream_memopen(string, flags) +/* VSTRING *string; +/* int flags; +/* +/* VSTREAM *vstream_memreopen(stream, string, flags) +/* VSTREAM *stream; +/* VSTRING *string; +/* int flags; +/* +/* int vstream_fclose(stream) +/* VSTREAM *stream; +/* +/* int vstream_fdclose(stream) +/* VSTREAM *stream; +/* +/* VSTREAM *vstream_printf(format, ...) +/* const char *format; +/* +/* VSTREAM *vstream_fprintf(stream, format, ...) +/* VSTREAM *stream; +/* const char *format; +/* +/* int VSTREAM_GETC(stream) +/* VSTREAM *stream; +/* +/* int VSTREAM_PUTC(ch, stream) +/* int ch; +/* +/* int VSTREAM_GETCHAR(void) +/* +/* int VSTREAM_PUTCHAR(ch) +/* int ch; +/* +/* int vstream_ungetc(stream, ch) +/* VSTREAM *stream; +/* int ch; +/* +/* int vstream_fputs(str, stream) +/* const char *str; +/* VSTREAM *stream; +/* +/* off_t vstream_ftell(stream) +/* VSTREAM *stream; +/* +/* off_t vstream_fseek(stream, offset, whence) +/* VSTREAM *stream; +/* off_t offset; +/* int whence; +/* +/* int vstream_fflush(stream) +/* VSTREAM *stream; +/* +/* int vstream_fpurge(stream, direction) +/* VSTREAM *stream; +/* int direction; +/* +/* ssize_t vstream_fread(stream, buf, len) +/* VSTREAM *stream; +/* void *buf; +/* ssize_t len; +/* +/* ssize_t vstream_fwrite(stream, buf, len) +/* VSTREAM *stream; +/* void *buf; +/* ssize_t len; +/* +/* ssize_t vstream_fread_app(stream, buf, len) +/* VSTREAM *stream; +/* VSTRING *buf; +/* ssize_t len; +/* +/* ssize_t vstream_fread_buf(stream, buf, len) +/* VSTREAM *stream; +/* VSTRING *buf; +/* ssize_t len; +/* +/* void vstream_control(stream, name, ...) +/* VSTREAM *stream; +/* int name; +/* +/* int vstream_fileno(stream) +/* VSTREAM *stream; +/* +/* const ssize_t vstream_req_bufsize(stream) +/* VSTREAM *stream; +/* +/* void *vstream_context(stream) +/* VSTREAM *stream; +/* +/* int vstream_ferror(stream) +/* VSTREAM *stream; +/* +/* int vstream_ftimeout(stream) +/* VSTREAM *stream; +/* +/* int vstream_feof(stream) +/* VSTREAM *stream; +/* +/* int vstream_clearerr(stream) +/* VSTREAM *stream; +/* +/* const char *VSTREAM_PATH(stream) +/* VSTREAM *stream; +/* +/* char *vstream_vprintf(format, ap) +/* const char *format; +/* va_list *ap; +/* +/* char *vstream_vfprintf(stream, format, ap) +/* VSTREAM *stream; +/* const char *format; +/* va_list *ap; +/* +/* ssize_t vstream_bufstat(stream, command) +/* VSTREAM *stream; +/* int command; +/* +/* ssize_t vstream_peek(stream) +/* VSTREAM *stream; +/* +/* const char *vstream_peek_data(stream) +/* VSTREAM *stream; +/* +/* int vstream_setjmp(stream) +/* VSTREAM *stream; +/* +/* void vstream_longjmp(stream, val) +/* VSTREAM *stream; +/* int val; +/* +/* time_t vstream_ftime(stream) +/* VSTREAM *stream; +/* +/* struct timeval vstream_ftimeval(stream) +/* VSTREAM *stream; +/* +/* int vstream_rd_error(stream) +/* VSTREAM *stream; +/* +/* int vstream_wr_error(stream) +/* VSTREAM *stream; +/* +/* int vstream_rd_timeout(stream) +/* VSTREAM *stream; +/* +/* int vstream_wr_timeout(stream) +/* VSTREAM *stream; +/* +/* int vstream_fstat(stream, flags) +/* VSTREAM *stream; +/* int flags; +/* DESCRIPTION +/* The \fIvstream\fR module implements light-weight buffered I/O +/* similar to the standard I/O routines. +/* +/* The interface is implemented in terms of VSTREAM structure +/* pointers, also called streams. For convenience, three streams +/* are predefined: VSTREAM_IN, VSTREAM_OUT, and VSTREAM_ERR. These +/* streams are connected to the standard input, output and error +/* file descriptors, respectively. +/* +/* Although the interface is patterned after the standard I/O +/* library, there are some major differences: +/* .IP \(bu +/* File descriptors are not limited to the range 0..255. This +/* was reason #1 to write these routines in the first place. +/* .IP \(bu +/* The application can switch between reading and writing on +/* the same stream without having to perform a flush or seek +/* operation, and can change write position without having to +/* flush. This was reason #2. Upon position or direction change, +/* unread input is discarded, and unwritten output is flushed +/* automatically. Exception: with double-buffered streams, unread +/* input is not discarded upon change of I/O direction, and +/* output flushing is delayed until the read buffer must be refilled. +/* .IP \(bu +/* A bidirectional stream can read and write with the same buffer +/* and file descriptor, or it can have separate read/write +/* buffers and/or file descriptors. +/* .IP \(bu +/* No automatic flushing of VSTREAM_OUT upon program exit, or of +/* VSTREAM_ERR at any time. No unbuffered or line buffered modes. +/* This functionality may be added when it is really needed. +/* .PP +/* vstream_fopen() opens the named file and associates a buffered +/* stream with it. The \fIpath\fR, \fIflags\fR and \fImode\fR +/* arguments are passed on to the open(2) routine. The result is +/* a null pointer in case of problems. The \fIpath\fR argument is +/* copied and can be looked up with VSTREAM_PATH(). +/* +/* vstream_fdopen() takes an open file and associates a buffered +/* stream with it. The \fIflags\fR argument specifies how the file +/* was opened. vstream_fdopen() either succeeds or never returns. +/* +/* vstream_memopen() opens a VSTRING as a stream. The \fIflags\fR +/* argument must specify one of O_RDONLY, O_WRONLY, or O_APPEND. +/* vstream_memopen() either succeeds or never returns. Streams +/* opened with vstream_memopen() have limitations: they can't +/* be opened in read/write mode, they can't seek beyond the +/* end of the VSTRING, and they don't support vstream_control() +/* methods that manipulate buffers, file descriptors, or I/O +/* functions. After a VSTRING is opened for writing, its content +/* will be in an indeterminate state while the stream is open, +/* and will be null-terminated when the stream is closed. +/* +/* vstream_memreopen() reopens a memory stream. When the +/* \fIstream\fR argument is a null pointer, the behavior is that +/* of vstream_memopen(). +/* +/* vstream_fclose() closes the named buffered stream. The result +/* is 0 in case of success, VSTREAM_EOF in case of problems. +/* vstream_fclose() reports the same errors as vstream_ferror(). +/* +/* vstream_fdclose() leaves the file(s) open but is otherwise +/* identical to vstream_fclose(). +/* +/* vstream_fprintf() formats its arguments according to the +/* \fIformat\fR argument and writes the result to the named stream. +/* The result is the stream argument. It understands the s, c, d, u, +/* o, x, X, e, f and g format types, the l modifier, field width and +/* precision, sign, and padding with zeros or spaces. In addition, +/* vstream_fprintf() recognizes the %m format specifier and expands +/* it to the error message corresponding to the current value of the +/* global \fIerrno\fR variable. +/* +/* vstream_printf() performs formatted output to the standard output +/* stream. +/* +/* VSTREAM_GETC() reads the next character from the named stream. +/* The result is VSTREAM_EOF when end-of-file is reached or if a read +/* error was detected. VSTREAM_GETC() is an unsafe macro that +/* evaluates some arguments more than once. +/* +/* VSTREAM_GETCHAR() is an alias for VSTREAM_GETC(VSTREAM_IN). +/* +/* VSTREAM_PUTC() appends the specified character to the specified +/* stream. The result is the stored character, or VSTREAM_EOF in +/* case of problems. VSTREAM_PUTC() is an unsafe macro that +/* evaluates some arguments more than once. +/* +/* VSTREAM_PUTCHAR(c) is an alias for VSTREAM_PUTC(c, VSTREAM_OUT). +/* +/* vstream_ungetc() pushes back a character onto the specified stream +/* and returns the character, or VSTREAM_EOF in case of problems. +/* It is an error to push back before reading (or immediately after +/* changing the stream offset via vstream_fseek()). Upon successful +/* return, vstream_ungetc() clears the end-of-file stream flag. +/* +/* vstream_fputs() appends the given null-terminated string to the +/* specified buffered stream. The result is 0 in case of success, +/* VSTREAM_EOF in case of problems. +/* +/* vstream_ftell() returns the file offset for the specified stream, +/* -1 if the stream is connected to a non-seekable file. +/* +/* vstream_fseek() changes the file position for the next read or write +/* operation. Unwritten output is flushed. With unidirectional streams, +/* unread input is discarded. The \fIoffset\fR argument specifies the file +/* position from the beginning of the file (\fIwhence\fR is SEEK_SET), +/* from the current file position (\fIwhence\fR is SEEK_CUR), or from +/* the file end (SEEK_END). The result value is the file offset +/* from the beginning of the file, -1 in case of problems. +/* +/* vstream_fflush() flushes unwritten data to a file that was +/* opened in read-write or write-only mode. +/* vstream_fflush() returns 0 in case of success, VSTREAM_EOF in +/* case of problems. It is an error to flush a read-only stream. +/* vstream_fflush() reports the same errors as vstream_ferror(). +/* +/* vstream_fpurge() discards the contents of the stream buffer. +/* If direction is VSTREAM_PURGE_READ, it discards unread data, +/* else if direction is VSTREAM_PURGE_WRITE, it discards unwritten +/* data. In the case of a double-buffered stream, if direction is +/* VSTREAM_PURGE_BOTH, it discards the content of both the read +/* and write buffers. vstream_fpurge() returns 0 in case of success, +/* VSTREAM_EOF in case of problems. +/* +/* vstream_fread() and vstream_fwrite() perform unformatted I/O +/* on the named stream. The result value is the number of bytes +/* transferred. A short count is returned in case of end-of-file +/* or error conditions. +/* +/* vstream_fread_buf() resets the buffer write position, +/* allocates space for the specified number of bytes in the +/* buffer, reads the bytes from the specified VSTREAM, and +/* adjusts the buffer write position. The buffer is NOT +/* null-terminated. The result value is as with vstream_fread(). +/* NOTE: do not skip calling vstream_fread_buf() when len == 0. +/* This function has side effects including resetting the buffer +/* write position, and skipping the call would invalidate the +/* buffer state. +/* +/* vstream_fread_app() is like vstream_fread_buf() but appends +/* to existing buffer content, instead of writing over it. +/* +/* vstream_control() allows the user to fine tune the behavior of +/* the specified stream. The arguments are a list of macros with +/* zero or more arguments, terminated with CA_VSTREAM_CTL_END +/* which has none. The following lists the names and the types +/* of the corresponding value arguments. +/* .IP "CA_VSTREAM_CTL_READ_FN(ssize_t (*)(int, void *, size_t, int, void *))" +/* The argument specifies an alternative for the timed_read(3) function, +/* for example, a read function that performs decryption. +/* This function receives as arguments a file descriptor, buffer pointer, +/* buffer length, timeout value, and the VSTREAM's context value. +/* A timeout value <= 0 disables the time limit. +/* This function should return the positive number of bytes transferred, +/* 0 upon EOF, and -1 upon error with errno set appropriately. +/* .IP "CA_VSTREAM_CTL_WRITE_FN(ssize_t (*)(int, void *, size_t, int, void *))" +/* The argument specifies an alternative for the timed_write(3) function, +/* for example, a write function that performs encryption. +/* This function receives as arguments a file descriptor, buffer pointer, +/* buffer length, timeout value, and the VSTREAM's context value. +/* A timeout value <= 0 disables the time limit. +/* This function should return the positive number of bytes transferred, +/* and -1 upon error with errno set appropriately. Instead of -1 it may +/* also return 0, e.g., upon remote party-initiated protocol shutdown. +/* .IP "CA_VSTREAM_CTL_CONTEXT(void *)" +/* The argument specifies application context that is passed on to +/* the application-specified read/write routines. No copy is made. +/* .IP "CA_VSTREAM_CTL_PATH(const char *)" +/* Updates the stored pathname of the specified stream. The pathname +/* is copied. +/* .IP "CA_VSTREAM_CTL_DOUBLE (no arguments)" +/* Use separate buffers for reading and for writing. This prevents +/* unread input from being discarded upon change of I/O direction. +/* .IP "CA_VSTREAM_CTL_READ_FD(int)" +/* The argument specifies the file descriptor to be used for reading. +/* This feature is limited to double-buffered streams, and makes the +/* stream non-seekable. +/* .IP "CA_VSTREAM_CTL_WRITE_FD(int)" +/* The argument specifies the file descriptor to be used for writing. +/* This feature is limited to double-buffered streams, and makes the +/* stream non-seekable. +/* .IP "CA_VSTREAM_CTL_SWAP_FD(VSTREAM *)" +/* The argument specifies a VSTREAM pointer; the request swaps the +/* file descriptor members of the two streams. This feature is limited +/* to streams that are both double-buffered or both single-buffered. +/* .IP "CA_VSTREAM_CTL_DUPFD(int)" +/* The argument specifies a minimum file descriptor value. If +/* the actual stream's file descriptors are below the minimum, +/* reallocate the descriptors to the first free value greater +/* than or equal to the minimum. The VSTREAM_CTL_DUPFD macro +/* is defined only on systems with fcntl() F_DUPFD support. +/* .IP "CA_VSTREAM_CTL_WAITPID_FN(int (*)(pid_t, WAIT_STATUS_T *, int))" +/* A pointer to function that behaves like waitpid(). This information +/* is used by the vstream_pclose() routine. +/* .IP "CA_VSTREAM_CTL_TIMEOUT(int)" +/* The deadline for a descriptor to become readable in case of a read +/* request, or writable in case of a write request. Specify a value +/* of 0 to disable deadlines. +/* .IP "CA_VSTREAM_CTL_EXCEPT (no arguments)" +/* Enable exception handling with vstream_setjmp() and vstream_longjmp(). +/* This involves allocation of additional memory that normally isn't +/* used. +/* .IP "CA_VSTREAM_CTL_BUFSIZE(ssize_t)" +/* Specify a non-default buffer size for the next read(2) or +/* write(2) operation, or zero to implement a no-op. Requests +/* to reduce the buffer size are silently ignored (i.e. any +/* positive value <= vstream_req_bufsize()). To get a buffer +/* size smaller than VSTREAM_BUFSIZE, make the VSTREAM_CTL_BUFSIZE +/* request before the first stream read or write operation +/* (i.e., vstream_req_bufsize() returns zero). Requests to +/* change a fixed-size buffer (i.e., VSTREAM_ERR) are not +/* allowed. +/* +/* NOTE: the vstream_*printf() routines may silently expand a +/* buffer, so that the result of some %letter specifiers can +/* be written to contiguous memory. +/* .IP CA_VSTREAM_CTL_START_DEADLINE (no arguments) +/* Change the VSTREAM_CTL_TIMEOUT behavior, to a deadline for +/* the total amount of time for all subsequent file descriptor +/* read or write operations, and recharge the deadline timer. +/* .IP CA_VSTREAM_CTL_STOP_DEADLINE (no arguments) +/* Revert VSTREAM_CTL_TIMEOUT behavior to the default, i.e. +/* a time limit for individual file descriptor read or write +/* operations. +/* .IP CA_VSTREAM_CTL_MIN_DATA_RATE (int) +/* When the DEADLINE is enabled, the amount of data that must +/* be transferred to add 1 second to the deadline. However, +/* the deadline will never exceed the timeout specified with +/* VSTREAM_CTL_TIMEOUT. A zero value requests no update to the +/* deadline as data is transferred; that is appropriate for +/* request/reply interactions. +/* .IP CA_VSTREAM_CTL_OWN_VSTRING (no arguments) +/* Transfer ownership of the VSTRING that was opened with +/* vstream_memopen() etc. to the stream, so that the VSTRING +/* is automatically destroyed when the stream is closed. +/* .PP +/* vstream_fileno() gives access to the file handle associated with +/* a buffered stream. With streams that have separate read/write +/* file descriptors, the result is the current descriptor. +/* +/* vstream_req_bufsize() returns the buffer size that will be +/* used for the next read(2) or write(2) operation on the named +/* stream. A zero result means that the next read(2) or write(2) +/* operation will use the default buffer size (VSTREAM_BUFSIZE). +/* +/* vstream_context() returns the application context that is passed on to +/* the application-specified read/write routines. +/* +/* VSTREAM_PATH() is an unsafe macro that returns the name stored +/* with vstream_fopen() or with vstream_control(). The macro is +/* unsafe because it evaluates some arguments more than once. +/* +/* vstream_feof() returns non-zero when a previous operation on the +/* specified stream caused an end-of-file condition. +/* Although further read requests after EOF may complete +/* successfully, vstream_feof() will keep returning non-zero +/* until vstream_clearerr() is called for that stream. +/* +/* vstream_ferror() returns non-zero when a previous operation on the +/* specified stream caused a non-EOF error condition, including timeout. +/* After a non-EOF error on a stream, no I/O request will +/* complete until after vstream_clearerr() is called for that stream. +/* +/* vstream_ftimeout() returns non-zero when a previous operation on the +/* specified stream caused a timeout error condition. See +/* vstream_ferror() for error persistence details. +/* +/* vstream_clearerr() resets the timeout, error and end-of-file indication +/* of the specified stream, and returns no useful result. +/* +/* vstream_vfprintf() provides an alternate interface +/* for formatting an argument list according to a format string. +/* +/* vstream_vprintf() provides a similar alternative interface. +/* +/* vstream_bufstat() provides input and output buffer status +/* information. The command is one of the following: +/* .IP VSTREAM_BST_IN_PEND +/* Return the number of characters that can be read without +/* refilling the read buffer. +/* .IP VSTREAM_BST_OUT_PEND +/* Return the number of characters that are waiting in the +/* write buffer. +/* .PP +/* vstream_peek() returns the number of characters that can be +/* read from the named stream without refilling the read buffer. +/* This is an alias for vstream_bufstat(stream, VSTREAM_BST_IN_PEND). +/* +/* vstream_peek_data() returns a pointer to the unread bytes +/* that exist according to vstream_peek(), or null if no unread +/* bytes are available. +/* +/* vstream_setjmp() saves processing context and makes that context +/* available for use with vstream_longjmp(). Normally, vstream_setjmp() +/* returns zero. A non-zero result means that vstream_setjmp() returned +/* through a vstream_longjmp() call; the result is the \fIval\fR argument +/* given to vstream_longjmp(). +/* +/* NB: non-local jumps such as vstream_longjmp() are not safe +/* for jumping out of any routine that manipulates VSTREAM data. +/* longjmp() like calls are best avoided in signal handlers. +/* +/* vstream_ftime() returns the time of initialization, the last buffer +/* fill operation, or the last buffer flush operation for the specified +/* stream. This information is maintained only when stream timeouts are +/* enabled. +/* +/* vstream_ftimeval() is like vstream_ftime() but returns more +/* detail. +/* +/* vstream_rd_mumble() and vstream_wr_mumble() report on +/* read and write error conditions, respectively. +/* +/* vstream_fstat() queries stream status information about +/* user-requested features. The \fIflags\fR argument is the +/* bitwise OR of one or more of the following, and the result +/* value is the bitwise OR of the features that are activated. +/* .IP VSTREAM_FLAG_DEADLINE +/* The deadline feature is activated. +/* .IP VSTREAM_FLAG_DOUBLE +/* The double-buffering feature is activated. +/* .IP VSTREAM_FLAG_MEMORY +/* The stream is connected to a VSTRING buffer. +/* .IP VSTREAM_FLAG_OWN_VSTRING +/* The stream 'owns' the VSTRING buffer, and is responsible +/* for cleaning up when the stream is closed. +/* DIAGNOSTICS +/* Panics: interface violations. Fatal errors: out of memory. +/* SEE ALSO +/* timed_read(3) default read routine +/* timed_write(3) default write routine +/* vbuf_print(3) formatting engine +/* setjmp(3) non-local jumps +/* BUGS +/* Should use mmap() on reasonable systems. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include /* 44BSD stdarg.h uses abort() */ +#include +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#define VSTRING_INTERNAL + +#include "mymalloc.h" +#include "msg.h" +#include "vbuf_print.h" +#include "iostuff.h" +#include "vstring.h" +#include "vstream.h" + +/* Application-specific. */ + + /* + * Forward declarations. + */ +static int vstream_buf_get_ready(VBUF *); +static int vstream_buf_put_ready(VBUF *); +static int vstream_buf_space(VBUF *, ssize_t); + + /* + * Initialization of the three pre-defined streams. Pre-allocate a static + * I/O buffer for the standard error stream, so that the error handler can + * produce a diagnostic even when memory allocation fails. + */ +static unsigned char vstream_fstd_buf[VSTREAM_BUFSIZE]; + +VSTREAM vstream_fstd[] = { + {{ + 0, /* flags */ + 0, 0, 0, 0, /* buffer */ + vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space, + }, STDIN_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write, + 0,}, + {{ + 0, /* flags */ + 0, 0, 0, 0, /* buffer */ + vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space, + }, STDOUT_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write, + 0,}, + {{ + VBUF_FLAG_FIXED | VSTREAM_FLAG_WRITE, + vstream_fstd_buf, VSTREAM_BUFSIZE, VSTREAM_BUFSIZE, vstream_fstd_buf, + vstream_buf_get_ready, vstream_buf_put_ready, vstream_buf_space, + }, STDERR_FILENO, (VSTREAM_RW_FN) timed_read, (VSTREAM_RW_FN) timed_write, + VSTREAM_BUFSIZE,}, +}; + +#define VSTREAM_STATIC(v) ((v) >= VSTREAM_IN && (v) <= VSTREAM_ERR) + + /* + * A bunch of macros to make some expressions more readable. XXX We're + * assuming that O_RDONLY == 0, O_WRONLY == 1, O_RDWR == 2. + */ +#define VSTREAM_ACC_MASK(f) ((f) & (O_APPEND | O_WRONLY | O_RDWR)) + +#define VSTREAM_CAN_READ(f) (VSTREAM_ACC_MASK(f) == O_RDONLY \ + || VSTREAM_ACC_MASK(f) == O_RDWR) +#define VSTREAM_CAN_WRITE(f) (VSTREAM_ACC_MASK(f) & O_WRONLY \ + || VSTREAM_ACC_MASK(f) & O_RDWR \ + || VSTREAM_ACC_MASK(f) & O_APPEND) + +#define VSTREAM_BUF_COUNT(bp, n) \ + ((bp)->flags & VSTREAM_FLAG_READ ? -(n) : (n)) + +#define VSTREAM_BUF_AT_START(bp) { \ + (bp)->cnt = VSTREAM_BUF_COUNT((bp), (bp)->len); \ + (bp)->ptr = (bp)->data; \ + } + +#define VSTREAM_BUF_AT_OFFSET(bp, offset) { \ + (bp)->ptr = (bp)->data + (offset); \ + (bp)->cnt = VSTREAM_BUF_COUNT(bp, (bp)->len - (offset)); \ + } + +#define VSTREAM_BUF_AT_END(bp) { \ + (bp)->cnt = 0; \ + (bp)->ptr = (bp)->data + (bp)->len; \ + } + +#define VSTREAM_BUF_ZERO(bp) { \ + (bp)->flags = 0; \ + (bp)->data = (bp)->ptr = 0; \ + (bp)->len = (bp)->cnt = 0; \ + } + +#define VSTREAM_BUF_ACTIONS(bp, get_action, put_action, space_action) { \ + (bp)->get_ready = (get_action); \ + (bp)->put_ready = (put_action); \ + (bp)->space = (space_action); \ + } + +#define VSTREAM_SAVE_STATE(stream, buffer, filedes) { \ + stream->buffer = stream->buf; \ + stream->filedes = stream->fd; \ + } + +#define VSTREAM_RESTORE_STATE(stream, buffer, filedes) do { \ + stream->buffer.flags = stream->buf.flags; \ + stream->buf = stream->buffer; \ + stream->fd = stream->filedes; \ + } while(0) + +#define VSTREAM_FORK_STATE(stream, buffer, filedes) { \ + stream->buffer = stream->buf; \ + stream->filedes = stream->fd; \ + stream->buffer.data = stream->buffer.ptr = 0; \ + stream->buffer.len = stream->buffer.cnt = 0; \ + stream->buffer.flags &= ~VSTREAM_FLAG_FIXED; \ + }; + +#define VSTREAM_FLAG_READ_DOUBLE (VSTREAM_FLAG_READ | VSTREAM_FLAG_DOUBLE) +#define VSTREAM_FLAG_WRITE_DOUBLE (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_DOUBLE) + +#define VSTREAM_FFLUSH_SOME(stream) \ + vstream_fflush_some((stream), (stream)->buf.len - (stream)->buf.cnt) + +/* Note: this does not change a negative result into a zero result. */ +#define VSTREAM_SUB_TIME(x, y, z) \ + do { \ + (x).tv_sec = (y).tv_sec - (z).tv_sec; \ + (x).tv_usec = (y).tv_usec - (z).tv_usec; \ + while ((x).tv_usec < 0) { \ + (x).tv_usec += 1000000; \ + (x).tv_sec -= 1; \ + } \ + while ((x).tv_usec >= 1000000) { \ + (x).tv_usec -= 1000000; \ + (x).tv_sec += 1; \ + } \ + } while (0) + +#define VSTREAM_ADD_TIME(x, y, z) \ + do { \ + (x).tv_sec = (y).tv_sec + (z).tv_sec; \ + (x).tv_usec = (y).tv_usec + (z).tv_usec; \ + while ((x).tv_usec >= 1000000) { \ + (x).tv_usec -= 1000000; \ + (x).tv_sec += 1; \ + } \ + } while (0) + +/* vstream_buf_init - initialize buffer */ + +static void vstream_buf_init(VBUF *bp, int flags) +{ + + /* + * Initialize the buffer such that the first data access triggers a + * buffer boundary action. + */ + VSTREAM_BUF_ZERO(bp); + VSTREAM_BUF_ACTIONS(bp, + VSTREAM_CAN_READ(flags) ? vstream_buf_get_ready : 0, + VSTREAM_CAN_WRITE(flags) ? vstream_buf_put_ready : 0, + vstream_buf_space); +} + +/* vstream_buf_alloc - allocate buffer memory */ + +static void vstream_buf_alloc(VBUF *bp, ssize_t len) +{ + VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf); + ssize_t used = bp->ptr - bp->data; + const char *myname = "vstream_buf_alloc"; + + if (len < bp->len) + msg_panic("%s: attempt to shrink buffer", myname); + if (bp->flags & VSTREAM_FLAG_FIXED) + msg_panic("%s: unable to extend fixed-size buffer", myname); + + /* + * Late buffer allocation allows the user to override the default policy. + * If a buffer already exists, allow for the presence of (output) data. + */ + bp->data = (unsigned char *) + (bp->data ? myrealloc((void *) bp->data, len) : mymalloc(len)); + if (bp->flags & VSTREAM_FLAG_MEMORY) + memset(bp->data + bp->len, 0, len - bp->len); + bp->len = len; + if (bp->flags & VSTREAM_FLAG_READ) { + bp->ptr = bp->data + used; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_SAVE_STATE(stream, read_buf, read_fd); + } else { + VSTREAM_BUF_AT_OFFSET(bp, used); + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_SAVE_STATE(stream, write_buf, write_fd); + } +} + +/* vstream_buf_wipe - reset buffer to initial state */ + +static void vstream_buf_wipe(VBUF *bp) +{ + if ((bp->flags & VBUF_FLAG_FIXED) == 0 && bp->data) + myfree((void *) bp->data); + VSTREAM_BUF_ZERO(bp); + VSTREAM_BUF_ACTIONS(bp, 0, 0, 0); +} + +/* vstream_fflush_some - flush some buffered data */ + +static int vstream_fflush_some(VSTREAM *stream, ssize_t to_flush) +{ + const char *myname = "vstream_fflush_some"; + VBUF *bp = &stream->buf; + ssize_t used; + ssize_t left_over; + void *data; + ssize_t len; + ssize_t n; + int timeout; + struct timeval before; + struct timeval elapsed; + struct timeval bonus; + + /* + * Sanity checks. It is illegal to flush a read-only stream. Otherwise, + * if there is buffered input, discard the input. If there is buffered + * output, require that the amount to flush is larger than the amount to + * keep, so that we can memcpy() the residue. + */ + if (bp->put_ready == 0) + msg_panic("%s: read-only stream", myname); + switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) { + case VSTREAM_FLAG_READ: /* discard input */ + VSTREAM_BUF_AT_END(bp); + /* FALLTHROUGH */ + case 0: /* flush after seek? */ + return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0); + case VSTREAM_FLAG_WRITE: /* output buffered */ + break; + case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ: + msg_panic("%s: read/write stream", myname); + } + used = bp->len - bp->cnt; + left_over = used - to_flush; + + if (msg_verbose > 2 && stream != VSTREAM_ERR) + msg_info("%s: fd %d flush %ld", myname, stream->fd, (long) to_flush); + if (to_flush < 0 || left_over < 0) + msg_panic("%s: bad to_flush %ld", myname, (long) to_flush); + if (to_flush < left_over) + msg_panic("%s: to_flush < left_over", myname); + if (to_flush == 0) + return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0); + if (bp->flags & VSTREAM_FLAG_ERR) + return (VSTREAM_EOF); + + /* + * When flushing a buffer, allow for partial writes. These can happen + * while talking to a network. Update the cached file seek position, if + * any. + * + * When deadlines are enabled, we count the elapsed time for each write + * operation instead of simply comparing the time-of-day clock with a + * per-stream deadline. The latter could result in anomalies when an + * application does lengthy processing between write operations. Keep in + * mind that a receiver may not be able to keep up when a sender suddenly + * floods it with a lot of data as it tries to catch up with a deadline. + */ + for (data = (void *) bp->data, len = to_flush; len > 0; len -= n, data += n) { + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0); + if (timeout <= 0) { + bp->flags |= (VSTREAM_FLAG_WR_ERR | VSTREAM_FLAG_WR_TIMEOUT); + errno = ETIMEDOUT; + return (VSTREAM_EOF); + } + if (len == to_flush) + GETTIMEOFDAY(&before); + else + before = stream->iotime; + } else + timeout = stream->timeout; + if ((n = stream->write_fn(stream->fd, data, len, timeout, stream->context)) <= 0) { + bp->flags |= VSTREAM_FLAG_WR_ERR; + if (errno == ETIMEDOUT) { + bp->flags |= VSTREAM_FLAG_WR_TIMEOUT; + stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0; + } + return (VSTREAM_EOF); + } + if (timeout) { + GETTIMEOFDAY(&stream->iotime); + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + VSTREAM_SUB_TIME(elapsed, stream->iotime, before); + VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed); + if (stream->min_data_rate > 0) { + bonus.tv_sec = n / stream->min_data_rate; + bonus.tv_usec = (n % stream->min_data_rate) * 1000000; + bonus.tv_usec /= stream->min_data_rate; + VSTREAM_ADD_TIME(stream->time_limit, stream->time_limit, + bonus); + if (stream->time_limit.tv_sec >= stream->timeout) { + stream->time_limit.tv_sec = stream->timeout; + stream->time_limit.tv_usec = 0; + } + } + } + } + if (msg_verbose > 2 && stream != VSTREAM_ERR && n != to_flush) + msg_info("%s: %d flushed %ld/%ld", myname, stream->fd, + (long) n, (long) to_flush); + } + if (bp->flags & VSTREAM_FLAG_SEEK) + stream->offset += to_flush; + + /* + * Allow for partial buffer flush requests. We use memcpy() for reasons + * of portability to pre-ANSI environments (SunOS 4.x or Ultrix 4.x :-). + * This is OK because we have already verified that the to_flush count is + * larger than the left_over count. + */ + if (left_over > 0) + memcpy(bp->data, bp->data + to_flush, left_over); + bp->cnt += to_flush; + bp->ptr -= to_flush; + return ((bp->flags & VSTREAM_FLAG_ERR) ? VSTREAM_EOF : 0); +} + +/* vstream_fflush_delayed - delayed stream flush for double-buffered stream */ + +static int vstream_fflush_delayed(VSTREAM *stream) +{ + int status; + + /* + * Sanity check. + */ + if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE) != VSTREAM_FLAG_READ_DOUBLE) + msg_panic("vstream_fflush_delayed: bad flags"); + + /* + * Temporarily swap buffers and flush unwritten data. This may seem like + * a lot of work, but it's peanuts compared to the write(2) call that we + * already have avoided. For example, delayed flush is never used on a + * non-pipelined SMTP connection. + */ + stream->buf.flags &= ~VSTREAM_FLAG_READ; + VSTREAM_SAVE_STATE(stream, read_buf, read_fd); + stream->buf.flags |= VSTREAM_FLAG_WRITE; + VSTREAM_RESTORE_STATE(stream, write_buf, write_fd); + + status = VSTREAM_FFLUSH_SOME(stream); + + stream->buf.flags &= ~VSTREAM_FLAG_WRITE; + VSTREAM_SAVE_STATE(stream, write_buf, write_fd); + stream->buf.flags |= VSTREAM_FLAG_READ; + VSTREAM_RESTORE_STATE(stream, read_buf, read_fd); + + return (status); +} + +/* vstream_buf_get_ready - vbuf callback to make buffer ready for reading */ + +static int vstream_buf_get_ready(VBUF *bp) +{ + VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf); + const char *myname = "vstream_buf_get_ready"; + ssize_t n; + struct timeval before; + struct timeval elapsed; + struct timeval bonus; + int timeout; + + /* + * Detect a change of I/O direction or position. If so, flush any + * unwritten output immediately when the stream is single-buffered, or + * when the stream is double-buffered and the read buffer is empty. + */ + switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) { + case VSTREAM_FLAG_WRITE: /* change direction */ + if (bp->ptr > bp->data) + if ((bp->flags & VSTREAM_FLAG_DOUBLE) == 0 + || stream->read_buf.cnt >= 0) + if (VSTREAM_FFLUSH_SOME(stream)) + return (VSTREAM_EOF); + bp->flags &= ~VSTREAM_FLAG_WRITE; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_SAVE_STATE(stream, write_buf, write_fd); + /* FALLTHROUGH */ + case 0: /* change position */ + bp->flags |= VSTREAM_FLAG_READ; + if (bp->flags & VSTREAM_FLAG_DOUBLE) { + VSTREAM_RESTORE_STATE(stream, read_buf, read_fd); + if (bp->cnt < 0) + return (0); + } + /* FALLTHROUGH */ + case VSTREAM_FLAG_READ: /* no change */ + break; + case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ: + msg_panic("%s: read/write stream", myname); + } + + /* + * If this is the first GET operation, allocate a buffer. Late buffer + * allocation gives the application a chance to override the default + * buffering policy. + * + * XXX Subtle code to set the preferred buffer size as late as possible. + */ + if (stream->req_bufsize == 0) + stream->req_bufsize = VSTREAM_BUFSIZE; + if (bp->len < stream->req_bufsize) + vstream_buf_alloc(bp, stream->req_bufsize); + + /* + * If the stream is double-buffered and the write buffer is not empty, + * this is the time to flush the write buffer. Delayed flushes reduce + * system call overhead, and on TCP sockets, avoid triggering Nagle's + * algorithm. + */ + if ((bp->flags & VSTREAM_FLAG_DOUBLE) + && stream->write_buf.len > stream->write_buf.cnt) + if (vstream_fflush_delayed(stream)) + return (VSTREAM_EOF); + + /* + * Did we receive an EOF indication? + */ + if (bp->flags & VSTREAM_FLAG_EOF) + return (VSTREAM_EOF); + + /* + * Fill the buffer with as much data as we can handle, or with as much + * data as is available right now, whichever is less. Update the cached + * file seek position, if any. + * + * When deadlines are enabled, we count the elapsed time for each read + * operation instead of simply comparing the time-of-day clock with a + * per-stream deadline. The latter could result in anomalies when an + * application does lengthy processing between read operations. Keep in + * mind that a sender may get blocked, and may not be able to keep up + * when a receiver suddenly wants to read a lot of data as it tries to + * catch up with a deadline. + */ + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + timeout = stream->time_limit.tv_sec + (stream->time_limit.tv_usec > 0); + if (timeout <= 0) { + bp->flags |= (VSTREAM_FLAG_RD_ERR | VSTREAM_FLAG_RD_TIMEOUT); + errno = ETIMEDOUT; + return (VSTREAM_EOF); + } + GETTIMEOFDAY(&before); + } else + timeout = stream->timeout; + switch (n = stream->read_fn(stream->fd, bp->data, bp->len, timeout, stream->context)) { + case -1: + bp->flags |= VSTREAM_FLAG_RD_ERR; + if (errno == ETIMEDOUT) { + bp->flags |= VSTREAM_FLAG_RD_TIMEOUT; + stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0; + } + return (VSTREAM_EOF); + case 0: + bp->flags |= VSTREAM_FLAG_EOF; + return (VSTREAM_EOF); + default: + if (timeout) { + GETTIMEOFDAY(&stream->iotime); + if (bp->flags & VSTREAM_FLAG_DEADLINE) { + VSTREAM_SUB_TIME(elapsed, stream->iotime, before); + VSTREAM_SUB_TIME(stream->time_limit, stream->time_limit, elapsed); + if (stream->min_data_rate > 0) { + bonus.tv_sec = n / stream->min_data_rate; + bonus.tv_usec = (n % stream->min_data_rate) * 1000000; + bonus.tv_usec /= stream->min_data_rate; + VSTREAM_ADD_TIME(stream->time_limit, stream->time_limit, + bonus); + if (stream->time_limit.tv_sec >= stream->timeout) { + stream->time_limit.tv_sec = stream->timeout; + stream->time_limit.tv_usec = 0; + } + } + } + } + if (msg_verbose > 2) + msg_info("%s: fd %d got %ld", myname, stream->fd, (long) n); + bp->cnt = -n; + bp->ptr = bp->data; + if (bp->flags & VSTREAM_FLAG_SEEK) + stream->offset += n; + return (0); + } +} + +/* vstream_buf_put_ready - vbuf callback to make buffer ready for writing */ + +static int vstream_buf_put_ready(VBUF *bp) +{ + VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf); + const char *myname = "vstream_buf_put_ready"; + + /* + * Sanity checks. Detect a change of I/O direction or position. If so, + * discard unread input, and reset the buffer to the beginning. + */ + switch (bp->flags & (VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ)) { + case VSTREAM_FLAG_READ: /* change direction */ + bp->flags &= ~VSTREAM_FLAG_READ; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_SAVE_STATE(stream, read_buf, read_fd); + /* FALLTHROUGH */ + case 0: /* change position */ + bp->flags |= VSTREAM_FLAG_WRITE; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_RESTORE_STATE(stream, write_buf, write_fd); + else + VSTREAM_BUF_AT_START(bp); + /* FALLTHROUGH */ + case VSTREAM_FLAG_WRITE: /* no change */ + break; + case VSTREAM_FLAG_WRITE | VSTREAM_FLAG_READ: + msg_panic("%s: read/write stream", myname); + } + + /* + * Remember the direction. If this is the first PUT operation for this + * stream or if the buffer is smaller than the requested size, allocate a + * new buffer; obviously there is no data to be flushed yet. Otherwise, + * flush the buffer. + * + * XXX Subtle code to set the preferred buffer size as late as possible. + */ + if (stream->req_bufsize == 0) + stream->req_bufsize = VSTREAM_BUFSIZE; + if (bp->len < stream->req_bufsize) { + vstream_buf_alloc(bp, stream->req_bufsize); + } else if (bp->cnt <= 0) { + if (VSTREAM_FFLUSH_SOME(stream)) + return (VSTREAM_EOF); + } + return (0); +} + +/* vstream_buf_space - reserve space ahead of time */ + +static int vstream_buf_space(VBUF *bp, ssize_t want) +{ + VSTREAM *stream = VBUF_TO_APPL(bp, VSTREAM, buf); + ssize_t used; + ssize_t incr; + ssize_t shortage; + const char *myname = "vstream_buf_space"; + + /* + * Sanity checks. Reserving space implies writing. It is illegal to write + * to a read-only stream. Detect a change of I/O direction or position. + * If so, reset the buffer to the beginning. + */ + if (bp->put_ready == 0) + msg_panic("%s: read-only stream", myname); + if (want < 0) + msg_panic("%s: bad length %ld", myname, (long) want); + switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) { + case VSTREAM_FLAG_READ: /* change direction */ + bp->flags &= ~VSTREAM_FLAG_READ; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_SAVE_STATE(stream, read_buf, read_fd); + /* FALLTHROUGH */ + case 0: /* change position */ + bp->flags |= VSTREAM_FLAG_WRITE; + if (bp->flags & VSTREAM_FLAG_DOUBLE) + VSTREAM_RESTORE_STATE(stream, write_buf, write_fd); + else + VSTREAM_BUF_AT_START(bp); + /* FALLTHROUGH */ + case VSTREAM_FLAG_WRITE: /* no change */ + break; + case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE: + msg_panic("%s: read/write stream", myname); + } + + /* + * See if enough space is available. If not, flush a multiple of + * VSTREAM_BUFSIZE bytes and resize the buffer to a multiple of + * VSTREAM_BUFSIZE. We flush multiples of VSTREAM_BUFSIZE in an attempt + * to keep file updates block-aligned for better performance. + * + * XXX Subtle code to set the preferred buffer size as late as possible. + */ +#define VSTREAM_TRUNCATE(count, base) (((count) / (base)) * (base)) +#define VSTREAM_ROUNDUP(count, base) VSTREAM_TRUNCATE(count + base - 1, base) + + if (stream->req_bufsize == 0) + stream->req_bufsize = VSTREAM_BUFSIZE; + if (want > bp->cnt) { + if ((used = bp->len - bp->cnt) > stream->req_bufsize) + if (vstream_fflush_some(stream, VSTREAM_TRUNCATE(used, stream->req_bufsize))) + return (VSTREAM_EOF); + if ((shortage = (want - bp->cnt)) > 0) { + if ((bp->flags & VSTREAM_FLAG_FIXED) + || shortage > __MAXINT__(ssize_t) -bp->len - stream->req_bufsize) { + bp->flags |= VSTREAM_FLAG_WR_ERR; + } else { + incr = VSTREAM_ROUNDUP(shortage, stream->req_bufsize); + vstream_buf_alloc(bp, bp->len + incr); + } + } + } + return (vstream_ferror(stream) ? VSTREAM_EOF : 0); /* mmap() may fail */ +} + +/* vstream_fpurge - discard unread or unwritten content */ + +int vstream_fpurge(VSTREAM *stream, int direction) +{ + const char *myname = "vstream_fpurge"; + VBUF *bp = &stream->buf; + +#define VSTREAM_MAYBE_PURGE_WRITE(d, b) if ((d) & VSTREAM_PURGE_WRITE) \ + VSTREAM_BUF_AT_START((b)) +#define VSTREAM_MAYBE_PURGE_READ(d, b) if ((d) & VSTREAM_PURGE_READ) \ + VSTREAM_BUF_AT_END((b)) + + /* + * To discard all unread contents, position the read buffer at its end, + * so that we skip over any unread data, and so that the next read + * operation will refill the buffer. + * + * To discard all unwritten content, position the write buffer at its + * beginning, so that the next write operation clobbers any unwritten + * data. + */ + switch (bp->flags & (VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE)) { + case VSTREAM_FLAG_READ_DOUBLE: + VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf); + /* FALLTHROUGH */ + case VSTREAM_FLAG_READ: + VSTREAM_MAYBE_PURGE_READ(direction, bp); + break; + case VSTREAM_FLAG_DOUBLE: + VSTREAM_MAYBE_PURGE_WRITE(direction, &stream->write_buf); + VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf); + break; + case VSTREAM_FLAG_WRITE_DOUBLE: + VSTREAM_MAYBE_PURGE_READ(direction, &stream->read_buf); + /* FALLTHROUGH */ + case VSTREAM_FLAG_WRITE: + VSTREAM_MAYBE_PURGE_WRITE(direction, bp); + break; + case VSTREAM_FLAG_READ_DOUBLE | VSTREAM_FLAG_WRITE: + case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE: + msg_panic("%s: read/write stream", myname); + } + + /* + * Invalidate the cached file seek position. + */ + bp->flags &= ~VSTREAM_FLAG_SEEK; + stream->offset = 0; + + return (0); +} + +/* vstream_fseek - change I/O position */ + +off_t vstream_fseek(VSTREAM *stream, off_t offset, int whence) +{ + const char *myname = "vstream_fseek"; + VBUF *bp = &stream->buf; + + /* + * TODO: support data length (data length != buffer length). Without data + * length information, Without explicit data length information, + * vstream_memopen(O_RDONLY) has to set the VSTREAM buffer length to the + * vstring payload length to avoid accessing unwritten data after + * vstream_fseek(), because for lseek() compatibility, vstream_fseek() + * must allow seeking past the end of a file. + */ + if (stream->buf.flags & VSTREAM_FLAG_MEMORY) { + if (whence == SEEK_CUR) + offset += (bp->ptr - bp->data); + else if (whence == SEEK_END) + offset += bp->len; + if (offset < 0) { + errno = EINVAL; + return (-1); + } + if (offset > bp->len && (bp->flags & VSTREAM_FLAG_WRITE)) + vstream_buf_space(bp, offset - bp->len); + VSTREAM_BUF_AT_OFFSET(bp, offset); + return (offset); + } + + /* + * Flush any unwritten output. Discard any unread input. Position the + * buffer at the end, so that the next GET or PUT operation triggers a + * buffer boundary action. + */ + switch (bp->flags & (VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE)) { + case VSTREAM_FLAG_WRITE: + if (bp->ptr > bp->data) { + if (whence == SEEK_CUR) + offset += (bp->ptr - bp->data); /* add unwritten data */ + else if (whence == SEEK_END) + bp->flags &= ~VSTREAM_FLAG_SEEK; + if (VSTREAM_FFLUSH_SOME(stream)) + return (-1); + } + VSTREAM_BUF_AT_END(bp); + break; + case VSTREAM_FLAG_READ: + if (whence == SEEK_CUR) + offset += bp->cnt; /* subtract unread data */ + else if (whence == SEEK_END) + bp->flags &= ~VSTREAM_FLAG_SEEK; + /* FALLTHROUGH */ + case 0: + VSTREAM_BUF_AT_END(bp); + break; + case VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE: + msg_panic("%s: read/write stream", myname); + } + + /* + * Clear the read/write flags to inform the buffer boundary action + * routines that we may have changed I/O position. + */ + bp->flags &= ~(VSTREAM_FLAG_READ | VSTREAM_FLAG_WRITE); + + /* + * Shave an unnecessary system call. + */ + if (bp->flags & VSTREAM_FLAG_NSEEK) { + errno = ESPIPE; + return (-1); + } + + /* + * Update the cached file seek position. + */ + if ((stream->offset = lseek(stream->fd, offset, whence)) < 0) { + if (errno == ESPIPE) + bp->flags |= VSTREAM_FLAG_NSEEK; + } else { + bp->flags |= VSTREAM_FLAG_SEEK; + } + bp->flags &= ~VSTREAM_FLAG_EOF; + return (stream->offset); +} + +/* vstream_ftell - return file offset */ + +off_t vstream_ftell(VSTREAM *stream) +{ + VBUF *bp = &stream->buf; + + /* + * Special case for memory buffer. + */ + if (stream->buf.flags & VSTREAM_FLAG_MEMORY) + return (bp->ptr - bp->data); + + /* + * Shave an unnecessary syscall. + */ + if (bp->flags & VSTREAM_FLAG_NSEEK) { + errno = ESPIPE; + return (-1); + } + + /* + * Use the cached file offset when available. This is the offset after + * the last read, write or seek operation. + */ + if ((bp->flags & VSTREAM_FLAG_SEEK) == 0) { + if ((stream->offset = lseek(stream->fd, (off_t) 0, SEEK_CUR)) < 0) { + bp->flags |= VSTREAM_FLAG_NSEEK; + return (-1); + } + bp->flags |= VSTREAM_FLAG_SEEK; + } + + /* + * If this is a read buffer, subtract the number of unread bytes from the + * cached offset. Remember that read counts are negative. + */ + if (bp->flags & VSTREAM_FLAG_READ) + return (stream->offset + bp->cnt); + + /* + * If this is a write buffer, add the number of unwritten bytes to the + * cached offset. + */ + if (bp->flags & VSTREAM_FLAG_WRITE) + return (stream->offset + (bp->ptr - bp->data)); + + /* + * Apparently, this is a new buffer, or a buffer after seek, so there is + * no need to account for unread or unwritten data. + */ + return (stream->offset); +} + +/* vstream_subopen - initialize everything except buffers and I/O handlers */ + +static VSTREAM *vstream_subopen(void) +{ + VSTREAM *stream; + + /* Note: memset() is not a portable way to initialize non-integer types. */ + stream = (VSTREAM *) mymalloc(sizeof(*stream)); + stream->offset = 0; + stream->path = 0; + stream->pid = 0; + stream->waitpid_fn = 0; + stream->timeout = 0; + stream->context = 0; + stream->jbuf = 0; + stream->iotime.tv_sec = stream->iotime.tv_usec = 0; + stream->time_limit.tv_sec = stream->time_limit.tv_usec = 0; + stream->req_bufsize = 0; + stream->vstring = 0; + stream->min_data_rate = 0; + return (stream); +} + +/* vstream_fdopen - add buffering to pre-opened stream */ + +VSTREAM *vstream_fdopen(int fd, int flags) +{ + VSTREAM *stream; + + /* + * Sanity check. + */ + if (fd < 0) + msg_panic("vstream_fdopen: bad file %d", fd); + + /* + * Initialize buffers etc. but do as little as possible. Late buffer + * allocation etc. gives the application a chance to override default + * policies. Either this, or the vstream*open() routines would have to + * have a really ugly interface with lots of mostly-unused arguments (can + * you say VMS?). + */ + stream = vstream_subopen(); + stream->fd = fd; + stream->read_fn = VSTREAM_CAN_READ(flags) ? (VSTREAM_RW_FN) timed_read : 0; + stream->write_fn = VSTREAM_CAN_WRITE(flags) ? (VSTREAM_RW_FN) timed_write : 0; + vstream_buf_init(&stream->buf, flags); + return (stream); +} + +/* vstream_fopen - open buffered file stream */ + +VSTREAM *vstream_fopen(const char *path, int flags, mode_t mode) +{ + VSTREAM *stream; + int fd; + + if ((fd = open(path, flags, mode)) < 0) { + return (0); + } else { + stream = vstream_fdopen(fd, flags); + stream->path = mystrdup(path); + return (stream); + } +} + +/* vstream_fflush - flush write buffer */ + +int vstream_fflush(VSTREAM *stream) +{ + + /* + * With VSTRING, the write pointer must be positioned behind the end of + * data. But vstream_fseek() changes the write position, and causes the + * data length to be forgotten. Before flushing to vstream, remember the + * current write position, move the write pointer and do what needs to be + * done, then move the write pointer back to the saved location. + */ + if (stream->buf.flags & VSTREAM_FLAG_MEMORY) { + if (stream->buf.flags & VSTREAM_FLAG_WRITE) { + VSTRING *string = stream->vstring; + +#ifdef PENDING_VSTREAM_FSEEK_FOR_MEMORY + VSTREAM_BUF_AT_OFFSET(&stream->buf, stream->buf.data_len); +#endif + memcpy(&string->vbuf, &stream->buf, sizeof(stream->buf)); + string->vbuf.flags &= VSTRING_FLAG_MASK; + VSTRING_TERMINATE(string); + } + return (0); + } + if ((stream->buf.flags & VSTREAM_FLAG_READ_DOUBLE) + == VSTREAM_FLAG_READ_DOUBLE + && stream->write_buf.len > stream->write_buf.cnt) + vstream_fflush_delayed(stream); + return (VSTREAM_FFLUSH_SOME(stream)); +} + +/* vstream_fclose - close buffered stream */ + +int vstream_fclose(VSTREAM *stream) +{ + int err; + + /* + * NOTE: Negative file descriptors are not part of the external + * interface. They are for internal use only, in order to support + * vstream_fdclose() without a lot of code duplication. Applications that + * rely on negative VSTREAM file descriptors will break without warning. + */ + if (stream->pid != 0) + msg_panic("vstream_fclose: stream has process"); + if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) + || ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0 + && stream->fd >= 0)) + vstream_fflush(stream); + /* Do not remove: vstream_fdclose() depends on this error test. */ + err = vstream_ferror(stream); + if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) { + if (stream->read_fd >= 0) + err |= close(stream->read_fd); + if (stream->write_fd != stream->read_fd) + if (stream->write_fd >= 0) + err |= close(stream->write_fd); + vstream_buf_wipe(&stream->read_buf); + vstream_buf_wipe(&stream->write_buf); + stream->buf = stream->read_buf; + } else { + if (stream->fd >= 0) + err |= close(stream->fd); + if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0) + vstream_buf_wipe(&stream->buf); + } + if (stream->path) + myfree(stream->path); + if (stream->jbuf) + myfree((void *) stream->jbuf); + if (stream->vstring && (stream->buf.flags & VSTREAM_FLAG_OWN_VSTRING)) + vstring_free(stream->vstring); + if (!VSTREAM_STATIC(stream)) + myfree((void *) stream); + return (err ? VSTREAM_EOF : 0); +} + +/* vstream_fdclose - close stream, leave file(s) open */ + +int vstream_fdclose(VSTREAM *stream) +{ + + /* + * Flush unwritten output, just like vstream_fclose(). Errors are + * reported by vstream_fclose(). + */ + if ((stream->buf.flags & VSTREAM_FLAG_WRITE_DOUBLE) != 0) + (void) vstream_fflush(stream); + + /* + * NOTE: Negative file descriptors are not part of the external + * interface. They are for internal use only, in order to support + * vstream_fdclose() without a lot of code duplication. Applications that + * rely on negative VSTREAM file descriptors will break without warning. + */ + if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) { + stream->fd = stream->read_fd = stream->write_fd = -1; + } else { + stream->fd = -1; + } + return (vstream_fclose(stream)); +} + +/* vstream_printf - formatted print to stdout */ + +VSTREAM *vstream_printf(const char *fmt,...) +{ + VSTREAM *stream = VSTREAM_OUT; + va_list ap; + + va_start(ap, fmt); + vbuf_print(&stream->buf, fmt, ap); + va_end(ap); + return (stream); +} + +/* vstream_fprintf - formatted print to buffered stream */ + +VSTREAM *vstream_fprintf(VSTREAM *stream, const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vbuf_print(&stream->buf, fmt, ap); + va_end(ap); + return (stream); +} + +/* vstream_fputs - write string to stream */ + +int vstream_fputs(const char *str, VSTREAM *stream) +{ + int ch; + + while ((ch = *str++) != 0) + if (VSTREAM_PUTC(ch, stream) == VSTREAM_EOF) + return (VSTREAM_EOF); + return (0); +} + +/* vstream_fread_buf - unformatted read to VSTRING */ + +ssize_t vstream_fread_buf(VSTREAM *fp, VSTRING *vp, ssize_t len) +{ + ssize_t ret; + + VSTRING_RESET(vp); + VSTRING_SPACE(vp, len); + ret = vstream_fread(fp, vstring_str(vp), len); + if (ret > 0) + VSTRING_AT_OFFSET(vp, ret); + return (ret); +} + +/* vstream_fread_app - unformatted read to VSTRING */ + +ssize_t vstream_fread_app(VSTREAM *fp, VSTRING *vp, ssize_t len) +{ + ssize_t ret; + + VSTRING_SPACE(vp, len); + ret = vstream_fread(fp, vstring_end(vp), len); + if (ret > 0) + VSTRING_AT_OFFSET(vp, VSTRING_LEN(vp) + ret); + return (ret); +} + +/* vstream_control - fine control */ + +void vstream_control(VSTREAM *stream, int name,...) +{ + const char *myname = "vstream_control"; + va_list ap; + int floor; + int old_fd; + ssize_t req_bufsize = 0; + VSTREAM *stream2; + int min_data_rate; + +#define SWAP(type,a,b) do { type temp = (a); (a) = (b); (b) = (temp); } while (0) + + /* + * A crude 'allow' filter for memory streams. + */ + int memory_ops = + ((1 << VSTREAM_CTL_END) | (1 << VSTREAM_CTL_CONTEXT) + | (1 << VSTREAM_CTL_PATH) | (1 << VSTREAM_CTL_EXCEPT) + | (1 << VSTREAM_CTL_OWN_VSTRING)); + + for (va_start(ap, name); name != VSTREAM_CTL_END; name = va_arg(ap, int)) { + if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) + && (memory_ops & (1 << name)) == 0) + msg_panic("%s: memory stream does not support VSTREAM_CTL_%d", + VSTREAM_PATH(stream), name); + switch (name) { + case VSTREAM_CTL_READ_FN: + stream->read_fn = va_arg(ap, VSTREAM_RW_FN); + break; + case VSTREAM_CTL_WRITE_FN: + stream->write_fn = va_arg(ap, VSTREAM_RW_FN); + break; + case VSTREAM_CTL_CONTEXT: + stream->context = va_arg(ap, void *); + break; + case VSTREAM_CTL_PATH: + if (stream->path) + myfree(stream->path); + stream->path = mystrdup(va_arg(ap, char *)); + break; + case VSTREAM_CTL_DOUBLE: + if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0) { + stream->buf.flags |= VSTREAM_FLAG_DOUBLE; + if (stream->buf.flags & VSTREAM_FLAG_READ) { + VSTREAM_SAVE_STATE(stream, read_buf, read_fd); + VSTREAM_FORK_STATE(stream, write_buf, write_fd); + } else { + VSTREAM_SAVE_STATE(stream, write_buf, write_fd); + VSTREAM_FORK_STATE(stream, read_buf, read_fd); + } + } + break; + case VSTREAM_CTL_READ_FD: + if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0) + msg_panic("VSTREAM_CTL_READ_FD requires double buffering"); + stream->read_fd = va_arg(ap, int); + stream->buf.flags |= VSTREAM_FLAG_NSEEK; + break; + case VSTREAM_CTL_WRITE_FD: + if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) == 0) + msg_panic("VSTREAM_CTL_WRITE_FD requires double buffering"); + stream->write_fd = va_arg(ap, int); + stream->buf.flags |= VSTREAM_FLAG_NSEEK; + break; + case VSTREAM_CTL_SWAP_FD: + stream2 = va_arg(ap, VSTREAM *); + if ((stream->buf.flags & VSTREAM_FLAG_DOUBLE) + != (stream2->buf.flags & VSTREAM_FLAG_DOUBLE)) + msg_panic("VSTREAM_CTL_SWAP_FD can't swap descriptors between " + "single-buffered and double-buffered streams"); + if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) { + SWAP(int, stream->read_fd, stream2->read_fd); + SWAP(int, stream->write_fd, stream2->write_fd); + stream->fd = ((stream->buf.flags & VSTREAM_FLAG_WRITE) ? + stream->write_fd : stream->read_fd); + } else { + SWAP(int, stream->fd, stream2->fd); + } + break; + case VSTREAM_CTL_TIMEOUT: + if (stream->timeout == 0) + GETTIMEOFDAY(&stream->iotime); + stream->timeout = va_arg(ap, int); + if (stream->timeout < 0) + msg_panic("%s: bad timeout %d", myname, stream->timeout); + break; + case VSTREAM_CTL_EXCEPT: + if (stream->jbuf == 0) + stream->jbuf = + (VSTREAM_JMP_BUF *) mymalloc(sizeof(VSTREAM_JMP_BUF)); + break; + +#ifdef VSTREAM_CTL_DUPFD + +#define VSTREAM_TRY_DUPFD(backup, fd, floor) do { \ + if (((backup) = (fd)) < floor) { \ + if (((fd) = fcntl((backup), F_DUPFD, (floor))) < 0) \ + msg_fatal("fcntl F_DUPFD %d: %m", (floor)); \ + (void) close(backup); \ + } \ + } while (0) + + case VSTREAM_CTL_DUPFD: + floor = va_arg(ap, int); + if (stream->buf.flags & VSTREAM_FLAG_DOUBLE) { + VSTREAM_TRY_DUPFD(old_fd, stream->read_fd, floor); + if (stream->write_fd == old_fd) + stream->write_fd = stream->read_fd; + else + VSTREAM_TRY_DUPFD(old_fd, stream->write_fd, floor); + stream->fd = (stream->buf.flags & VSTREAM_FLAG_READ) ? + stream->read_fd : stream->write_fd; + } else { + VSTREAM_TRY_DUPFD(old_fd, stream->fd, floor); + } + break; +#endif + + /* + * Postpone memory (re)allocation until the space is needed. + */ + case VSTREAM_CTL_BUFSIZE: + req_bufsize = va_arg(ap, ssize_t); + /* Heuristic to detect missing (ssize_t) type cast on LP64 hosts. */ + if (req_bufsize < 0 || req_bufsize > INT_MAX) + msg_panic("unreasonable VSTREAM_CTL_BUFSIZE request: %ld", + (long) req_bufsize); + if ((stream->buf.flags & VSTREAM_FLAG_FIXED) == 0 + && req_bufsize > stream->req_bufsize) { + if (msg_verbose) + msg_info("fd=%d: stream buffer size old=%ld new=%ld", + vstream_fileno(stream), + (long) stream->req_bufsize, + (long) req_bufsize); + stream->req_bufsize = req_bufsize; + } + break; + + /* + * Make no gettimeofday() etc. system call until we really know + * that we need to do I/O. This avoids a performance hit when + * sending or receiving body content one line at a time. + */ + case VSTREAM_CTL_STOP_DEADLINE: + stream->buf.flags &= ~VSTREAM_FLAG_DEADLINE; + break; + case VSTREAM_CTL_START_DEADLINE: + if (stream->timeout <= 0) + msg_panic("%s: bad timeout %d", myname, stream->timeout); + stream->buf.flags |= VSTREAM_FLAG_DEADLINE; + stream->time_limit.tv_sec = stream->timeout; + stream->time_limit.tv_usec = 0; + break; + case VSTREAM_CTL_MIN_DATA_RATE: + min_data_rate = va_arg(ap, int); + if (min_data_rate < 0) + msg_panic("%s: bad min_data_rate %d", myname, min_data_rate); + stream->min_data_rate = min_data_rate; + break; + case VSTREAM_CTL_OWN_VSTRING: + if ((stream->buf.flags |= VSTREAM_FLAG_MEMORY) == 0) + msg_panic("%s: operation on non-VSTRING stream", myname); + stream->buf.flags |= VSTREAM_FLAG_OWN_VSTRING; + break; + default: + msg_panic("%s: bad name %d", myname, name); + } + } + va_end(ap); +} + +/* vstream_vprintf - formatted print to stdout */ + +VSTREAM *vstream_vprintf(const char *format, va_list ap) +{ + VSTREAM *vp = VSTREAM_OUT; + + vbuf_print(&vp->buf, format, ap); + return (vp); +} + +/* vstream_vfprintf - formatted print engine */ + +VSTREAM *vstream_vfprintf(VSTREAM *vp, const char *format, va_list ap) +{ + vbuf_print(&vp->buf, format, ap); + return (vp); +} + +/* vstream_bufstat - get stream buffer status */ + +ssize_t vstream_bufstat(VSTREAM *vp, int command) +{ + VBUF *bp; + + switch (command & VSTREAM_BST_MASK_DIR) { + case VSTREAM_BST_FLAG_IN: + if (vp->buf.flags & VSTREAM_FLAG_READ) { + bp = &vp->buf; + } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) { + bp = &vp->read_buf; + } else { + bp = 0; + } + switch (command & ~VSTREAM_BST_MASK_DIR) { + case VSTREAM_BST_FLAG_PEND: + return (bp ? -bp->cnt : 0); + /* Add other requests below. */ + } + break; + case VSTREAM_BST_FLAG_OUT: + if (vp->buf.flags & VSTREAM_FLAG_WRITE) { + bp = &vp->buf; + } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) { + bp = &vp->write_buf; + } else { + bp = 0; + } + switch (command & ~VSTREAM_BST_MASK_DIR) { + case VSTREAM_BST_FLAG_PEND: + return (bp ? bp->len - bp->cnt : 0); + /* Add other requests below. */ + } + break; + } + msg_panic("vstream_bufstat: unknown command: %d", command); +} + +#undef vstream_peek /* API binary compatibility. */ + +/* vstream_peek - peek at a stream */ + +ssize_t vstream_peek(VSTREAM *vp) +{ + if (vp->buf.flags & VSTREAM_FLAG_READ) { + return (-vp->buf.cnt); + } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) { + return (-vp->read_buf.cnt); + } else { + return (0); + } +} + +/* vstream_peek_data - peek at unread data */ + +const char *vstream_peek_data(VSTREAM *vp) +{ + if (vp->buf.flags & VSTREAM_FLAG_READ) { + return ((const char *) vp->buf.ptr); + } else if (vp->buf.flags & VSTREAM_FLAG_DOUBLE) { + return ((const char *) vp->read_buf.ptr); + } else { + return (0); + } +} + +/* vstream_memopen - open a VSTRING */ + +VSTREAM *vstream_memreopen(VSTREAM *stream, VSTRING *string, int flags) +{ + if (stream == 0) + stream = vstream_subopen(); + else if ((stream->buf.flags & VSTREAM_FLAG_MEMORY) == 0) + msg_panic("vstream_memreopen: cannot reopen non-memory stream"); + stream->fd = -1; + stream->read_fn = 0; + stream->write_fn = 0; + stream->vstring = string; + memcpy(&stream->buf, &stream->vstring->vbuf, sizeof(stream->buf)); + stream->buf.flags |= VSTREAM_FLAG_MEMORY; + switch (VSTREAM_ACC_MASK(flags)) { + case O_RDONLY: + stream->buf.flags |= VSTREAM_FLAG_READ; + /* Prevent reading unwritten data after vstream_fseek(). */ + stream->buf.len = stream->buf.ptr - stream->buf.data; + VSTREAM_BUF_AT_OFFSET(&stream->buf, 0); + break; + case O_WRONLY: + stream->buf.flags |= VSTREAM_FLAG_WRITE; + VSTREAM_BUF_AT_OFFSET(&stream->buf, 0); + break; + case O_APPEND: + stream->buf.flags |= VSTREAM_FLAG_WRITE; + VSTREAM_BUF_AT_OFFSET(&stream->buf, + stream->buf.ptr - stream->buf.data); + break; + default: + msg_panic("vstream_memopen: flags must be one of " + "O_RDONLY, O_WRONLY, or O_APPEND"); + } + return (stream); +} + +#ifdef TEST + +static void copy_line(ssize_t bufsize) +{ + int c; + + /* + * Demonstrates that VSTREAM_CTL_BUFSIZE increases the buffer size, but + * does not decrease it. Uses VSTREAM_ERR for non-test output to avoid + * interfering with the test. + */ + vstream_fprintf(VSTREAM_ERR, "buffer size test: copy text with %ld buffer size, ignore requests to shrink\n", + (long) bufsize); + vstream_fflush(VSTREAM_ERR); + vstream_control(VSTREAM_IN, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END); + vstream_control(VSTREAM_OUT, CA_VSTREAM_CTL_BUFSIZE(bufsize), VSTREAM_CTL_END); + while ((c = VSTREAM_GETC(VSTREAM_IN)) != VSTREAM_EOF) { + VSTREAM_PUTC(c, VSTREAM_OUT); + if (c == '\n') + break; + } + vstream_fflush(VSTREAM_OUT); + vstream_fprintf(VSTREAM_ERR, "actual read/write buffer sizes: %ld/%ld\n\n", + (long) VSTREAM_IN->buf.len, (long) VSTREAM_OUT->buf.len); + vstream_fflush(VSTREAM_ERR); +} + +static void printf_number(void) +{ + + /* + * Demonstrates that vstream_printf() use vbuf_print(). + */ + vstream_printf("formatting test: print a number\n"); + vstream_printf("%d\n\n", 1234567890); + vstream_fflush(VSTREAM_OUT); +} + +static void do_memory_stream(void) +{ + VSTRING *buf = vstring_alloc(1); + VSTREAM *fp; + off_t offset; + int ch; + + /* + * Preload the string. + */ + vstream_printf("memory stream test prep: prefill the VSTRING\n"); + vstring_strcpy(buf, "01234567"); + vstream_printf("VSTRING content length: %ld/%ld, content: %s\n", + (long) VSTRING_LEN(buf), (long) buf->vbuf.len, + vstring_str(buf)); + VSTREAM_PUTCHAR('\n'); + vstream_fflush(VSTREAM_OUT); + + /* + * Test: open the memory VSTREAM in write-only mode, and clobber it. + */ + vstream_printf("memory stream test: open the VSTRING for writing, overwrite, close\n"); + fp = vstream_memopen(buf, O_WRONLY); + vstream_printf("initial memory VSTREAM write offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_fprintf(fp, "hallo"); + vstream_printf("final memory VSTREAM write offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_fclose(fp); + vstream_printf("VSTRING content length: %ld/%ld, content: %s\n", + (long) VSTRING_LEN(buf), (long) buf->vbuf.len, + vstring_str(buf)); + VSTREAM_PUTCHAR('\n'); + vstream_fflush(VSTREAM_OUT); + + /* + * Test: open the memory VSTREAM for append. vstream_memopen() sets the + * buffer length to the VSTRING buffer length, and positions the write + * pointer at the VSTRING write position. Write some content, then + * overwrite one character. + */ + vstream_printf("memory stream test: open the VSTRING for append, write multiple, then overwrite 1\n"); + fp = vstream_memopen(buf, O_APPEND); + vstream_printf("initial memory VSTREAM write offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_fprintf(fp, " world"); + vstream_printf("final memory VSTREAM write offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + if (vstream_fflush(fp)) + msg_fatal("vstream_fflush: %m"); + vstream_printf("VSTRING content length: %ld/%ld, content: %s\n", + (long) VSTRING_LEN(buf), (long) buf->vbuf.len, + vstring_str(buf)); + VSTREAM_PUTCHAR('\n'); + + /* + * While the stream is still open, replace the second character. + */ + vstream_printf("replace second character and close\n"); + if ((offset = vstream_fseek(fp, 1, SEEK_SET)) != 1) + msg_panic("unexpected vstream_fseek return: %ld, expected: %ld", + (long) offset, (long) 1); + VSTREAM_PUTC('e', fp); + + /* + * Skip to the end of the content, so that vstream_fflush() will update + * the VSTRING with the right content length. + */ + if ((offset = vstream_fseek(fp, VSTRING_LEN(buf), SEEK_SET)) != VSTRING_LEN(buf)) + msg_panic("unexpected vstream_fseek return: %ld, expected: %ld", + (long) offset, (long) VSTRING_LEN(buf)); + vstream_fclose(fp); + + vstream_printf("VSTRING content length: %ld/%ld, content: %s\n", + (long) VSTRING_LEN(buf), (long) buf->vbuf.len, + vstring_str(buf)); + VSTREAM_PUTCHAR('\n'); + vstream_fflush(VSTREAM_OUT); + + /* + * TODO: test that in write/append mode, seek past the end of data will + * result in zero-filled space. + */ + + /* + * Test: Open the VSTRING for reading. This time, vstream_memopen() will + * set the VSTREAM buffer length to the content length of the VSTRING, so + * that it won't attempt to read past the end of the content. + */ + vstream_printf("memory stream test: open VSTRING for reading, then read\n"); + fp = vstream_memopen(buf, O_RDONLY); + vstream_printf("initial memory VSTREAM read offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_printf("reading memory VSTREAM: "); + while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) + VSTREAM_PUTCHAR(ch); + VSTREAM_PUTCHAR('\n'); + vstream_printf("final memory VSTREAM read offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_printf("seeking to offset %ld should work: ", + (long) fp->buf.len + 1); + vstream_fflush(VSTREAM_OUT); + if ((offset = vstream_fseek(fp, fp->buf.len + 1, SEEK_SET)) != fp->buf.len + 1) + msg_panic("unexpected vstream_fseek return: %ld, expected: %ld", + (long) offset, (long) fp->buf.len + 1); + vstream_printf("PASS\n"); + vstream_fflush(VSTREAM_OUT); + vstream_printf("VSTREAM_GETC should return VSTREAM_EOF\n"); + ch = VSTREAM_GETC(fp); + if (ch != VSTREAM_EOF) + msg_panic("unexpected vstream_fseek VSTREAM_GETC return: %d, expected: %d", + ch, VSTREAM_EOF); + vstream_printf("PASS\n"); + vstream_printf("final memory VSTREAM read offset: %ld/%ld\n", + (long) vstream_ftell(fp), (long) fp->buf.len); + vstream_printf("VSTRING content length: %ld/%ld, content: %s\n", + (long) VSTRING_LEN(buf), (long) buf->vbuf.len, + vstring_str(buf)); + VSTREAM_PUTCHAR('\n'); + vstream_fflush(VSTREAM_OUT); + vstream_fclose(fp); + vstring_free(buf); +} + + /* + * Exercise some of the features. + */ + +#include + +int main(int argc, char **argv) +{ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Test buffer expansion and shrinking. Formatted print may silently + * expand the write buffer and cause multiple bytes to be written. + */ + copy_line(1); /* one-byte read/write */ + copy_line(2); /* two-byte read/write */ + copy_line(1); /* two-byte read/write */ + printf_number(); /* multi-byte write */ + do_memory_stream(); + + exit(0); +} + +#endif diff --git a/src/util/vstream.h b/src/util/vstream.h new file mode 100644 index 0000000..23688c7 --- /dev/null +++ b/src/util/vstream.h @@ -0,0 +1,293 @@ +#ifndef _VSTREAM_H_INCLUDED_ +#define _VSTREAM_H_INCLUDED_ + +/*++ +/* NAME +/* vstream 3h +/* SUMMARY +/* simple buffered I/O package +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include +#include +#include +#include +#include +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Simple buffered stream. The members of this structure are not part of the + * official interface and can change without prior notice. + */ +typedef ssize_t (*VSTREAM_RW_FN) (int, void *, size_t, int, void *); +typedef pid_t(*VSTREAM_WAITPID_FN) (pid_t, WAIT_STATUS_T *, int); + +#ifdef NO_SIGSETJMP +#define VSTREAM_JMP_BUF jmp_buf +#else +#define VSTREAM_JMP_BUF sigjmp_buf +#endif + +typedef struct VSTREAM { + VBUF buf; /* generic intelligent buffer */ + int fd; /* file handle, no 256 limit */ + VSTREAM_RW_FN read_fn; /* buffer fill action */ + VSTREAM_RW_FN write_fn; /* buffer flush action */ + ssize_t req_bufsize; /* requested read/write buffer size */ + void *context; /* application context */ + off_t offset; /* cached seek info */ + char *path; /* give it at least try */ + int read_fd; /* read channel (double-buffered) */ + int write_fd; /* write channel (double-buffered) */ + VBUF read_buf; /* read buffer (double-buffered) */ + VBUF write_buf; /* write buffer (double-buffered) */ + pid_t pid; /* vstream_popen/close() */ + VSTREAM_WAITPID_FN waitpid_fn; /* vstream_popen/close() */ + int timeout; /* read/write timeout */ + VSTREAM_JMP_BUF *jbuf; /* exception handling */ + struct timeval iotime; /* time of last fill/flush */ + struct timeval time_limit; /* read/write time limit */ + int min_data_rate; /* min data rate for time limit */ + struct VSTRING *vstring; /* memory-backed stream */ +} VSTREAM; + +extern VSTREAM vstream_fstd[]; /* pre-defined streams */ + +#define VSTREAM_IN (&vstream_fstd[0]) +#define VSTREAM_OUT (&vstream_fstd[1]) +#define VSTREAM_ERR (&vstream_fstd[2]) + +#define VSTREAM_FLAG_RD_ERR VBUF_FLAG_RD_ERR /* read error */ +#define VSTREAM_FLAG_WR_ERR VBUF_FLAG_WR_ERR /* write error */ +#define VSTREAM_FLAG_RD_TIMEOUT VBUF_FLAG_RD_TIMEOUT /* read timeout */ +#define VSTREAM_FLAG_WR_TIMEOUT VBUF_FLAG_WR_TIMEOUT /* write timeout */ + +#define VSTREAM_FLAG_ERR VBUF_FLAG_ERR /* some I/O error */ +#define VSTREAM_FLAG_EOF VBUF_FLAG_EOF /* end of file */ +#define VSTREAM_FLAG_TIMEOUT VBUF_FLAG_TIMEOUT /* timeout error */ +#define VSTREAM_FLAG_FIXED VBUF_FLAG_FIXED /* fixed-size buffer */ +#define VSTREAM_FLAG_BAD VBUF_FLAG_BAD + +/* Flags 1<<24 and above are reserved for VSTRING. */ +#define VSTREAM_FLAG_READ (1<<8) /* read buffer */ +#define VSTREAM_FLAG_WRITE (1<<9) /* write buffer */ +#define VSTREAM_FLAG_SEEK (1<<10) /* seek info valid */ +#define VSTREAM_FLAG_NSEEK (1<<11) /* can't seek this file */ +#define VSTREAM_FLAG_DOUBLE (1<<12) /* double buffer */ +#define VSTREAM_FLAG_DEADLINE (1<<13) /* deadline active */ +#define VSTREAM_FLAG_MEMORY (1<<14) /* internal stream */ +#define VSTREAM_FLAG_OWN_VSTRING (1<<15)/* owns VSTRING resource */ + +#define VSTREAM_PURGE_READ (1<<0) /* flush unread data */ +#define VSTREAM_PURGE_WRITE (1<<1) /* flush unwritten data */ +#define VSTREAM_PURGE_BOTH (VSTREAM_PURGE_READ|VSTREAM_PURGE_WRITE) + +#define VSTREAM_BUFSIZE 4096 + +extern VSTREAM *vstream_fopen(const char *, int, mode_t); +extern int vstream_fclose(VSTREAM *); +extern off_t WARN_UNUSED_RESULT vstream_fseek(VSTREAM *, off_t, int); +extern off_t vstream_ftell(VSTREAM *); +extern int vstream_fpurge(VSTREAM *, int); +extern int vstream_fflush(VSTREAM *); +extern int vstream_fputs(const char *, VSTREAM *); +extern VSTREAM *vstream_fdopen(int, int); +extern int vstream_fdclose(VSTREAM *); + +#define vstream_fread(v, b, n) vbuf_read(&(v)->buf, (b), (n)) +#define vstream_fwrite(v, b, n) vbuf_write(&(v)->buf, (b), (n)) + +#define VSTREAM_PUTC(ch, vp) VBUF_PUT(&(vp)->buf, (ch)) +#define VSTREAM_GETC(vp) VBUF_GET(&(vp)->buf) +#define vstream_ungetc(vp, ch) vbuf_unget(&(vp)->buf, (ch)) +#define VSTREAM_EOF VBUF_EOF + +#define VSTREAM_PUTCHAR(ch) VSTREAM_PUTC((ch), VSTREAM_OUT) +#define VSTREAM_GETCHAR() VSTREAM_GETC(VSTREAM_IN) + +#define vstream_fileno(vp) ((vp)->fd) +#define vstream_req_bufsize(vp) ((const ssize_t) ((vp)->req_bufsize)) +#define vstream_context(vp) ((vp)->context) +#define vstream_rd_error(vp) vbuf_rd_error(&(vp)->buf) +#define vstream_wr_error(vp) vbuf_wr_error(&(vp)->buf) +#define vstream_ferror(vp) vbuf_error(&(vp)->buf) +#define vstream_feof(vp) vbuf_eof(&(vp)->buf) +#define vstream_rd_timeout(vp) vbuf_rd_timeout(&(vp)->buf) +#define vstream_wr_timeout(vp) vbuf_wr_timeout(&(vp)->buf) +#define vstream_ftimeout(vp) vbuf_timeout(&(vp)->buf) +#define vstream_clearerr(vp) vbuf_clearerr(&(vp)->buf) +#define VSTREAM_PATH(vp) ((vp)->path ? (const char *) (vp)->path : "unknown_stream") +#define vstream_ftime(vp) ((time_t) ((vp)->iotime.tv_sec)) +#define vstream_ftimeval(vp) ((vp)->iotime) + +#define vstream_fstat(vp, fl) ((vp)->buf.flags & (fl)) + +extern ssize_t vstream_fread_buf(VSTREAM *, struct VSTRING *, ssize_t); +extern ssize_t vstream_fread_app(VSTREAM *, struct VSTRING *, ssize_t); +extern void vstream_control(VSTREAM *, int,...); + +/* Legacy API: type-unchecked arguments, internal use. */ +#define VSTREAM_CTL_END 0 +#define VSTREAM_CTL_READ_FN 1 +#define VSTREAM_CTL_WRITE_FN 2 +#define VSTREAM_CTL_PATH 3 +#define VSTREAM_CTL_DOUBLE 4 +#define VSTREAM_CTL_READ_FD 5 +#define VSTREAM_CTL_WRITE_FD 6 +#define VSTREAM_CTL_WAITPID_FN 7 +#define VSTREAM_CTL_TIMEOUT 8 +#define VSTREAM_CTL_EXCEPT 9 +#define VSTREAM_CTL_CONTEXT 10 +#ifdef F_DUPFD +#define VSTREAM_CTL_DUPFD 11 +#endif +#define VSTREAM_CTL_BUFSIZE 12 +#define VSTREAM_CTL_SWAP_FD 13 +#define VSTREAM_CTL_START_DEADLINE 14 +#define VSTREAM_CTL_STOP_DEADLINE 15 +#define VSTREAM_CTL_OWN_VSTRING 16 +#define VSTREAM_CTL_MIN_DATA_RATE 17 + +/* Safer API: type-checked arguments, external use. */ +#define CA_VSTREAM_CTL_END VSTREAM_CTL_END +#define CA_VSTREAM_CTL_READ_FN(v) VSTREAM_CTL_READ_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_RW_FN, (v)) +#define CA_VSTREAM_CTL_WRITE_FN(v) VSTREAM_CTL_WRITE_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_RW_FN, (v)) +#define CA_VSTREAM_CTL_PATH(v) VSTREAM_CTL_PATH, CHECK_CPTR(VSTREAM_CTL, char, (v)) +#define CA_VSTREAM_CTL_DOUBLE VSTREAM_CTL_DOUBLE +#define CA_VSTREAM_CTL_READ_FD(v) VSTREAM_CTL_READ_FD, CHECK_VAL(VSTREAM_CTL, int, (v)) +#define CA_VSTREAM_CTL_WRITE_FD(v) VSTREAM_CTL_WRITE_FD, CHECK_VAL(VSTREAM_CTL, int, (v)) +#define CA_VSTREAM_CTL_WAITPID_FN(v) VSTREAM_CTL_WAITPID_FN, CHECK_VAL(VSTREAM_CTL, VSTREAM_WAITPID_FN, (v)) +#define CA_VSTREAM_CTL_TIMEOUT(v) VSTREAM_CTL_TIMEOUT, CHECK_VAL(VSTREAM_CTL, int, (v)) +#define CA_VSTREAM_CTL_EXCEPT VSTREAM_CTL_EXCEPT +#define CA_VSTREAM_CTL_CONTEXT(v) VSTREAM_CTL_CONTEXT, CHECK_PTR(VSTREAM_CTL, void, (v)) +#ifdef F_DUPFD +#define CA_VSTREAM_CTL_DUPFD(v) VSTREAM_CTL_DUPFD, CHECK_VAL(VSTREAM_CTL, int, (v)) +#endif +#define CA_VSTREAM_CTL_BUFSIZE(v) VSTREAM_CTL_BUFSIZE, CHECK_VAL(VSTREAM_CTL, ssize_t, (v)) +#define CA_VSTREAM_CTL_SWAP_FD(v) VSTREAM_CTL_SWAP_FD, CHECK_PTR(VSTREAM_CTL, VSTREAM, (v)) +#define CA_VSTREAM_CTL_START_DEADLINE VSTREAM_CTL_START_DEADLINE +#define CA_VSTREAM_CTL_STOP_DEADLINE VSTREAM_CTL_STOP_DEADLINE +#define CA_VSTREAM_CTL_MIN_DATA_RATE(v) VSTREAM_CTL_MIN_DATA_RATE, CHECK_VAL(VSTREAM_CTL, int, (v)) + +CHECK_VAL_HELPER_DCL(VSTREAM_CTL, ssize_t); +CHECK_VAL_HELPER_DCL(VSTREAM_CTL, int); +CHECK_VAL_HELPER_DCL(VSTREAM_CTL, VSTREAM_WAITPID_FN); +CHECK_VAL_HELPER_DCL(VSTREAM_CTL, VSTREAM_RW_FN); +CHECK_PTR_HELPER_DCL(VSTREAM_CTL, void); +CHECK_PTR_HELPER_DCL(VSTREAM_CTL, VSTREAM); +CHECK_CPTR_HELPER_DCL(VSTREAM_CTL, char); + +extern VSTREAM *PRINTFLIKE(1, 2) vstream_printf(const char *,...); +extern VSTREAM *PRINTFLIKE(2, 3) vstream_fprintf(VSTREAM *, const char *,...); + +extern VSTREAM *vstream_popen(int,...); +extern int vstream_pclose(VSTREAM *); + +#define vstream_ispipe(vp) ((vp)->pid != 0) + +/* Legacy API: type-unchecked arguments, internal use. */ +#define VSTREAM_POPEN_END 0 /* terminator */ +#define VSTREAM_POPEN_COMMAND 1 /* command is string */ +#define VSTREAM_POPEN_ARGV 2 /* command is array */ +#define VSTREAM_POPEN_UID 3 /* privileges */ +#define VSTREAM_POPEN_GID 4 /* privileges */ +#define VSTREAM_POPEN_ENV 5 /* extra environment */ +#define VSTREAM_POPEN_SHELL 6 /* alternative shell */ +#define VSTREAM_POPEN_WAITPID_FN 7 /* child catcher, waitpid() compat. */ +#define VSTREAM_POPEN_EXPORT 8 /* exportable environment */ + +/* Safer API: type-checked arguments, external use. */ +#define CA_VSTREAM_POPEN_END VSTREAM_POPEN_END +#define CA_VSTREAM_POPEN_COMMAND(v) VSTREAM_POPEN_COMMAND, CHECK_CPTR(VSTREAM_PPN, char, (v)) +#define CA_VSTREAM_POPEN_ARGV(v) VSTREAM_POPEN_ARGV, CHECK_PPTR(VSTREAM_PPN, char, (v)) +#define CA_VSTREAM_POPEN_UID(v) VSTREAM_POPEN_UID, CHECK_VAL(VSTREAM_PPN, uid_t, (v)) +#define CA_VSTREAM_POPEN_GID(v) VSTREAM_POPEN_GID, CHECK_VAL(VSTREAM_PPN, gid_t, (v)) +#define CA_VSTREAM_POPEN_ENV(v) VSTREAM_POPEN_ENV, CHECK_PPTR(VSTREAM_PPN, char, (v)) +#define CA_VSTREAM_POPEN_SHELL(v) VSTREAM_POPEN_SHELL, CHECK_CPTR(VSTREAM_PPN, char, (v)) +#define CA_VSTREAM_POPEN_WAITPID_FN(v) VSTREAM_POPEN_WAITPID_FN, CHECK_VAL(VSTREAM_PPN, VSTREAM_WAITPID_FN, (v)) +#define CA_VSTREAM_POPEN_EXPORT(v) VSTREAM_POPEN_EXPORT, CHECK_PPTR(VSTREAM_PPN, char, (v)) + +CHECK_VAL_HELPER_DCL(VSTREAM_PPN, uid_t); +CHECK_VAL_HELPER_DCL(VSTREAM_PPN, gid_t); +CHECK_VAL_HELPER_DCL(VSTREAM_PPN, VSTREAM_WAITPID_FN); +CHECK_PPTR_HELPER_DCL(VSTREAM_PPN, char); +CHECK_CPTR_HELPER_DCL(VSTREAM_PPN, char); + +extern VSTREAM *vstream_vprintf(const char *, va_list); +extern VSTREAM *vstream_vfprintf(VSTREAM *, const char *, va_list); + +extern ssize_t vstream_peek(VSTREAM *); +extern ssize_t vstream_bufstat(VSTREAM *, int); + +#define VSTREAM_BST_FLAG_IN (1<<0) +#define VSTREAM_BST_FLAG_OUT (1<<1) +#define VSTREAM_BST_FLAG_PEND (1<<2) + +#define VSTREAM_BST_MASK_DIR (VSTREAM_BST_FLAG_IN | VSTREAM_BST_FLAG_OUT) +#define VSTREAM_BST_IN_PEND (VSTREAM_BST_FLAG_IN | VSTREAM_BST_FLAG_PEND) +#define VSTREAM_BST_OUT_PEND (VSTREAM_BST_FLAG_OUT | VSTREAM_BST_FLAG_PEND) + +#define vstream_peek(vp) vstream_bufstat((vp), VSTREAM_BST_IN_PEND) + +extern const char *vstream_peek_data(VSTREAM *); + + /* + * Exception handling. We use pointer to jmp_buf to avoid a lot of unused + * baggage for streams that don't need this functionality. + * + * XXX sigsetjmp()/siglongjmp() save and restore the signal mask which can + * avoid surprises in code that manipulates signals, but unfortunately some + * systems have bugs in their implementation. + */ +#ifdef NO_SIGSETJMP +#define vstream_setjmp(stream) setjmp((stream)->jbuf[0]) +#define vstream_longjmp(stream, val) longjmp((stream)->jbuf[0], (val)) +#else +#define vstream_setjmp(stream) sigsetjmp((stream)->jbuf[0], 1) +#define vstream_longjmp(stream, val) siglongjmp((stream)->jbuf[0], (val)) +#endif + + /* + * Tweaks and workarounds. + */ +extern int vstream_tweak_sock(VSTREAM *); +extern int vstream_tweak_tcp(VSTREAM *); + +#define vstream_flags(stream) ((const int) (stream)->buf.flags) + + /* + * Read/write VSTRING memory. + */ +#define vstream_memopen(string, flags) \ + vstream_memreopen((VSTREAM *) 0, (string), (flags)) +VSTREAM *vstream_memreopen(VSTREAM *, struct VSTRING *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/vstream_popen.c b/src/util/vstream_popen.c new file mode 100644 index 0000000..d00d49e --- /dev/null +++ b/src/util/vstream_popen.c @@ -0,0 +1,363 @@ +/*++ +/* NAME +/* vstream_popen 3 +/* SUMMARY +/* open stream to child process +/* SYNOPSIS +/* #include +/* +/* VSTREAM *vstream_popen(flags, key, value, ...) +/* int flags; +/* int key; +/* +/* int vstream_pclose(stream) +/* VSTREAM *stream; +/* DESCRIPTION +/* vstream_popen() opens a one-way or two-way stream to a user-specified +/* command, which is executed by a child process. The \fIflags\fR +/* argument is as with vstream_fopen(). The child's standard input and +/* standard output are redirected to the stream, which is based on a +/* socketpair or other suitable local IPC. vstream_popen() takes a list +/* of macros with zero or more arguments, terminated by +/* CA_VSTREAM_POPEN_END. The following is a listing of macros +/* with the expected argument type. +/* .RS +/* .IP "CA_VSTREAM_POPEN_COMMAND(const char *)" +/* Specifies the command to execute as a string. The string is +/* passed to the shell when it contains shell meta characters +/* or when it appears to be a shell built-in command, otherwise +/* the command is executed without invoking a shell. +/* One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified. +/* .IP "CA_VSTREAM_POPEN_ARGV(char **)" +/* The command is specified as an argument vector. This vector is +/* passed without further inspection to the \fIexecvp\fR() routine. +/* One of CA_VSTREAM_POPEN_COMMAND or VSTREAM_POPEN_ARGV must be specified. +/* See also the CA_VSTREAM_POPEN_SHELL attribute below. +/* .IP "CA_VSTREAM_POPEN_ENV(char **)" +/* Additional environment information, in the form of a null-terminated +/* list of name, value, name, value, ... elements. By default only the +/* command search path is initialized to _PATH_DEFPATH. +/* .IP "CA_VSTREAM_POPEN_EXPORT(char **)" +/* This argument is passed to clean_env(). +/* Null-terminated array of names of environment parameters +/* that can be exported. By default, everything is exported. +/* .IP "CA_VSTREAM_POPEN_UID(uid_t)" +/* The user ID to execute the command as. The user ID must be non-zero. +/* .IP "CA_VSTREAM_POPEN_GID(gid_t)" +/* The group ID to execute the command as. The group ID must be non-zero. +/* .IP "CA_VSTREAM_POPEN_SHELL(const char *)" +/* The shell to use when executing the command specified with +/* CA_VSTREAM_POPEN_COMMAND. This shell is invoked regardless of the +/* command content. +/* .IP "CA_VSTREAM_POPEN_WAITPID_FN(pid_t (*)(pid_t, WAIT_STATUS_T *, int))" +/* waitpid()-like function to reap the child exit status when +/* vstream_pclose() is called. +/* .RE +/* .PP +/* vstream_pclose() closes the named stream and returns the child +/* exit status. It is an error to specify a stream that was not +/* returned by vstream_popen() or that is no longer open. +/* DIAGNOSTICS +/* Panics: interface violations. Fatal errors: out of memory. +/* +/* vstream_popen() returns a null pointer in case of trouble. +/* The nature of the problem is specified via the \fIerrno\fR +/* global variable. +/* +/* vstream_pclose() returns -1 in case of trouble. +/* The nature of the problem is specified via the \fIerrno\fR +/* global variable. +/* SEE ALSO +/* vstream(3) light-weight buffered I/O +/* BUGS +/* The interface, stolen from popen()/pclose(), ignores errors +/* returned when the stream is closed, and does not distinguish +/* between exit status codes and kill signals. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#ifdef USE_PATHS_H +#include +#endif +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +typedef struct VSTREAM_POPEN_ARGS { + char **argv; + char *command; + uid_t uid; + gid_t gid; + int privileged; + char **env; + char **export; + char *shell; + VSTREAM_WAITPID_FN waitpid_fn; +} VSTREAM_POPEN_ARGS; + +/* vstream_parse_args - get arguments from variadic list */ + +static void vstream_parse_args(VSTREAM_POPEN_ARGS *args, va_list ap) +{ + const char *myname = "vstream_parse_args"; + int key; + + /* + * First, set the default values (on all non-zero entries) + */ + args->argv = 0; + args->command = 0; + args->uid = 0; + args->gid = 0; + args->privileged = 0; + args->env = 0; + args->export = 0; + args->shell = 0; + args->waitpid_fn = 0; + + /* + * Then, override the defaults with user-supplied inputs. + */ + while ((key = va_arg(ap, int)) != VSTREAM_POPEN_END) { + switch (key) { + case VSTREAM_POPEN_ARGV: + if (args->command != 0) + msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname); + args->argv = va_arg(ap, char **); + break; + case VSTREAM_POPEN_COMMAND: + if (args->argv != 0) + msg_panic("%s: got VSTREAM_POPEN_ARGV and VSTREAM_POPEN_COMMAND", myname); + args->command = va_arg(ap, char *); + break; + case VSTREAM_POPEN_UID: + args->privileged = 1; + args->uid = va_arg(ap, uid_t); + break; + case VSTREAM_POPEN_GID: + args->privileged = 1; + args->gid = va_arg(ap, gid_t); + break; + case VSTREAM_POPEN_ENV: + args->env = va_arg(ap, char **); + break; + case VSTREAM_POPEN_EXPORT: + args->export = va_arg(ap, char **); + break; + case VSTREAM_POPEN_SHELL: + args->shell = va_arg(ap, char *); + break; + case VSTREAM_POPEN_WAITPID_FN: + args->waitpid_fn = va_arg(ap, VSTREAM_WAITPID_FN); + break; + default: + msg_panic("%s: unknown key: %d", myname, key); + } + } + + if (args->command == 0 && args->argv == 0) + msg_panic("%s: missing VSTREAM_POPEN_ARGV or VSTREAM_POPEN_COMMAND", myname); + if (args->privileged != 0 && args->uid == 0) + msg_panic("%s: privileged uid", myname); + if (args->privileged != 0 && args->gid == 0) + msg_panic("%s: privileged gid", myname); +} + +/* vstream_popen - open stream to child process */ + +VSTREAM *vstream_popen(int flags,...) +{ + const char *myname = "vstream_popen"; + VSTREAM_POPEN_ARGS args; + va_list ap; + VSTREAM *stream; + int sockfd[2]; + int pid; + int fd; + ARGV *argv; + char **cpp; + + va_start(ap, flags); + vstream_parse_args(&args, ap); + va_end(ap); + + if (args.command == 0) + args.command = args.argv[0]; + + if (duplex_pipe(sockfd) < 0) + return (0); + + switch (pid = fork()) { + case -1: /* error */ + (void) close(sockfd[0]); + (void) close(sockfd[1]); + return (0); + case 0: /* child */ + (void) msg_cleanup((MSG_CLEANUP_FN) 0); + if (close(sockfd[1])) + msg_warn("close: %m"); + for (fd = 0; fd < 2; fd++) + if (sockfd[0] != fd) + if (DUP2(sockfd[0], fd) < 0) + msg_fatal("dup2: %m"); + if (sockfd[0] >= 2 && close(sockfd[0])) + msg_warn("close: %m"); + + /* + * Don't try to become someone else unless the user specified it. + */ + if (args.privileged) + set_ugid(args.uid, args.gid); + + /* + * Environment plumbing. Always reset the command search path. XXX + * That should probably be done by clean_env(). + */ + if (args.export) + clean_env(args.export); + if (setenv("PATH", _PATH_DEFPATH, 1)) + msg_fatal("%s: setenv: %m", myname); + if (args.env) + for (cpp = args.env; *cpp; cpp += 2) + if (setenv(cpp[0], cpp[1], 1)) + msg_fatal("setenv: %m"); + + /* + * Process plumbing. If possible, avoid running a shell. + */ + closelog(); + if (args.argv) { + execvp(args.argv[0], args.argv); + msg_fatal("%s: execvp %s: %m", myname, args.argv[0]); + } else if (args.shell && *args.shell) { + argv = argv_split(args.shell, CHARS_SPACE); + argv_add(argv, args.command, (char *) 0); + argv_terminate(argv); + execvp(argv->argv[0], argv->argv); + msg_fatal("%s: execvp %s: %m", myname, argv->argv[0]); + } else { + exec_command(args.command); + } + /* NOTREACHED */ + default: /* parent */ + if (close(sockfd[0])) + msg_warn("close: %m"); + stream = vstream_fdopen(sockfd[1], flags); + stream->waitpid_fn = args.waitpid_fn; + stream->pid = pid; + return (stream); + } +} + +/* vstream_pclose - close stream to child process */ + +int vstream_pclose(VSTREAM *stream) +{ + pid_t saved_pid = stream->pid; + VSTREAM_WAITPID_FN saved_waitpid_fn = stream->waitpid_fn; + pid_t pid; + WAIT_STATUS_T wait_status; + + /* + * Close the pipe. Don't trigger an alarm in vstream_fclose(). + */ + if (saved_pid == 0) + msg_panic("vstream_pclose: stream has no process"); + stream->pid = 0; + vstream_fclose(stream); + + /* + * Reap the child exit status. + */ + do { + if (saved_waitpid_fn != 0) + pid = saved_waitpid_fn(saved_pid, &wait_status, 0); + else + pid = waitpid(saved_pid, &wait_status, 0); + } while (pid == -1 && errno == EINTR); + return (pid == -1 ? -1 : + WIFSIGNALED(wait_status) ? WTERMSIG(wait_status) : + WEXITSTATUS(wait_status)); +} + +#ifdef TEST + +#include +#include +#include + + /* + * Test program. Run a command and copy lines one by one. + */ +int main(int argc, char **argv) +{ + VSTRING *buf = vstring_alloc(100); + VSTREAM *stream; + int status; + + /* + * Sanity check. + */ + if (argc < 2) + msg_fatal("usage: %s 'command'", argv[0]); + + /* + * Open stream to child process. + */ + if ((stream = vstream_popen(O_RDWR, + VSTREAM_POPEN_ARGV, argv + 1, + VSTREAM_POPEN_END)) == 0) + msg_fatal("vstream_popen: %m"); + + /* + * Copy loop, one line at a time. + */ + while (vstring_fgets(buf, stream) != 0) { + if (vstream_fwrite(VSTREAM_OUT, vstring_str(buf), VSTRING_LEN(buf)) + != VSTRING_LEN(buf)) + msg_fatal("vstream_fwrite: %m"); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("vstream_fflush: %m"); + if (vstring_fgets(buf, VSTREAM_IN) == 0) + break; + if (vstream_fwrite(stream, vstring_str(buf), VSTRING_LEN(buf)) + != VSTRING_LEN(buf)) + msg_fatal("vstream_fwrite: %m"); + } + + /* + * Cleanup. + */ + vstring_free(buf); + if ((status = vstream_pclose(stream)) != 0) + msg_warn("exit status: %d", status); + + exit(status); +} + +#endif diff --git a/src/util/vstream_test.in b/src/util/vstream_test.in new file mode 100644 index 0000000..b6687c8 --- /dev/null +++ b/src/util/vstream_test.in @@ -0,0 +1,3 @@ +abcdef +ghijkl +mnopqr diff --git a/src/util/vstream_test.ref b/src/util/vstream_test.ref new file mode 100644 index 0000000..eaa9952 --- /dev/null +++ b/src/util/vstream_test.ref @@ -0,0 +1,41 @@ +buffer size test: copy text with 1 buffer size, ignore requests to shrink +abcdef +actual read/write buffer sizes: 1/1 + +buffer size test: copy text with 2 buffer size, ignore requests to shrink +ghijkl +actual read/write buffer sizes: 2/2 + +buffer size test: copy text with 1 buffer size, ignore requests to shrink +mnopqr +actual read/write buffer sizes: 2/2 + +formatting test: print a number +1234567890 + +memory stream test prep: prefill the VSTRING +VSTRING content length: 8/8, content: 01234567 + +memory stream test: open the VSTRING for writing, overwrite, close +initial memory VSTREAM write offset: 0/8 +final memory VSTREAM write offset: 5/8 +VSTRING content length: 5/8, content: hallo + +memory stream test: open the VSTRING for append, write multiple, then overwrite 1 +initial memory VSTREAM write offset: 5/8 +final memory VSTREAM write offset: 11/16 +VSTRING content length: 11/16, content: hallo world + +replace second character and close +VSTRING content length: 11/16, content: hello world + +memory stream test: open VSTRING for reading, then read +initial memory VSTREAM read offset: 0/11 +reading memory VSTREAM: hello world +final memory VSTREAM read offset: 11/11 +seeking to offset 12 should work: PASS +VSTREAM_GETC should return VSTREAM_EOF +PASS +final memory VSTREAM read offset: 12/11 +VSTRING content length: 11/16, content: hello world + diff --git a/src/util/vstream_tweak.c b/src/util/vstream_tweak.c new file mode 100644 index 0000000..7100bc6 --- /dev/null +++ b/src/util/vstream_tweak.c @@ -0,0 +1,168 @@ +/*++ +/* NAME +/* vstream_tweak 3 +/* SUMMARY +/* performance tweaks +/* SYNOPSIS +/* #include +/* +/* VSTREAM *vstream_tweak_sock(stream) +/* VSTREAM *stream; +/* +/* VSTREAM *vstream_tweak_tcp(stream) +/* VSTREAM *stream; +/* DESCRIPTION +/* vstream_tweak_sock() does a best effort to boost your +/* network performance on the specified generic stream. +/* +/* vstream_tweak_tcp() does a best effort to boost your +/* Internet performance on the specified TCP stream. +/* +/* Arguments: +/* .IP stream +/* The stream being boosted. +/* DIAGNOSTICS +/* Panics: interface violations. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#ifdef HAS_IPV6 +#define SOCKADDR_STORAGE struct sockaddr_storage +#else +#define SOCKADDR_STORAGE struct sockaddr +#endif + +/* vstream_tweak_sock - boost your generic network performance */ + +int vstream_tweak_sock(VSTREAM *fp) +{ + SOCKADDR_STORAGE ss; + struct sockaddr *sa = (struct sockaddr *) &ss; + SOCKADDR_SIZE sa_length = sizeof(ss); + int ret; + + /* + * If the caller doesn't know if this socket is AF_LOCAL, AF_INET, etc., + * figure it out for them. + */ + if ((ret = getsockname(vstream_fileno(fp), sa, &sa_length)) >= 0) { + switch (sa->sa_family) { +#ifdef AF_INET6 + case AF_INET6: +#endif + case AF_INET: + ret = vstream_tweak_tcp(fp); + break; + } + } + return (ret); +} + +/* vstream_tweak_tcp - boost your TCP performance */ + +int vstream_tweak_tcp(VSTREAM *fp) +{ + const char *myname = "vstream_tweak_tcp"; + int mss = 0; + SOCKOPT_SIZE mss_len = sizeof(mss); + int err; + + /* + * Avoid Nagle delays when VSTREAM buffers are smaller than the MSS. + * + * Forcing TCP_NODELAY to be "always on" would hurt performance in the + * common case where VSTREAM buffers are larger than the MSS. + * + * Instead we ask the kernel what the current MSS is, and take appropriate + * action. Linux <= 2.2 getsockopt(TCP_MAXSEG) always returns zero (or + * whatever value was stored last with setsockopt()). + * + * Some ancient FreeBSD kernels don't report 'host unreachable' errors with + * getsockopt(SO_ERROR), and then treat getsockopt(TCP_MAXSEG) as a NOOP, + * leaving the mss parameter value unchanged. To work around these two + * getsockopt() bugs we set mss = 0, which is a harmless value. + */ + if ((err = getsockopt(vstream_fileno(fp), IPPROTO_TCP, TCP_MAXSEG, + (void *) &mss, &mss_len)) < 0 + && errno != ECONNRESET) { + msg_warn("%s: getsockopt TCP_MAXSEG: %m", myname); + return (err); + } + if (msg_verbose) + msg_info("%s: TCP_MAXSEG %d", myname, mss); + + /* + * Fix for recent Postfix versions: increase the VSTREAM buffer size if + * it is smaller than the MSS. Note: the MSS may change when the route + * changes and IP path MTU discovery is turned on, so we choose a + * somewhat larger buffer. + * + * Note: as of 20120527, the CA_VSTREAM_CTL_BUFSIZE request can reduce the + * stream buffer size to less than VSTREAM_BUFSIZE, when the request is + * made before the first stream read or write operation. We don't want to + * reduce the buffer size. + * + * As of 20190820 we increase the mss size multiplier from 2x to 4x, because + * some LINUX loopback TCP stacks report an MSS of 21845 which is 3x + * smaller than the MTU of 65536. Even with a VSTREAM buffer 2x the + * reported MSS size, performance would suck due to Nagle or delayed ACK + * delays. + */ +#define EFF_BUFFER_SIZE(fp) (vstream_req_bufsize(fp) ? \ + vstream_req_bufsize(fp) : VSTREAM_BUFSIZE) + +#ifdef CA_VSTREAM_CTL_BUFSIZE + if (mss > EFF_BUFFER_SIZE(fp) / 4) { + if (mss < INT_MAX / 2) + mss *= 2; + if (mss < INT_MAX / 2) + mss *= 2; + vstream_control(fp, + CA_VSTREAM_CTL_BUFSIZE(mss), + CA_VSTREAM_CTL_END); + } + + /* + * Workaround for older Postfix versions: turn on TCP_NODELAY if the + * VSTREAM buffer size is smaller than the MSS. + */ +#else + if (mss > VSTREAM_BUFSIZE) { + int nodelay = 1; + + if ((err = setsockopt(vstream_fileno(fp), IPPROTO_TCP, TCP_NODELAY, + (void *) &nodelay, sizeof(nodelay))) < 0 + && errno != ECONNRESET) + msg_warn("%s: setsockopt TCP_NODELAY: %m", myname); + } +#endif + return (err); +} diff --git a/src/util/vstring.c b/src/util/vstring.c new file mode 100644 index 0000000..43897eb --- /dev/null +++ b/src/util/vstring.c @@ -0,0 +1,719 @@ +/*++ +/* NAME +/* vstring 3 +/* SUMMARY +/* arbitrary-length string manager +/* SYNOPSIS +/* #include +/* +/* VSTRING *vstring_alloc(len) +/* ssize_t len; +/* +/* vstring_ctl(vp, type, value, ..., VSTRING_CTL_END) +/* VSTRING *vp; +/* int type; +/* +/* VSTRING *vstring_free(vp) +/* VSTRING *vp; +/* +/* char *vstring_str(vp) +/* VSTRING *vp; +/* +/* ssize_t VSTRING_LEN(vp) +/* VSTRING *vp; +/* +/* char *vstring_end(vp) +/* VSTRING *vp; +/* +/* void VSTRING_ADDCH(vp, ch) +/* VSTRING *vp; +/* int ch; +/* +/* int VSTRING_SPACE(vp, len) +/* VSTRING *vp; +/* ssize_t len; +/* +/* ssize_t vstring_avail(vp) +/* VSTRING *vp; +/* +/* VSTRING *vstring_truncate(vp, len) +/* VSTRING *vp; +/* ssize_t len; +/* +/* VSTRING *vstring_set_payload_size(vp, len) +/* VSTRING *vp; +/* ssize_t len; +/* +/* void VSTRING_RESET(vp) +/* VSTRING *vp; +/* +/* void VSTRING_TERMINATE(vp) +/* VSTRING *vp; +/* +/* void VSTRING_SKIP(vp) +/* VSTRING *vp; +/* +/* VSTRING *vstring_strcpy(vp, src) +/* VSTRING *vp; +/* const char *src; +/* +/* VSTRING *vstring_strncpy(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* ssize_t len; +/* +/* VSTRING *vstring_strcat(vp, src) +/* VSTRING *vp; +/* const char *src; +/* +/* VSTRING *vstring_strncat(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* ssize_t len; +/* +/* VSTRING *vstring_memcpy(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* ssize_t len; +/* +/* VSTRING *vstring_memcat(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* ssize_t len; +/* +/* char *vstring_memchr(vp, ch) +/* VSTRING *vp; +/* int ch; +/* +/* VSTRING *vstring_insert(vp, start, src, len) +/* VSTRING *vp; +/* ssize_t start; +/* const char *src; +/* ssize_t len; +/* +/* VSTRING *vstring_prepend(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* ssize_t len; +/* +/* VSTRING *vstring_sprintf(vp, format, ...) +/* VSTRING *vp; +/* const char *format; +/* +/* VSTRING *vstring_sprintf_append(vp, format, ...) +/* VSTRING *vp; +/* const char *format; +/* +/* VSTRING *vstring_sprintf_prepend(vp, format, ...) +/* VSTRING *vp; +/* const char *format; +/* +/* VSTRING *vstring_vsprintf(vp, format, ap) +/* VSTRING *vp; +/* const char *format; +/* va_list ap; +/* +/* VSTRING *vstring_vsprintf_append(vp, format, ap) +/* VSTRING *vp; +/* const char *format; +/* va_list ap; +/* AUXILIARY FUNCTIONS +/* char *vstring_export(vp) +/* VSTRING *vp; +/* +/* VSTRING *vstring_import(str) +/* char *str; +/* DESCRIPTION +/* The functions and macros in this module implement arbitrary-length +/* strings and common operations on those strings. The strings do not +/* need to be null terminated and may contain arbitrary binary data. +/* The strings manage their own memory and grow automatically when full. +/* The optional string null terminator does not add to the string length. +/* +/* vstring_alloc() allocates storage for a variable-length string +/* of at least "len" bytes. The minimal length is 1. The result +/* is a null-terminated string of length zero. +/* +/* vstring_ctl() gives additional control over VSTRING behavior. +/* The function takes a VSTRING pointer and a list of zero or +/* more macros with zer or more arguments, terminated with +/* CA_VSTRING_CTL_END which has none. +/* .IP "CA_VSTRING_CTL_MAXLEN(ssize_t len)" +/* Specifies a hard upper limit on a string's length. When the +/* length would be exceeded, the program simulates a memory +/* allocation problem (i.e. it terminates through msg_fatal()). +/* This functionality is currently unimplemented. +/* .IP "CA_VSTRING_CTL_EXACT (no argument)" +/* Allocate the requested amounts, instead of rounding up. +/* This should be used for tests only. +/* .IP "CA_VSTRING_CTL_END (no argument)" +/* Specifies the end of the argument list. Forgetting to terminate +/* the argument list may cause the program to crash. +/* .PP +/* VSTRING_SPACE() ensures that the named string has room for +/* "len" more characters. VSTRING_SPACE() is an unsafe macro +/* that either returns zero or never returns. +/* +/* vstring_avail() returns the number of bytes that can be placed +/* into the buffer before the buffer would need to grow. +/* +/* vstring_free() reclaims storage for a variable-length string. +/* It conveniently returns a null pointer. +/* +/* vstring_str() is a macro that returns the string value +/* of a variable-length string. It is a safe macro that +/* evaluates its argument only once. +/* +/* VSTRING_LEN() is a macro that returns the current length of +/* its argument (i.e. the distance from the start of the string +/* to the current write position). VSTRING_LEN() is an unsafe macro +/* that evaluates its argument more than once. +/* +/* vstring_end() is a macro that returns the current write position of +/* its argument. It is a safe macro that evaluates its argument only once. +/* +/* VSTRING_ADDCH() adds a character to a variable-length string +/* and extends the string if it fills up. \fIvs\fP is a pointer +/* to a VSTRING structure; \fIch\fP the character value to be written. +/* The result is the written character. +/* Note that VSTRING_ADDCH() is an unsafe macro that evaluates some +/* arguments more than once. The result is NOT null-terminated. +/* +/* vstring_truncate() truncates the named string to the specified +/* length. If length is negative, the trailing portion is kept. +/* The operation has no effect when the string is shorter. +/* The string is not null-terminated. +/* +/* vstring_set_payload_size() sets the number of 'used' bytes +/* in the named buffer's metadata. This determines the buffer +/* write position and the VSTRING_LEN() result. The payload +/* size must be within the closed range [0, number of allocated +/* bytes]. The typical usage is to request buffer space with +/* VSTRING_SPACE(), to use some non-VSTRING operations to write +/* to the buffer, and to call vstring_set_payload_size() to +/* update buffer metadata, perhaps followed by VSTRING_TERMINATE(). +/* +/* VSTRING_RESET() is a macro that resets the write position of its +/* string argument to the very beginning. Note that VSTRING_RESET() +/* is an unsafe macro that evaluates some arguments more than once. +/* The result is NOT null-terminated. +/* +/* VSTRING_TERMINATE() null-terminates its string argument. +/* VSTRING_TERMINATE() is an unsafe macro that evaluates some +/* arguments more than once. +/* VSTRING_TERMINATE() does not return an interesting result. +/* +/* VSTRING_SKIP() is a macro that moves the write position to the first +/* null byte after the current write position. VSTRING_SKIP() is an unsafe +/* macro that evaluates some arguments more than once. +/* +/* vstring_strcpy() copies a null-terminated string to a variable-length +/* string. \fIsrc\fP provides the data to be copied; \fIvp\fP is the +/* target and result value. The result is null-terminated. +/* +/* vstring_strncpy() copies at most \fIlen\fR characters. Otherwise it is +/* identical to vstring_strcpy(). +/* +/* vstring_strcat() appends a null-terminated string to a variable-length +/* string. \fIsrc\fP provides the data to be copied; \fIvp\fP is the +/* target and result value. The result is null-terminated. +/* +/* vstring_strncat() copies at most \fIlen\fR characters. Otherwise it is +/* identical to vstring_strcat(). +/* +/* vstring_memcpy() copies \fIlen\fR bytes to a variable-length string. +/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the +/* target and result value. The result is not null-terminated. +/* +/* vstring_memcat() appends \fIlen\fR bytes to a variable-length string. +/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the +/* target and result value. The result is not null-terminated. +/* +/* vstring_memchr() locates a byte in a variable-length string. +/* +/* vstring_insert() inserts a buffer content into a variable-length +/* string at the specified start position. The result is +/* null-terminated. +/* +/* vstring_prepend() prepends a buffer content to a variable-length +/* string. The result is null-terminated. +/* +/* vstring_sprintf() produces a formatted string according to its +/* \fIformat\fR argument. See vstring_vsprintf() for details. +/* +/* vstring_sprintf_append() is like vstring_sprintf(), but appends +/* to the end of the result buffer. +/* +/* vstring_sprintf_append() is like vstring_sprintf(), but prepends +/* to the beginning of the result buffer. +/* +/* vstring_vsprintf() returns a null-terminated string according to +/* the \fIformat\fR argument. It understands the s, c, d, u, +/* o, x, X, p, e, f and g format types, the l modifier, field width +/* and precision, sign, and null or space padding. This module +/* can format strings as large as available memory permits. +/* +/* vstring_vsprintf_append() is like vstring_vsprintf(), but appends +/* to the end of the result buffer. +/* +/* In addition to stdio-like format specifiers, vstring_vsprintf() +/* recognizes %m and expands it to the corresponding errno text. +/* +/* vstring_export() extracts the string value from a VSTRING. +/* The VSTRING is destroyed. The result should be passed to myfree(). +/* +/* vstring_import() takes a `bare' string and converts it to +/* a VSTRING. The string argument must be obtained from mymalloc(). +/* The string argument is not copied. +/* DIAGNOSTICS +/* Fatal errors: memory allocation failure. +/* BUGS +/* Auto-resizing may change the address of the string data in +/* a vstring structure. Beware of dangling pointers. +/* HISTORY +/* .ad +/* .fi +/* A vstring module appears in the UNPROTO software by Wietse Venema. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System libraries. */ + +#include +#include +#include /* 44BSD stdarg.h uses abort() */ +#include +#include + +/* Utility library. */ + +#define VSTRING_INTERNAL + +#include "mymalloc.h" +#include "msg.h" +#include "vbuf_print.h" +#include "vstring.h" + +/* vstring_extend - variable-length string buffer extension policy */ + +static void vstring_extend(VBUF *bp, ssize_t incr) +{ + size_t used = bp->ptr - bp->data; + ssize_t new_len; + + /* + * Note: vp->vbuf.len is the current buffer size (both on entry and on + * exit of this routine). We round up the increment size to the buffer + * size to avoid silly little buffer increments. With really large + * strings we might want to abandon the length doubling strategy, and go + * to fixed increments. + * + * The length overflow tests here and in vstring_alloc() should protect us + * against all length overflow problems within vstring library routines. + * + * Safety net: add a gratuitous null terminator so that C-style string + * operations won't scribble past the end. + */ + if ((bp->flags & VSTRING_FLAG_EXACT) == 0 && bp->len > incr) + incr = bp->len; + if (bp->len > SSIZE_T_MAX - incr - 1) + msg_fatal("vstring_extend: length overflow"); + new_len = bp->len + incr; + bp->data = (unsigned char *) myrealloc((void *) bp->data, new_len + 1); + bp->data[new_len] = 0; + bp->len = new_len; + bp->ptr = bp->data + used; + bp->cnt = bp->len - used; +} + +/* vstring_buf_get_ready - vbuf callback for read buffer empty condition */ + +static int vstring_buf_get_ready(VBUF *unused_buf) +{ + return (VBUF_EOF); /* be VSTREAM-friendly */ +} + +/* vstring_buf_put_ready - vbuf callback for write buffer full condition */ + +static int vstring_buf_put_ready(VBUF *bp) +{ + vstring_extend(bp, 1); + return (0); +} + +/* vstring_buf_space - vbuf callback to reserve space */ + +static int vstring_buf_space(VBUF *bp, ssize_t len) +{ + ssize_t need; + + if (len < 0) + msg_panic("vstring_buf_space: bad length %ld", (long) len); + if ((need = len - bp->cnt) > 0) + vstring_extend(bp, need); + return (0); +} + +/* vstring_alloc - create variable-length string */ + +VSTRING *vstring_alloc(ssize_t len) +{ + VSTRING *vp; + + /* + * Safety net: add a gratuitous null terminator so that C-style string + * operations won't scribble past the end. + */ + if (len < 1 || len > SSIZE_T_MAX - 1) + msg_panic("vstring_alloc: bad length %ld", (long) len); + vp = (VSTRING *) mymalloc(sizeof(*vp)); + vp->vbuf.flags = 0; + vp->vbuf.len = 0; + vp->vbuf.data = (unsigned char *) mymalloc(len + 1); + vp->vbuf.data[len] = 0; + vp->vbuf.len = len; + VSTRING_RESET(vp); + vp->vbuf.data[0] = 0; + vp->vbuf.get_ready = vstring_buf_get_ready; + vp->vbuf.put_ready = vstring_buf_put_ready; + vp->vbuf.space = vstring_buf_space; + return (vp); +} + +/* vstring_free - destroy variable-length string */ + +VSTRING *vstring_free(VSTRING *vp) +{ + if (vp->vbuf.data) + myfree((void *) vp->vbuf.data); + myfree((void *) vp); + return (0); +} + +/* vstring_ctl - modify memory management policy */ + +void vstring_ctl(VSTRING *vp,...) +{ + va_list ap; + int code; + + va_start(ap, vp); + while ((code = va_arg(ap, int)) != VSTRING_CTL_END) { + switch (code) { + default: + msg_panic("vstring_ctl: unknown code: %d", code); + case VSTRING_CTL_EXACT: + vp->vbuf.flags |= VSTRING_FLAG_EXACT; + break; + } + } + va_end(ap); +} + +/* vstring_truncate - truncate string */ + +VSTRING *vstring_truncate(VSTRING *vp, ssize_t len) +{ + ssize_t move; + + if (len < 0) { + len = (-len); + if ((move = VSTRING_LEN(vp) - len) > 0) + memmove(vstring_str(vp), vstring_str(vp) + move, len); + } + if (len < VSTRING_LEN(vp)) + VSTRING_AT_OFFSET(vp, len); + return (vp); +} + +/* vstring_set_payload_size - public version of VSTRING_AT_OFFSET */ + +VSTRING *vstring_set_payload_size(VSTRING *vp, ssize_t len) +{ + if (len < 0 || len > vp->vbuf.len) + msg_panic("vstring_set_payload_size: invalid offset: %ld", (long) len); + if (vp->vbuf.data[vp->vbuf.len] != 0) + msg_panic("vstring_set_payload_size: no safety null byte"); + VSTRING_AT_OFFSET(vp, len); + return (vp); +} + +/* vstring_strcpy - copy string */ + +VSTRING *vstring_strcpy(VSTRING *vp, const char *src) +{ + VSTRING_RESET(vp); + + while (*src) { + VSTRING_ADDCH(vp, *src); + src++; + } + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_strncpy - copy string of limited length */ + +VSTRING *vstring_strncpy(VSTRING *vp, const char *src, ssize_t len) +{ + VSTRING_RESET(vp); + + while (len-- > 0 && *src) { + VSTRING_ADDCH(vp, *src); + src++; + } + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_strcat - append string */ + +VSTRING *vstring_strcat(VSTRING *vp, const char *src) +{ + while (*src) { + VSTRING_ADDCH(vp, *src); + src++; + } + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_strncat - append string of limited length */ + +VSTRING *vstring_strncat(VSTRING *vp, const char *src, ssize_t len) +{ + while (len-- > 0 && *src) { + VSTRING_ADDCH(vp, *src); + src++; + } + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_memcpy - copy buffer of limited length */ + +VSTRING *vstring_memcpy(VSTRING *vp, const char *src, ssize_t len) +{ + VSTRING_RESET(vp); + + VSTRING_SPACE(vp, len); + memcpy(vstring_str(vp), src, len); + VSTRING_AT_OFFSET(vp, len); + return (vp); +} + +/* vstring_memcat - append buffer of limited length */ + +VSTRING *vstring_memcat(VSTRING *vp, const char *src, ssize_t len) +{ + VSTRING_SPACE(vp, len); + memcpy(vstring_end(vp), src, len); + len += VSTRING_LEN(vp); + VSTRING_AT_OFFSET(vp, len); + return (vp); +} + +/* vstring_memchr - locate byte in buffer */ + +char *vstring_memchr(VSTRING *vp, int ch) +{ + unsigned char *cp; + + for (cp = (unsigned char *) vstring_str(vp); cp < (unsigned char *) vstring_end(vp); cp++) + if (*cp == ch) + return ((char *) cp); + return (0); +} + +/* vstring_insert - insert text into string */ + +VSTRING *vstring_insert(VSTRING *vp, ssize_t start, const char *buf, ssize_t len) +{ + ssize_t new_len; + + /* + * Sanity check. + */ + if (start < 0 || start >= VSTRING_LEN(vp)) + msg_panic("vstring_insert: bad start %ld", (long) start); + if (len < 0) + msg_panic("vstring_insert: bad length %ld", (long) len); + + /* + * Move the existing content and copy the new content. + */ + new_len = VSTRING_LEN(vp) + len; + VSTRING_SPACE(vp, len); + memmove(vstring_str(vp) + start + len, vstring_str(vp) + start, + VSTRING_LEN(vp) - start); + memcpy(vstring_str(vp) + start, buf, len); + VSTRING_AT_OFFSET(vp, new_len); + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_prepend - prepend text to string */ + +VSTRING *vstring_prepend(VSTRING *vp, const char *buf, ssize_t len) +{ + ssize_t new_len; + + /* + * Sanity check. + */ + if (len < 0) + msg_panic("vstring_prepend: bad length %ld", (long) len); + + /* + * Move the existing content and copy the new content. + */ + new_len = VSTRING_LEN(vp) + len; + VSTRING_SPACE(vp, len); + memmove(vstring_str(vp) + len, vstring_str(vp), VSTRING_LEN(vp)); + memcpy(vstring_str(vp), buf, len); + VSTRING_AT_OFFSET(vp, new_len); + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_export - VSTRING to bare string */ + +char *vstring_export(VSTRING *vp) +{ + char *cp; + + cp = (char *) vp->vbuf.data; + vp->vbuf.data = 0; + myfree((void *) vp); + return (cp); +} + +/* vstring_import - bare string to vstring */ + +VSTRING *vstring_import(char *str) +{ + VSTRING *vp; + ssize_t len; + + vp = (VSTRING *) mymalloc(sizeof(*vp)); + len = strlen(str); + vp->vbuf.flags = 0; + vp->vbuf.len = 0; + vp->vbuf.data = (unsigned char *) str; + vp->vbuf.len = len + 1; + VSTRING_AT_OFFSET(vp, len); + vp->vbuf.get_ready = vstring_buf_get_ready; + vp->vbuf.put_ready = vstring_buf_put_ready; + vp->vbuf.space = vstring_buf_space; + return (vp); +} + +/* vstring_sprintf - formatted string */ + +VSTRING *vstring_sprintf(VSTRING *vp, const char *format,...) +{ + va_list ap; + + va_start(ap, format); + vp = vstring_vsprintf(vp, format, ap); + va_end(ap); + return (vp); +} + +/* vstring_vsprintf - format string, vsprintf-like interface */ + +VSTRING *vstring_vsprintf(VSTRING *vp, const char *format, va_list ap) +{ + VSTRING_RESET(vp); + vbuf_print(&vp->vbuf, format, ap); + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_sprintf_append - append formatted string */ + +VSTRING *vstring_sprintf_append(VSTRING *vp, const char *format,...) +{ + va_list ap; + + va_start(ap, format); + vp = vstring_vsprintf_append(vp, format, ap); + va_end(ap); + return (vp); +} + +/* vstring_vsprintf_append - format + append string, vsprintf-like interface */ + +VSTRING *vstring_vsprintf_append(VSTRING *vp, const char *format, va_list ap) +{ + vbuf_print(&vp->vbuf, format, ap); + VSTRING_TERMINATE(vp); + return (vp); +} + +/* vstring_sprintf_prepend - format + prepend string, vsprintf-like interface */ + +VSTRING *vstring_sprintf_prepend(VSTRING *vp, const char *format,...) +{ + va_list ap; + ssize_t old_len = VSTRING_LEN(vp); + ssize_t result_len; + + /* Construct: old|new|free */ + va_start(ap, format); + vp = vstring_vsprintf_append(vp, format, ap); + va_end(ap); + result_len = VSTRING_LEN(vp); + + /* Construct: old|new|old|free */ + VSTRING_SPACE(vp, old_len); + vstring_memcat(vp, vstring_str(vp), old_len); + + /* Construct: new|old|free */ + memmove(vstring_str(vp), vstring_str(vp) + old_len, result_len); + VSTRING_AT_OFFSET(vp, result_len); + VSTRING_TERMINATE(vp); + return (vp); +} + +#ifdef TEST + + /* + * Test program - concatenate all command-line arguments into one string. + */ +#include + +int main(int argc, char **argv) +{ + VSTRING *vp = vstring_alloc(1); + int n; + + /* + * Report the location of the gratuitous null terminator. + */ + for (n = 1; n <= 5; n++) { + VSTRING_ADDCH(vp, 'x'); + printf("payload/buffer size %d/%ld, strlen() %ld\n", + n, (long) (vp)->vbuf.len, (long) strlen(vstring_str(vp))); + } + + VSTRING_RESET(vp); + while (argc-- > 0) { + vstring_strcat(vp, *argv++); + vstring_strcat(vp, "."); + } + printf("argv concatenated: %s\n", vstring_str(vp)); + vstring_free(vp); + return (0); +} + +#endif diff --git a/src/util/vstring.h b/src/util/vstring.h new file mode 100644 index 0000000..49fd960 --- /dev/null +++ b/src/util/vstring.h @@ -0,0 +1,125 @@ +#ifndef _VSTRING_H_INCLUDED_ +#define _VSTRING_H_INCLUDED_ + +/*++ +/* NAME +/* vstring 3h +/* SUMMARY +/* arbitrary-length string manager +/* SYNOPSIS +/* #include "vstring.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include + + /* + * We can't allow bare VBUFs in the interface, because VSTRINGs have a + * specific initialization and destruction sequence. + */ +typedef struct VSTRING { + VBUF vbuf; +} VSTRING; + +extern VSTRING *vstring_alloc(ssize_t); +extern void vstring_ctl(VSTRING *,...); +extern VSTRING *vstring_truncate(VSTRING *, ssize_t); +extern VSTRING *vstring_set_payload_size(VSTRING *, ssize_t); +extern VSTRING *vstring_free(VSTRING *); +extern VSTRING *vstring_strcpy(VSTRING *, const char *); +extern VSTRING *vstring_strncpy(VSTRING *, const char *, ssize_t); +extern VSTRING *vstring_strcat(VSTRING *, const char *); +extern VSTRING *vstring_strncat(VSTRING *, const char *, ssize_t); +extern VSTRING *vstring_memcpy(VSTRING *, const char *, ssize_t); +extern VSTRING *vstring_memcat(VSTRING *, const char *, ssize_t); +extern char *vstring_memchr(VSTRING *, int); +extern VSTRING *vstring_insert(VSTRING *, ssize_t, const char *, ssize_t); +extern VSTRING *vstring_prepend(VSTRING *, const char *, ssize_t); +extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf(VSTRING *, const char *,...); +extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf_append(VSTRING *, const char *,...); +extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf_prepend(VSTRING *, const char *,...); +extern char *vstring_export(VSTRING *); +extern VSTRING *vstring_import(char *); + +/* Legacy API: constant plus type-unchecked argument. */ +#define VSTRING_CTL_EXACT 2 +#define VSTRING_CTL_END 0 + +/* Safer API: type-checked arguments. */ +#define CA_VSTRING_CTL_END VSTRING_CTL_END +#define CA_VSTRING_CTL_EXACT VSTRING_CTL_EXACT + +CHECK_VAL_HELPER_DCL(VSTRING_CTL, ssize_t); + +/* Flags 24..31 are reserved for VSTRING. */ +#define VSTRING_FLAG_EXACT (1<<24) /* exact allocation for tests */ +#define VSTRING_FLAG_MASK (255 << 24) + + /* + * Macros. Unsafe macros have UPPERCASE names. + */ +#define VSTRING_SPACE(vp, len) ((vp)->vbuf.space(&(vp)->vbuf, (len))) +#define vstring_str(vp) ((char *) (vp)->vbuf.data) +#define VSTRING_LEN(vp) ((ssize_t) ((vp)->vbuf.ptr - (vp)->vbuf.data)) +#define vstring_end(vp) ((char *) (vp)->vbuf.ptr) +#define VSTRING_TERMINATE(vp) do { \ + *(vp)->vbuf.ptr = 0; \ + } while (0) +#define VSTRING_RESET(vp) do { \ + (vp)->vbuf.ptr = (vp)->vbuf.data; \ + (vp)->vbuf.cnt = (vp)->vbuf.len; \ + } while (0) +#define VSTRING_ADDCH(vp, ch) VBUF_PUT(&(vp)->vbuf, ch) +#define VSTRING_SKIP(vp) do { \ + while ((vp)->vbuf.cnt > 0 && *(vp)->vbuf.ptr) \ + (vp)->vbuf.ptr++, (vp)->vbuf.cnt--; \ + } while (0) +#define vstring_avail(vp) ((vp)->vbuf.cnt) + + /* + * The following macro is not part of the public interface, because it can + * really screw up a buffer by positioning past allocated memory. + */ +#ifdef VSTRING_INTERNAL +#define VSTRING_AT_OFFSET(vp, offset) do { \ + (vp)->vbuf.ptr = (vp)->vbuf.data + (offset); \ + (vp)->vbuf.cnt = (vp)->vbuf.len - (offset); \ + } while (0) +#endif + +extern VSTRING *vstring_vsprintf(VSTRING *, const char *, va_list); +extern VSTRING *vstring_vsprintf_append(VSTRING *, const char *, va_list); + +/* BUGS +/* Auto-resizing may change the address of the string data in +/* a vstring structure. Beware of dangling pointers. +/* HISTORY +/* .ad +/* .fi +/* A vstring module appears in the UNPROTO software by Wietse Venema. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/vstring_test.ref b/src/util/vstring_test.ref new file mode 100644 index 0000000..54c54cf --- /dev/null +++ b/src/util/vstring_test.ref @@ -0,0 +1,6 @@ +payload/buffer size 1/1, strlen() 1 +payload/buffer size 2/2, strlen() 2 +payload/buffer size 3/4, strlen() 4 +payload/buffer size 4/4, strlen() 4 +payload/buffer size 5/8, strlen() 8 +argv concatenated: ./vstring.one.two.three. diff --git a/src/util/vstring_vstream.c b/src/util/vstring_vstream.c new file mode 100644 index 0000000..451cc50 --- /dev/null +++ b/src/util/vstring_vstream.c @@ -0,0 +1,267 @@ +/*++ +/* NAME +/* vstring_vstream 3 +/* SUMMARY +/* auto-resizing string library, standard I/O interface +/* SYNOPSIS +/* #include +/* +/* int vstring_get_flags(vp, fp, flags) +/* VSTRING *vp; +/* VSTREAM *fp; +/* int flags +/* +/* int vstring_get_flags_nonl(vp, fp, flags) +/* VSTRING *vp; +/* VSTREAM *fp; +/* int flags +/* +/* int vstring_get_flags_null(vp, fp, flags) +/* VSTRING *vp; +/* VSTREAM *fp; +/* int flags +/* +/* int vstring_get_flags_bound(vp, fp, flags, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* int flags +/* +/* int vstring_get_flags_nonl_bound(vp, fp, flags, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* int flags +/* +/* int vstring_get_flags_null_bound(vp, fp, flags, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* int flags +/* CONVENIENCE API +/* int vstring_get(vp, fp) +/* VSTRING *vp; +/* VSTREAM *fp; +/* +/* int vstring_get_nonl(vp, fp) +/* VSTRING *vp; +/* VSTREAM *fp; +/* +/* int vstring_get_null(vp, fp) +/* VSTRING *vp; +/* VSTREAM *fp; +/* +/* int vstring_get_bound(vp, fp, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* +/* int vstring_get_nonl_bound(vp, fp, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* +/* int vstring_get_null_bound(vp, fp, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* ssize_t bound; +/* DESCRIPTION +/* The routines in this module each read one newline or null-terminated +/* string from an input stream. In all cases the result is either the +/* last character read, typically the record terminator, or VSTREAM_EOF. +/* The flags argument is VSTRING_GET_FLAG_NONE (default) or +/* VSTRING_GET_FLAG_APPEND (append instead of overwrite). +/* +/* vstring_get_flags() reads one line from the named stream, including the +/* terminating newline character if present. +/* +/* vstring_get_flags_nonl() reads a line from the named stream and strips +/* the trailing newline character. +/* +/* vstring_get_flags_null() reads a null-terminated string from the named +/* stream. +/* +/* the vstring_get_flags_bound() routines read no more +/* than \fIbound\fR characters. Otherwise they behave like the +/* unbounded versions documented above. +/* +/* The functions without _flags in their name accept the same +/* arguments except flags. These functions use the default +/* flags value. +/* DIAGNOSTICS +/* Fatal errors: memory allocation failure. +/* Panic: improper string bound. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include + +/* Application-specific. */ + +#include "msg.h" +#include "vstring.h" +#include "vstream.h" +#include "vstring_vstream.h" + + /* + * Macro to return the last character added to a VSTRING, for consistency. + */ +#define VSTRING_GET_RESULT(vp, baselen) \ + (VSTRING_LEN(vp) > (base_len) ? vstring_end(vp)[-1] : VSTREAM_EOF) + +/* vstring_get_flags - read line from file, keep newline */ + +int vstring_get_flags(VSTRING *vp, VSTREAM *fp, int flags) +{ + int c; + ssize_t base_len; + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF) { + VSTRING_ADDCH(vp, c); + if (c == '\n') + break; + } + VSTRING_TERMINATE(vp); + return (VSTRING_GET_RESULT(vp, baselen)); +} + +/* vstring_get_flags_nonl - read line from file, strip newline */ + +int vstring_get_flags_nonl(VSTRING *vp, VSTREAM *fp, int flags) +{ + int c; + ssize_t base_len; + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n') + VSTRING_ADDCH(vp, c); + VSTRING_TERMINATE(vp); + return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen)); +} + +/* vstring_get_flags_null - read null-terminated string from file */ + +int vstring_get_flags_null(VSTRING *vp, VSTREAM *fp, int flags) +{ + int c; + ssize_t base_len; + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while ((c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0) + VSTRING_ADDCH(vp, c); + VSTRING_TERMINATE(vp); + return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen)); +} + +/* vstring_get_flags_bound - read line from file, keep newline, up to bound */ + +int vstring_get_flags_bound(VSTRING *vp, VSTREAM *fp, int flags, + ssize_t bound) +{ + int c; + ssize_t base_len; + + if (bound <= 0) + msg_panic("vstring_get_bound: invalid bound %ld", (long) bound); + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF) { + VSTRING_ADDCH(vp, c); + if (c == '\n') + break; + } + VSTRING_TERMINATE(vp); + return (VSTRING_GET_RESULT(vp, baselen)); +} + +/* vstring_get_flags_nonl_bound - read line from file, strip newline, up to bound */ + +int vstring_get_flags_nonl_bound(VSTRING *vp, VSTREAM *fp, int flags, + ssize_t bound) +{ + int c; + ssize_t base_len; + + if (bound <= 0) + msg_panic("vstring_get_nonl_bound: invalid bound %ld", (long) bound); + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != '\n') + VSTRING_ADDCH(vp, c); + VSTRING_TERMINATE(vp); + return (c == '\n' ? c : VSTRING_GET_RESULT(vp, baselen)); +} + +/* vstring_get_flags_null_bound - read null-terminated string from file */ + +int vstring_get_flags_null_bound(VSTRING *vp, VSTREAM *fp, int flags, + ssize_t bound) +{ + int c; + ssize_t base_len; + + if (bound <= 0) + msg_panic("vstring_get_null_bound: invalid bound %ld", (long) bound); + + if ((flags & VSTRING_GET_FLAG_APPEND) == 0) + VSTRING_RESET(vp); + base_len = VSTRING_LEN(vp); + while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0) + VSTRING_ADDCH(vp, c); + VSTRING_TERMINATE(vp); + return (c == 0 ? c : VSTRING_GET_RESULT(vp, baselen)); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: copy the source to this module to stdout. + */ +#include + +#define TEXT_VSTREAM "vstring_vstream.c" + +int main(void) +{ + VSTRING *vp = vstring_alloc(1); + VSTREAM *fp; + + if ((fp = vstream_fopen(TEXT_VSTREAM, O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", TEXT_VSTREAM); + while (vstring_fgets(vp, fp)) + vstream_fprintf(VSTREAM_OUT, "%s", vstring_str(vp)); + vstream_fclose(fp); + vstream_fflush(VSTREAM_OUT); + vstring_free(vp); + return (0); +} + +#endif diff --git a/src/util/vstring_vstream.h b/src/util/vstring_vstream.h new file mode 100644 index 0000000..e0dc801 --- /dev/null +++ b/src/util/vstring_vstream.h @@ -0,0 +1,82 @@ +#ifndef _VSTRING_VSTREAM_H_INCLUDED_ +#define _VSTRING_VSTREAM_H_INCLUDED_ + +/*++ +/* NAME +/* vstring_vstream 3h +/* SUMMARY +/* auto-resizing string library +/* SYNOPSIS +/* #include +/* DESCRIPTION + + /* + * Utility library. + */ +#include +#include + + /* + * External interface. + */ +#define VSTRING_GET_FLAG_NONE (0) +#define VSTRING_GET_FLAG_APPEND (1<<1) /* append instead of overwrite */ + +extern int WARN_UNUSED_RESULT vstring_get_flags(VSTRING *, VSTREAM *, int); +extern int WARN_UNUSED_RESULT vstring_get_flags_nonl(VSTRING *, VSTREAM *, int); +extern int WARN_UNUSED_RESULT vstring_get_flags_null(VSTRING *, VSTREAM *, int); +extern int WARN_UNUSED_RESULT vstring_get_flags_bound(VSTRING *, VSTREAM *, int, ssize_t); +extern int WARN_UNUSED_RESULT vstring_get_flags_nonl_bound(VSTRING *, VSTREAM *, int, ssize_t); +extern int WARN_UNUSED_RESULT vstring_get_flags_null_bound(VSTRING *, VSTREAM *, int, ssize_t); + + /* + * Convenience aliases for most use cases. + */ +#define vstring_get(string, stream) \ + vstring_get_flags((string), (stream), VSTRING_GET_FLAG_NONE) +#define vstring_get_nonl(string, stream) \ + vstring_get_flags_nonl((string), (stream), VSTRING_GET_FLAG_NONE) +#define vstring_get_null(string, stream) \ + vstring_get_flags_null((string), (stream), VSTRING_GET_FLAG_NONE) + +#define vstring_get_bound(string, stream, size) \ + vstring_get_flags_bound((string), (stream), VSTRING_GET_FLAG_NONE, size) +#define vstring_get_nonl_bound(string, stream, size) \ + vstring_get_flags_nonl_bound((string), (stream), \ + VSTRING_GET_FLAG_NONE, size) +#define vstring_get_null_bound(string, stream, size) \ + vstring_get_flags_null_bound((string), (stream), \ + VSTRING_GET_FLAG_NONE, size) + + /* + * Backwards compatibility for code that still uses the vstring_fgets() + * interface. Unfortunately we can't change the macro name to upper case. + */ +#define vstring_fgets(s, p) \ + (vstring_get((s), (p)) == VSTREAM_EOF ? 0 : (s)) +#define vstring_fgets_nonl(s, p) \ + (vstring_get_nonl((s), (p)) == VSTREAM_EOF ? 0 : (s)) +#define vstring_fgets_null(s, p) \ + (vstring_get_null((s), (p)) == VSTREAM_EOF ? 0 : (s)) +#define vstring_fgets_bound(s, p, l) \ + (vstring_get_bound((s), (p), (l)) == VSTREAM_EOF ? 0 : (s)) +#define vstring_fgets_nonl_bound(s, p, l) \ + (vstring_get_nonl_bound((s), (p), (l)) == VSTREAM_EOF ? 0 : (s)) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/util/warn_stat.c b/src/util/warn_stat.c new file mode 100644 index 0000000..fd885d9 --- /dev/null +++ b/src/util/warn_stat.c @@ -0,0 +1,101 @@ +/*++ +/* NAME +/* warn_stat 3 +/* SUMMARY +/* baby-sit stat() error returns +/* SYNOPSIS +/* #include +/* +/* int warn_stat(path, st) +/* const char *path; +/* struct stat *st; +/* +/* int warn_lstat(path, st) +/* const char *path; +/* struct stat *st; +/* +/* int warn_fstat(fd, st) +/* int fd; +/* struct stat *st; +/* DESCRIPTION +/* warn_stat(), warn_fstat() and warn_lstat() wrap the stat(), +/* fstat() and lstat() system calls with code that logs a +/* diagnosis for common error cases. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include + +/* Utility library. */ + +#include +#define WARN_STAT_INTERNAL +#include + +/* diagnose_stat - log stat warning */ + +static void diagnose_stat(void) +{ + struct stat st; + + /* + * When *stat() fails with EOVERFLOW, and the interface uses 32-bit data + * types, suggest that the program be recompiled with larger data types. + */ +#ifdef EOVERFLOW + if (errno == EOVERFLOW && sizeof(st.st_size) == 4) { + msg_warn("this program was built for 32-bit file handles, " + "but some number does not fit in 32 bits"); + msg_warn("possible solution: recompile in 64-bit mode, or " + "recompile in 32-bit mode with 'large file' support"); + } +#endif +} + +/* warn_stat - stat with warning */ + +int warn_stat(const char *path, struct stat * st) +{ + int ret; + + ret = stat(path, st); + if (ret < 0) + diagnose_stat(); + return (ret); +} + +/* warn_lstat - lstat with warning */ + +int warn_lstat(const char *path, struct stat * st) +{ + int ret; + + ret = lstat(path, st); + if (ret < 0) + diagnose_stat(); + return (ret); +} + +/* warn_fstat - fstat with warning */ + +int warn_fstat(int fd, struct stat * st) +{ + int ret; + + ret = fstat(fd, st); + if (ret < 0) + diagnose_stat(); + return (ret); +} diff --git a/src/util/warn_stat.h b/src/util/warn_stat.h new file mode 100644 index 0000000..92ddce0 --- /dev/null +++ b/src/util/warn_stat.h @@ -0,0 +1,38 @@ +#ifndef _WARN_STAT_H_ +#define _WARN_STAT_H_ + +/*++ +/* NAME +/* warn_stat 3h +/* SUMMARY +/* baby-sit stat() error returns +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +#ifndef WARN_STAT_INTERNAL +#define stat(p, s) warn_stat((p), (s)) +#define lstat(p, s) warn_lstat((p), (s)) +#define fstat(f, s) warn_fstat((f), (s)) +#endif + +extern int warn_stat(const char *path, struct stat *); +extern int warn_lstat(const char *path, struct stat *); +extern int warn_fstat(int, struct stat *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/watchdog.c b/src/util/watchdog.c new file mode 100644 index 0000000..3ec1fbc --- /dev/null +++ b/src/util/watchdog.c @@ -0,0 +1,314 @@ +/*++ +/* NAME +/* watchdog 3 +/* SUMMARY +/* watchdog timer +/* SYNOPSIS +/* #include +/* +/* WATCHDOG *watchdog_create(timeout, action, context) +/* unsigned timeout; +/* void (*action)(WATCHDOG *watchdog, char *context); +/* char *context; +/* +/* void watchdog_start(watchdog) +/* WATCHDOG *watchdog; +/* +/* void watchdog_stop(watchdog) +/* WATCHDOG *watchdog; +/* +/* void watchdog_destroy(watchdog) +/* WATCHDOG *watchdog; +/* +/* void watchdog_pat() +/* DESCRIPTION +/* This module implements watchdog timers that are based on ugly +/* UNIX alarm timers. The module is designed to survive systems +/* with clocks that jump occasionally. +/* +/* Watchdog timers can be stacked. Only one watchdog timer can be +/* active at a time. Only the last created watchdog timer can be +/* manipulated. Watchdog timers must be destroyed in reverse order +/* of creation. +/* +/* watchdog_create() suspends the current watchdog timer, if any, +/* and instantiates a new watchdog timer. +/* +/* watchdog_start() starts or restarts the watchdog timer. +/* +/* watchdog_stop() stops the watchdog timer. +/* +/* watchdog_destroy() stops the watchdog timer, and resumes the +/* watchdog timer instance that was suspended by watchdog_create(). +/* +/* watchdog_pat() pats the watchdog, so it stays quiet. +/* +/* Arguments: +/* .IP timeout +/* The watchdog time limit. When the watchdog timer runs, the +/* process must invoke watchdog_start(), watchdog_stop() or +/* watchdog_destroy() before the time limit is reached. +/* .IP action +/* A null pointer, or pointer to function that is called when the +/* watchdog alarm goes off. The default action is to terminate +/* the process with a fatal error. +/* .IP context +/* Application context that is passed to the action routine. +/* .IP watchdog +/* Must be a pointer to the most recently created watchdog instance. +/* This argument is checked upon each call. +/* BUGS +/* UNIX alarm timers are not stackable, so there can be at most one +/* watchdog instance active at any given time. +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem, system call failure. +/* Panics: interface violations. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Application-specific. */ + + /* + * Rather than having one timer that goes off when it is too late, we break + * up the time limit into smaller intervals so that we can deal with clocks + * that jump occasionally. + */ +#define WATCHDOG_STEPS 3 + + /* + * UNIX alarms are not stackable, but we can save and restore state, so that + * watchdogs can at least be nested, sort of. + */ +struct WATCHDOG { + unsigned timeout; /* our time resolution */ + WATCHDOG_FN action; /* application routine */ + char *context; /* application context */ + int trip_run; /* number of successive timeouts */ + WATCHDOG *saved_watchdog; /* saved state */ + struct sigaction saved_action; /* saved state */ + unsigned saved_time; /* saved state */ +}; + + /* + * However, only one watchdog instance can be current, and the caller has to + * restore state before a prior watchdog instance can be manipulated. + */ +static WATCHDOG *watchdog_curr; + + /* + * Workaround for systems where the alarm signal does not wakeup the event + * machinery, and therefore does not restart the watchdog timer in the + * single_server etc. skeletons. The symptom is that programs abort when the + * watchdog timeout is less than the max_idle time. + */ +#ifdef USE_WATCHDOG_PIPE +#include +#include +#include + +static int watchdog_pipe[2]; + +/* watchdog_read - read event pipe */ + +static void watchdog_read(int unused_event, void *unused_context) +{ + char ch; + + while (read(watchdog_pipe[0], &ch, 1) > 0) + /* void */ ; +} + +#endif /* USE_WATCHDOG_PIPE */ + +/* watchdog_event - handle timeout event */ + +static void watchdog_event(int unused_sig) +{ + const char *myname = "watchdog_event"; + WATCHDOG *wp; + + /* + * This routine runs as a signal handler. We should not do anything that + * could involve memory allocation/deallocation, but exiting without + * proper explanation would be unacceptable. For this reason, msg(3) was + * made safe for usage by signal handlers that terminate the process. + */ + if ((wp = watchdog_curr) == 0) + msg_panic("%s: no instance", myname); + if (msg_verbose > 1) + msg_info("%s: %p %d", myname, (void *) wp, wp->trip_run); + if (++(wp->trip_run) < WATCHDOG_STEPS) { +#ifdef USE_WATCHDOG_PIPE + int saved_errno = errno; + + /* Wake up the events(3) engine. */ + if (write(watchdog_pipe[1], "", 1) != 1) + msg_warn("%s: write watchdog_pipe: %m", myname); + errno = saved_errno; +#endif + alarm(wp->timeout); + } else { + if (wp->action) + wp->action(wp, wp->context); + else { + killme_after(5); +#ifdef TEST + pause(); +#endif + msg_fatal("watchdog timeout"); + } + } +} + +/* watchdog_create - create watchdog instance */ + +WATCHDOG *watchdog_create(unsigned timeout, WATCHDOG_FN action, char *context) +{ + const char *myname = "watchdog_create"; + struct sigaction sig_action; + WATCHDOG *wp; + + wp = (WATCHDOG *) mymalloc(sizeof(*wp)); + if ((wp->timeout = timeout / WATCHDOG_STEPS) == 0) + msg_panic("%s: timeout %d is too small", myname, timeout); + wp->action = action; + wp->context = context; + wp->saved_watchdog = watchdog_curr; + wp->saved_time = alarm(0); + sigemptyset(&sig_action.sa_mask); +#ifdef SA_RESTART + sig_action.sa_flags = SA_RESTART; +#else + sig_action.sa_flags = 0; +#endif + sig_action.sa_handler = watchdog_event; + if (sigaction(SIGALRM, &sig_action, &wp->saved_action) < 0) + msg_fatal("%s: sigaction(SIGALRM): %m", myname); + if (msg_verbose > 1) + msg_info("%s: %p %d", myname, (void *) wp, timeout); +#ifdef USE_WATCHDOG_PIPE + if (watchdog_curr == 0) { + if (pipe(watchdog_pipe) < 0) + msg_fatal("%s: pipe: %m", myname); + non_blocking(watchdog_pipe[0], NON_BLOCKING); + non_blocking(watchdog_pipe[1], NON_BLOCKING); + close_on_exec(watchdog_pipe[0], CLOSE_ON_EXEC); /* Fix 20190126 */ + close_on_exec(watchdog_pipe[1], CLOSE_ON_EXEC); /* Fix 20190126 */ + event_enable_read(watchdog_pipe[0], watchdog_read, (void *) 0); + } +#endif + return (watchdog_curr = wp); +} + +/* watchdog_destroy - destroy watchdog instance, restore state */ + +void watchdog_destroy(WATCHDOG *wp) +{ + const char *myname = "watchdog_destroy"; + + watchdog_stop(wp); + watchdog_curr = wp->saved_watchdog; + if (sigaction(SIGALRM, &wp->saved_action, (struct sigaction *) 0) < 0) + msg_fatal("%s: sigaction(SIGALRM): %m", myname); + if (wp->saved_time) + alarm(wp->saved_time); + myfree((void *) wp); +#ifdef USE_WATCHDOG_PIPE + if (watchdog_curr == 0) { + event_disable_readwrite(watchdog_pipe[0]); + (void) close(watchdog_pipe[0]); + (void) close(watchdog_pipe[1]); + } +#endif + if (msg_verbose > 1) + msg_info("%s: %p", myname, (void *) wp); +} + +/* watchdog_start - enable watchdog timer */ + +void watchdog_start(WATCHDOG *wp) +{ + const char *myname = "watchdog_start"; + + if (wp != watchdog_curr) + msg_panic("%s: wrong watchdog instance", myname); + wp->trip_run = 0; + alarm(wp->timeout); + if (msg_verbose > 1) + msg_info("%s: %p", myname, (void *) wp); +} + +/* watchdog_stop - disable watchdog timer */ + +void watchdog_stop(WATCHDOG *wp) +{ + const char *myname = "watchdog_stop"; + + if (wp != watchdog_curr) + msg_panic("%s: wrong watchdog instance", myname); + alarm(0); + if (msg_verbose > 1) + msg_info("%s: %p", myname, (void *) wp); +} + +/* watchdog_pat - pat the dog so it stays quiet */ + +void watchdog_pat(void) +{ + const char *myname = "watchdog_pat"; + + if (watchdog_curr) + watchdog_curr->trip_run = 0; + if (msg_verbose > 1) + msg_info("%s: %p", myname, (void *) watchdog_curr); +} + +#ifdef TEST + +#include + +int main(int unused_argc, char **unused_argv) +{ + WATCHDOG *wp; + + msg_verbose = 2; + + wp = watchdog_create(10, (WATCHDOG_FN) 0, (void *) 0); + watchdog_start(wp); + do { + watchdog_pat(); + } while (VSTREAM_GETCHAR() != VSTREAM_EOF); + watchdog_destroy(wp); + return (0); +} + +#endif diff --git a/src/util/watchdog.h b/src/util/watchdog.h new file mode 100644 index 0000000..ee01faf --- /dev/null +++ b/src/util/watchdog.h @@ -0,0 +1,36 @@ +#ifndef _WATCHDOG_H_INCLUDED_ +#define _WATCHDOG_H_INCLUDED_ + +/*++ +/* NAME +/* watchdog 3h +/* SUMMARY +/* watchdog timer +/* SYNOPSIS +/* #include "watchdog.h" + DESCRIPTION + .nf + + /* + * External interface. + */ +typedef struct WATCHDOG WATCHDOG; +typedef void (*WATCHDOG_FN) (WATCHDOG *, char *); +extern WATCHDOG *watchdog_create(unsigned, WATCHDOG_FN, char *); +extern void watchdog_start(WATCHDOG *); +extern void watchdog_stop(WATCHDOG *); +extern void watchdog_destroy(WATCHDOG *); +extern void watchdog_pat(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/util/write_buf.c b/src/util/write_buf.c new file mode 100644 index 0000000..968a468 --- /dev/null +++ b/src/util/write_buf.c @@ -0,0 +1,89 @@ +/*++ +/* NAME +/* write_buf 3 +/* SUMMARY +/* write buffer or bust +/* SYNOPSIS +/* #include +/* +/* ssize_t write_buf(fd, buf, len, timeout) +/* int fd; +/* const char *buf; +/* ssize_t len; +/* int timeout; +/* DESCRIPTION +/* write_buf() writes a buffer to the named stream in as many +/* fragments as needed, and returns the number of bytes written, +/* which is always the number requested or an error indication. +/* +/* Arguments: +/* .IP fd +/* File descriptor in the range 0..FD_SETSIZE. +/* .IP buf +/* Address of data to be written. +/* .IP len +/* Amount of data to be written. +/* .IP timeout +/* Bounds the time in seconds to wait until \fIfd\fD becomes writable. +/* A value <= 0 means do not wait; this is useful only when \fIfd\fR +/* uses blocking I/O. +/* DIAGNOSTICS +/* write_buf() returns -1 in case of trouble. The global \fIerrno\fR +/* variable reflects the nature of the problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include + +/* write_buf - write buffer or bust */ + +ssize_t write_buf(int fd, const char *buf, ssize_t len, int timeout) +{ + const char *start = buf; + ssize_t count; + time_t expire; + int time_left = timeout; + + if (time_left > 0) + expire = time((time_t *) 0) + time_left; + + while (len > 0) { + if (time_left > 0 && write_wait(fd, time_left) < 0) + return (-1); + if ((count = write(fd, buf, len)) < 0) { + if ((errno == EAGAIN && time_left > 0) || errno == EINTR) + /* void */ ; + else + return (-1); + } else { + buf += count; + len -= count; + } + if (len > 0 && time_left > 0) { + time_left = expire - time((time_t *) 0); + if (time_left <= 0) { + errno = ETIMEDOUT; + return (-1); + } + } + } + return (buf - start); +} diff --git a/src/verify/.indent.pro b/src/verify/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/verify/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/verify/Makefile.in b/src/verify/Makefile.in new file mode 100644 index 0000000..b5ba328 --- /dev/null +++ b/src/verify/Makefile.in @@ -0,0 +1,98 @@ +SHELL = /bin/sh +SRCS = verify.c +OBJS = verify.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = verify +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: test + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +verify.o: ../../include/argv.h +verify.o: ../../include/attr.h +verify.o: ../../include/check_arg.h +verify.o: ../../include/cleanup_user.h +verify.o: ../../include/data_redirect.h +verify.o: ../../include/deliver_request.h +verify.o: ../../include/dict.h +verify.o: ../../include/dict_cache.h +verify.o: ../../include/dict_ht.h +verify.o: ../../include/dsn.h +verify.o: ../../include/events.h +verify.o: ../../include/htable.h +verify.o: ../../include/int_filt.h +verify.o: ../../include/iostuff.h +verify.o: ../../include/mail_conf.h +verify.o: ../../include/mail_params.h +verify.o: ../../include/mail_proto.h +verify.o: ../../include/mail_server.h +verify.o: ../../include/mail_version.h +verify.o: ../../include/msg.h +verify.o: ../../include/msg_stats.h +verify.o: ../../include/myflock.h +verify.o: ../../include/mymalloc.h +verify.o: ../../include/nvtable.h +verify.o: ../../include/post_mail.h +verify.o: ../../include/recipient_list.h +verify.o: ../../include/set_eugid.h +verify.o: ../../include/smtputf8.h +verify.o: ../../include/split_at.h +verify.o: ../../include/stringops.h +verify.o: ../../include/sys_defs.h +verify.o: ../../include/vbuf.h +verify.o: ../../include/verify_clnt.h +verify.o: ../../include/verify_sender_addr.h +verify.o: ../../include/vstream.h +verify.o: ../../include/vstring.h +verify.o: verify.c diff --git a/src/verify/verify.c b/src/verify/verify.c new file mode 100644 index 0000000..fea131c --- /dev/null +++ b/src/verify/verify.c @@ -0,0 +1,776 @@ +/*++ +/* NAME +/* verify 8 +/* SUMMARY +/* Postfix address verification server +/* SYNOPSIS +/* \fBverify\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBverify\fR(8) address verification server maintains a record +/* of what recipient addresses are known to be deliverable or +/* undeliverable. +/* +/* Addresses are verified by injecting probe messages into the +/* Postfix queue. Probe messages are run through all the routing +/* and rewriting machinery except for final delivery, and are +/* discarded rather than being deferred or bounced. +/* +/* Address verification relies on the answer from the nearest +/* MTA for the specified address, and will therefore not detect +/* all undeliverable addresses. +/* +/* The \fBverify\fR(8) server is designed to run under control +/* by the Postfix +/* master server. It maintains an optional persistent database. +/* To avoid being interrupted by "postfix stop" in the middle +/* of a database update, the process runs in a separate process +/* group. +/* +/* The \fBverify\fR(8) server implements the following requests: +/* .IP "\fBupdate\fI address status text\fR" +/* Update the status and text of the specified address. +/* .IP "\fBquery\fI address\fR" +/* Look up the \fIstatus\fR and \fItext\fR for the specified +/* \fIaddress\fR. +/* If the status is unknown, a probe is sent and an "in progress" +/* status is returned. +/* SECURITY +/* .ad +/* .fi +/* The address verification server is not security-sensitive. It does +/* not talk to the network, and it does not talk to local users. +/* The verify server can run chrooted at fixed low privilege. +/* +/* The address verification server can be coerced to store +/* unlimited amounts of garbage. Limiting the cache expiry +/* time +/* trades one problem (disk space exhaustion) for another +/* one (poor response time to client requests). +/* +/* With Postfix version 2.5 and later, the \fBverify\fR(8) +/* server no longer uses root privileges when opening the +/* \fBaddress_verify_map\fR cache file. The file should now +/* be stored under the Postfix-owned \fBdata_directory\fR. As +/* a migration aid, an attempt to open a cache file under a +/* non-Postfix directory is redirected to the Postfix-owned +/* \fBdata_directory\fR, and a warning is logged. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* BUGS +/* Address verification probe messages add additional traffic +/* to the mail queue. +/* Recipient verification may cause an increased load on +/* down-stream servers in the case of a dictionary attack or +/* a flood of backscatter bounces. +/* Sender address verification may cause your site to be +/* denylisted by some providers. +/* +/* If the persistent database ever gets corrupted then the world +/* comes to an end and human intervention is needed. This violates +/* a basic Postfix principle. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are not picked up automatically, +/* as \fBverify\fR(8) +/* processes are long-lived. Use the command "\fBpostfix reload\fR" after +/* a configuration change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* PROBE MESSAGE CONTROLS +/* .ad +/* .fi +/* .IP "\fBaddress_verify_sender ($double_bounce_sender)\fR" +/* The sender address to use in address verification probes; prior +/* to Postfix 2.5 the default was "postmaster". +/* .PP +/* Available with Postfix 2.9 and later: +/* .IP "\fBaddress_verify_sender_ttl (0s)\fR" +/* The time between changes in the time-dependent portion of address +/* verification probe sender addresses. +/* CACHE CONTROLS +/* .ad +/* .fi +/* .IP "\fBaddress_verify_map (see 'postconf -d' output)\fR" +/* Lookup table for persistent address verification status +/* storage. +/* .IP "\fBaddress_verify_positive_expire_time (31d)\fR" +/* The time after which a successful probe expires from the address +/* verification cache. +/* .IP "\fBaddress_verify_positive_refresh_time (7d)\fR" +/* The time after which a successful address verification probe needs +/* to be refreshed. +/* .IP "\fBaddress_verify_negative_cache (yes)\fR" +/* Enable caching of failed address verification probe results. +/* .IP "\fBaddress_verify_negative_expire_time (3d)\fR" +/* The time after which a failed probe expires from the address +/* verification cache. +/* .IP "\fBaddress_verify_negative_refresh_time (3h)\fR" +/* The time after which a failed address verification probe needs to +/* be refreshed. +/* .PP +/* Available with Postfix 2.7 and later: +/* .IP "\fBaddress_verify_cache_cleanup_interval (12h)\fR" +/* The amount of time between \fBverify\fR(8) address verification +/* database cleanup runs. +/* PROBE MESSAGE ROUTING CONTROLS +/* .ad +/* .fi +/* By default, probe messages are delivered via the same route +/* as regular messages. The following parameters can be used to +/* override specific message routing mechanisms. +/* .IP "\fBaddress_verify_relayhost ($relayhost)\fR" +/* Overrides the relayhost parameter setting for address verification +/* probes. +/* .IP "\fBaddress_verify_transport_maps ($transport_maps)\fR" +/* Overrides the transport_maps parameter setting for address verification +/* probes. +/* .IP "\fBaddress_verify_local_transport ($local_transport)\fR" +/* Overrides the local_transport parameter setting for address +/* verification probes. +/* .IP "\fBaddress_verify_virtual_transport ($virtual_transport)\fR" +/* Overrides the virtual_transport parameter setting for address +/* verification probes. +/* .IP "\fBaddress_verify_relay_transport ($relay_transport)\fR" +/* Overrides the relay_transport parameter setting for address +/* verification probes. +/* .IP "\fBaddress_verify_default_transport ($default_transport)\fR" +/* Overrides the default_transport parameter setting for address +/* verification probes. +/* .PP +/* Available in Postfix 2.3 and later: +/* .IP "\fBaddress_verify_sender_dependent_relayhost_maps ($sender_dependent_relayhost_maps)\fR" +/* Overrides the sender_dependent_relayhost_maps parameter setting for address +/* verification probes. +/* .PP +/* Available in Postfix 2.7 and later: +/* .IP "\fBaddress_verify_sender_dependent_default_transport_maps ($sender_dependent_default_transport_maps)\fR" +/* Overrides the sender_dependent_default_transport_maps parameter +/* setting for address verification probes. +/* SMTPUTF8 CONTROLS +/* .ad +/* .fi +/* Preliminary SMTPUTF8 support is introduced with Postfix 3.0. +/* .IP "\fBsmtputf8_autodetect_classes (sendmail, verify)\fR" +/* Detect that a message requires SMTPUTF8 support for the specified +/* mail origin classes. +/* .PP +/* Available in Postfix version 3.2 and later: +/* .IP "\fBenable_idna2003_compatibility (no)\fR" +/* Enable 'transitional' compatibility between IDNA2003 and IDNA2008, +/* when converting UTF-8 domain names to/from the ASCII form that is +/* used for DNS lookups. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* smtpd(8), Postfix SMTP server +/* cleanup(8), enqueue Postfix message +/* postconf(5), configuration parameters +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* ADDRESS_VERIFICATION_README, address verification howto +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* This service was introduced with Postfix version 2.1. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Server skeleton. */ + +#include + +/* Application-specific. */ + + /* + * Tunable parameters. + */ +char *var_verify_map; +int var_verify_pos_exp; +int var_verify_pos_try; +int var_verify_neg_exp; +int var_verify_neg_try; +int var_verify_scan_cache; + + /* + * State. + */ +static DICT_CACHE *verify_map; + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define STREQ(x,y) (strcmp(x,y) == 0) + + /* + * The address verification database consists of (address, data) tuples. The + * format of the data field is "status:probed:updated:text". The meaning of + * each field is: + * + * status: one of the four recipient status codes (OK, DEFER, BOUNCE or TODO). + * In the case of TODO, we have no information about the address, and the + * address is being probed. + * + * probed: if non-zero, the time the currently outstanding address probe was + * sent. If zero, there is no outstanding address probe. + * + * updated: if non-zero, the time the address probe result was received. If + * zero, we have no information about the address, and the address is being + * probed. + * + * text: descriptive text from delivery agents etc. + */ + + /* + * Quick test to see status without parsing the whole entry. + */ +#define STATUS_FROM_RAW_ENTRY(e) atoi(e) + +/* verify_make_entry - construct table entry */ + +static void verify_make_entry(VSTRING *buf, int status, long probed, + long updated, const char *text) +{ + vstring_sprintf(buf, "%d:%ld:%ld:%s", status, probed, updated, text); +} + +/* verify_parse_entry - parse table entry */ + +static int verify_parse_entry(char *buf, int *status, long *probed, + long *updated, char **text) +{ + char *probed_text; + char *updated_text; + + if ((probed_text = split_at(buf, ':')) != 0 + && (updated_text = split_at(probed_text, ':')) != 0 + && (*text = split_at(updated_text, ':')) != 0 + && alldig(buf) + && alldig(probed_text) + && alldig(updated_text)) { + *probed = atol(probed_text); + *updated = atol(updated_text); + *status = atoi(buf); + + /* + * Coverity 200604: the code incorrectly tested (probed || updated), + * so that the sanity check never detected all-zero time stamps. Such + * records are never written. If we read a record with all-zero time + * stamps, then something is badly broken. + */ + if ((*status == DEL_RCPT_STAT_OK + || *status == DEL_RCPT_STAT_DEFER + || *status == DEL_RCPT_STAT_BOUNCE + || *status == DEL_RCPT_STAT_TODO) + && (*probed || *updated)) + return (0); + } + msg_warn("bad address verify table entry: %.100s", buf); + return (-1); +} + +/* verify_stat2name - status to name */ + +static const char *verify_stat2name(int addr_status) +{ + if (addr_status == DEL_RCPT_STAT_OK) + return ("deliverable"); + if (addr_status == DEL_RCPT_STAT_DEFER) + return ("undeliverable"); + if (addr_status == DEL_RCPT_STAT_BOUNCE) + return ("undeliverable"); + return (0); +} + +/* verify_update_service - update address service */ + +static void verify_update_service(VSTREAM *client_stream) +{ + VSTRING *buf = vstring_alloc(10); + VSTRING *addr = vstring_alloc(10); + int addr_status; + VSTRING *text = vstring_alloc(10); + const char *status_name; + const char *raw_data; + long probed; + long updated; + + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_ADDR, addr), + RECV_ATTR_INT(MAIL_ATTR_ADDR_STATUS, &addr_status), + RECV_ATTR_STR(MAIL_ATTR_WHY, text), + ATTR_TYPE_END) == 3) { + /* FIX 200501 IPv6 patch did not neuter ":" in address literals. */ + translit(STR(addr), ":", "_"); + if ((status_name = verify_stat2name(addr_status)) == 0) { + msg_warn("bad recipient status %d for recipient %s", + addr_status, STR(addr)); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_BAD), + ATTR_TYPE_END); + } else { + + /* + * Robustness: don't allow a failed probe to clobber an OK + * address before it expires. The failed probe is ignored so that + * the address will be re-probed upon the next query. As long as + * some probes succeed the address will remain cached as OK. + */ + if (addr_status == DEL_RCPT_STAT_OK + || (raw_data = dict_cache_lookup(verify_map, STR(addr))) == 0 + || STATUS_FROM_RAW_ENTRY(raw_data) != DEL_RCPT_STAT_OK) { + probed = 0; + updated = (long) time((time_t *) 0); + printable(STR(text), '?'); + verify_make_entry(buf, addr_status, probed, updated, STR(text)); + if (msg_verbose) + msg_info("PUT %s status=%d probed=%ld updated=%ld text=%s", + STR(addr), addr_status, probed, updated, STR(text)); + dict_cache_update(verify_map, STR(addr), STR(buf)); + } + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_OK), + ATTR_TYPE_END); + } + } + vstring_free(buf); + vstring_free(addr); + vstring_free(text); +} + +/* verify_post_mail_fclose_action - callback */ + +static void verify_post_mail_fclose_action(int unused_status, + void *unused_context) +{ + /* no code here, we just need to avoid blocking in post_mail_fclose() */ +} + +/* verify_post_mail_action - callback */ + +static void verify_post_mail_action(VSTREAM *stream, void *context) +{ + + /* + * Probe messages need no body content, because they are never delivered, + * deferred, or bounced. + */ + if (stream != 0) + post_mail_fclose_async(stream, verify_post_mail_fclose_action, context); +} + +/* verify_query_service - query address status */ + +static void verify_query_service(VSTREAM *client_stream) +{ + VSTRING *addr = vstring_alloc(10); + VSTRING *get_buf = 0; + VSTRING *put_buf = 0; + const char *raw_data; + int addr_status; + long probed; + long updated; + char *text; + + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_ADDR, addr), + ATTR_TYPE_END) == 1) { + long now = (long) time((time_t *) 0); + + /* + * Produce a default record when no usable record exists. + * + * If negative caching is disabled, purge an expired record from the + * database. + * + * XXX Assume that a probe is lost if no response is received in 1000 + * seconds. If this number is too small the queue will slowly fill up + * with delayed probes. + * + * XXX Maintain a moving average for the probe turnaround time, and + * allow probe "retransmission" when a probe is outstanding for, say + * some minimal amount of time (1000 sec) plus several times the + * observed probe turnaround time. This causes probing to back off + * when the mail system becomes congested. + */ +#define POSITIVE_ENTRY_EXPIRED(addr_status, updated) \ + (addr_status == DEL_RCPT_STAT_OK && updated + var_verify_pos_exp < now) +#define NEGATIVE_ENTRY_EXPIRED(addr_status, updated) \ + (addr_status != DEL_RCPT_STAT_OK && updated + var_verify_neg_exp < now) +#define PROBE_TTL 1000 + + /* FIX 200501 IPv6 patch did not neuter ":" in address literals. */ + translit(STR(addr), ":", "_"); + if ((raw_data = dict_cache_lookup(verify_map, STR(addr))) == 0 /* not found */ + || ((get_buf = vstring_alloc(10)), + vstring_strcpy(get_buf, raw_data), /* malformed */ + verify_parse_entry(STR(get_buf), &addr_status, &probed, + &updated, &text) < 0) + || (now - probed > PROBE_TTL /* safe to probe */ + && (POSITIVE_ENTRY_EXPIRED(addr_status, updated) + || NEGATIVE_ENTRY_EXPIRED(addr_status, updated)))) { + addr_status = DEL_RCPT_STAT_TODO; + probed = 0; + updated = 0; + text = "Address verification in progress"; + if (raw_data != 0 && var_verify_neg_cache == 0) + dict_cache_delete(verify_map, STR(addr)); + } + if (msg_verbose) + msg_info("GOT %s status=%d probed=%ld updated=%ld text=%s", + STR(addr), addr_status, probed, updated, text); + + /* + * Respond to the client. + */ + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_OK), + SEND_ATTR_INT(MAIL_ATTR_ADDR_STATUS, addr_status), + SEND_ATTR_STR(MAIL_ATTR_WHY, text), + ATTR_TYPE_END); + + /* + * Send a new probe when the information needs to be refreshed. + * + * XXX For an initial proof of concept implementation, use synchronous + * mail submission. This needs to be made async for high-volume + * sites, which makes it even more interesting to eliminate duplicate + * queries while a probe is being built. + * + * If negative caching is turned off, update the database only when + * refreshing an existing entry. + */ +#define POSITIVE_REFRESH_NEEDED(addr_status, updated) \ + (addr_status == DEL_RCPT_STAT_OK && updated + var_verify_pos_try < now) +#define NEGATIVE_REFRESH_NEEDED(addr_status, updated) \ + (addr_status != DEL_RCPT_STAT_OK && updated + var_verify_neg_try < now) + + if (now - probed > PROBE_TTL + && (POSITIVE_REFRESH_NEEDED(addr_status, updated) + || NEGATIVE_REFRESH_NEEDED(addr_status, updated))) { + if (msg_verbose) + msg_info("PROBE %s status=%d probed=%ld updated=%ld", + STR(addr), addr_status, now, updated); + post_mail_fopen_async(make_verify_sender_addr(), STR(addr), + MAIL_SRC_MASK_VERIFY, + DEL_REQ_FLAG_MTA_VRFY, + SMTPUTF8_FLAG_NONE, + (VSTRING *) 0, + verify_post_mail_action, + (void *) 0); + if (updated != 0 || var_verify_neg_cache != 0) { + put_buf = vstring_alloc(10); + verify_make_entry(put_buf, addr_status, now, updated, text); + if (msg_verbose) + msg_info("PUT %s status=%d probed=%ld updated=%ld text=%s", + STR(addr), addr_status, now, updated, text); + dict_cache_update(verify_map, STR(addr), STR(put_buf)); + } + } + } + vstring_free(addr); + if (get_buf) + vstring_free(get_buf); + if (put_buf) + vstring_free(put_buf); +} + +/* verify_cache_validator - cache cleanup validator */ + +static int verify_cache_validator(const char *addr, const char *raw_data, + void *context) +{ + VSTRING *get_buf = (VSTRING *) context; + int addr_status; + long probed; + long updated; + char *text; + long now = (long) event_time(); + +#define POS_OR_NEG_ENTRY_EXPIRED(stat, stamp) \ + (POSITIVE_ENTRY_EXPIRED((stat), (stamp)) \ + || NEGATIVE_ENTRY_EXPIRED((stat), (stamp))) + + vstring_strcpy(get_buf, raw_data); + return (verify_parse_entry(STR(get_buf), &addr_status, /* syntax OK */ + &probed, &updated, &text) == 0 + && (now - probed < PROBE_TTL /* probe in progress */ + || !POS_OR_NEG_ENTRY_EXPIRED(addr_status, updated))); +} + +/* verify_service - perform service for client */ + +static void verify_service(VSTREAM *client_stream, char *unused_service, + char **argv) +{ + VSTRING *request = vstring_alloc(10); + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the socket dedicated + * to the address verification service. All connection-management stuff + * is handled by the common code in multi_server.c. + */ + if (attr_scan(client_stream, + ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + RECV_ATTR_STR(MAIL_ATTR_REQ, request), + ATTR_TYPE_END) == 1) { + if (STREQ(STR(request), VRFY_REQ_UPDATE)) { + verify_update_service(client_stream); + } else if (STREQ(STR(request), VRFY_REQ_QUERY)) { + verify_query_service(client_stream); + } else { + msg_warn("unrecognized request: \"%s\", ignored", STR(request)); + attr_print(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(MAIL_ATTR_STATUS, VRFY_STAT_BAD), + ATTR_TYPE_END); + } + } + vstream_fflush(client_stream); + vstring_free(request); +} + +/* verify_dump - dump some statistics */ + +static void verify_dump(char *unused_name, char **unused_argv) +{ + + /* + * Dump preliminary cache cleanup statistics when the process commits + * suicide while a cache cleanup run is in progress. We can't currently + * distinguish between "postfix reload" (we should restart) or "maximal + * idle time reached" (we could finish the cache cleanup first). + */ + dict_cache_close(verify_map); + verify_map = 0; +} + +/* post_jail_init - post-jail initialization */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * If the database is in volatile memory only, prevent automatic process + * suicide after a limited number of client requests or after a limited + * amount of idle time. + */ + if (*var_verify_map == 0) { + var_use_limit = 0; + var_idle_limit = 0; + } + + /* + * Start the cache cleanup thread. + */ + if (var_verify_scan_cache > 0) { + int cache_flags; + + cache_flags = DICT_CACHE_FLAG_STATISTICS; + if (msg_verbose) + cache_flags |= DICT_CACHE_FLAG_VERBOSE; + dict_cache_control(verify_map, + CA_DICT_CACHE_CTL_FLAGS(cache_flags), + CA_DICT_CACHE_CTL_INTERVAL(var_verify_scan_cache), + CA_DICT_CACHE_CTL_VALIDATOR(verify_cache_validator), + CA_DICT_CACHE_CTL_CONTEXT((void *) vstring_alloc(100)), + CA_DICT_CACHE_CTL_END); + } +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + mode_t saved_mask; + VSTRING *redirect; + + /* + * Never, ever, get killed by a master signal, as that would corrupt the + * database when we're in the middle of an update. + */ + setsid(); + + /* + * Security: don't create root-owned files that contain untrusted data. + * And don't create Postfix-owned files in root-owned directories, + * either. We want a correct relationship between (file/directory) + * ownership and (file/directory) content. + * + * XXX Non-root open can violate the principle of least surprise: Postfix + * can't open an *SQL config file for database read-write access, even + * though it can open that same control file for database read-only + * access. + * + * The solution is to query a map type and obtain its properties before + * opening it. A clean solution is to add a dict_info() API that is + * similar to dict_open() except it returns properties (dict flags) only. + * A pragmatic solution is to overload the existing API and have + * dict_open() return a dummy map when given a null map name. + * + * However, the proxymap daemon has been opening *SQL maps as non-root for + * years now without anyone complaining, let's not solve a problem that + * doesn't exist. + */ + SAVE_AND_SET_EUGID(var_owner_uid, var_owner_gid); + redirect = vstring_alloc(100); + + /* + * Keep state in persistent (external) or volatile (internal) map. + * + * Start the cache cleanup thread after permanently dropping privileges. + */ +#define VERIFY_DICT_OPEN_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_SYNC_UPDATE \ + | DICT_FLAG_OPEN_LOCK | DICT_FLAG_UTF8_REQUEST) + + saved_mask = umask(022); + verify_map = + dict_cache_open(*var_verify_map ? + data_redirect_map(redirect, var_verify_map) : + "internal:verify", + O_CREAT | O_RDWR, VERIFY_DICT_OPEN_FLAGS); + (void) umask(saved_mask); + + /* + * Clean up and restore privilege. + */ + vstring_free(redirect); + RESTORE_SAVED_EUGID(); +} + +/* post_accept_init - announce our protocol */ + +static void post_accept_init(VSTREAM *stream, char *unused_name, + char **unused_argv, HTABLE *unused_table) +{ + + /* + * Announce the protocol. + */ + attr_print(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_VERIFY), + ATTR_TYPE_END); + (void) vstream_fflush(stream); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the multi-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_STR_TABLE str_table[] = { + VAR_VERIFY_MAP, DEF_VERIFY_MAP, &var_verify_map, 0, 0, + VAR_VERIFY_SENDER, DEF_VERIFY_SENDER, &var_verify_sender, 0, 0, + 0, + }; + static const CONFIG_TIME_TABLE time_table[] = { + VAR_VERIFY_POS_EXP, DEF_VERIFY_POS_EXP, &var_verify_pos_exp, 1, 0, + VAR_VERIFY_POS_TRY, DEF_VERIFY_POS_TRY, &var_verify_pos_try, 1, 0, + VAR_VERIFY_NEG_EXP, DEF_VERIFY_NEG_EXP, &var_verify_neg_exp, 1, 0, + VAR_VERIFY_NEG_TRY, DEF_VERIFY_NEG_TRY, &var_verify_neg_try, 1, 0, + VAR_VERIFY_SCAN_CACHE, DEF_VERIFY_SCAN_CACHE, &var_verify_scan_cache, 0, 0, + VAR_VERIFY_SENDER_TTL, DEF_VERIFY_SENDER_TTL, &var_verify_sender_ttl, 0, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + multi_server_main(argc, argv, verify_service, + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_PRE_INIT(pre_jail_init), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_POST_ACCEPT(post_accept_init), + CA_MAIL_SERVER_SOLITARY, + CA_MAIL_SERVER_EXIT(verify_dump), + 0); +} diff --git a/src/virtual/.indent.pro b/src/virtual/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/virtual/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/virtual/.printfck b/src/virtual/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/virtual/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/virtual/Makefile.in b/src/virtual/Makefile.in new file mode 100644 index 0000000..31fff0e --- /dev/null +++ b/src/virtual/Makefile.in @@ -0,0 +1,234 @@ +SHELL = /bin/sh +SRCS = virtual.c mailbox.c recipient.c deliver_attr.c maildir.c unknown.c +OBJS = virtual.o mailbox.o recipient.o deliver_attr.o maildir.o unknown.o +HDRS = virtual.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +PROG = virtual +TESTPROG= +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +deliver_attr.o: ../../include/argv.h +deliver_attr.o: ../../include/attr.h +deliver_attr.o: ../../include/check_arg.h +deliver_attr.o: ../../include/deliver_request.h +deliver_attr.o: ../../include/dict.h +deliver_attr.o: ../../include/dsn.h +deliver_attr.o: ../../include/dsn_buf.h +deliver_attr.o: ../../include/htable.h +deliver_attr.o: ../../include/maps.h +deliver_attr.o: ../../include/mbox_conf.h +deliver_attr.o: ../../include/msg.h +deliver_attr.o: ../../include/msg_stats.h +deliver_attr.o: ../../include/myflock.h +deliver_attr.o: ../../include/mymalloc.h +deliver_attr.o: ../../include/nvtable.h +deliver_attr.o: ../../include/recipient_list.h +deliver_attr.o: ../../include/sys_defs.h +deliver_attr.o: ../../include/vbuf.h +deliver_attr.o: ../../include/vstream.h +deliver_attr.o: ../../include/vstring.h +deliver_attr.o: deliver_attr.c +deliver_attr.o: virtual.h +mailbox.o: ../../include/argv.h +mailbox.o: ../../include/attr.h +mailbox.o: ../../include/bounce.h +mailbox.o: ../../include/check_arg.h +mailbox.o: ../../include/defer.h +mailbox.o: ../../include/deliver_request.h +mailbox.o: ../../include/dict.h +mailbox.o: ../../include/dsn.h +mailbox.o: ../../include/dsn_buf.h +mailbox.o: ../../include/dsn_util.h +mailbox.o: ../../include/htable.h +mailbox.o: ../../include/mail_addr_find.h +mailbox.o: ../../include/mail_addr_form.h +mailbox.o: ../../include/mail_copy.h +mailbox.o: ../../include/mail_params.h +mailbox.o: ../../include/maps.h +mailbox.o: ../../include/mbox_conf.h +mailbox.o: ../../include/mbox_open.h +mailbox.o: ../../include/msg.h +mailbox.o: ../../include/msg_stats.h +mailbox.o: ../../include/myflock.h +mailbox.o: ../../include/mymalloc.h +mailbox.o: ../../include/nvtable.h +mailbox.o: ../../include/recipient_list.h +mailbox.o: ../../include/safe_open.h +mailbox.o: ../../include/sent.h +mailbox.o: ../../include/set_eugid.h +mailbox.o: ../../include/stringops.h +mailbox.o: ../../include/sys_defs.h +mailbox.o: ../../include/vbuf.h +mailbox.o: ../../include/vstream.h +mailbox.o: ../../include/vstring.h +mailbox.o: mailbox.c +mailbox.o: virtual.h +maildir.o: ../../include/argv.h +maildir.o: ../../include/attr.h +maildir.o: ../../include/bounce.h +maildir.o: ../../include/check_arg.h +maildir.o: ../../include/defer.h +maildir.o: ../../include/deliver_request.h +maildir.o: ../../include/dict.h +maildir.o: ../../include/dsn.h +maildir.o: ../../include/dsn_buf.h +maildir.o: ../../include/dsn_util.h +maildir.o: ../../include/get_hostname.h +maildir.o: ../../include/htable.h +maildir.o: ../../include/mail_copy.h +maildir.o: ../../include/mail_params.h +maildir.o: ../../include/make_dirs.h +maildir.o: ../../include/maps.h +maildir.o: ../../include/mbox_conf.h +maildir.o: ../../include/mbox_open.h +maildir.o: ../../include/msg.h +maildir.o: ../../include/msg_stats.h +maildir.o: ../../include/myflock.h +maildir.o: ../../include/mymalloc.h +maildir.o: ../../include/nvtable.h +maildir.o: ../../include/recipient_list.h +maildir.o: ../../include/safe_open.h +maildir.o: ../../include/sane_fsops.h +maildir.o: ../../include/sent.h +maildir.o: ../../include/set_eugid.h +maildir.o: ../../include/stringops.h +maildir.o: ../../include/sys_defs.h +maildir.o: ../../include/vbuf.h +maildir.o: ../../include/vstream.h +maildir.o: ../../include/vstring.h +maildir.o: ../../include/warn_stat.h +maildir.o: maildir.c +maildir.o: virtual.h +recipient.o: ../../include/argv.h +recipient.o: ../../include/attr.h +recipient.o: ../../include/bounce.h +recipient.o: ../../include/check_arg.h +recipient.o: ../../include/deliver_request.h +recipient.o: ../../include/dict.h +recipient.o: ../../include/dsn.h +recipient.o: ../../include/dsn_buf.h +recipient.o: ../../include/htable.h +recipient.o: ../../include/maps.h +recipient.o: ../../include/mbox_conf.h +recipient.o: ../../include/msg.h +recipient.o: ../../include/msg_stats.h +recipient.o: ../../include/myflock.h +recipient.o: ../../include/mymalloc.h +recipient.o: ../../include/nvtable.h +recipient.o: ../../include/recipient_list.h +recipient.o: ../../include/stringops.h +recipient.o: ../../include/sys_defs.h +recipient.o: ../../include/vbuf.h +recipient.o: ../../include/vstream.h +recipient.o: ../../include/vstring.h +recipient.o: recipient.c +recipient.o: virtual.h +unknown.o: ../../include/argv.h +unknown.o: ../../include/attr.h +unknown.o: ../../include/bounce.h +unknown.o: ../../include/check_arg.h +unknown.o: ../../include/deliver_request.h +unknown.o: ../../include/dict.h +unknown.o: ../../include/dsn.h +unknown.o: ../../include/dsn_buf.h +unknown.o: ../../include/htable.h +unknown.o: ../../include/maps.h +unknown.o: ../../include/mbox_conf.h +unknown.o: ../../include/msg.h +unknown.o: ../../include/msg_stats.h +unknown.o: ../../include/myflock.h +unknown.o: ../../include/mymalloc.h +unknown.o: ../../include/nvtable.h +unknown.o: ../../include/recipient_list.h +unknown.o: ../../include/sys_defs.h +unknown.o: ../../include/vbuf.h +unknown.o: ../../include/vstream.h +unknown.o: ../../include/vstring.h +unknown.o: unknown.c +unknown.o: virtual.h +virtual.o: ../../include/argv.h +virtual.o: ../../include/attr.h +virtual.o: ../../include/check_arg.h +virtual.o: ../../include/deliver_completed.h +virtual.o: ../../include/deliver_request.h +virtual.o: ../../include/dict.h +virtual.o: ../../include/dsn.h +virtual.o: ../../include/dsn_buf.h +virtual.o: ../../include/flush_clnt.h +virtual.o: ../../include/htable.h +virtual.o: ../../include/iostuff.h +virtual.o: ../../include/mail_addr_find.h +virtual.o: ../../include/mail_addr_form.h +virtual.o: ../../include/mail_conf.h +virtual.o: ../../include/mail_params.h +virtual.o: ../../include/mail_queue.h +virtual.o: ../../include/mail_server.h +virtual.o: ../../include/mail_version.h +virtual.o: ../../include/maps.h +virtual.o: ../../include/mbox_conf.h +virtual.o: ../../include/msg.h +virtual.o: ../../include/msg_stats.h +virtual.o: ../../include/myflock.h +virtual.o: ../../include/mymalloc.h +virtual.o: ../../include/nvtable.h +virtual.o: ../../include/recipient_list.h +virtual.o: ../../include/set_eugid.h +virtual.o: ../../include/sys_defs.h +virtual.o: ../../include/vbuf.h +virtual.o: ../../include/vstream.h +virtual.o: ../../include/vstring.h +virtual.o: virtual.c +virtual.o: virtual.h diff --git a/src/virtual/deliver_attr.c b/src/virtual/deliver_attr.c new file mode 100644 index 0000000..3a5c0af --- /dev/null +++ b/src/virtual/deliver_attr.c @@ -0,0 +1,90 @@ +/*++ +/* NAME +/* deliver_attr 3 +/* SUMMARY +/* initialize message delivery attributes +/* SYNOPSIS +/* #include "virtual.h" +/* +/* void deliver_attr_init(attrp) +/* DELIVER_ATTR *attrp; +/* +/* void deliver_attr_dump(attrp) +/* DELIVER_ATTR *attrp; +/* +/* void deliver_attr_free(attrp) +/* DELIVER_ATTR *attrp; +/* DESCRIPTION +/* deliver_attr_init() initializes a structure with message delivery +/* attributes to a known initial state (all zeros). +/* +/* deliver_attr_dump() logs the contents of the given attribute list. +/* +/* deliver_attr_free() releases memory that was allocated by +/* deliver_attr_init(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include + +/* Application-specific. */ + +#include "virtual.h" + +/* deliver_attr_init - set message delivery attributes to all-zero state */ + +void deliver_attr_init(DELIVER_ATTR *attrp) +{ + attrp->level = 0; + attrp->fp = 0; + attrp->queue_name = 0; + attrp->queue_id = 0; + attrp->offset = 0; + attrp->sender = 0; + RECIPIENT_ASSIGN(&(attrp->rcpt), 0, 0, 0, 0, 0); + attrp->user = 0; + attrp->delivered = 0; + attrp->relay = 0; + attrp->why = dsb_create(); +} + +/* deliver_attr_dump - log message delivery attributes */ + +void deliver_attr_dump(DELIVER_ATTR *attrp) +{ + msg_info("level: %d", attrp->level); + msg_info("path: %s", VSTREAM_PATH(attrp->fp)); + msg_info("fp: 0x%lx", (long) attrp->fp); + msg_info("queue_name: %s", attrp->queue_name ? attrp->queue_name : "null"); + msg_info("queue_id: %s", attrp->queue_id ? attrp->queue_id : "null"); + msg_info("offset: %ld", attrp->offset); + msg_info("sender: %s", attrp->sender ? attrp->sender : "null"); + msg_info("recipient: %s", attrp->rcpt.address ? attrp->rcpt.address : "null"); + msg_info("user: %s", attrp->user ? attrp->user : "null"); + msg_info("delivered: %s", attrp->delivered ? attrp->delivered : "null"); + msg_info("relay: %s", attrp->relay ? attrp->relay : "null"); + msg_info("why: %s", attrp->why ? "buffer" : "null"); +} + + +/* deliver_attr_free - release storage */ + +void deliver_attr_free(DELIVER_ATTR *attrp) +{ + dsb_free(attrp->why); +} diff --git a/src/virtual/mailbox.c b/src/virtual/mailbox.c new file mode 100644 index 0000000..19afca8 --- /dev/null +++ b/src/virtual/mailbox.c @@ -0,0 +1,283 @@ +/*++ +/* NAME +/* mailbox 3 +/* SUMMARY +/* mailbox delivery +/* SYNOPSIS +/* #include "virtual.h" +/* +/* int deliver_mailbox(state, usr_attr, statusp) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* int *statusp; +/* DESCRIPTION +/* deliver_mailbox() delivers to UNIX-style mailbox or to maildir. +/* +/* A zero result means that the named user was not found. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* .IP usr_attr +/* Attributes describing user rights and mailbox location. +/* .IP statusp +/* Delivery status: see below. +/* DIAGNOSTICS +/* The message delivery status is non-zero when delivery should be tried +/* again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "virtual.h" + +#define YES 1 +#define NO 0 + +/* deliver_mailbox_file - deliver to recipient mailbox */ + +static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_mailbox_file"; + DSN_BUF *why = state.msg_attr.why; + MBOX *mp; + int mail_copy_status; + int deliver_status; + int copy_flags; + struct stat st; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Don't deliver trace-only requests. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to mailbox"); + return (sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr))); + } + + /* + * Initialize. Assume the operation will fail. Set the delivered + * attribute to reflect the final recipient. + */ + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); + state.msg_attr.delivered = state.msg_attr.rcpt.address; + mail_copy_status = MAIL_COPY_STAT_WRITE; + + /* + * Lock the mailbox and open/create the mailbox file. + * + * Write the file as the recipient, so that file quota work. + */ + copy_flags = MAIL_COPY_MBOX; + + set_eugid(usr_attr.uid, usr_attr.gid); + mp = mbox_open(usr_attr.mailbox, O_APPEND | O_WRONLY | O_CREAT, + S_IRUSR | S_IWUSR, &st, -1, -1, + virtual_mbox_lock_mask, "4.2.0", why); + if (mp != 0) { + if (S_ISREG(st.st_mode) == 0) { + vstream_fclose(mp->fp); + msg_warn("recipient %s: destination %s is not a regular file", + state.msg_attr.rcpt.address, usr_attr.mailbox); + dsb_simple(why, "5.3.5", "mail system configuration error"); + } else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) { + vstream_fclose(mp->fp); + dsb_simple(why, "4.2.0", + "destination %s is not owned by recipient", usr_attr.mailbox); + msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch", + VAR_STRICT_MBOX_OWNER); + } else { + if (vstream_fseek(mp->fp, (off_t) 0, SEEK_END) < 0) + msg_fatal("%s: seek mailbox file %s: %m", + myname, VSTREAM_PATH(mp->fp)); + mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, + copy_flags, "\n", why); + } + mbox_release(mp); + } + set_eugid(var_owner_uid, var_owner_gid); + + /* + * As the mail system, bounce, defer delivery, or report success. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + vstring_sprintf_prepend(why->reason, "delivery failed to mailbox %s: ", + usr_attr.mailbox); + deliver_status = + (STR(why->status)[0] == '4' ? + defer_append : bounce_append) + (BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + dsb_simple(why, "2.0.0", "delivered to mailbox"); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + } + return (deliver_status); +} + +/* deliver_mailbox - deliver to recipient mailbox */ + +int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) +{ + const char *myname = "deliver_mailbox"; + const char *mailbox_res; + const char *uid_res; + const char *gid_res; + DSN_BUF *why = state.msg_attr.why; + long n; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Sanity check. + */ + if (*var_virt_mailbox_base != '/') + msg_fatal("do not specify relative pathname: %s = %s", + VAR_VIRT_MAILBOX_BASE, var_virt_mailbox_base); + + /* + * Look up the mailbox location. Bounce if not found, defer in case of + * trouble. + */ +#define IGNORE_EXTENSION ((char **) 0) + + mailbox_res = mail_addr_find(virtual_mailbox_maps, state.msg_attr.user, + IGNORE_EXTENSION); + if (mailbox_res == 0) { + if (virtual_mailbox_maps->error == 0) + return (NO); + msg_warn("table %s: lookup %s: %m", virtual_mailbox_maps->title, + state.msg_attr.user); + dsb_simple(why, "4.3.5", "mail system configuration error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + return (YES); + } + usr_attr.mailbox = concatenate(var_virt_mailbox_base, "/", + mailbox_res, (char *) 0); + +#define RETURN(res) { myfree(usr_attr.mailbox); return (res); } + + /* + * Look up the mailbox owner rights. Defer in case of trouble. + */ + uid_res = mail_addr_find(virtual_uid_maps, state.msg_attr.user, + IGNORE_EXTENSION); + if (uid_res == 0) { + msg_warn("recipient %s: not found in %s", + state.msg_attr.user, virtual_uid_maps->title); + dsb_simple(why, "4.3.5", "mail system configuration error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + RETURN(YES); + } + if ((n = atol(uid_res)) < var_virt_minimum_uid) { + msg_warn("recipient %s: bad uid %s in %s", + state.msg_attr.user, uid_res, virtual_uid_maps->title); + dsb_simple(why, "4.3.5", "mail system configuration error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + RETURN(YES); + } + usr_attr.uid = (uid_t) n; + + /* + * Look up the mailbox group rights. Defer in case of trouble. + */ + gid_res = mail_addr_find(virtual_gid_maps, state.msg_attr.user, + IGNORE_EXTENSION); + if (gid_res == 0) { + msg_warn("recipient %s: not found in %s", + state.msg_attr.user, virtual_gid_maps->title); + dsb_simple(why, "4.3.5", "mail system configuration error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + RETURN(YES); + } + if ((n = atol(gid_res)) <= 0) { + msg_warn("recipient %s: bad gid %s in %s", + state.msg_attr.user, gid_res, virtual_gid_maps->title); + dsb_simple(why, "4.3.5", "mail system configuration error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + RETURN(YES); + } + usr_attr.gid = (gid_t) n; + + if (msg_verbose) + msg_info("%s[%d]: set user_attr: %s, uid = %u, gid = %u", + myname, state.level, usr_attr.mailbox, + (unsigned) usr_attr.uid, (unsigned) usr_attr.gid); + + /* + * Deliver to mailbox or to maildir. + */ +#define LAST_CHAR(s) (s[strlen(s) - 1]) + + if (LAST_CHAR(usr_attr.mailbox) == '/') + *statusp = deliver_maildir(state, usr_attr); + else + *statusp = deliver_mailbox_file(state, usr_attr); + + /* + * Cleanup. + */ + RETURN(YES); +} diff --git a/src/virtual/maildir.c b/src/virtual/maildir.c new file mode 100644 index 0000000..a677061 --- /dev/null +++ b/src/virtual/maildir.c @@ -0,0 +1,254 @@ +/*++ +/* NAME +/* maildir 3 +/* SUMMARY +/* delivery to maildir +/* SYNOPSIS +/* #include "virtual.h" +/* +/* int deliver_maildir(state, usr_attr) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* DESCRIPTION +/* deliver_maildir() delivers a message to a qmail-style maildir. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* .IP usr_attr +/* Attributes describing user rights and environment information. +/* DIAGNOSTICS +/* deliver_maildir() always succeeds or it bounces the message. +/* SEE ALSO +/* bounce(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include + +/* Application-specific. */ + +#include "virtual.h" + +/* deliver_maildir - delivery to maildir-style mailbox */ + +int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_maildir"; + char *newdir; + char *tmpdir; + char *curdir; + char *tmpfile; + char *newfile; + DSN_BUF *why = state.msg_attr.why; + VSTRING *buf; + VSTREAM *dst; + int mail_copy_status; + int deliver_status; + int copy_flags; + struct stat st; + struct timeval starttime; + + GETTIMEOFDAY(&starttime); + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Don't deliver trace-only requests. + */ + if (DEL_REQ_TRACE_ONLY(state.request->flags)) { + dsb_simple(why, "2.0.0", "delivers to maildir"); + return (sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr))); + } + + /* + * Initialize. Assume the operation will fail. Set the delivered + * attribute to reflect the final recipient. + */ + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); + state.msg_attr.delivered = state.msg_attr.rcpt.address; + mail_copy_status = MAIL_COPY_STAT_WRITE; + buf = vstring_alloc(100); + + copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH + | MAIL_COPY_DELIVERED | MAIL_COPY_ORIG_RCPT; + + newdir = concatenate(usr_attr.mailbox, "new/", (char *) 0); + tmpdir = concatenate(usr_attr.mailbox, "tmp/", (char *) 0); + curdir = concatenate(usr_attr.mailbox, "cur/", (char *) 0); + + /* + * Create and write the file as the recipient, so that file quota work. + * Create any missing directories on the fly. The file name is chosen + * according to ftp://koobera.math.uic.edu/www/proto/maildir.html: + * + * "A unique name has three pieces, separated by dots. On the left is the + * result of time(). On the right is the result of gethostname(). In the + * middle is something that doesn't repeat within one second on a single + * host. I fork a new process for each delivery, so I just use the + * process ID. If you're delivering several messages from one process, + * use starttime.pid_count.host, where starttime is the time that your + * process started, and count is the number of messages you've + * delivered." + * + * Well, that stopped working on fast machines, and on operating systems + * that randomize process ID values. When creating a file in tmp/ we use + * the process ID because it still is an exclusive resource. When moving + * the file to new/ we use the device number and inode number. I do not + * care if this breaks on a remote AFS file system, because people should + * know better. + * + * On January 26, 2003, http://cr.yp.to/proto/maildir.html said: + * + * A unique name has three pieces, separated by dots. On the left is the + * result of time() or the second counter from gettimeofday(). On the + * right is the result of gethostname(). (To deal with invalid host + * names, replace / with \057 and : with \072.) In the middle is a + * delivery identifier, discussed below. + * + * [...] + * + * Modern delivery identifiers are created by concatenating enough of the + * following strings to guarantee uniqueness: + * + * [...] + * + * In, where n is (in hexadecimal) the UNIX inode number of this file. + * Unfortunately, inode numbers aren't always available through NFS. + * + * Vn, where n is (in hexadecimal) the UNIX device number of this file. + * Unfortunately, device numbers aren't always available through NFS. + * (Device numbers are also not helpful with the standard UNIX + * filesystem: a maildir has to be within a single UNIX device for link() + * and rename() to work.) + * + * Mn, where n is (in decimal) the microsecond counter from the same + * gettimeofday() used for the left part of the unique name. + * + * Pn, where n is (in decimal) the process ID. + * + * [...] + */ + set_eugid(usr_attr.uid, usr_attr.gid); + vstring_sprintf(buf, "%lu.P%d.%s", + (unsigned long) starttime.tv_sec, var_pid, get_hostname()); + tmpfile = concatenate(tmpdir, STR(buf), (char *) 0); + newfile = 0; + if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0 + && (errno != ENOENT + || make_dirs(tmpdir, 0700) < 0 + || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) { + dsb_simple(why, mbox_dsn(errno, "4.2.0"), + "create maildir file %s: %m", tmpfile); + } else if (fstat(vstream_fileno(dst), &st) < 0) { + + /* + * Coverity 200604: file descriptor leak in code that never executes. + * Code replaced by msg_fatal(), as it is not worthwhile to continue + * after an impossible error condition. + */ + msg_fatal("fstat %s: %m", tmpfile); + } else { + vstring_sprintf(buf, "%lu.V%lxI%lxM%lu.%s", + (unsigned long) starttime.tv_sec, + (unsigned long) st.st_dev, + (unsigned long) st.st_ino, + (unsigned long) starttime.tv_usec, + get_hostname()); + newfile = concatenate(newdir, STR(buf), (char *) 0); + if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), + dst, copy_flags, "\n", + why)) == 0) { + if (sane_link(tmpfile, newfile) < 0 + && (errno != ENOENT + || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0 + || sane_link(tmpfile, newfile) < 0)) { + dsb_simple(why, mbox_dsn(errno, "4.2.0"), + "create maildir file %s: %m", newfile); + mail_copy_status = MAIL_COPY_STAT_WRITE; + } + } + if (unlink(tmpfile) < 0) + msg_warn("remove %s: %m", tmpfile); + } + set_eugid(var_owner_uid, var_owner_gid); + + /* + * The maildir location is controlled by the mail administrator. If + * delivery fails, try again later. We would just bounce when the maildir + * location possibly under user control. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + if (errno == EACCES) { + msg_warn("maildir access problem for UID/GID=%lu/%lu: %s", + (long) usr_attr.uid, (long) usr_attr.gid, + STR(why->reason)); + msg_warn("perhaps you need to create the maildirs in advance"); + } + vstring_sprintf_prepend(why->reason, "maildir delivery failed: "); + deliver_status = + (STR(why->status)[0] == '4' ? + defer_append : bounce_append) + (BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + } else { + dsb_simple(why, "2.0.0", "delivered to maildir"); + deliver_status = sent(BOUNCE_FLAGS(state.request), + SENT_ATTR(state.msg_attr)); + } + vstring_free(buf); + myfree(newdir); + myfree(tmpdir); + myfree(curdir); + myfree(tmpfile); + if (newfile) + myfree(newfile); + return (deliver_status); +} diff --git a/src/virtual/recipient.c b/src/virtual/recipient.c new file mode 100644 index 0000000..fd151f2 --- /dev/null +++ b/src/virtual/recipient.c @@ -0,0 +1,94 @@ +/*++ +/* NAME +/* recipient 3 +/* SUMMARY +/* deliver to one local recipient +/* SYNOPSIS +/* #include "virtual.h" +/* +/* int deliver_recipient(state, usr_attr) +/* LOCAL_STATE state; +/* USER_ATTR *usr_attr; +/* DESCRIPTION +/* deliver_recipient() delivers a message to a local recipient. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, sender, and more. +/* .IP usr_attr +/* Attributes describing user rights and mailbox location. +/* DIAGNOSTICS +/* deliver_recipient() returns non-zero when delivery should be +/* tried again. +/* SEE ALSO +/* mailbox(3) delivery to UNIX-style mailbox +/* maildir(3) delivery to qmail-style maildir +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include "virtual.h" + +/* deliver_recipient - deliver one local recipient */ + +int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr) +{ + const char *myname = "deliver_recipient"; + VSTRING *folded; + int rcpt_stat; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Set up the recipient-specific attributes. The recipient's lookup + * handle is the full address. + */ + if (state.msg_attr.delivered == 0) + state.msg_attr.delivered = state.msg_attr.rcpt.address; + folded = vstring_alloc(100); + state.msg_attr.user = casefold(folded, state.msg_attr.rcpt.address); + + /* + * Deliver + */ + if (msg_verbose) + deliver_attr_dump(&state.msg_attr); + + if (deliver_mailbox(state, usr_attr, &rcpt_stat) == 0) + rcpt_stat = deliver_unknown(state); + + /* + * Cleanup. + */ + vstring_free(folded); + + return (rcpt_stat); +} diff --git a/src/virtual/unknown.c b/src/virtual/unknown.c new file mode 100644 index 0000000..ad8d64a --- /dev/null +++ b/src/virtual/unknown.c @@ -0,0 +1,65 @@ +/*++ +/* NAME +/* unknown 3 +/* SUMMARY +/* delivery of unknown recipients +/* SYNOPSIS +/* #include "virtual.h" +/* +/* int deliver_unknown(state) +/* LOCAL_STATE state; +/* DESCRIPTION +/* deliver_unknown() delivers a message for unknown recipients. +/* .PP +/* Arguments: +/* .IP state +/* Message delivery attributes (sender, recipient etc.). +/* .IP usr_attr +/* Attributes describing user rights and mailbox location. +/* DIAGNOSTICS +/* The result status is non-zero when delivery should be tried again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include "virtual.h" + +/* deliver_unknown - delivery for unknown recipients */ + +int deliver_unknown(LOCAL_STATE state) +{ + const char *myname = "deliver_unknown"; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + dsb_simple(state.msg_attr.why, "5.1.1", + "unknown user: \"%s\"", state.msg_attr.user); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); +} diff --git a/src/virtual/virtual.c b/src/virtual/virtual.c new file mode 100644 index 0000000..6fa9f1e --- /dev/null +++ b/src/virtual/virtual.c @@ -0,0 +1,573 @@ +/*++ +/* NAME +/* virtual 8 +/* SUMMARY +/* Postfix virtual domain mail delivery agent +/* SYNOPSIS +/* \fBvirtual\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBvirtual\fR(8) delivery agent is designed for virtual mail +/* hosting services. Originally based on the Postfix \fBlocal\fR(8) +/* delivery +/* agent, this agent looks up recipients with map lookups of their +/* full recipient address, instead of using hard-coded unix password +/* file lookups of the address local part only. +/* +/* This delivery agent only delivers mail. Other features such as +/* mail forwarding, out-of-office notifications, etc., must be +/* configured via virtual_alias maps or via similar lookup mechanisms. +/* MAILBOX LOCATION +/* .ad +/* .fi +/* The mailbox location is controlled by the \fBvirtual_mailbox_base\fR +/* and \fBvirtual_mailbox_maps\fR configuration parameters (see below). +/* The \fBvirtual_mailbox_maps\fR table is indexed by the recipient +/* address as described under TABLE SEARCH ORDER below. +/* +/* The mailbox pathname is constructed as follows: +/* +/* .nf +/* \fB$virtual_mailbox_base/$virtual_mailbox_maps(\fIrecipient\fB)\fR +/* .fi +/* +/* where \fIrecipient\fR is the full recipient address. +/* UNIX MAILBOX FORMAT +/* .ad +/* .fi +/* When the mailbox location does not end in \fB/\fR, the message +/* is delivered in UNIX mailbox format. This format stores multiple +/* messages in one textfile. +/* +/* The \fBvirtual\fR(8) delivery agent prepends a "\fBFrom \fIsender +/* time_stamp\fR" envelope header to each message, prepends a +/* \fBDelivered-To:\fR message header with the envelope recipient +/* address, +/* prepends an \fBX-Original-To:\fR header with the recipient address as +/* given to Postfix, +/* prepends a \fBReturn-Path:\fR message header with the +/* envelope sender address, prepends a \fB>\fR character to lines +/* beginning with "\fBFrom \fR", and appends an empty line. +/* +/* The mailbox is locked for exclusive access while delivery is in +/* progress. In case of problems, an attempt is made to truncate the +/* mailbox to its original length. +/* QMAIL MAILDIR FORMAT +/* .ad +/* .fi +/* When the mailbox location ends in \fB/\fR, the message is delivered +/* in qmail \fBmaildir\fR format. This format stores one message per file. +/* +/* The \fBvirtual\fR(8) delivery agent prepends a \fBDelivered-To:\fR +/* message header with the final envelope recipient address, +/* prepends an \fBX-Original-To:\fR header with the recipient address as +/* given to Postfix, and prepends a +/* \fBReturn-Path:\fR message header with the envelope sender address. +/* +/* By definition, \fBmaildir\fR format does not require application-level +/* file locking during mail delivery or retrieval. +/* MAILBOX OWNERSHIP +/* .ad +/* .fi +/* Mailbox ownership is controlled by the \fBvirtual_uid_maps\fR +/* and \fBvirtual_gid_maps\fR lookup tables, which are indexed +/* with the full recipient address. Each table provides +/* a string with the numerical user and group ID, respectively. +/* +/* The \fBvirtual_minimum_uid\fR parameter imposes a lower bound on +/* numerical user ID values that may be specified in any +/* \fBvirtual_uid_maps\fR. +/* CASE FOLDING +/* .ad +/* .fi +/* All delivery decisions are made using the full recipient +/* address, folded to lower case. See also the next section +/* for a few exceptions with optional address extensions. +/* TABLE SEARCH ORDER +/* .ad +/* .fi +/* Normally, a lookup table is specified as a text file that +/* serves as input to the \fBpostmap\fR(1) command. The result, an +/* indexed file in \fBdbm\fR or \fBdb\fR format, is used for fast +/* searching by the mail system. +/* +/* The search order is as follows. The search stops +/* upon the first successful lookup. +/* .IP \(bu +/* When the recipient has an optional address extension the +/* \fIuser+extension@domain.tld\fR address is looked up first. +/* .sp +/* With Postfix versions before 2.1, the optional address extension +/* is always ignored. +/* .IP \(bu +/* The \fIuser@domain.tld\fR address, without address extension, +/* is looked up next. +/* .IP \(bu +/* Finally, the recipient \fI@domain\fR is looked up. +/* .PP +/* When the table is provided via other means such as NIS, LDAP +/* or SQL, the same lookups are done as for ordinary indexed files. +/* +/* Alternatively, a table can be provided as a regular-expression +/* map where patterns are given as regular expressions. In that case, +/* only the full recipient address is given to the regular-expression +/* map. +/* SECURITY +/* .ad +/* .fi +/* The \fBvirtual\fR(8) delivery agent is not security sensitive, provided +/* that the lookup tables with recipient user/group ID information are +/* adequately protected. This program is not designed to run chrooted. +/* +/* The \fBvirtual\fR(8) delivery agent disallows regular expression +/* substitution of $1 etc. in regular expression lookup tables, +/* because that would open a security hole. +/* +/* The \fBvirtual\fR(8) delivery agent will silently ignore requests +/* to use the \fBproxymap\fR(8) server. Instead it will open the +/* table directly. Before Postfix version 2.2, the virtual +/* delivery agent will terminate with a fatal error. +/* STANDARDS +/* RFC 822 (ARPA Internet Text Messages) +/* DIAGNOSTICS +/* Mail bounces when the recipient has no mailbox or when the +/* recipient is over disk quota. In all other problem cases, mail for +/* an existing recipient is deferred and a warning is logged. +/* +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* Corrupted message files are marked so that the queue +/* manager can move them to the \fBcorrupt\fR queue afterwards. +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces and of other trouble. +/* BUGS +/* This delivery agent supports address extensions in email +/* addresses and in lookup table keys, but does not propagate +/* address extension information to the result of table lookup. +/* +/* Postfix should have lookup tables that can return multiple result +/* attributes. In order to avoid the inconvenience of maintaining +/* three tables, use an LDAP or MYSQL database. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* Changes to \fBmain.cf\fR are picked up automatically, as +/* \fBvirtual\fR(8) +/* processes run for only a limited amount of time. Use the command +/* "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* MAILBOX DELIVERY CONTROLS +/* .ad +/* .fi +/* .IP "\fBvirtual_mailbox_base (empty)\fR" +/* A prefix that the \fBvirtual\fR(8) delivery agent prepends to all pathname +/* results from $virtual_mailbox_maps table lookups. +/* .IP "\fBvirtual_mailbox_maps (empty)\fR" +/* Optional lookup tables with all valid addresses in the domains that +/* match $virtual_mailbox_domains. +/* .IP "\fBvirtual_minimum_uid (100)\fR" +/* The minimum user ID value that the \fBvirtual\fR(8) delivery agent accepts +/* as a result from $virtual_uid_maps table lookup. +/* .IP "\fBvirtual_uid_maps (empty)\fR" +/* Lookup tables with the per-recipient user ID that the \fBvirtual\fR(8) +/* delivery agent uses while writing to the recipient's mailbox. +/* .IP "\fBvirtual_gid_maps (empty)\fR" +/* Lookup tables with the per-recipient group ID for \fBvirtual\fR(8) mailbox +/* delivery. +/* .PP +/* Available in Postfix version 2.0 and later: +/* .IP "\fBvirtual_mailbox_domains ($virtual_mailbox_maps)\fR" +/* Postfix is the final destination for the specified list of domains; +/* mail is delivered via the $virtual_transport mail delivery transport. +/* .IP "\fBvirtual_transport (virtual)\fR" +/* The default mail delivery transport and next-hop destination for +/* final delivery to domains listed with $virtual_mailbox_domains. +/* .PP +/* Available in Postfix version 2.5.3 and later: +/* .IP "\fBstrict_mailbox_ownership (yes)\fR" +/* Defer delivery when a mailbox file is not owned by its recipient. +/* LOCKING CONTROLS +/* .ad +/* .fi +/* .IP "\fBvirtual_mailbox_lock (see 'postconf -d' output)\fR" +/* How to lock a UNIX-style \fBvirtual\fR(8) mailbox before attempting +/* delivery. +/* .IP "\fBdeliver_lock_attempts (20)\fR" +/* The maximal number of attempts to acquire an exclusive lock on a +/* mailbox file or \fBbounce\fR(8) logfile. +/* .IP "\fBdeliver_lock_delay (1s)\fR" +/* The time between attempts to acquire an exclusive lock on a mailbox +/* file or \fBbounce\fR(8) logfile. +/* .IP "\fBstale_lock_time (500s)\fR" +/* The time after which a stale exclusive mailbox lockfile is removed. +/* RESOURCE AND RATE CONTROLS +/* .ad +/* .fi +/* .IP "\fBvirtual_mailbox_limit (51200000)\fR" +/* The maximal size in bytes of an individual \fBvirtual\fR(8) mailbox or +/* maildir file, or zero (no limit). +/* .PP +/* Implemented in the qmgr(8) daemon: +/* .IP "\fBvirtual_destination_concurrency_limit ($default_destination_concurrency_limit)\fR" +/* The maximal number of parallel deliveries to the same destination +/* via the virtual message delivery transport. +/* .IP "\fBvirtual_destination_recipient_limit ($default_destination_recipient_limit)\fR" +/* The maximal number of recipients per message for the virtual +/* message delivery transport. +/* MISCELLANEOUS CONTROLS +/* .ad +/* .fi +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBdelay_logging_resolution_limit (2)\fR" +/* The maximal number of digits after the decimal point when logging +/* sub-second delay values. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix version 3.0 and later: +/* .IP "\fBvirtual_delivery_status_filter ($default_delivery_status_filter)\fR" +/* Optional filter for the \fBvirtual\fR(8) delivery agent to change the +/* delivery status code or explanatory text of successful or unsuccessful +/* deliveries. +/* .PP +/* Available in Postfix version 3.3 and later: +/* .IP "\fBenable_original_recipient (yes)\fR" +/* Enable support for the original recipient address after an +/* address is rewritten to a different address (for example with +/* aliasing or with canonical mapping). +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* .PP +/* Available in Postfix 3.5 and later: +/* .IP "\fBinfo_log_address_format (external)\fR" +/* The email address form that will be used in non-debug logging +/* (info, warning, etc.). +/* SEE ALSO +/* qmgr(8), queue manager +/* bounce(8), delivery status reports +/* postconf(5), configuration parameters +/* postlogd(8), Postfix logging +/* syslogd(8), system logging +/* README_FILES +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* VIRTUAL_README, domain hosting howto +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* This delivery agent was originally based on the Postfix local delivery +/* agent. Modifications mainly consisted of removing code that either +/* was not applicable or that was not safe in this context: aliases, +/* ~user/.forward files, delivery to "|command" or to /file/name. +/* +/* The \fBDelivered-To:\fR message header appears in the \fBqmail\fR +/* system by Daniel Bernstein. +/* +/* The \fBmaildir\fR structure appears in the \fBqmail\fR system +/* by Daniel Bernstein. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/* +/* Andrew McNamara +/* andrewm@connect.com.au +/* connect.com.au Pty. Ltd. +/* Level 3, 213 Miller St +/* North Sydney 2060, NSW, Australia +/*--*/ + +/* System library. */ + +#include +#include +#ifdef USE_PATHS_H +#include /* XXX mail_spool_dir dependency */ +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Single server skeleton. */ + +#include + +/* Application-specific. */ + +#include "virtual.h" + + /* + * Tunable parameters. + */ +char *var_virt_mailbox_maps; +char *var_virt_uid_maps; +char *var_virt_gid_maps; +int var_virt_minimum_uid; +char *var_virt_mailbox_base; +char *var_virt_mailbox_lock; +long var_virt_mailbox_limit; +char *var_mail_spool_dir; /* XXX dependency fix */ +bool var_strict_mbox_owner; +char *var_virt_dsn_filter; + + /* + * Mappings. + */ +MAPS *virtual_mailbox_maps; +MAPS *virtual_uid_maps; +MAPS *virtual_gid_maps; + + /* + * Bit masks. + */ +int virtual_mbox_lock_mask; + +/* local_deliver - deliver message with extreme prejudice */ + +static int local_deliver(DELIVER_REQUEST *rqst, char *service) +{ + const char *myname = "local_deliver"; + RECIPIENT *rcpt_end = rqst->rcpt_list.info + rqst->rcpt_list.len; + RECIPIENT *rcpt; + int rcpt_stat; + int msg_stat; + LOCAL_STATE state; + USER_ATTR usr_attr; + + if (msg_verbose) + msg_info("local_deliver: %s from %s", rqst->queue_id, rqst->sender); + + /* + * Initialize the delivery attributes that are not recipient specific. + */ + state.level = 0; + deliver_attr_init(&state.msg_attr); + state.msg_attr.queue_name = rqst->queue_name; + state.msg_attr.queue_id = rqst->queue_id; + state.msg_attr.fp = rqst->fp; + state.msg_attr.offset = rqst->data_offset; + state.msg_attr.sender = rqst->sender; + state.msg_attr.dsn_envid = rqst->dsn_envid; + state.msg_attr.dsn_ret = rqst->dsn_ret; + state.msg_attr.relay = service; + state.msg_attr.msg_stats = rqst->msg_stats; + RESET_USER_ATTR(usr_attr, state.level); + state.request = rqst; + + /* + * Iterate over each recipient named in the delivery request. When the + * mail delivery status for a given recipient is definite (i.e. bounced + * or delivered), update the message queue file and cross off the + * recipient. Update the per-message delivery status. + */ + for (msg_stat = 0, rcpt = rqst->rcpt_list.info; rcpt < rcpt_end; rcpt++) { + state.msg_attr.rcpt = *rcpt; + rcpt_stat = deliver_recipient(state, usr_attr); + if (rcpt_stat == 0 && (rqst->flags & DEL_REQ_FLAG_SUCCESS)) + deliver_completed(state.msg_attr.fp, rcpt->offset); + msg_stat |= rcpt_stat; + } + + deliver_attr_free(&state.msg_attr); + return (msg_stat); +} + +/* local_service - perform service for client */ + +static void local_service(VSTREAM *stream, char *service, char **argv) +{ + DELIVER_REQUEST *request; + int status; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs whenever a client connects to the UNIX-domain socket + * that is dedicated to local mail delivery service. What we see below is + * a little protocol to (1) tell the client that we are ready, (2) read a + * delivery request from the client, and (3) report the completion status + * of that request. + */ + if ((request = deliver_request_read(stream)) != 0) { + status = local_deliver(request, service); + deliver_request_done(stream, request, status); + } +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + const char *table; + + if ((table = dict_changed_name()) != 0) { + msg_info("table %s has changed -- restarting", table); + exit(0); + } +} + +/* post_init - post-jail initialization */ + +static void post_init(char *unused_name, char **unused_argv) +{ + + /* + * Drop privileges most of the time. + */ + set_eugid(var_owner_uid, var_owner_gid); + + /* + * No case folding needed: the recipient address is case folded. + */ + virtual_mailbox_maps = + maps_create(VAR_VIRT_MAILBOX_MAPS, var_virt_mailbox_maps, + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_UTF8_REQUEST); + + virtual_uid_maps = + maps_create(VAR_VIRT_UID_MAPS, var_virt_uid_maps, + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_UTF8_REQUEST); + + virtual_gid_maps = + maps_create(VAR_VIRT_GID_MAPS, var_virt_gid_maps, + DICT_FLAG_LOCK | DICT_FLAG_PARANOID + | DICT_FLAG_UTF8_REQUEST); + + virtual_mbox_lock_mask = mbox_lock_mask(var_virt_mailbox_lock); +} + +/* pre_init - pre-jail initialization */ + +static void pre_init(char *unused_name, char **unused_argv) +{ + + /* + * Reset the file size limit from the message size limit to the mailbox + * size limit. + * + * We can't have mailbox size limit smaller than the message size limit, + * because that prohibits the delivery agent from updating the queue + * file. + */ + if (ENFORCING_SIZE_LIMIT(var_virt_mailbox_limit)) { + if (!ENFORCING_SIZE_LIMIT(var_message_limit)) + msg_fatal("configuration error: %s is limited but %s is " + "unlimited", VAR_VIRT_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT); + if (var_virt_mailbox_limit < var_message_limit) + msg_fatal("configuration error: %s is smaller than %s", + VAR_VIRT_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT); + set_file_limit(var_virt_mailbox_limit); + } + + /* + * flush client. + */ + flush_init(); +} + +MAIL_VERSION_STAMP_DECLARE; + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_INT_TABLE int_table[] = { + VAR_VIRT_MINUID, DEF_VIRT_MINUID, &var_virt_minimum_uid, 1, 0, + 0, + }; + static const CONFIG_LONG_TABLE long_table[] = { + VAR_VIRT_MAILBOX_LIMIT, DEF_VIRT_MAILBOX_LIMIT, &var_virt_mailbox_limit, 0, 0, + 0, + }; + static const CONFIG_STR_TABLE str_table[] = { + VAR_MAIL_SPOOL_DIR, DEF_MAIL_SPOOL_DIR, &var_mail_spool_dir, 0, 0, + VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0, + VAR_VIRT_UID_MAPS, DEF_VIRT_UID_MAPS, &var_virt_uid_maps, 0, 0, + VAR_VIRT_GID_MAPS, DEF_VIRT_GID_MAPS, &var_virt_gid_maps, 0, 0, + VAR_VIRT_MAILBOX_BASE, DEF_VIRT_MAILBOX_BASE, &var_virt_mailbox_base, 1, 0, + VAR_VIRT_MAILBOX_LOCK, DEF_VIRT_MAILBOX_LOCK, &var_virt_mailbox_lock, 1, 0, + VAR_VIRT_DSN_FILTER, DEF_VIRT_DSN_FILTER, &var_virt_dsn_filter, 0, 0, + 0, + }; + static const CONFIG_BOOL_TABLE bool_table[] = { + VAR_STRICT_MBOX_OWNER, DEF_STRICT_MBOX_OWNER, &var_strict_mbox_owner, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + single_server_main(argc, argv, local_service, + CA_MAIL_SERVER_INT_TABLE(int_table), + CA_MAIL_SERVER_LONG_TABLE(long_table), + CA_MAIL_SERVER_STR_TABLE(str_table), + CA_MAIL_SERVER_BOOL_TABLE(bool_table), + CA_MAIL_SERVER_PRE_INIT(pre_init), + CA_MAIL_SERVER_POST_INIT(post_init), + CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), + CA_MAIL_SERVER_PRIVILEGED, + CA_MAIL_SERVER_BOUNCE_INIT(VAR_VIRT_DSN_FILTER, + &var_virt_dsn_filter), + 0); +} diff --git a/src/virtual/virtual.h b/src/virtual/virtual.h new file mode 100644 index 0000000..75dd6cd --- /dev/null +++ b/src/virtual/virtual.h @@ -0,0 +1,150 @@ +/*++ +/* NAME +/* virtual 3h +/* SUMMARY +/* virtual mail delivery +/* SYNOPSIS +/* #include "virtual.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include + + /* + * Utility library. + */ +#include +#include + + /* + * Global library. + */ +#include +#include +#include +#include +#include + + /* + * Mappings. + */ +extern MAPS *virtual_mailbox_maps; +extern MAPS *virtual_uid_maps; +extern MAPS *virtual_gid_maps; + + /* + * User attributes: these control the privileges for delivery to external + * commands, external files, or mailboxes, and the initial environment of + * external commands. + */ +typedef struct USER_ATTR { + uid_t uid; /* file/command access */ + gid_t gid; /* file/command access */ + char *mailbox; /* mailbox file or directory */ +} USER_ATTR; + + /* + * Critical macros. Not for obscurity, but to ensure consistency. + */ +#define RESET_USER_ATTR(usr_attr, level) { \ + usr_attr.uid = 0; usr_attr.gid = 0; usr_attr.mailbox = 0; \ + if (msg_verbose) \ + msg_info("%s[%d]: reset user_attr", myname, level); \ + } + + /* + * The delivery attributes are inherited from files, from aliases, and from + * whatnot. Some of the information is changed on the fly. DELIVER_ATTR + * structures are therefore passed by value, so there is no need to undo + * changes. + */ +typedef struct DELIVER_ATTR { + int level; /* recursion level */ + VSTREAM *fp; /* open queue file */ + char *queue_name; /* mail queue id */ + char *queue_id; /* mail queue id */ + long offset; /* data offset */ + const char *sender; /* taken from envelope */ + char *dsn_envid; /* DSN envelope ID */ + int dsn_ret; /* DSN headers/full */ + RECIPIENT rcpt; /* from delivery request */ + char *user; /* recipient lookup handle */ + const char *delivered; /* for loop detection */ + char *relay; /* relay host */ + MSG_STATS msg_stats; /* time profile */ + DSN_BUF *why; /* delivery status */ +} DELIVER_ATTR; + +extern void deliver_attr_init(DELIVER_ATTR *); +extern void deliver_attr_dump(DELIVER_ATTR *); +extern void deliver_attr_free(DELIVER_ATTR *); + +#define FEATURE_NODELIVERED (1<<0) /* no delivered-to */ + + /* + * Rather than schlepping around dozens of arguments, here is one that has + * all. Well, almost. The user attributes are just a bit too sensitive, so + * they are passed around separately. + */ +typedef struct LOCAL_STATE { + int level; /* nesting level, for logging */ + DELIVER_ATTR msg_attr; /* message/recipient attributes */ + DELIVER_REQUEST *request; /* as from queue manager */ +} LOCAL_STATE; + + /* + * Bundle up some often-user attributes. + */ +#define BOUNCE_FLAGS(request) DEL_REQ_TRACE_FLAGS((request)->flags) + +#define BOUNCE_ATTR(attr) \ + attr.queue_id, &attr.msg_stats, &attr.rcpt, attr.relay, \ + DSN_FROM_DSN_BUF(attr.why) +#define SENT_ATTR(attr) \ + attr.queue_id, &attr.msg_stats, &attr.rcpt, attr.relay, \ + DSN_FROM_DSN_BUF(attr.why) +#define COPY_ATTR(attr) \ + attr.sender, attr.rcpt.orig_addr, attr.delivered, attr.fp + +#define MSG_LOG_STATE(m, p) \ + msg_info("%s[%d]: recip %s deliver %s", m, \ + p.level, \ + p.msg_attr.rcpt.address ? p.msg_attr.rcpt.address : "", \ + p.msg_attr.delivered ? p.msg_attr.delivered : "") + + /* + * "inner" nodes of the delivery graph. + */ +extern int deliver_recipient(LOCAL_STATE, USER_ATTR); + + /* + * "leaf" nodes of the delivery graph. + */ +extern int deliver_mailbox(LOCAL_STATE, USER_ATTR, int *); +extern int deliver_file(LOCAL_STATE, USER_ATTR, char *); +extern int deliver_maildir(LOCAL_STATE, USER_ATTR); +extern int deliver_unknown(LOCAL_STATE); + + /* + * Mailbox lock protocol. + */ +extern int virtual_mbox_lock_mask; + + /* + * Silly little macros. + */ +#define STR(s) vstring_str(s) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/src/xsasl/.indent.pro b/src/xsasl/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/xsasl/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro \ No newline at end of file diff --git a/src/xsasl/Makefile.in b/src/xsasl/Makefile.in new file mode 100644 index 0000000..ad48302 --- /dev/null +++ b/src/xsasl/Makefile.in @@ -0,0 +1,165 @@ +SHELL = /bin/sh +SRCS = xsasl_server.c xsasl_cyrus_server.c xsasl_cyrus_log.c \ + xsasl_cyrus_security.c xsasl_client.c xsasl_cyrus_client.c \ + xsasl_dovecot_server.c +OBJS = xsasl_server.o xsasl_cyrus_server.o xsasl_cyrus_log.o \ + xsasl_cyrus_security.o xsasl_client.o xsasl_cyrus_client.o \ + xsasl_dovecot_server.o +HDRS = xsasl.h +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +INCL = +LIB = libxsasl.a +TESTPROG= + +LIBS = ../../lib/lib$(LIB_PERFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PERFIX)util$(LIB_SUFFIX) +LIB_DIR = ../../lib +INC_DIR = ../../include +MAKES = + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +all: $(LIB) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +$(LIB): $(OBJS) + $(_AR) $(ARFL) $(LIB) $? + $(_RANLIB) $(LIB) + +$(LIB_DIR)/$(LIB): $(LIB) + cp $(LIB) $(LIB_DIR) + $(_RANLIB) $(LIB_DIR)/$(LIB) + +update: $(LIB_DIR)/$(LIB) $(HDRS) + -for i in $(HDRS); \ + do \ + cmp -s $$i $(INC_DIR)/$$i 2>/dev/null || cp $$i $(INC_DIR); \ + done + cd $(INC_DIR); chmod 644 $(HDRS) + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o $(LIB) *core $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +foo: $(LIB) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(LIBS) $(SYSLIBS) + mv junk $@.o + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +xsasl_client.o: ../../include/argv.h +xsasl_client.o: ../../include/check_arg.h +xsasl_client.o: ../../include/msg.h +xsasl_client.o: ../../include/mymalloc.h +xsasl_client.o: ../../include/sys_defs.h +xsasl_client.o: ../../include/vbuf.h +xsasl_client.o: ../../include/vstream.h +xsasl_client.o: ../../include/vstring.h +xsasl_client.o: xsasl.h +xsasl_client.o: xsasl_client.c +xsasl_client.o: xsasl_cyrus.h +xsasl_cyrus_client.o: ../../include/argv.h +xsasl_cyrus_client.o: ../../include/check_arg.h +xsasl_cyrus_client.o: ../../include/mail_params.h +xsasl_cyrus_client.o: ../../include/msg.h +xsasl_cyrus_client.o: ../../include/mymalloc.h +xsasl_cyrus_client.o: ../../include/stringops.h +xsasl_cyrus_client.o: ../../include/sys_defs.h +xsasl_cyrus_client.o: ../../include/vbuf.h +xsasl_cyrus_client.o: ../../include/vstream.h +xsasl_cyrus_client.o: ../../include/vstring.h +xsasl_cyrus_client.o: xsasl.h +xsasl_cyrus_client.o: xsasl_cyrus.h +xsasl_cyrus_client.o: xsasl_cyrus_client.c +xsasl_cyrus_client.o: xsasl_cyrus_common.h +xsasl_cyrus_log.o: ../../include/msg.h +xsasl_cyrus_log.o: ../../include/sys_defs.h +xsasl_cyrus_log.o: xsasl_cyrus_common.h +xsasl_cyrus_log.o: xsasl_cyrus_log.c +xsasl_cyrus_security.o: ../../include/check_arg.h +xsasl_cyrus_security.o: ../../include/name_mask.h +xsasl_cyrus_security.o: ../../include/sys_defs.h +xsasl_cyrus_security.o: ../../include/vbuf.h +xsasl_cyrus_security.o: ../../include/vstring.h +xsasl_cyrus_security.o: xsasl_cyrus_common.h +xsasl_cyrus_security.o: xsasl_cyrus_security.c +xsasl_cyrus_server.o: ../../include/argv.h +xsasl_cyrus_server.o: ../../include/check_arg.h +xsasl_cyrus_server.o: ../../include/mail_params.h +xsasl_cyrus_server.o: ../../include/msg.h +xsasl_cyrus_server.o: ../../include/mymalloc.h +xsasl_cyrus_server.o: ../../include/name_mask.h +xsasl_cyrus_server.o: ../../include/stringops.h +xsasl_cyrus_server.o: ../../include/sys_defs.h +xsasl_cyrus_server.o: ../../include/vbuf.h +xsasl_cyrus_server.o: ../../include/vstream.h +xsasl_cyrus_server.o: ../../include/vstring.h +xsasl_cyrus_server.o: xsasl.h +xsasl_cyrus_server.o: xsasl_cyrus.h +xsasl_cyrus_server.o: xsasl_cyrus_common.h +xsasl_cyrus_server.o: xsasl_cyrus_server.c +xsasl_dovecot_server.o: ../../include/argv.h +xsasl_dovecot_server.o: ../../include/check_arg.h +xsasl_dovecot_server.o: ../../include/connect.h +xsasl_dovecot_server.o: ../../include/iostuff.h +xsasl_dovecot_server.o: ../../include/mail_params.h +xsasl_dovecot_server.o: ../../include/msg.h +xsasl_dovecot_server.o: ../../include/myaddrinfo.h +xsasl_dovecot_server.o: ../../include/mymalloc.h +xsasl_dovecot_server.o: ../../include/name_mask.h +xsasl_dovecot_server.o: ../../include/split_at.h +xsasl_dovecot_server.o: ../../include/stringops.h +xsasl_dovecot_server.o: ../../include/sys_defs.h +xsasl_dovecot_server.o: ../../include/vbuf.h +xsasl_dovecot_server.o: ../../include/vstream.h +xsasl_dovecot_server.o: ../../include/vstring.h +xsasl_dovecot_server.o: ../../include/vstring_vstream.h +xsasl_dovecot_server.o: xsasl.h +xsasl_dovecot_server.o: xsasl_dovecot.h +xsasl_dovecot_server.o: xsasl_dovecot_server.c +xsasl_server.o: ../../include/argv.h +xsasl_server.o: ../../include/check_arg.h +xsasl_server.o: ../../include/msg.h +xsasl_server.o: ../../include/mymalloc.h +xsasl_server.o: ../../include/sys_defs.h +xsasl_server.o: ../../include/vbuf.h +xsasl_server.o: ../../include/vstream.h +xsasl_server.o: ../../include/vstring.h +xsasl_server.o: xsasl.h +xsasl_server.o: xsasl_cyrus.h +xsasl_server.o: xsasl_dovecot.h +xsasl_server.o: xsasl_server.c diff --git a/src/xsasl/README b/src/xsasl/README new file mode 100644 index 0000000..d1b2f5a --- /dev/null +++ b/src/xsasl/README @@ -0,0 +1,109 @@ +Purpose of this document +======================== + +This document describes how to add your own SASL implementation to +Postfix. You don't have to provide both the server and client side. +You can provide just one and omit the other. The examples below +assume you do both. + +The plug-in API is described in cyrus_server.c and cyrus_client.c. +It was unavoidably contaminated^h^h^h^h^h^h^h^h^h^h^h^hinfluenced +by Cyrus SASL and may need revision as other implementations are +added. + +For an example of how the plug-in interface is implemented, have a +look at the xsasl/xsasl_cyrus_client.c and xsasl/xsasl_cyrus_server.c. + +Configuration features +====================== + +There are two configuration parameters that allow you to pass +information from main.cf into the plug_in: + + smtpd_sasl_path, smtpd_sasl_security_options + smtp_sasl_path, smtp_sasl_security_options + lmtp_sasl_path, lmtp_sasl_security_options + +As usual, newline characters are removed from multi-line parameter +values, and $name is expanded recursively. The parameter values +are passed to the plug-in without any further processing. The +following restrictions are imposed by the main.cf file parser: + +- parameter values never contain newlines, + +- parameter values never start or end with whitespace characters. + +The _path parameter value is passed only once during process +initialization (i.e. it is a class variable). The path typically +specifies the location of a configuration file or rendez-vous point. +The _security_options parameter value is passed each time SASL is +turned on for a connection (i.e. it is an instance variable). The +options may depend on whether or not TLS encryption is turned on. +Remember that one Postfix process may perform up to 100 mail +transactions during its life time. Things that happen in one +transaction must not affect later transactions. + +Adding Postfix support for your own SASL implementation +======================================================= + +To add your own SASL implementation, say, FOOBAR: + +- Copy xsasl/xsasl_cyrus.h to xsasl/xsasl_foobar.h and replace + CYRUS by FOOBAR: + + #if defined(USE_SASL_AUTH) && defined(USE_FOOBAR_SASL) + /* + * SASL protocol interface + */ + #define XSASL_TYPE_FOOBAR "foobar" + extern XSASL_SERVER_IMPL *xsasl_foobar_server_init(const char *, const char *); + extern XSASL_CLIENT_IMPL *xsasl_foobar_client_init(const char *, const char *); + #endif + +- Edit xsasl/xsasl_server.c, add your #include line + under #include at the top, and add your initialization + function in the table at the bottom as shown below: + + static XSASL_SERVER_IMPL_INFO server_impl_info[] = { + #ifdef XSASL_TYPE_CYRUS + XSASL_TYPE_CYRUS, xsasl_cyrus_server_init, + #endif + #ifdef XSASL_TYPE_FOOBAR + XSASL_TYPE_FOOBAR, xsasl_foobar_server_init, + #endif + 0, + }; + +- Repeat the (almost) same procedure for xsasl/xsasl_client.c. + +- Create your own xsasl/xsasl_foobar_{client,server}.c and support + files. Perhaps it's convenient to copy the cyrus files, rip out + the function bodies, and replace CYRUS by FOOBAR. + +- List your source files in Makefile.in. Don't forget to do "make + depend" after you do "make makefiles" in the step that follows + after this one. + + SRCS = xsasl_server.c xsasl_cyrus_server.c xsasl_cyrus_log.c \ + xsasl_cyrus_security.c xsasl_client.c xsasl_cyrus_client.c \ + xsasl_foobar_client.c xsasl_foobar_server.c + OBJS = xsasl_server.o xsasl_cyrus_server.o xsasl_cyrus_log.o \ + xsasl_cyrus_security.o xsasl_client.o xsasl_cyrus_client.o \ + xsasl_foobar_client.o xsasl_foobar_server.o + +- Create the Postfix makefiles from the top-level directory: + + % make makefiles CCARGS='-DUSE_SASL_AUTH -DUSE_FOOBAR_SASL \ + -DDEF_CLIENT_SASL_TYPE=\"foobar\" -DDEF_SERVER_SASL_TYPE=\"foobar\" \ + -I/some/where/include' AUXLIBS='-L/some/where/lib -lfoobar' + + Yes, you can have different default SASL implementation types for + the client and server plug-ins. + + Of course you don't have to override the default SASL implementation + type; it is shown here as an example. + + +- Don't forget to do "make depend" in the xsasl directory. + +- Document your build and configuration with a README document. diff --git a/src/xsasl/xsasl.h b/src/xsasl/xsasl.h new file mode 100644 index 0000000..b494d7e --- /dev/null +++ b/src/xsasl/xsasl.h @@ -0,0 +1,146 @@ +#ifndef _XSASL_H_INCLUDED_ +#define _XSASL_H_INCLUDED_ + +/*++ +/* NAME +/* xsasl 3h +/* SUMMARY +/* Postfix SASL plug-in interface +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include +#include +#include + + /* + * Generic server object. Specific instances extend this with their own + * private data. + */ +typedef struct XSASL_SERVER { + void (*free) (struct XSASL_SERVER *); + int (*first) (struct XSASL_SERVER *, const char *, const char *, VSTRING *); + int (*next) (struct XSASL_SERVER *, const char *, VSTRING *); + const char *(*get_mechanism_list) (struct XSASL_SERVER *); + const char *(*get_username) (struct XSASL_SERVER *); +} XSASL_SERVER; + +#define xsasl_server_free(server) (server)->free(server) +#define xsasl_server_first(server, method, init_resp, reply) \ + (server)->first((server), (method), (init_resp), (reply)) +#define xsasl_server_next(server, request, reply) \ + (server)->next((server), (request), (reply)) +#define xsasl_server_get_mechanism_list(server) \ + (server)->get_mechanism_list((server)) +#define xsasl_server_get_username(server) \ + (server)->get_username((server)) + + /* + * Generic server implementation. Specific instances extend this with their + * own private data. + */ +typedef struct XSASL_SERVER_CREATE_ARGS { + VSTREAM *stream; + int addr_family; + const char *server_addr; + const char *server_port; + const char *client_addr; + const char *client_port; + const char *service; + const char *user_realm; + const char *security_options; + int tls_flag; +} XSASL_SERVER_CREATE_ARGS; + +typedef struct XSASL_SERVER_IMPL { + XSASL_SERVER *(*create) (struct XSASL_SERVER_IMPL *, XSASL_SERVER_CREATE_ARGS *); + void (*done) (struct XSASL_SERVER_IMPL *); +} XSASL_SERVER_IMPL; + +extern XSASL_SERVER_IMPL *xsasl_server_init(const char *, const char *); +extern ARGV *xsasl_server_types(void); + +#define xsasl_server_create(impl, args) \ + (impl)->create((impl), (args)) +#define XSASL_SERVER_CREATE(impl, args, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10) \ + xsasl_server_create((impl), (((args)->a1), ((args)->a2), ((args)->a3), \ + ((args)->a4), ((args)->a5), ((args)->a6), ((args)->a7), ((args)->a8), \ + ((args)->a9), ((args)->a10), (args))) +#define xsasl_server_done(impl) (impl)->done((impl)); + + /* + * Generic client object. Specific instances extend this with their own + * private data. + */ +typedef struct XSASL_CLIENT { + void (*free) (struct XSASL_CLIENT *); + int (*first) (struct XSASL_CLIENT *, const char *, const char *, const char *, const char **, VSTRING *); + int (*next) (struct XSASL_CLIENT *, const char *, VSTRING *); +} XSASL_CLIENT; + +#define xsasl_client_free(client) (client)->free(client) +#define xsasl_client_first(client, server, method, user, pass, init_resp) \ + (client)->first((client), (server), (method), (user), (pass), (init_resp)) +#define xsasl_client_next(client, request, reply) \ + (client)->next((client), (request), (reply)) +#define xsasl_client_set_password(client, user, pass) \ + (client)->set_password((client), (user), (pass)) + + /* + * Generic client implementation. Specific instances extend this with their + * own private data. + */ +typedef struct XSASL_CLIENT_CREATE_ARGS { + VSTREAM *stream; + const char *service; + const char *server_name; + const char *security_options; +} XSASL_CLIENT_CREATE_ARGS; + +typedef struct XSASL_CLIENT_IMPL { + XSASL_CLIENT *(*create) (struct XSASL_CLIENT_IMPL *, XSASL_CLIENT_CREATE_ARGS *); + void (*done) (struct XSASL_CLIENT_IMPL *); +} XSASL_CLIENT_IMPL; + +extern XSASL_CLIENT_IMPL *xsasl_client_init(const char *, const char *); +extern ARGV *xsasl_client_types(void); + +#define xsasl_client_create(impl, args) \ + (impl)->create((impl), (args)) +#define XSASL_CLIENT_CREATE(impl, args, a1, a2, a3, a4) \ + xsasl_client_create((impl), (((args)->a1), ((args)->a2), ((args)->a3), \ + ((args)->a4), (args))) +#define xsasl_client_done(impl) (impl)->done((impl)); + + /* + * Status codes. + */ +#define XSASL_AUTH_OK 1 /* Success */ +#define XSASL_AUTH_MORE 2 /* Need another c/s protocol exchange */ +#define XSASL_AUTH_DONE 3 /* Authentication completed */ +#define XSASL_AUTH_FORM 4 /* Cannot decode response */ +#define XSASL_AUTH_FAIL 5 /* Error */ +#define XSASL_AUTH_TEMP 6 /* Temporary error condition */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +#endif diff --git a/src/xsasl/xsasl_client.c b/src/xsasl/xsasl_client.c new file mode 100644 index 0000000..0bddd41 --- /dev/null +++ b/src/xsasl/xsasl_client.c @@ -0,0 +1,240 @@ +/*++ +/* NAME +/* xsasl_client 3 +/* SUMMARY +/* Postfix SASL client plug-in interface +/* SYNOPSIS +/* #include +/* +/* XSASL_CLIENT_IMPL *xsasl_client_init(client_type, path_info) +/* const char *client_type; +/* const char *path_info; +/* +/* void xsasl_client_done(implementation) +/* XSASL_CLIENT_IMPL *implementation; +/* +/* ARGV *xsasl_client_types() +/* +/* .in +4 +/* typedef struct XSASL_CLIENT_CREATE_ARGS { +/* VSTREAM *stream; +/* const char *service; +/* const char *server_name; +/* const char *security_options; +/* } XSASL_CLIENT_CREATE_ARGS; +/* .in -4 +/* +/* XSASL_CLIENT *xsasl_client_create(implementation, create_args) +/* XSASL_CLIENT_IMPL *implementation; +/* XSASL_CLIENT_CREATE_ARGS *create_args; +/* +/* XSASL_CLIENT *XSASL_CLIENT_CREATE(implementation, create_args, +/* stream = stream_val, +/* ..., +/* security_options = prop_val) +/* XSASL_CLIENT_IMPL *implementation; +/* XSASL_CLIENT_CREATE_ARGS *create_args; +/* +/* void xsasl_client_free(client) +/* XSASL_CLIENT *client; +/* +/* int xsasl_client_first(client, stream, mech_list, username, +/* password, auth_method, init_resp) +/* XSASL_CLIENT *client; +/* const char *mech_list; +/* const char *username; +/* const char *password; +/* const char **auth_method; +/* VSTRING *init_resp; +/* +/* int xsasl_client_next(client, server_reply, client_reply) +/* XSASL_CLIENT *client; +/* const char *server_reply; +/* VSTRING *client_reply; +/* DESCRIPTION +/* The XSASL_CLIENT abstraction implements a generic interface +/* to one or more SASL authentication implementations. +/* +/* xsasl_client_init() is called once during process initialization. +/* It selects a SASL implementation by name, specifies the +/* location of a configuration file or rendez-vous point, and +/* returns an implementation handle that can be used to generate +/* SASL client instances. This function is typically used to +/* initialize the underlying implementation. +/* +/* xsasl_client_done() disposes of an implementation handle, +/* and allows the underlying implementation to release resources. +/* +/* xsasl_client_types() lists the available implementation types. +/* The result should be destroyed by the caller. +/* +/* xsasl_client_create() is called at the start of an SMTP +/* session. It generates a Postfix SASL plug-in client instance +/* for the specified service and server name, with the specified +/* security properties. The stream handle is stored so that +/* encryption can be turned on after successful negotiations. +/* +/* XSASL_CLIENT_CREATE() is a macro that provides an interface +/* with named parameters. Named parameters do not have to +/* appear in a fixed order. The parameter names correspond to +/* the member names of the XSASL_CLIENT_CREATE_ARGS structure. +/* +/* xsasl_client_free() is called at the end of an SMTP session. +/* It destroys a SASL client instance, and disables further +/* read/write operations if encryption was turned on. +/* +/* xsasl_client_first() produces the client input for the AUTH +/* command. The input is an authentication method list from +/* an EHLO response, a username and a password. On return, the +/* method argument specifies the authentication method; storage +/* space is owned by the underlying implementation. The initial +/* response and client non-error replies are BASE64 encoded. +/* Client error replies are 7-bit ASCII text without control +/* characters, and without BASE64 encoding. They are meant for +/* the local application, not for transmission to the server. +/* The client may negotiate encryption of the client-server +/* connection. +/* +/* The result is one of the following: +/* .IP XSASL_AUTH_OK +/* Success. +/* .IP XSASL_AUTH_FORM +/* The server reply is incorrectly formatted. The client error +/* reply explains why. +/* .IP XSASL_AUTH_FAIL +/* Other error. The client error reply explains why. +/* .PP +/* xsasl_client_next() supports the subsequent stages of the +/* AUTH protocol. Both the client reply and client non-error +/* responses are BASE64 encoded. See xsasl_client_first() for +/* other details. +/* +/* Arguments: +/* .IP client +/* SASL plug-in client handle. +/* .IP client_reply +/* BASE64 encoded non-error client reply, or ASCII error +/* description for the user. +/* .IP client_type +/* The name of a Postfix SASL client plug_in implementation. +/* .IP client_types +/* Null-terminated array of strings with SASL client plug-in +/* implementation names. +/* .IP init_resp +/* The AUTH command initial response. +/* .IP implementation +/* Implementation handle that was obtained with xsasl_client_init(). +/* .IP mech_list +/* List of SASL mechanisms as announced by the server. +/* .IP auth_method +/* The AUTH command authentication method. +/* .IP password +/* Information from the Postfix SASL password file or equivalent. +/* .IP path_info +/* The value of the smtp_sasl_path parameter or equivalent. +/* This specifies the implementation-dependent location of a +/* configuration file, rendez-vous point, etc., and is passed +/* unchanged to the plug-in. +/* .IP security_options +/* The value of the smtp_sasl_security_options parameter or +/* equivalent. This is passed unchanged to the plug-in. +/* .IP server_name +/* The remote server fully qualified hostname. +/* .IP server_reply +/* BASE64 encoded server reply without SMTP reply code or +/* enhanced status code. +/* .IP service +/* The service that is implemented by the local client (typically, +/* "lmtp" or "smtp"). +/* .IP stream +/* The connection between client and server. +/* When SASL encryption is negotiated, the plug-in will +/* transparently intercept the socket read/write operations. +/* .IP username +/* Information from the Postfix SASL password file. +/* SECURITY +/* .ad +/* .fi +/* The caller does not sanitize the server reply. It is the +/* responsibility of the underlying SASL client implementation +/* to produce 7-bit ASCII without control characters as client +/* non-error and error replies. +/* DIAGNOSTICS +/* In case of error, xsasl_client_init() and xsasl_client_create() +/* log a warning and return a null pointer. +/* +/* Functions that normally return XSASL_AUTH_OK will log a warning +/* and return an appropriate result value. +/* +/* Panic: interface violation. +/* +/* Fatal errors: out of memory. +/* SEE ALSO +/* cyrus_security(3) Cyrus SASL security features +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include + +/* SASL implementations. */ + +#include +#include + + /* + * Lookup table for available SASL client implementations. + */ +typedef struct { + char *client_type; + struct XSASL_CLIENT_IMPL *(*client_init) (const char *, const char *); +} XSASL_CLIENT_IMPL_INFO; + +static const XSASL_CLIENT_IMPL_INFO client_impl_info[] = { +#ifdef XSASL_TYPE_CYRUS + XSASL_TYPE_CYRUS, xsasl_cyrus_client_init, +#endif + 0, +}; + +/* xsasl_client_init - look up client implementation by name */ + +XSASL_CLIENT_IMPL *xsasl_client_init(const char *client_type, + const char *path_info) +{ + const XSASL_CLIENT_IMPL_INFO *xp; + + for (xp = client_impl_info; xp->client_type; xp++) + if (strcmp(client_type, xp->client_type) == 0) + return (xp->client_init(client_type, path_info)); + msg_warn("unsupported SASL client implementation: %s", client_type); + return (0); +} + +/* xsasl_client_types - report available implementation types */ + +ARGV *xsasl_client_types(void) +{ + const XSASL_CLIENT_IMPL_INFO *xp; + ARGV *argv = argv_alloc(1); + + for (xp = client_impl_info; xp->client_type; xp++) + argv_add(argv, xp->client_type, ARGV_END); + return (argv); +} diff --git a/src/xsasl/xsasl_cyrus.h b/src/xsasl/xsasl_cyrus.h new file mode 100644 index 0000000..ad8557e --- /dev/null +++ b/src/xsasl/xsasl_cyrus.h @@ -0,0 +1,47 @@ +#ifndef _XSASL_CYRUS_H_INCLUDED_ +#define _XSASL_CYRUS_H_INCLUDED_ + +/*++ +/* NAME +/* xsasl_cyrus 3h +/* SUMMARY +/* Cyrus SASL plug-in +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * XSASL library. + */ +#include + +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + + /* + * SASL protocol interface + */ +#define XSASL_TYPE_CYRUS "cyrus" + +extern XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *, const char *); +extern XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(const char *, const char *); + + /* + * Internal definitions for client and server module. + */ +typedef int (*XSASL_CYRUS_CB) (void); + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/xsasl/xsasl_cyrus_client.c b/src/xsasl/xsasl_cyrus_client.c new file mode 100644 index 0000000..fc799c9 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_client.c @@ -0,0 +1,585 @@ +/*++ +/* NAME +/* xsasl_cyrus_client 3 +/* SUMMARY +/* Cyrus SASL client-side plug-in +/* SYNOPSIS +/* #include +/* +/* XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(client_type, path_info) +/* const char *client_type; +/* DESCRIPTION +/* This module implements the Cyrus SASL client-side authentication +/* plug-in. +/* +/* xsasl_cyrus_client_init() initializes the Cyrus SASL library and +/* returns an implementation handle that can be used to generate +/* SASL client instances. +/* +/* Arguments: +/* .IP client_type +/* The plug-in SASL client type (cyrus). This argument is +/* ignored, but it could be used when one implementation +/* provides multiple variants. +/* .IP path_info +/* Implementation-specific information to specify the location +/* of a configuration file, rendez-vous point, etc. This +/* information is ignored by the Cyrus SASL client plug-in. +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* Panic: interface violation. +/* +/* Other: the routines log a warning and return an error result +/* as specified in xsasl_client(3). +/* SEE ALSO +/* xsasl_client(3) Client API +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Original author: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System library. + */ +#include +#include +#include + + /* + * Utility library + */ +#include +#include +#include + + /* + * Global library + */ +#include + + /* + * Application-specific + */ +#include +#include +#include + +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + +#include +#include + +/* + * Silly little macros. + */ +#define STR(s) vstring_str(s) + + /* + * Macros to handle API differences between SASLv1 and SASLv2. Specifics: + * + * The SASL_LOG_* constants were renamed in SASLv2. + * + * SASLv2's sasl_client_new takes two new parameters to specify local and + * remote IP addresses for auth mechs that use them. + * + * SASLv2's sasl_client_start function no longer takes the secret parameter. + * + * SASLv2's sasl_decode64 function takes an extra parameter for the length of + * the output buffer. + * + * The other major change is that SASLv2 now takes more responsibility for + * deallocating memory that it allocates internally. Thus, some of the + * function parameters are now 'const', to make sure we don't try to free + * them too. This is dealt with in the code later on. + */ +#if SASL_VERSION_MAJOR < 2 +/* SASL version 1.x */ +#define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \ + sasl_client_new(srv, fqdn, prompt, secflags, pconn) +#define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \ + sasl_client_start(conn, mechlst, secret, prompt, clout, cllen, mech) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outlen) +typedef char *CLIENTOUT_TYPE; + +#endif + +#if SASL_VERSION_MAJOR >= 2 +/* SASL version > 2.x */ +#define SASL_CLIENT_NEW(srv, fqdn, lport, rport, prompt, secflags, pconn) \ + sasl_client_new(srv, fqdn, lport, rport, prompt, secflags, pconn) +#define SASL_CLIENT_START(conn, mechlst, secret, prompt, clout, cllen, mech) \ + sasl_client_start(conn, mechlst, prompt, clout, cllen, mech) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outmaxlen, outlen) +typedef const char *CLIENTOUT_TYPE; + +#endif + + /* + * The XSASL_CYRUS_CLIENT object is derived from the generic XSASL_CLIENT + * object. + */ +typedef struct { + XSASL_CLIENT xsasl; /* generic members, must be first */ + VSTREAM *stream; /* client-server connection */ + sasl_conn_t *sasl_conn; /* SASL context */ + VSTRING *decoded; /* decoded server challenge */ + sasl_callback_t *callbacks; /* user/password lookup */ + char *username; + char *password; +} XSASL_CYRUS_CLIENT; + + /* + * Forward declarations. + */ +static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *); +static XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *, + XSASL_CLIENT_CREATE_ARGS *); +static int xsasl_cyrus_client_set_security(XSASL_CLIENT *, const char *); +static int xsasl_cyrus_client_first(XSASL_CLIENT *, const char *, const char *, + const char *, const char **, VSTRING *); +static int xsasl_cyrus_client_next(XSASL_CLIENT *, const char *, VSTRING *); +static void xsasl_cyrus_client_free(XSASL_CLIENT *); + +/* xsasl_cyrus_client_get_user - username lookup call-back routine */ + +static int xsasl_cyrus_client_get_user(void *context, int unused_id, + const char **result, + unsigned *len) +{ + const char *myname = "xsasl_cyrus_client_get_user"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context; + + if (msg_verbose) + msg_info("%s: %s", myname, client->username); + + /* + * Sanity check. + */ + if (client->password == 0) + msg_panic("%s: no username looked up", myname); + + *result = client->username; + if (len) + *len = strlen(client->username); + return (SASL_OK); +} + +/* xsasl_cyrus_client_get_passwd - password lookup call-back routine */ + +static int xsasl_cyrus_client_get_passwd(sasl_conn_t *conn, void *context, + int id, sasl_secret_t **psecret) +{ + const char *myname = "xsasl_cyrus_client_get_passwd"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) context; + int len; + + if (msg_verbose) + msg_info("%s: %s", myname, client->password); + + /* + * Sanity check. + */ + if (!conn || !psecret || id != SASL_CB_PASS) + return (SASL_BADPARAM); + if (client->password == 0) + msg_panic("%s: no password looked up", myname); + + /* + * Convert the password into a counted string. + */ + len = strlen(client->password); + if ((*psecret = (sasl_secret_t *) malloc(sizeof(sasl_secret_t) + len)) == 0) + return (SASL_NOMEM); + (*psecret)->len = len; + memcpy((*psecret)->data, client->password, len + 1); + + return (SASL_OK); +} + +/* xsasl_cyrus_client_init - initialize Cyrus SASL library */ + +XSASL_CLIENT_IMPL *xsasl_cyrus_client_init(const char *unused_client_type, + const char *unused_path_info) +{ + XSASL_CLIENT_IMPL *xp; + int sasl_status; + + /* + * Global callbacks. These have no per-session context. + */ + static sasl_callback_t callbacks[] = { + {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, 0}, + {SASL_CB_LIST_END, 0, 0} + }; + +#if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \ + || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19)) + int sasl_major; + int sasl_minor; + int sasl_step; + + /* + * DLL hell guard. + */ + sasl_version_info((const char **) 0, (const char **) 0, + &sasl_major, &sasl_minor, + &sasl_step, (int *) 0); + if (sasl_major != SASL_VERSION_MAJOR +#if 0 + || sasl_minor != SASL_VERSION_MINOR + || sasl_step != SASL_VERSION_STEP +#endif + ) { + msg_warn("incorrect SASL library version. " + "Postfix was built with include files from version %d.%d.%d, " + "but the run-time library version is %d.%d.%d", + SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, + sasl_major, sasl_minor, sasl_step); + return (0); + } +#endif + + if (*var_cyrus_conf_path) { +#ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */ + if (sasl_set_path(SASL_PATH_TYPE_CONFIG, + var_cyrus_conf_path) != SASL_OK) + msg_warn("failed to set Cyrus SASL configuration path: \"%s\"", + var_cyrus_conf_path); +#else + msg_warn("%s is not empty, but setting the Cyrus SASL configuration " + "path is not supported with SASL library version %d.%d.%d", + VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR, + SASL_VERSION_MINOR, SASL_VERSION_STEP); +#endif + } + + /* + * Initialize the SASL library. + */ + if ((sasl_status = sasl_client_init(callbacks)) != SASL_OK) { + msg_warn("SASL library initialization error: %s", + xsasl_cyrus_strerror(sasl_status)); + return (0); + } + + /* + * Return a generic XSASL_CLIENT_IMPL object. We don't need to extend it + * with our own methods or data. + */ + xp = (XSASL_CLIENT_IMPL *) mymalloc(sizeof(*xp)); + xp->create = xsasl_cyrus_client_create; + xp->done = xsasl_cyrus_client_done; + return (xp); +} + +/* xsasl_cyrus_client_done - dispose of implementation */ + +static void xsasl_cyrus_client_done(XSASL_CLIENT_IMPL *impl) +{ + myfree((void *) impl); + sasl_done(); +} + +/* xsasl_cyrus_client_create - per-session SASL initialization */ + +XSASL_CLIENT *xsasl_cyrus_client_create(XSASL_CLIENT_IMPL *unused_impl, + XSASL_CLIENT_CREATE_ARGS *args) +{ + XSASL_CYRUS_CLIENT *client = 0; + static sasl_callback_t callbacks[] = { + {SASL_CB_USER, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0}, + {SASL_CB_AUTHNAME, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_user, 0}, + {SASL_CB_PASS, (XSASL_CYRUS_CB) &xsasl_cyrus_client_get_passwd, 0}, + {SASL_CB_LIST_END, 0, 0} + }; + sasl_conn_t *sasl_conn = 0; + sasl_callback_t *custom_callbacks = 0; + sasl_callback_t *cp; + int sasl_status; + + /* + * The optimizer will eliminate code duplication and/or dead code. + */ +#define XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(x) \ + do { \ + if (client) { \ + xsasl_cyrus_client_free(&client->xsasl); \ + } else { \ + if (custom_callbacks) \ + myfree((void *) custom_callbacks); \ + if (sasl_conn) \ + sasl_dispose(&sasl_conn); \ + } \ + return (x); \ + } while (0) + + /* + * Per-session initialization. Provide each session with its own callback + * context. + */ +#define NULL_SECFLAGS 0 + + custom_callbacks = (sasl_callback_t *) mymalloc(sizeof(callbacks)); + memcpy((void *) custom_callbacks, callbacks, sizeof(callbacks)); + +#define NULL_SERVER_ADDR ((char *) 0) +#define NULL_CLIENT_ADDR ((char *) 0) + + if ((sasl_status = SASL_CLIENT_NEW(args->service, args->server_name, + NULL_CLIENT_ADDR, NULL_SERVER_ADDR, + var_cyrus_sasl_authzid ? custom_callbacks : + custom_callbacks + 1, NULL_SECFLAGS, + &sasl_conn)) != SASL_OK) { + msg_warn("per-session SASL client initialization: %s", + xsasl_cyrus_strerror(sasl_status)); + XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0); + } + + /* + * Extend the XSASL_CLIENT object with our own state. We use long-lived + * conversion buffers rather than local variables to avoid memory leaks + * in case of read/write timeout or I/O error. + * + * XXX If we enable SASL encryption, there needs to be a way to inform the + * application, so that they can turn off connection caching, refuse + * STARTTLS, etc. + */ + client = (XSASL_CYRUS_CLIENT *) mymalloc(sizeof(*client)); + client->xsasl.free = xsasl_cyrus_client_free; + client->xsasl.first = xsasl_cyrus_client_first; + client->xsasl.next = xsasl_cyrus_client_next; + client->stream = args->stream; + client->sasl_conn = sasl_conn; + client->callbacks = custom_callbacks; + client->decoded = vstring_alloc(20); + client->username = 0; + client->password = 0; + + for (cp = custom_callbacks; cp->id != SASL_CB_LIST_END; cp++) + cp->context = (void *) client; + + if (xsasl_cyrus_client_set_security(&client->xsasl, + args->security_options) + != XSASL_AUTH_OK) + XSASL_CYRUS_CLIENT_CREATE_ERROR_RETURN(0); + + return (&client->xsasl); +} + +/* xsasl_cyrus_client_set_security - set security properties */ + +static int xsasl_cyrus_client_set_security(XSASL_CLIENT *xp, + const char *sasl_opts_val) +{ + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + sasl_security_properties_t sec_props; + int sasl_status; + + /* + * Per-session security properties. XXX This routine is not sufficiently + * documented. What is the purpose of all this? + */ + memset(&sec_props, 0, sizeof(sec_props)); + sec_props.min_ssf = 0; + sec_props.max_ssf = 0; /* don't allow real SASL + * security layer */ + if (*sasl_opts_val == 0) { + sec_props.security_flags = 0; + } else { + sec_props.security_flags = + xsasl_cyrus_security_parse_opts(sasl_opts_val); + if (sec_props.security_flags == 0) { + msg_warn("bad per-session SASL security properties"); + return (XSASL_AUTH_FAIL); + } + } + sec_props.maxbufsize = 0; + sec_props.property_names = 0; + sec_props.property_values = 0; + if ((sasl_status = sasl_setprop(client->sasl_conn, SASL_SEC_PROPS, + &sec_props)) != SASL_OK) { + msg_warn("set per-session SASL security properties: %s", + xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_client_first - run authentication protocol */ + +static int xsasl_cyrus_client_first(XSASL_CLIENT *xp, + const char *mechanism_list, + const char *username, + const char *password, + const char **mechanism, + VSTRING *init_resp) +{ + const char *myname = "xsasl_cyrus_client_first"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + unsigned enc_length; + unsigned enc_length_out; + CLIENTOUT_TYPE clientout; + unsigned clientoutlen; + int sasl_status; + +#define NO_SASL_SECRET 0 +#define NO_SASL_INTERACTION 0 + + /* + * Save the username and password for the call-backs. + */ + if (client->username) + myfree(client->username); + client->username = mystrdup(username); + if (client->password) + myfree(client->password); + client->password = mystrdup(password); + + /* + * Start the client side authentication protocol. + */ + sasl_status = SASL_CLIENT_START((sasl_conn_t *) client->sasl_conn, + mechanism_list, + NO_SASL_SECRET, NO_SASL_INTERACTION, + &clientout, &clientoutlen, mechanism); + if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) { + vstring_strcpy(init_resp, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + + /* + * Generate the AUTH command and the optional initial client response. + * sasl_encode64() produces four bytes for each complete or incomplete + * triple of input bytes. Allocate an extra byte for string termination. + */ +#define ENCODE64_LENGTH(n) ((((n) + 2) / 3) * 4) + + if (clientoutlen > 0) { + if (msg_verbose) { + escape(client->decoded, clientout, clientoutlen); + msg_info("%s: uncoded initial reply: %s", + myname, STR(client->decoded)); + } + enc_length = ENCODE64_LENGTH(clientoutlen) + 1; + VSTRING_RESET(init_resp); /* Fix 200512 */ + VSTRING_SPACE(init_resp, enc_length); + if ((sasl_status = sasl_encode64(clientout, clientoutlen, + STR(init_resp), + vstring_avail(init_resp), + &enc_length_out)) != SASL_OK) + msg_panic("%s: sasl_encode64 botch: %s", + myname, xsasl_cyrus_strerror(sasl_status)); + vstring_set_payload_size(init_resp, enc_length_out); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(clientout); +#endif + } else { + vstring_strcpy(init_resp, ""); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_client_next - continue authentication */ + +static int xsasl_cyrus_client_next(XSASL_CLIENT *xp, const char *server_reply, + VSTRING *client_reply) +{ + const char *myname = "xsasl_cyrus_client_next"; + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + unsigned enc_length; + unsigned enc_length_out; + CLIENTOUT_TYPE clientout; + unsigned clientoutlen; + unsigned serverinlen; + int sasl_status; + + /* + * Process a server challenge. + */ + serverinlen = strlen(server_reply); + VSTRING_RESET(client->decoded); /* Fix 200512 */ + VSTRING_SPACE(client->decoded, serverinlen); + if ((sasl_status = SASL_DECODE64(server_reply, serverinlen, + STR(client->decoded), + vstring_avail(client->decoded), + &enc_length)) != SASL_OK) { + vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FORM); + } + if (msg_verbose) + msg_info("%s: decoded challenge: %.*s", + myname, (int) enc_length, STR(client->decoded)); + sasl_status = sasl_client_step(client->sasl_conn, STR(client->decoded), + enc_length, NO_SASL_INTERACTION, + &clientout, &clientoutlen); + if (sasl_status != SASL_OK && sasl_status != SASL_CONTINUE) { + vstring_strcpy(client_reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + + /* + * Send a client response. + */ + if (clientoutlen > 0) { + if (msg_verbose) + msg_info("%s: uncoded client response %.*s", + myname, (int) clientoutlen, clientout); + enc_length = ENCODE64_LENGTH(clientoutlen) + 1; + VSTRING_RESET(client_reply); /* Fix 200512 */ + VSTRING_SPACE(client_reply, enc_length); + if ((sasl_status = sasl_encode64(clientout, clientoutlen, + STR(client_reply), + vstring_avail(client_reply), + &enc_length_out)) != SASL_OK) + msg_panic("%s: sasl_encode64 botch: %s", + myname, xsasl_cyrus_strerror(sasl_status)); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(clientout); +#endif + } else { + /* XXX Can't happen. */ + vstring_strcpy(client_reply, ""); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_client_free - per-session cleanup */ + +void xsasl_cyrus_client_free(XSASL_CLIENT *xp) +{ + XSASL_CYRUS_CLIENT *client = (XSASL_CYRUS_CLIENT *) xp; + + if (client->username) + myfree(client->username); + if (client->password) + myfree(client->password); + if (client->sasl_conn) + sasl_dispose(&client->sasl_conn); + myfree((void *) client->callbacks); + vstring_free(client->decoded); + myfree((void *) client); +} + +#endif diff --git a/src/xsasl/xsasl_cyrus_common.h b/src/xsasl/xsasl_cyrus_common.h new file mode 100644 index 0000000..5447378 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_common.h @@ -0,0 +1,39 @@ +#ifndef _CYRUS_COMMON_H_INCLUDED_ +#define _CYRUS_COMMON_H_INCLUDED_ + +/*++ +/* NAME +/* cyrus_common 3h +/* SUMMARY +/* Cyrus SASL plug-in helpers +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + +#define NO_SASL_LANGLIST ((const char *) 0) +#define NO_SASL_OUTLANG ((const char **) 0) +#define xsasl_cyrus_strerror(status) \ + sasl_errstring((status), NO_SASL_LANGLIST, NO_SASL_OUTLANG) +extern int xsasl_cyrus_log(void *, int, const char *); +extern int xsasl_cyrus_security_parse_opts(const char *); + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/xsasl/xsasl_cyrus_log.c b/src/xsasl/xsasl_cyrus_log.c new file mode 100644 index 0000000..7bf25c3 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_log.c @@ -0,0 +1,104 @@ +/*++ +/* NAME +/* xsasl_cyrus_log 3 +/* SUMMARY +/* Cyrus SASL logging call-back routine +/* SYNOPSIS +/* #include +/* +/* int xsasl_cyrus_log(context, priority, text) +/* void *context; +/* int priority; +/* const char *text; +/* DESCRIPTION +/* xsasl_cyrus_log() logs a Cyrus message. +/* DIAGNOSTICS: +/* Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include + +/* Application-specific */ + +#include + +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + +#include +#include + +/* xsasl_cyrus_log - logging callback */ + +int xsasl_cyrus_log(void *unused_context, int priority, + const char *message) +{ + switch (priority) { + case SASL_LOG_ERR: /* unusual errors */ +#ifdef SASL_LOG_WARN /* non-fatal warnings (Cyrus-SASL v2) */ + case SASL_LOG_WARN: +#endif +#ifdef SASL_LOG_WARNING /* non-fatal warnings (Cyrus-SASL v1) */ + case SASL_LOG_WARNING: +#endif + msg_warn("SASL authentication problem: %s", message); + break; +#ifdef SASL_LOG_INFO + case SASL_LOG_INFO: /* other info (Cyrus-SASL v1) */ + if (msg_verbose) + msg_info("SASL authentication info: %s", message); + break; +#endif +#ifdef SASL_LOG_NOTE + case SASL_LOG_NOTE: /* other info (Cyrus-SASL v2) */ + if (msg_verbose) + msg_info("SASL authentication info: %s", message); + break; +#endif +#ifdef SASL_LOG_FAIL + case SASL_LOG_FAIL: /* authentication failures + * (Cyrus-SASL v2) */ + msg_warn("SASL authentication failure: %s", message); + break; +#endif +#ifdef SASL_LOG_DEBUG + case SASL_LOG_DEBUG: /* more verbose than LOG_NOTE + * (Cyrus-SASL v2) */ + if (msg_verbose > 1) + msg_info("SASL authentication debug: %s", message); + break; +#endif +#ifdef SASL_LOG_TRACE + case SASL_LOG_TRACE: /* traces of internal + * protocols (Cyrus-SASL v2) */ + if (msg_verbose > 1) + msg_info("SASL authentication trace: %s", message); + break; +#endif +#ifdef SASL_LOG_PASS + case SASL_LOG_PASS: /* traces of internal + * protocols, including + * passwords (Cyrus-SASL v2) */ + if (msg_verbose > 1) + msg_info("SASL authentication pass: %s", message); + break; +#endif + } + return (SASL_OK); +} + +#endif diff --git a/src/xsasl/xsasl_cyrus_security.c b/src/xsasl/xsasl_cyrus_security.c new file mode 100644 index 0000000..7ca7216 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_security.c @@ -0,0 +1,87 @@ +/*++ +/* NAME +/* xsasl_cyrus_security 3 +/* SUMMARY +/* convert Cyrus SASL security properties to bit mask +/* SYNOPSIS +/* #include +/* +/* int xsasl_cyrus_security_parse_opts(properties) +/* const char *properties; +/* DESCRIPTION +/* xsasl_cyrus_security_parse_opts() converts a list of security +/* properties to a bit mask. The result is zero in case of error. +/* +/* Arguments: +/* .IP properties +/* A comma or space separated list of zero or more of the +/* following: +/* .RS +/* .IP noplaintext +/* Disallow authentication methods that use plaintext passwords. +/* .IP noactive +/* Disallow authentication methods that are vulnerable to +/* non-dictionary active attacks. +/* .IP nodictionary +/* Disallow authentication methods that are vulnerable to +/* passive dictionary attack. +/* .IP forward_secrecy +/* Require forward secrecy between sessions. +/* .IP noanonymous +/* Disallow anonymous logins. +/* .RE +/* DIAGNOSTICS: +/* Warning: bad input. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include + +/* Utility library. */ + +#include + +/* Application-specific. */ + +#include + +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + +#include + + /* + * SASL Security options. + */ +static const NAME_MASK xsasl_cyrus_sec_mask[] = { + "noplaintext", SASL_SEC_NOPLAINTEXT, + "noactive", SASL_SEC_NOACTIVE, + "nodictionary", SASL_SEC_NODICTIONARY, +#ifdef SASL_SEC_FORWARD_SECRECY + "forward_secrecy", SASL_SEC_FORWARD_SECRECY, +#endif + "noanonymous", SASL_SEC_NOANONYMOUS, +#if SASL_VERSION_MAJOR >= 2 + "mutual_auth", SASL_SEC_MUTUAL_AUTH, +#endif + 0, +}; + +/* xsasl_cyrus_security - parse security options */ + +int xsasl_cyrus_security_parse_opts(const char *sasl_opts_val) +{ + return (name_mask_opt("SASL security options", xsasl_cyrus_sec_mask, + sasl_opts_val, NAME_MASK_RETURN)); +} + +#endif diff --git a/src/xsasl/xsasl_cyrus_server.c b/src/xsasl/xsasl_cyrus_server.c new file mode 100644 index 0000000..4bf2ed2 --- /dev/null +++ b/src/xsasl/xsasl_cyrus_server.c @@ -0,0 +1,640 @@ +/*++ +/* NAME +/* xsasl_cyrus_server 3 +/* SUMMARY +/* Cyrus SASL server-side plug-in +/* SYNOPSIS +/* #include +/* +/* XSASL_SERVER_IMPL *xsasl_cyrus_server_init(server_type, path_info) +/* const char *server_type; +/* const char *path_info; +/* DESCRIPTION +/* This module implements the Cyrus SASL server-side authentication +/* plug-in. +/* +/* xsasl_cyrus_server_init() initializes the Cyrus SASL library and +/* returns an implementation handle that can be used to generate +/* SASL server instances. +/* +/* Arguments: +/* .IP server_type +/* The server type (cyrus). This argument is ignored, but it +/* could be used when one implementation provides multiple +/* variants. +/* .IP path_info +/* The base name of the SASL server configuration file (example: +/* smtpd becomes /usr/lib/sasl2/smtpd.conf). +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* Panic: interface violation. +/* +/* Other: the routines log a warning and return an error result +/* as specified in xsasl_server(3). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Till Franke +/* SuSE Rhein/Main AG +/* 65760 Eschborn, Germany +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +/* Utility library. */ + +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include +#include +#include + +#if defined(USE_SASL_AUTH) && defined(USE_CYRUS_SASL) + +#include +#include + +/* + * Silly little macros. + */ +#define STR(s) vstring_str(s) + + /* + * Macros to handle API differences between SASLv1 and SASLv2. Specifics: + * + * The SASL_LOG_* constants were renamed in SASLv2. + * + * SASLv2's sasl_server_new takes two new parameters to specify local and + * remote IP addresses for auth mechs that use them. + * + * SASLv2's sasl_server_start and sasl_server_step no longer have the errstr + * parameter. + * + * SASLv2's sasl_decode64 function takes an extra parameter for the length of + * the output buffer. + * + * The other major change is that SASLv2 now takes more responsibility for + * deallocating memory that it allocates internally. Thus, some of the + * function parameters are now 'const', to make sure we don't try to free + * them too. This is dealt with in the code later on. + */ + +#if SASL_VERSION_MAJOR < 2 +/* SASL version 1.x */ +#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ + sasl_server_new(srv, fqdn, rlm, cb, secflags, pconn) +#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen, err) +#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_step(conn, clin, clinlen, srvout, srvoutlen, err) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outlen) +typedef char *MECHANISM_TYPE; +typedef unsigned MECHANISM_COUNT_TYPE; +typedef char *SERVEROUT_TYPE; +typedef void *VOID_SERVEROUT_TYPE; + +#endif + +#if SASL_VERSION_MAJOR >= 2 +/* SASL version > 2.x */ +#define SASL_SERVER_NEW(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) \ + sasl_server_new(srv, fqdn, rlm, lport, rport, cb, secflags, pconn) +#define SASL_SERVER_START(conn, mech, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_start(conn, mech, clin, clinlen, srvout, srvoutlen) +#define SASL_SERVER_STEP(conn, clin, clinlen, srvout, srvoutlen, err) \ + sasl_server_step(conn, clin, clinlen, srvout, srvoutlen) +#define SASL_DECODE64(in, inlen, out, outmaxlen, outlen) \ + sasl_decode64(in, inlen, out, outmaxlen, outlen) +typedef const char *MECHANISM_TYPE; +typedef int MECHANISM_COUNT_TYPE; +typedef const char *SERVEROUT_TYPE; +typedef const void *VOID_SERVEROUT_TYPE; + +#endif + +#ifndef NO_IP_CYRUS_SASL_AUTH +#define USE_IP_CYRUS_SASL_AUTH +#endif + + /* + * The XSASL_CYRUS_SERVER object is derived from the generic XSASL_SERVER + * object. + */ +typedef struct { + XSASL_SERVER xsasl; /* generic members, must be first */ + VSTREAM *stream; /* client-server connection */ + sasl_conn_t *sasl_conn; /* SASL context */ + VSTRING *decoded; /* decoded challenge or response */ + char *username; /* authenticated user */ + char *mechanism_list; /* applicable mechanisms */ +} XSASL_CYRUS_SERVER; + + /* + * Forward declarations. + */ +static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *); +static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *, + XSASL_SERVER_CREATE_ARGS *); +static void xsasl_cyrus_server_free(XSASL_SERVER *); +static int xsasl_cyrus_server_first(XSASL_SERVER *, const char *, + const char *, VSTRING *); +static int xsasl_cyrus_server_next(XSASL_SERVER *, const char *, VSTRING *); +static int xsasl_cyrus_server_set_security(XSASL_SERVER *, const char *); +static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *); +static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *); + + /* + * SASL callback interface structure. These call-backs have no per-session + * context. + */ +#define NO_CALLBACK_CONTEXT 0 + +static sasl_callback_t callbacks[] = { + {SASL_CB_LOG, (XSASL_CYRUS_CB) &xsasl_cyrus_log, NO_CALLBACK_CONTEXT}, + {SASL_CB_LIST_END, 0, 0} +}; + +/* xsasl_cyrus_server_init - create implementation handle */ + +XSASL_SERVER_IMPL *xsasl_cyrus_server_init(const char *unused_server_type, + const char *path_info) +{ + const char *myname = "xsasl_cyrus_server_init"; + XSASL_SERVER_IMPL *xp; + int sasl_status; + +#if SASL_VERSION_MAJOR >= 2 && (SASL_VERSION_MINOR >= 2 \ + || (SASL_VERSION_MINOR == 1 && SASL_VERSION_STEP >= 19)) + int sasl_major; + int sasl_minor; + int sasl_step; + + /* + * DLL hell guard. + */ + sasl_version_info((const char **) 0, (const char **) 0, + &sasl_major, &sasl_minor, + &sasl_step, (int *) 0); + if (sasl_major != SASL_VERSION_MAJOR +#if 0 + || sasl_minor != SASL_VERSION_MINOR + || sasl_step != SASL_VERSION_STEP +#endif + ) { + msg_warn("incorrect SASL library version. " + "Postfix was built with include files from version %d.%d.%d, " + "but the run-time library version is %d.%d.%d", + SASL_VERSION_MAJOR, SASL_VERSION_MINOR, SASL_VERSION_STEP, + sasl_major, sasl_minor, sasl_step); + return (0); + } +#endif + + if (*var_cyrus_conf_path) { +#ifdef SASL_PATH_TYPE_CONFIG /* Cyrus SASL 2.1.22 */ + if (sasl_set_path(SASL_PATH_TYPE_CONFIG, + var_cyrus_conf_path) != SASL_OK) + msg_warn("failed to set Cyrus SASL configuration path: \"%s\"", + var_cyrus_conf_path); +#else + msg_warn("%s is not empty, but setting the Cyrus SASL configuration " + "path is not supported with SASL library version %d.%d.%d", + VAR_CYRUS_CONF_PATH, SASL_VERSION_MAJOR, + SASL_VERSION_MINOR, SASL_VERSION_STEP); +#endif + } + + /* + * Initialize the library: load SASL plug-in routines, etc. + */ + if (msg_verbose) + msg_info("%s: SASL config file is %s.conf", myname, path_info); + if ((sasl_status = sasl_server_init(callbacks, path_info)) != SASL_OK) { + msg_warn("SASL per-process initialization failed: %s", + xsasl_cyrus_strerror(sasl_status)); + return (0); + } + + /* + * Return a generic XSASL_SERVER_IMPL object. We don't need to extend it + * with our own methods or data. + */ + xp = (XSASL_SERVER_IMPL *) mymalloc(sizeof(*xp)); + xp->create = xsasl_cyrus_server_create; + xp->done = xsasl_cyrus_server_done; + return (xp); +} + +/* xsasl_cyrus_server_done - dispose of implementation */ + +static void xsasl_cyrus_server_done(XSASL_SERVER_IMPL *impl) +{ + myfree((void *) impl); + sasl_done(); +} + +/* xsasl_cyrus_server_create - create server instance */ + +static XSASL_SERVER *xsasl_cyrus_server_create(XSASL_SERVER_IMPL *unused_impl, + XSASL_SERVER_CREATE_ARGS *args) +{ + const char *myname = "xsasl_cyrus_server_create"; + char *server_addr_port = 0; + char *client_addr_port = 0; + sasl_conn_t *sasl_conn = 0; + XSASL_CYRUS_SERVER *server = 0; + int sasl_status; + + if (msg_verbose) + msg_info("%s: SASL service=%s, realm=%s", + myname, args->service, args->user_realm ? + args->user_realm : "(null)"); + + /* + * The optimizer will eliminate code duplication and/or dead code. + */ +#define XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(x) \ + do { \ + if (server) { \ + xsasl_cyrus_server_free(&server->xsasl); \ + } else { \ + if (sasl_conn) \ + sasl_dispose(&sasl_conn); \ + } \ + XSASL_CYRUS_SERVER_CREATE_RETURN(x); \ + } while (0) + +#define XSASL_CYRUS_SERVER_CREATE_RETURN(x) \ + do { \ + if (server_addr_port) \ + myfree(server_addr_port); \ + if (client_addr_port) \ + myfree(client_addr_port); \ + return (x); \ + } while (0) + + /* + * Set up a new server context. + */ +#define NO_SECURITY_LAYERS (0) +#define NO_SESSION_CALLBACKS ((sasl_callback_t *) 0) +#define NO_AUTH_REALM ((char *) 0) + +#if SASL_VERSION_MAJOR >= 2 && defined(USE_IP_CYRUS_SASL_AUTH) + + /* + * Get IP address and port of local and remote endpoints for SASL. Some + * implementation supports "[ipv6addr]:port" and "ipv4addr:port" (e.g., + * https://illumos.org/man/3sasl/sasl_server_new), They still support the + * historical "address;port" syntax, so we stick with that for now. + */ + server_addr_port = (*args->server_addr && *args->server_port ? + concatenate(args->server_addr, ";", + args->server_port, (char *) 0) : 0); + client_addr_port = (*args->client_addr && *args->client_port ? + concatenate(args->client_addr, ";", + args->client_port, (char *) 0) : 0); +#else + + /* + * Don't give any IP address information to SASL. + */ +#endif + + if ((sasl_status = + SASL_SERVER_NEW(args->service, var_myhostname, + args->user_realm ? args->user_realm : NO_AUTH_REALM, + server_addr_port, client_addr_port, + NO_SESSION_CALLBACKS, NO_SECURITY_LAYERS, + &sasl_conn)) != SASL_OK) { + msg_warn("SASL per-connection server initialization: %s", + xsasl_cyrus_strerror(sasl_status)); + XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); + } + + /* + * Extend the XSASL_SERVER object with our own data. We use long-lived + * conversion buffers rather than local variables to avoid memory leaks + * in case of read/write timeout or I/O error. + */ + server = (XSASL_CYRUS_SERVER *) mymalloc(sizeof(*server)); + server->xsasl.free = xsasl_cyrus_server_free; + server->xsasl.first = xsasl_cyrus_server_first; + server->xsasl.next = xsasl_cyrus_server_next; + server->xsasl.get_mechanism_list = xsasl_cyrus_server_get_mechanism_list; + server->xsasl.get_username = xsasl_cyrus_server_get_username; + server->stream = args->stream; + server->sasl_conn = sasl_conn; + server->decoded = vstring_alloc(20); + server->username = 0; + server->mechanism_list = 0; + + if (xsasl_cyrus_server_set_security(&server->xsasl, args->security_options) + != XSASL_AUTH_OK) + XSASL_CYRUS_SERVER_CREATE_ERROR_RETURN(0); + + XSASL_CYRUS_SERVER_CREATE_RETURN(&server->xsasl); +} + +/* xsasl_cyrus_server_set_security - set security properties */ + +static int xsasl_cyrus_server_set_security(XSASL_SERVER *xp, + const char *sasl_opts_val) +{ + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + sasl_security_properties_t sec_props; + int sasl_status; + + /* + * Security options. Some information can be found in the sasl.h include + * file. + */ + memset(&sec_props, 0, sizeof(sec_props)); + sec_props.min_ssf = 0; + sec_props.max_ssf = 0; /* don't allow real SASL + * security layer */ + if (*sasl_opts_val == 0) { + sec_props.security_flags = 0; + } else { + sec_props.security_flags = + xsasl_cyrus_security_parse_opts(sasl_opts_val); + if (sec_props.security_flags == 0) { + msg_warn("bad per-session SASL security properties"); + return (XSASL_AUTH_FAIL); + } + } + sec_props.maxbufsize = 0; + sec_props.property_names = 0; + sec_props.property_values = 0; + + if ((sasl_status = sasl_setprop(server->sasl_conn, SASL_SEC_PROPS, + &sec_props)) != SASL_OK) { + msg_warn("SASL per-connection security setup; %s", + xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FAIL); + } + return (XSASL_AUTH_OK); +} + +/* xsasl_cyrus_server_get_mechanism_list - get available mechanisms */ + +static const char *xsasl_cyrus_server_get_mechanism_list(XSASL_SERVER *xp) +{ + const char *myname = "xsasl_cyrus_server_get_mechanism_list"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + MECHANISM_TYPE mechanism_list; + MECHANISM_COUNT_TYPE mechanism_count; + int sasl_status; + + /* + * Get the list of authentication mechanisms. + */ +#define UNSUPPORTED_USER ((char *) 0) +#define IGNORE_MECHANISM_LEN ((unsigned *) 0) + + if ((sasl_status = sasl_listmech(server->sasl_conn, UNSUPPORTED_USER, + "", " ", "", + &mechanism_list, + IGNORE_MECHANISM_LEN, + &mechanism_count)) != SASL_OK) { + msg_warn("%s: %s", myname, xsasl_cyrus_strerror(sasl_status)); + return (0); + } + if (mechanism_count <= 0) { + msg_warn("%s: no applicable SASL mechanisms", myname); + return (0); + } + server->mechanism_list = mystrdup(mechanism_list); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(mechanism_list); +#endif + return (server->mechanism_list); +} + +/* xsasl_cyrus_server_free - destroy server instance */ + +static void xsasl_cyrus_server_free(XSASL_SERVER *xp) +{ + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + + sasl_dispose(&server->sasl_conn); + vstring_free(server->decoded); + if (server->username) + myfree(server->username); + if (server->mechanism_list) + myfree(server->mechanism_list); + myfree((void *) server); +} + +/* xsasl_cyrus_server_auth_response - encode server first/next response */ + +static int xsasl_cyrus_server_auth_response(int sasl_status, + SERVEROUT_TYPE serverout, + unsigned serveroutlen, + VSTRING *reply) +{ + const char *myname = "xsasl_cyrus_server_auth_response"; + unsigned enc_length; + unsigned enc_length_out; + + /* + * Encode the server first/next non-error response; otherwise return the + * unencoded error text that corresponds to the SASL error status. + * + * Regarding the hairy expression below: output from sasl_encode64() comes + * in multiples of four bytes for each triple of input bytes, plus four + * bytes for any incomplete last triple, plus one byte for the null + * terminator. + */ + if (sasl_status == SASL_OK) { + vstring_strcpy(reply, ""); + return (XSASL_AUTH_DONE); + } else if (sasl_status == SASL_CONTINUE) { + if (msg_verbose) + msg_info("%s: uncoded server challenge: %.*s", + myname, (int) serveroutlen, serverout); + enc_length = ((serveroutlen + 2) / 3) * 4 + 1; + VSTRING_RESET(reply); /* Fix 200512 */ + VSTRING_SPACE(reply, enc_length); + if ((sasl_status = sasl_encode64(serverout, serveroutlen, + STR(reply), vstring_avail(reply), + &enc_length_out)) != SASL_OK) + msg_panic("%s: sasl_encode64 botch: %s", + myname, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_MORE); + } else { + if (sasl_status == SASL_NOUSER) /* privacy */ + sasl_status = SASL_BADAUTH; + vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); + switch (sasl_status) { + case SASL_FAIL: + case SASL_NOMEM: + case SASL_TRYAGAIN: + case SASL_UNAVAIL: + return XSASL_AUTH_TEMP; + default: + return (XSASL_AUTH_FAIL); + } + } +} + +/* xsasl_cyrus_server_first - per-session authentication */ + +int xsasl_cyrus_server_first(XSASL_SERVER *xp, const char *sasl_method, + const char *init_response, VSTRING *reply) +{ + const char *myname = "xsasl_cyrus_server_first"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + char *dec_buffer; + unsigned dec_length; + unsigned reply_len; + unsigned serveroutlen; + int sasl_status; + SERVEROUT_TYPE serverout = 0; + int xsasl_status; + +#if SASL_VERSION_MAJOR < 2 + const char *errstr = 0; + +#endif + +#define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3)) + + if (msg_verbose) + msg_info("%s: sasl_method %s%s%s", myname, sasl_method, + IFELSE(init_response, ", init_response ", ""), + IFELSE(init_response, init_response, "")); + + /* + * SASL authentication protocol start-up. Process any initial client + * response that was sent along in the AUTH command. + */ + if (init_response) { + reply_len = strlen(init_response); + VSTRING_RESET(server->decoded); /* Fix 200512 */ + VSTRING_SPACE(server->decoded, reply_len); + if ((sasl_status = SASL_DECODE64(init_response, reply_len, + dec_buffer = STR(server->decoded), + vstring_avail(server->decoded), + &dec_length)) != SASL_OK) { + vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FORM); + } + if (msg_verbose) + msg_info("%s: decoded initial response %s", myname, dec_buffer); + } else { + dec_buffer = 0; + dec_length = 0; + } + sasl_status = SASL_SERVER_START(server->sasl_conn, sasl_method, dec_buffer, + dec_length, &serverout, + &serveroutlen, &errstr); + xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, + serveroutlen, reply); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(serverout); +#endif + return (xsasl_status); +} + +/* xsasl_cyrus_server_next - continue authentication */ + +static int xsasl_cyrus_server_next(XSASL_SERVER *xp, const char *request, + VSTRING *reply) +{ + const char *myname = "xsasl_cyrus_server_next"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + unsigned dec_length; + unsigned request_len; + unsigned serveroutlen; + int sasl_status; + SERVEROUT_TYPE serverout = 0; + int xsasl_status; + +#if SASL_VERSION_MAJOR < 2 + const char *errstr = 0; + +#endif + + request_len = strlen(request); + VSTRING_RESET(server->decoded); /* Fix 200512 */ + VSTRING_SPACE(server->decoded, request_len); + if ((sasl_status = SASL_DECODE64(request, request_len, + STR(server->decoded), + vstring_avail(server->decoded), + &dec_length)) != SASL_OK) { + vstring_strcpy(reply, xsasl_cyrus_strerror(sasl_status)); + return (XSASL_AUTH_FORM); + } + if (msg_verbose) + msg_info("%s: decoded response: %.*s", + myname, (int) dec_length, STR(server->decoded)); + sasl_status = SASL_SERVER_STEP(server->sasl_conn, STR(server->decoded), + dec_length, &serverout, + &serveroutlen, &errstr); + xsasl_status = xsasl_cyrus_server_auth_response(sasl_status, serverout, + serveroutlen, reply); +#if SASL_VERSION_MAJOR < 2 + /* SASL version 1 doesn't free memory that it allocates. */ + free(serverout); +#endif + return (xsasl_status); +} + +/* xsasl_cyrus_server_get_username - get authenticated username */ + +static const char *xsasl_cyrus_server_get_username(XSASL_SERVER *xp) +{ + const char *myname = "xsasl_cyrus_server_get_username"; + XSASL_CYRUS_SERVER *server = (XSASL_CYRUS_SERVER *) xp; + VOID_SERVEROUT_TYPE serverout = 0; + int sasl_status; + + /* + * XXX Do not free(serverout). + */ + if (server->username) + myfree(server->username); + sasl_status = sasl_getprop(server->sasl_conn, SASL_USERNAME, &serverout); + if (sasl_status != SASL_OK || serverout == 0) { + server->username = 0; + } else { + server->username = mystrdup(serverout); + printable(server->username, '?'); + } + return (server->username); +} + +#endif diff --git a/src/xsasl/xsasl_dovecot.h b/src/xsasl/xsasl_dovecot.h new file mode 100644 index 0000000..f99850e --- /dev/null +++ b/src/xsasl/xsasl_dovecot.h @@ -0,0 +1,41 @@ +#ifndef _XSASL_DOVECOT_H_INCLUDED_ +#define _XSASL_DOVECOT_H_INCLUDED_ + +/*++ +/* NAME +/* xsasl_dovecot 3h +/* SUMMARY +/* Dovecot SASL plug-in +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * XSASL library. + */ +#include + +#if defined(USE_SASL_AUTH) + + /* + * SASL protocol interface + */ +#define XSASL_TYPE_DOVECOT "dovecot" + +extern XSASL_SERVER_IMPL *xsasl_dovecot_server_init(const char *, const char *); + +#endif + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/src/xsasl/xsasl_dovecot_server.c b/src/xsasl/xsasl_dovecot_server.c new file mode 100644 index 0000000..1d1c570 --- /dev/null +++ b/src/xsasl/xsasl_dovecot_server.c @@ -0,0 +1,747 @@ +/*++ +/* NAME +/* xsasl_dovecot_server 3 +/* SUMMARY +/* Dovecot SASL server-side plug-in +/* SYNOPSIS +/* XSASL_SERVER_IMPL *xsasl_dovecot_server_init(server_type, appl_name) +/* const char *server_type; +/* const char *appl_name; +/* DESCRIPTION +/* This module implements the Dovecot SASL server-side authentication +/* plug-in. +/* +/* .IP server_type +/* The plug-in type that was specified to xsasl_server_init(). +/* The argument is ignored, because the Dovecot plug-in +/* implements only one plug-in type. +/* .IP path_info +/* The location of the Dovecot authentication server's UNIX-domain +/* socket. Note: the Dovecot plug-in uses late binding, therefore +/* all connect operations are done with Postfix privileges. +/* DIAGNOSTICS +/* Fatal: out of memory. +/* +/* Panic: interface violation. +/* +/* Other: the routines log a warning and return an error result +/* as specified in xsasl_server(3). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Initial implementation by: +/* Timo Sirainen +/* Procontrol +/* Finland +/* +/* Adopted by: +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include +#include +#include + +#ifdef STRCASECMP_IN_STRINGS_H +#include +#endif + +/* Utility library. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Global library. */ + +#include + +/* Application-specific. */ + +#include +#include + +#ifdef USE_SASL_AUTH + +/* Major version changes are not backwards compatible, + minor version numbers can be ignored. */ +#define AUTH_PROTOCOL_MAJOR_VERSION 1 +#define AUTH_PROTOCOL_MINOR_VERSION 0 + + /* + * Enforce read/write time limits, so that we can produce accurate + * diagnostics instead of getting killed by the watchdog timer. + */ +#define AUTH_TIMEOUT 10 + + /* + * Security property bitmasks. + */ +#define SEC_PROPS_NOPLAINTEXT (1 << 0) +#define SEC_PROPS_NOACTIVE (1 << 1) +#define SEC_PROPS_NODICTIONARY (1 << 2) +#define SEC_PROPS_NOANONYMOUS (1 << 3) +#define SEC_PROPS_FWD_SECRECY (1 << 4) +#define SEC_PROPS_MUTUAL_AUTH (1 << 5) +#define SEC_PROPS_PRIVATE (1 << 6) + +#define SEC_PROPS_POS_MASK (SEC_PROPS_MUTUAL_AUTH | SEC_PROPS_FWD_SECRECY) +#define SEC_PROPS_NEG_MASK (SEC_PROPS_NOPLAINTEXT | SEC_PROPS_NOACTIVE | \ + SEC_PROPS_NODICTIONARY | SEC_PROPS_NOANONYMOUS) + + /* + * Security properties as specified in the Postfix main.cf file. + */ +static const NAME_MASK xsasl_dovecot_conf_sec_props[] = { + "noplaintext", SEC_PROPS_NOPLAINTEXT, + "noactive", SEC_PROPS_NOACTIVE, + "nodictionary", SEC_PROPS_NODICTIONARY, + "noanonymous", SEC_PROPS_NOANONYMOUS, + "forward_secrecy", SEC_PROPS_FWD_SECRECY, + "mutual_auth", SEC_PROPS_MUTUAL_AUTH, + 0, 0, +}; + + /* + * Security properties as specified in the Dovecot protocol. See + * http://wiki.dovecot.org/Authentication_Protocol. + */ +static const NAME_MASK xsasl_dovecot_serv_sec_props[] = { + "plaintext", SEC_PROPS_NOPLAINTEXT, + "active", SEC_PROPS_NOACTIVE, + "dictionary", SEC_PROPS_NODICTIONARY, + "anonymous", SEC_PROPS_NOANONYMOUS, + "forward-secrecy", SEC_PROPS_FWD_SECRECY, + "mutual-auth", SEC_PROPS_MUTUAL_AUTH, + "private", SEC_PROPS_PRIVATE, + 0, 0, +}; + + /* + * Class variables. + */ +typedef struct XSASL_DCSRV_MECH { + char *mech_name; /* mechanism name */ + int sec_props; /* mechanism properties */ + struct XSASL_DCSRV_MECH *next; +} XSASL_DCSRV_MECH; + +typedef struct { + XSASL_SERVER_IMPL xsasl; + VSTREAM *sasl_stream; + char *socket_path; + XSASL_DCSRV_MECH *mechanism_list; /* unfiltered mechanism list */ + unsigned int request_id_counter; +} XSASL_DOVECOT_SERVER_IMPL; + + /* + * The XSASL_DOVECOT_SERVER object is derived from the generic XSASL_SERVER + * object. + */ +typedef struct { + XSASL_SERVER xsasl; /* generic members, must be first */ + XSASL_DOVECOT_SERVER_IMPL *impl; + unsigned int last_request_id; + char *service; + char *username; /* authenticated user */ + VSTRING *sasl_line; + unsigned int sec_props; /* Postfix mechanism filter */ + int tls_flag; /* TLS enabled in this session */ + char *mechanism_list; /* filtered mechanism list */ + ARGV *mechanism_argv; /* ditto */ + char *client_addr; /* remote IP address */ + char *server_addr; /* remote IP address */ +} XSASL_DOVECOT_SERVER; + + /* + * Forward declarations. + */ +static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *); +static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *, + XSASL_SERVER_CREATE_ARGS *); +static void xsasl_dovecot_server_free(XSASL_SERVER *); +static int xsasl_dovecot_server_first(XSASL_SERVER *, const char *, + const char *, VSTRING *); +static int xsasl_dovecot_server_next(XSASL_SERVER *, const char *, VSTRING *); +static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *); +static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *); + +/* xsasl_dovecot_server_mech_append - append server mechanism entry */ + +static void xsasl_dovecot_server_mech_append(XSASL_DCSRV_MECH **mech_list, + const char *mech_name, int sec_props) +{ + XSASL_DCSRV_MECH **mpp; + XSASL_DCSRV_MECH *mp; + + for (mpp = mech_list; *mpp != 0; mpp = &mpp[0]->next) + /* void */ ; + + mp = (XSASL_DCSRV_MECH *) mymalloc(sizeof(*mp)); + mp->mech_name = mystrdup(mech_name); + mp->sec_props = sec_props; + mp->next = 0; + *mpp = mp; +} + +/* xsasl_dovecot_server_mech_free - destroy server mechanism list */ + +static void xsasl_dovecot_server_mech_free(XSASL_DCSRV_MECH *mech_list) +{ + XSASL_DCSRV_MECH *mp; + XSASL_DCSRV_MECH *next; + + for (mp = mech_list; mp != 0; mp = next) { + myfree(mp->mech_name); + next = mp->next; + myfree((void *) mp); + } +} + +/* xsasl_dovecot_server_mech_filter - filter server mechanism list */ + +static char *xsasl_dovecot_server_mech_filter(ARGV *mechanism_argv, + XSASL_DCSRV_MECH *mechanism_list, + unsigned int conf_props) +{ + const char *myname = "xsasl_dovecot_server_mech_filter"; + unsigned int pos_conf_props = (conf_props & SEC_PROPS_POS_MASK); + unsigned int neg_conf_props = (conf_props & SEC_PROPS_NEG_MASK); + VSTRING *mechanisms_str = vstring_alloc(10); + XSASL_DCSRV_MECH *mp; + + /* + * Match Postfix properties against Dovecot server properties. + */ + for (mp = mechanism_list; mp != 0; mp = mp->next) { + if ((mp->sec_props & pos_conf_props) == pos_conf_props + && (mp->sec_props & neg_conf_props) == 0) { + if (VSTRING_LEN(mechanisms_str) > 0) + VSTRING_ADDCH(mechanisms_str, ' '); + vstring_strcat(mechanisms_str, mp->mech_name); + argv_add(mechanism_argv, mp->mech_name, (char *) 0); + if (msg_verbose) + msg_info("%s: keep mechanism: %s", myname, mp->mech_name); + } else { + if (msg_verbose) + msg_info("%s: skip mechanism: %s", myname, mp->mech_name); + } + } + return (vstring_export(mechanisms_str)); +} + +/* xsasl_dovecot_server_connect - initial auth server handshake */ + +static int xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL *xp) +{ + const char *myname = "xsasl_dovecot_server_connect"; + VSTRING *line_str; + VSTREAM *sasl_stream; + char *line, *cmd, *mech_name; + unsigned int major_version, minor_version; + int fd, success, have_mech_line; + int sec_props; + const char *path; + + if (msg_verbose) + msg_info("%s: Connecting", myname); + + /* + * Not documented, but necessary for testing. + */ + path = xp->socket_path; + if (strncmp(path, "inet:", 5) == 0) { + fd = inet_connect(path + 5, BLOCKING, AUTH_TIMEOUT); + } else { + if (strncmp(path, "unix:", 5) == 0) + path += 5; + fd = unix_connect(path, BLOCKING, AUTH_TIMEOUT); + } + if (fd < 0) { + msg_warn("SASL: Connect to Dovecot auth socket '%s' failed: %m", + xp->socket_path); + return (-1); + } + sasl_stream = vstream_fdopen(fd, O_RDWR); + vstream_control(sasl_stream, + CA_VSTREAM_CTL_PATH(xp->socket_path), + CA_VSTREAM_CTL_TIMEOUT(AUTH_TIMEOUT), + CA_VSTREAM_CTL_END); + + /* XXX Encapsulate for logging. */ + vstream_fprintf(sasl_stream, + "VERSION\t%u\t%u\n" + "CPID\t%u\n", + AUTH_PROTOCOL_MAJOR_VERSION, + AUTH_PROTOCOL_MINOR_VERSION, + (unsigned int) getpid()); + if (vstream_fflush(sasl_stream) == VSTREAM_EOF) { + msg_warn("SASL: Couldn't send handshake: %m"); + return (-1); + } + success = 0; + have_mech_line = 0; + line_str = vstring_alloc(256); + /* XXX Encapsulate for logging. */ + while (vstring_get_nonl(line_str, sasl_stream) != VSTREAM_EOF) { + line = vstring_str(line_str); + + if (msg_verbose) + msg_info("%s: auth reply: %s", myname, line); + + cmd = line; + line = split_at(line, '\t'); + + if (strcmp(cmd, "VERSION") == 0) { + if (sscanf(line, "%u\t%u", &major_version, &minor_version) != 2) { + msg_warn("SASL: Protocol version error"); + break; + } + if (major_version != AUTH_PROTOCOL_MAJOR_VERSION) { + /* Major version is different from ours. */ + msg_warn("SASL: Protocol version mismatch (%d vs. %d)", + major_version, AUTH_PROTOCOL_MAJOR_VERSION); + break; + } + } else if (strcmp(cmd, "MECH") == 0 && line != NULL) { + mech_name = line; + have_mech_line = 1; + line = split_at(line, '\t'); + if (line != 0) { + sec_props = + name_mask_delim_opt(myname, + xsasl_dovecot_serv_sec_props, + line, "\t", + NAME_MASK_ANY_CASE | NAME_MASK_IGNORE); + if ((sec_props & SEC_PROPS_PRIVATE) != 0) + continue; + } else + sec_props = 0; + xsasl_dovecot_server_mech_append(&xp->mechanism_list, mech_name, + sec_props); + } else if (strcmp(cmd, "SPID") == 0) { + + /* + * Unfortunately the auth protocol handshake wasn't designed well + * to differentiate between auth-client/userdb/master. + * auth-userdb and auth-master send VERSION + SPID lines only and + * nothing afterwards, while auth-client sends VERSION + MECH + + * SPID + CUID + more. The simplest way that we can determine if + * we've connected to the correct socket is to see if MECH line + * exists or not (alternatively we'd have to have a small timeout + * after SPID to see if CUID is sent or not). + */ + if (!have_mech_line) { + msg_warn("SASL: Connected to wrong auth socket (auth-master instead of auth-client)"); + break; + } + } else if (strcmp(cmd, "DONE") == 0) { + /* Handshake finished. */ + success = 1; + break; + } else { + /* ignore any unknown commands */ + } + } + vstring_free(line_str); + + if (!success) { + /* handshake failed */ + (void) vstream_fclose(sasl_stream); + return (-1); + } + xp->sasl_stream = sasl_stream; + return (0); +} + +/* xsasl_dovecot_server_disconnect - dispose of server connection state */ + +static void xsasl_dovecot_server_disconnect(XSASL_DOVECOT_SERVER_IMPL *xp) +{ + if (xp->sasl_stream) { + (void) vstream_fclose(xp->sasl_stream); + xp->sasl_stream = 0; + } + if (xp->mechanism_list) { + xsasl_dovecot_server_mech_free(xp->mechanism_list); + xp->mechanism_list = 0; + } +} + +/* xsasl_dovecot_server_init - create implementation handle */ + +XSASL_SERVER_IMPL *xsasl_dovecot_server_init(const char *server_type, + const char *path_info) +{ + XSASL_DOVECOT_SERVER_IMPL *xp; + + xp = (XSASL_DOVECOT_SERVER_IMPL *) mymalloc(sizeof(*xp)); + xp->xsasl.create = xsasl_dovecot_server_create; + xp->xsasl.done = xsasl_dovecot_server_done; + xp->socket_path = mystrdup(path_info); + xp->sasl_stream = 0; + xp->mechanism_list = 0; + xp->request_id_counter = 0; + return (&xp->xsasl); +} + +/* xsasl_dovecot_server_done - dispose of implementation */ + +static void xsasl_dovecot_server_done(XSASL_SERVER_IMPL *impl) +{ + XSASL_DOVECOT_SERVER_IMPL *xp = (XSASL_DOVECOT_SERVER_IMPL *) impl; + + xsasl_dovecot_server_disconnect(xp); + myfree(xp->socket_path); + myfree((void *) impl); +} + +/* xsasl_dovecot_server_create - create server instance */ + +static XSASL_SERVER *xsasl_dovecot_server_create(XSASL_SERVER_IMPL *impl, + XSASL_SERVER_CREATE_ARGS *args) +{ + const char *myname = "xsasl_dovecot_server_create"; + XSASL_DOVECOT_SERVER *server; + struct sockaddr_storage ss; + struct sockaddr *sa = (struct sockaddr *) &ss; + SOCKADDR_SIZE salen; + MAI_HOSTADDR_STR server_addr; + + if (msg_verbose) + msg_info("%s: SASL service=%s, realm=%s", + myname, args->service, args->user_realm ? + args->user_realm : "(null)"); + + /* + * Extend the XSASL_SERVER_IMPL object with our own data. We use + * long-lived conversion buffers rather than local variables to avoid + * memory leaks in case of read/write timeout or I/O error. + */ + server = (XSASL_DOVECOT_SERVER *) mymalloc(sizeof(*server)); + server->xsasl.free = xsasl_dovecot_server_free; + server->xsasl.first = xsasl_dovecot_server_first; + server->xsasl.next = xsasl_dovecot_server_next; + server->xsasl.get_mechanism_list = xsasl_dovecot_server_get_mechanism_list; + server->xsasl.get_username = xsasl_dovecot_server_get_username; + server->impl = (XSASL_DOVECOT_SERVER_IMPL *) impl; + server->sasl_line = vstring_alloc(256); + server->username = 0; + server->service = mystrdup(args->service); + server->last_request_id = 0; + server->mechanism_list = 0; + server->mechanism_argv = 0; + server->tls_flag = args->tls_flag; + server->sec_props = + name_mask_opt(myname, xsasl_dovecot_conf_sec_props, + args->security_options, + NAME_MASK_ANY_CASE | NAME_MASK_FATAL); + server->client_addr = mystrdup(args->client_addr); + + /* + * XXX Temporary code until smtpd_peer.c is updated. + */ + if (args->server_addr && *args->server_addr) { + server->server_addr = mystrdup(args->server_addr); + } else { + salen = sizeof(ss); + if (getsockname(vstream_fileno(args->stream), sa, &salen) < 0 + || sockaddr_to_hostaddr(sa, salen, &server_addr, 0, 0) != 0) + server_addr.buf[0] = 0; + server->server_addr = mystrdup(server_addr.buf); + } + + return (&server->xsasl); +} + +/* xsasl_dovecot_server_get_mechanism_list - get available mechanisms */ + +static const char *xsasl_dovecot_server_get_mechanism_list(XSASL_SERVER *xp) +{ + XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp; + + if (!server->impl->sasl_stream) { + if (xsasl_dovecot_server_connect(server->impl) < 0) + return (0); + } + if (server->mechanism_list == 0) { + server->mechanism_argv = argv_alloc(2); + server->mechanism_list = + xsasl_dovecot_server_mech_filter(server->mechanism_argv, + server->impl->mechanism_list, + server->sec_props); + } + return (server->mechanism_list[0] ? server->mechanism_list : 0); +} + +/* xsasl_dovecot_server_free - destroy server instance */ + +static void xsasl_dovecot_server_free(XSASL_SERVER *xp) +{ + XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp; + + vstring_free(server->sasl_line); + if (server->username) + myfree(server->username); + if (server->mechanism_list) { + myfree(server->mechanism_list); + argv_free(server->mechanism_argv); + } + myfree(server->service); + myfree(server->server_addr); + myfree(server->client_addr); + myfree((void *) server); +} + +/* xsasl_dovecot_server_auth_response - encode server first/next response */ + +static int xsasl_dovecot_parse_reply(XSASL_DOVECOT_SERVER *server, char **line) +{ + char *id; + + if (*line == NULL) { + msg_warn("SASL: Protocol error"); + return -1; + } + id = *line; + *line = split_at(*line, '\t'); + + if (strtoul(id, NULL, 0) != server->last_request_id) { + /* reply to another request, shouldn't really happen.. */ + return -1; + } + return 0; +} + +static void xsasl_dovecot_parse_reply_args(XSASL_DOVECOT_SERVER *server, + char *line, VSTRING *reply, + int success) +{ + char *next; + + if (server->username) { + myfree(server->username); + server->username = 0; + } + + /* + * Note: TAB is part of the Dovecot protocol and must not appear in + * legitimate Dovecot usernames, otherwise the protocol would break. + */ + for (; line != NULL; line = next) { + next = split_at(line, '\t'); + if (strncmp(line, "user=", 5) == 0) { + server->username = mystrdup(line + 5); + printable(server->username, '?'); + } else if (strncmp(line, "reason=", 7) == 0) { + if (!success) { + printable(line + 7, '?'); + vstring_strcpy(reply, line + 7); + } + } + } +} + +/* xsasl_dovecot_handle_reply - receive and process auth reply */ + +static int xsasl_dovecot_handle_reply(XSASL_DOVECOT_SERVER *server, + VSTRING *reply) +{ + const char *myname = "xsasl_dovecot_handle_reply"; + char *line, *cmd; + + /* XXX Encapsulate for logging. */ + while (vstring_get_nonl(server->sasl_line, + server->impl->sasl_stream) != VSTREAM_EOF) { + line = vstring_str(server->sasl_line); + + if (msg_verbose) + msg_info("%s: auth reply: %s", myname, line); + + cmd = line; + line = split_at(line, '\t'); + + if (strcmp(cmd, "OK") == 0) { + if (xsasl_dovecot_parse_reply(server, &line) == 0) { + /* authentication successful */ + xsasl_dovecot_parse_reply_args(server, line, reply, 1); + if (server->username == 0) { + msg_warn("missing Dovecot server %s username field", cmd); + vstring_strcpy(reply, "Authentication backend error"); + return XSASL_AUTH_FAIL; + } + return XSASL_AUTH_DONE; + } + } else if (strcmp(cmd, "CONT") == 0) { + if (xsasl_dovecot_parse_reply(server, &line) == 0) { + if (line == 0) { + msg_warn("missing Dovecot server %s reply field", cmd); + vstring_strcpy(reply, "Authentication backend error"); + return XSASL_AUTH_FAIL; + } + vstring_strcpy(reply, line); + return XSASL_AUTH_MORE; + } + } else if (strcmp(cmd, "FAIL") == 0) { + if (xsasl_dovecot_parse_reply(server, &line) == 0) { + /* authentication failure */ + xsasl_dovecot_parse_reply_args(server, line, reply, 0); + return XSASL_AUTH_FAIL; + } + } else { + /* ignore */ + } + } + + vstring_strcpy(reply, "Connection lost to authentication server"); + return XSASL_AUTH_TEMP; +} + +/* is_valid_base64 - input sanitized */ + +static int is_valid_base64(const char *data) +{ + + /* + * XXX Maybe use ISALNUM() (isascii && isalnum, i.e. locale independent). + */ + for (; *data != '\0'; data++) { + if (!((*data >= '0' && *data <= '9') || + (*data >= 'a' && *data <= 'z') || + (*data >= 'A' && *data <= 'Z') || + *data == '+' || *data == '/' || *data == '=')) + return 0; + } + return 1; +} + +/* xsasl_dovecot_server_first - per-session authentication */ + +int xsasl_dovecot_server_first(XSASL_SERVER *xp, const char *sasl_method, + const char *init_response, VSTRING *reply) +{ + const char *myname = "xsasl_dovecot_server_first"; + XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp; + int i; + char **cpp; + +#define IFELSE(e1,e2,e3) ((e1) ? (e2) : (e3)) + + if (msg_verbose) + msg_info("%s: sasl_method %s%s%s", myname, sasl_method, + IFELSE(init_response, ", init_response ", ""), + IFELSE(init_response, init_response, "")); + + if (server->mechanism_argv == 0) + msg_panic("%s: no mechanism list", myname); + + for (cpp = server->mechanism_argv->argv; /* see below */ ; cpp++) { + if (*cpp == 0) { + vstring_strcpy(reply, "Invalid authentication mechanism"); + return XSASL_AUTH_FAIL; + } + if (strcasecmp(sasl_method, *cpp) == 0) + break; + } + if (init_response) + if (!is_valid_base64(init_response)) { + vstring_strcpy(reply, "Invalid base64 data in initial response"); + return XSASL_AUTH_FAIL; + } + for (i = 0; i < 2; i++) { + if (!server->impl->sasl_stream) { + if (xsasl_dovecot_server_connect(server->impl) < 0) + return XSASL_AUTH_TEMP; + } + /* send the request */ + server->last_request_id = ++server->impl->request_id_counter; + /* XXX Encapsulate for logging. */ + vstream_fprintf(server->impl->sasl_stream, + "AUTH\t%u\t%s\tservice=%s\tnologin\tlip=%s\trip=%s", + server->last_request_id, sasl_method, + server->service, server->server_addr, + server->client_addr); + if (server->tls_flag) + /* XXX Encapsulate for logging. */ + vstream_fputs("\tsecured", server->impl->sasl_stream); + if (init_response) { + + /* + * initial response is already base64 encoded, so we can send it + * directly. + */ + /* XXX Encapsulate for logging. */ + vstream_fprintf(server->impl->sasl_stream, + "\tresp=%s", init_response); + } + /* XXX Encapsulate for logging. */ + VSTREAM_PUTC('\n', server->impl->sasl_stream); + + if (vstream_fflush(server->impl->sasl_stream) != VSTREAM_EOF) + break; + + if (i == 1) { + vstring_strcpy(reply, "Can't connect to authentication server"); + return XSASL_AUTH_TEMP; + } + + /* + * Reconnect and try again. + */ + xsasl_dovecot_server_disconnect(server->impl); + } + + return xsasl_dovecot_handle_reply(server, reply); +} + +/* xsasl_dovecot_server_next - continue authentication */ + +static int xsasl_dovecot_server_next(XSASL_SERVER *xp, const char *request, + VSTRING *reply) +{ + XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp; + + if (!is_valid_base64(request)) { + vstring_strcpy(reply, "Invalid base64 data in continued response"); + return XSASL_AUTH_FAIL; + } + /* XXX Encapsulate for logging. */ + vstream_fprintf(server->impl->sasl_stream, + "CONT\t%u\t%s\n", server->last_request_id, request); + if (vstream_fflush(server->impl->sasl_stream) == VSTREAM_EOF) { + vstring_strcpy(reply, "Connection lost to authentication server"); + return XSASL_AUTH_TEMP; + } + return xsasl_dovecot_handle_reply(server, reply); +} + +/* xsasl_dovecot_server_get_username - get authenticated username */ + +static const char *xsasl_dovecot_server_get_username(XSASL_SERVER *xp) +{ + XSASL_DOVECOT_SERVER *server = (XSASL_DOVECOT_SERVER *) xp; + + return (server->username); +} + +#endif diff --git a/src/xsasl/xsasl_server.c b/src/xsasl/xsasl_server.c new file mode 100644 index 0000000..e8d7e16 --- /dev/null +++ b/src/xsasl/xsasl_server.c @@ -0,0 +1,270 @@ +/*++ +/* NAME +/* xsasl-server 3 +/* SUMMARY +/* Postfix SASL server plug-in interface +/* SYNOPSIS +/* #include +/* +/* XSASL_SERVER_IMPL *xsasl_server_init(server_type, path_info) +/* const char *server_type; +/* const char *path_info; +/* +/* void xsasl_server_done(implementation) +/* XSASL_SERVER_IMPL *implementation; +/* +/* ARGV *xsasl_server_types() +/* +/* .in +4 +/* typedef struct XSASL_SERVER_CREATE_ARGS { +/* VSTREAM *stream; +/* const char *server_addr; +/* const char *client_addr; +/* const char *service; +/* const char *user_realm; +/* const char *security_options; +/* int tls_flag; +/* } XSASL_SERVER_CREATE_ARGS; +/* .in -4 +/* +/* XSASL_SERVER *xsasl_server_create(implementation, args) +/* XSASL_SERVER_IMPL *implementation; +/* XSASL_SERVER_CREATE_ARGS *args; +/* +/* XSASL_SERVER *XSASL_SERVER_CREATE(implementation, args, +/* stream = stream_value, +/* ..., +/* tls_flag = tls_flag_value) +/* XSASL_SERVER_IMPL *implementation; +/* XSASL_SERVER_CREATE_ARGS *args; +/* +/* void xsasl_server_free(server) +/* XSASL_SERVER *server; +/* +/* int xsasl_server_first(server, auth_method, init_resp, server_reply) +/* XSASL_SERVER *server; +/* const char *auth_method; +/* const char *init_resp; +/* VSTRING *server_reply; +/* +/* int xsasl_server_next(server, client_request, server_reply) +/* XSASL_SERVER *server; +/* const char *client_request; +/* VSTRING *server_reply; +/* +/* const char *xsasl_server_get_mechanism_list(server) +/* XSASL_SERVER *server; +/* +/* const char *xsasl_server_get_username(server) +/* XSASL_SERVER *server; +/* DESCRIPTION +/* The XSASL_SERVER abstraction implements a generic interface +/* to one or more SASL authentication implementations. +/* +/* xsasl_server_init() is called once during process initialization. +/* It selects a SASL implementation by name, specifies the +/* location of a configuration file or rendez-vous point, and +/* returns an implementation handle that can be used to generate +/* SASL server instances. This function is typically used to +/* initialize the underlying implementation. +/* +/* xsasl_server_done() disposes of an implementation handle, +/* and allows the underlying implementation to release resources. +/* +/* xsasl_server_types() lists the available implementation types. +/* The result should be destroyed by the caller. +/* +/* xsasl_server_create() is called at the start of an SMTP +/* session. It generates a Postfix SASL plug-in server instance +/* for the specified service and authentication realm, and +/* with the specified security properties. Specify a null +/* pointer when no realm should be used. The stream handle is +/* stored so that encryption can be turned on after successful +/* negotiations. Specify zero-length strings when a client or +/* server address is unavailable. +/* +/* XSASL_SERVER_CREATE() is a macro that provides an interface +/* with named parameters. Named parameters do not have to +/* appear in a fixed order. The parameter names correspond to +/* the member names of the XSASL_SERVER_CREATE_ARGS structure. +/* +/* xsasl_server_free() is called at the end of an SMTP session. +/* It destroys a SASL server instance, and disables further +/* read/write operations if encryption was turned on. +/* +/* xsasl_server_first() produces the server response for the +/* client AUTH command. The client input are an authentication +/* method, and an optional initial response or null pointer. +/* The initial response and server non-error replies are BASE64 +/* encoded. Server error replies are 7-bit ASCII text without +/* control characters, without BASE64 encoding, and without +/* SMTP reply code or enhanced status code. +/* +/* The result is one of the following: +/* .IP XSASL_AUTH_MORE +/* More client input is needed. The server reply specifies +/* what. +/* .IP XSASL_AUTH_DONE +/* Authentication completed successfully. +/* .IP XSASL_AUTH_FORM +/* The client input is incorrectly formatted. The server error +/* reply explains why. +/* .IP XSASL_AUTH_FAIL +/* Authentication failed. The server error reply explains why. +/* .PP +/* xsasl_server_next() supports the subsequent stages of the +/* client-server AUTH protocol. Both the client input and +/* server non-error responses are BASE64 encoded. See +/* xsasl_server_first() for other details. +/* +/* xsasl_server_get_mechanism_list() returns the authentication +/* mechanisms that match the security properties, as a white-space +/* separated list. This is meant to be used in the SMTP EHLO +/* reply. +/* +/* xsasl_server_get_username() returns the stored username +/* after successful authentication. +/* +/* Arguments: +/* .IP addr_family +/* The network address family: AF_INET6 or AF_INET. +/* .IP auth_method +/* AUTH command authentication method. +/* .IP client_addr +/* IPv4 or IPv6 address (no surrounding [] or ipv6: prefix), +/* or zero-length string if unavailable. +/* .IP client_port +/* TCP port or zero-length string if unavailable. +/* .IP init_resp +/* AUTH command initial response or null pointer. +/* .IP implementation +/* Implementation handle that was obtained with xsasl_server_init(). +/* .IP path_info +/* The value of the smtpd_sasl_path parameter or equivalent. +/* This specifies the implementation-dependent location of a +/* configuration file, rendez-vous point, etc., and is passed +/* unchanged to the plug-in. +/* .IP security_options +/* The value of the smtpd_security_options parameter or +/* equivalent. This is passed unchanged to the plug-in. +/* .IP server +/* SASL plug-in server handle. +/* .IP server_addr +/* IPv4 or IPv6 address (no surrounding [] or ipv6: prefix), +/* or zero-length string if unavailable. +/* .IP server_port +/* TCP port or zero-length string if unavailable. +/* .IP server_reply +/* BASE64 encoded server non-error reply (without SMTP reply +/* code or enhanced status code), or ASCII error description. +/* .IP server_type +/* The name of a Postfix SASL server plug_in implementation. +/* .IP server_types +/* Null-terminated array of strings with SASL server plug-in +/* implementation names. +/* .IP service +/* The service that is implemented by the local server, typically +/* "smtp" or "lmtp". +/* .IP stream +/* The connection between client and server. When SASL +/* encryption is negotiated, the plug-in will transparently +/* intercept the socket read/write operations. +/* .IP user_realm +/* Authentication domain or null pointer. +/* SECURITY +/* .ad +/* .fi +/* The caller does not sanitize client input. It is the +/* responsibility of the underlying SASL server implementation +/* to produce 7-bit ASCII without control characters as server +/* non-error and error replies, and as the result from +/* xsasl_server_method() and xsasl_server_username(). +/* DIAGNOSTICS +/* In case of failure, xsasl_server_init(), xsasl_server_create(), +/* xsasl_server_get_mechanism_list() and xsasl_server_get_username() +/* log a warning and return a null pointer. +/* +/* Functions that normally return XSASL_AUTH_OK will log a warning +/* and return an appropriate result value. +/* +/* Fatal errors: out of memory. +/* +/* Panic: interface violations. +/* SEE ALSO +/* cyrus_security(3) Cyrus SASL security features +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this +/* software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include +#include + +/* Utility library. */ + +#include +#include + +/* SASL implementations. */ + +#include +#include +#include + + /* + * Lookup table for available SASL server implementations. + */ +typedef struct { + char *server_type; + struct XSASL_SERVER_IMPL *(*server_init) (const char *, const char *); +} XSASL_SERVER_IMPL_INFO; + +static const XSASL_SERVER_IMPL_INFO server_impl_info[] = { +#ifdef XSASL_TYPE_CYRUS + {XSASL_TYPE_CYRUS, xsasl_cyrus_server_init}, +#endif +#ifdef XSASL_TYPE_DOVECOT + {XSASL_TYPE_DOVECOT, xsasl_dovecot_server_init}, +#endif + {0, 0} +}; + +/* xsasl_server_init - look up server implementation by name */ + +XSASL_SERVER_IMPL *xsasl_server_init(const char *server_type, + const char *path_info) +{ + const XSASL_SERVER_IMPL_INFO *xp; + + for (xp = server_impl_info; xp->server_type; xp++) + if (strcmp(server_type, xp->server_type) == 0) + return (xp->server_init(server_type, path_info)); + msg_warn("unsupported SASL server implementation: %s", server_type); + return (0); +} + +/* xsasl_server_types - report available implementation types */ + +ARGV *xsasl_server_types(void) +{ + const XSASL_SERVER_IMPL_INFO *xp; + ARGV *argv = argv_alloc(1); + + for (xp = server_impl_info; xp->server_type; xp++) + argv_add(argv, xp->server_type, ARGV_END); + return (argv); +} -- cgit v1.2.3